1 // The license of this source is "Ruby License" 2 /** 3 * HotRuby 4 * @class 5 * @construtor 6 */ 7 var HotRuby = function() { 8 /** 9 * Global Variables 10 * @type Object 11 */ 12 this.globalVars = {}; 13 /** 14 * END blocks 15 * @type Array 16 */ 17 this.endBlocks = []; 18 /** 19 * Debug DOM 20 * @type HTMLElement 21 */ 22 this.debugDom = document.getElementById("debug"); 23 24 if (this.debugDom == null) { 25 this.debugDom = document.body; 26 } 27 }; 28 29 /** 30 * StackFrame 31 * @class 32 * @construtor 33 */ 34 HotRuby.StackFrame = function() { 35 /** 36 * Stack Pointer 37 * @type Number 38 */ 39 this.sp = 0; 40 /** 41 * Local Variables 42 * @type Array 43 */ 44 this.localVars = []; 45 /** 46 * Stack 47 * @type Array 48 */ 49 this.stack = []; 50 /** 51 * Current class to define methods 52 * @type Object 53 */ 54 this.classObj = null; 55 /** 56 * Current method name 57 * @type String 58 */ 59 this.methodName = ""; 60 /** 61 * Current line no 62 * @type Number 63 */ 64 this.lineNo = 0; 65 /** 66 * File name 67 * @type String 68 */ 69 this.fileName = ""; 70 /** 71 * self 72 * @type Object 73 */ 74 this.self = null; 75 /** 76 * Parent StackFrame 77 * @type HotRuby.StackFrame 78 */ 79 this.parentStackFrame = null; 80 /** 81 * Is Proc(Block) 82 * @type boolean 83 */ 84 this.isProc = false; 85 /** 86 * Object Specific class 87 * @type Object 88 */ 89 this.cbaseObj = null; 90 }; 91 92 HotRuby.prototype = { 93 /** 94 * Run the script. 95 * @param {Array} opcode 96 */ 97 run : function(opcode) { 98 try { 99 this.runOpcode(opcode, this.classes["<global>"], null, null, [], null, false, null); 100 } catch(e) { 101 alert(e); 102 } 103 }, 104 105 /** 106 * Run the opcode. 107 * @param {Array} opcode 108 * @param {Object} classObj 109 * @param {String} methodName 110 * @param {Object} self 111 * @param {Array} args 112 * @param {HotRuby.StackFrame} parentSF Parent StackFrame 113 * @param {boolean} isProc 114 * @param {Object} cbaseObj 115 * @private 116 */ 117 runOpcode : function(opcode, classObj, methodName, self, args, parentSF, isProc, cbaseObj) { 118 if(args.length != opcode[4].arg_size) 119 throw "wrong number of arguments (" + args.length + " for " + opcode[4].arg_size + ")"; 120 121 // Create Stack Frame 122 var sf = new HotRuby.StackFrame(); 123 sf.localVars = new Array(opcode[4].local_size + 1); 124 sf.stack = new Array(opcode[4].stack_max); 125 sf.fileName = opcode[6]; 126 sf.classObj = classObj; 127 sf.methodName = methodName; 128 sf.self = self; 129 sf.parentStackFrame = parentSF; 130 sf.isProc = isProc; 131 sf.cbaseObj = cbaseObj; 132 133 // Copy args to localVars. Fill from last. 134 for (var i = 0;i < args.length; i++) { 135 sf.localVars[sf.localVars.length - 1 - i] = args[i]; 136 } 137 138 // Run the mainLoop 139 this.mainLoop(opcode[11], sf); 140 141 // Copy the stack to the parent stack frame 142 if (parentSF != null) { 143 for (var i = 0;i < sf.sp; i++) { 144 parentSF.stack[parentSF.sp++] = sf.stack[i]; 145 } 146 } else { 147 // Run END blocks 148 if(this.endBlocks.length > 0) { 149 this.run(this.endBlocks.pop()); 150 } 151 } 152 }, 153 154 /** 155 * Main loop for opcodes. 156 * @param {Array} opcode 157 * @param {HotRuby.StackFrame} sf 158 * @private 159 */ 160 mainLoop : function(opcode, sf) { 161 // Create label to ip 162 if(!("label2ip" in opcode)) { 163 opcode.label2ip = {}; 164 for (var ip = 0;ip < opcode.length; ip++) { 165 // If "cmd is a String then it is a jump label 166 var cmd = opcode[ip]; 167 if (typeof(cmd) == "string") { 168 opcode.label2ip[cmd] = ip; 169 opcode[ip] = null; 170 } 171 } 172 } 173 174 for (var ip = 0;ip < opcode.length; ip++) { 175 // Get the next command 176 var cmd = opcode[ip]; 177 if (cmd == null) 178 continue; 179 180 // If "cmd" is a Number then it is the line number. 181 if (typeof(cmd) == "number") { 182 sf.lineNo = cmd; 183 continue; 184 } 185 // "cmd" must be an Array 186 if (!(cmd instanceof Array)) 187 continue; 188 189 switch (cmd[0]) { 190 case "jump" : 191 ip = opcode.label2ip[cmd[1]]; 192 break; 193 case "branchif" : 194 var val = sf.stack[--sf.sp]; 195 if(val != null && val != false) { 196 ip = opcode.label2ip[cmd[1]]; 197 } 198 break; 199 case "branchunless" : 200 var val = sf.stack[--sf.sp]; 201 if(val == null || val == false) { 202 ip = opcode.label2ip[cmd[1]]; 203 } 204 break; 205 case "leave" : 206 return; 207 case "putnil" : 208 sf.stack[sf.sp++] = null; 209 break; 210 case "putself" : 211 sf.stack[sf.sp++] = sf.self; 212 break; 213 case "putobject" : 214 var value = cmd[1]; 215 if(typeof(value) == "string") { 216 if(value.match(/^(\d+)\.\.(\d+)$/)) { 217 value = this.createRubyRange( 218 parseInt(RegExp.$2), 219 parseInt(RegExp.$1), 220 false); 221 } 222 } 223 sf.stack[sf.sp++] = value; 224 break; 225 case "putstring" : 226 sf.stack[sf.sp++] = this.createRubyString(cmd[1]); 227 break; 228 case "concatstrings" : 229 sf.stack[sf.sp++] = this.createRubyString( 230 sf.stack.slice(sf.stack.length - cmd[1], sf.stack.length).join()); 231 break; 232 case "newarray" : 233 var value = this.createRubyArray(sf.stack.slice(sf.sp - cmd[1], sf.sp)); 234 sf.sp -= value.length; 235 sf.stack[sf.sp++] = value; 236 break; 237 case "duparray" : 238 sf.stack[sf.sp++] = this.createRubyArray(cmd[1]); 239 break; 240 case "expandarray" : 241 var ary = sf.stack[--sf.sp]; 242 if(ary instanceof Array) { 243 for(var i=0; i<cmd[1]; i++) { 244 sf.stack[sf.sp++] = ary[i]; 245 } 246 if(cmd[2] && 1) { 247 // TODO 248 } 249 if(cmd[2] && 2) { 250 // TODO 251 } 252 if(cmd[2] && 4) { 253 // TODO 254 } 255 } else { 256 sf.stack[sf.sp++] = ary; 257 for (var i = 0;i < cmd[1] - 1; i++) { 258 sf.stack[sf.sp++] = null; 259 } 260 } 261 break; 262 case "newhash" : 263 var hash = this.createRubyHash(sf.stack.slice(sf.sp - cmd[1], sf.sp)); 264 sf.sp -= cmd[1]; 265 sf.stack[sf.sp++] = hash; 266 break; 267 case "newrange" : 268 var value = this.createRubyRange(sf.stack[--sf.sp], sf.stack[--sf.sp], cmd[1]); 269 sf.stack[sf.sp++] = value; 270 break; 271 case "setlocal" : 272 var localSF = sf; 273 while (localSF.isProc) { 274 localSF = localSF.parentStackFrame; 275 } 276 localSF.localVars[cmd[1]] = sf.stack[--sf.sp]; 277 break; 278 case "getlocal" : 279 var localSF = sf; 280 while (localSF.isProc) { 281 localSF = localSF.parentStackFrame; 282 } 283 sf.stack[sf.sp++] = localSF.localVars[cmd[1]]; 284 break; 285 case "setglobal" : 286 this.globalVars[cmd[1]] = sf.stack[--sf.sp]; 287 break; 288 case "getglobal" : 289 sf.stack[sf.sp++] = this.globalVars[cmd[1]]; 290 break; 291 case "setconstant" : 292 this.setConstant(sf, sf.stack[--sf.sp], cmd[1], sf.stack[--sf.sp]); 293 break; 294 case "getconstant" : 295 var value = this.getConstant(sf, sf.stack[--sf.sp], cmd[1]); 296 sf.stack[sf.sp++] = value; 297 break; 298 case "setinstancevariable" : 299 sf.self.__instanceVars[cmd[1]] = sf.stack[--sf.sp]; 300 break; 301 case "getinstancevariable" : 302 sf.stack[sf.sp++] = sf.self.__instanceVars[cmd[1]]; 303 break; 304 case "setclassvariable" : 305 sf.classObj.__classVars[cmd[1]] = sf.stack[--sf.sp]; 306 break; 307 case "getclassvariable" : 308 var searchClass = sf.classObj; 309 while (true) { 310 if (cmd[1] in searchClass.__classVars) { 311 sf.stack[sf.sp++] = searchClass.__classVars[cmd[1]]; 312 break; 313 } 314 searchClass = searchClass.__parentClass; 315 if (searchClass == null) { 316 throw "Cannot find class variable : " + cmd[1]; 317 } 318 } 319 break; 320 case "getdynamic" : 321 var lookupSF = sf; 322 for (var i = 0;i < cmd[2]; i++) { 323 lookupSF = lookupSF.parentStackFrame; 324 } 325 sf.stack[sf.sp++] = lookupSF.localVars[cmd[1]]; 326 break; 327 case "setdynamic" : 328 var lookupSF = sf; 329 for (var i = 0;i < cmd[2]; i++) { 330 lookupSF = lookupSF.parentStackFrame; 331 } 332 lookupSF.localVars[cmd[1]] = sf.stack[--sf.sp]; 333 break; 334 // case "getspecial" : 335 // break; 336 // case "setspecial" : 337 // break; 338 case "pop" : 339 sf.sp--; 340 break; 341 case "dup" : 342 sf.stack[sf.sp] = sf.stack[sf.sp - 1]; 343 sf.sp++; 344 break; 345 case "dupn" : 346 for (var i = 0;i < cmd[1]; i++) { 347 sf.stack[sf.sp + i] = sf.stack[sf.sp + i - cmd[1]]; 348 } 349 sf.sp += cmd[1]; 350 break; 351 case "swap" : 352 var tmp = sf.stack[sf.sp - 1]; 353 sf.stack[sf.sp - 1] = sf.stack[sf.sp - 2]; 354 sf.stack[sf.sp - 2] = tmp; 355 break; 356 case "topn" : 357 sf.stack[sf.sp] = sf.stack[sf.sp - cmd[1]]; 358 sf.sp++; 359 break; 360 case "setn" : 361 sf.stack[sf.sp - cmd[1]] = sf.stack[sf.sp - 1]; 362 break; 363 case "emptstack" : 364 sf.sp = 0; 365 break; 366 case "send" : 367 var args = sf.stack.slice(sf.sp - cmd[2], sf.sp); 368 sf.sp -= cmd[2]; 369 var recver = sf.stack[--sf.sp]; 370 if (recver == null) 371 recver = sf.self; 372 if(cmd[3] instanceof Array) 373 cmd[3] = this.createRubyProc(cmd[3], sf); 374 if(cmd[3] != null) 375 args.push(cmd[3]); 376 this.invokeMethod(recver, cmd[1], args, sf, cmd[4], false); 377 break; 378 case "invokesuper" : 379 var args = sf.stack.slice(sf.sp - cmd[1], sf.sp); 380 sf.sp -= cmd[1]; 381 // TODO When to use this autoPassAllArgs? 382 var autoPassAllArgs = sf.stack[--sf.sp]; 383 if(cmd[2] instanceof Array) 384 cmd[2] = this.createRubyProc(cmd[1], sf); 385 if(cmd[2] != null) 386 args.push(cmd[2]); 387 this.invokeMethod(sf.self, sf.methodName, args, sf, cmd[3], true); 388 break; 389 case "definemethod" : 390 var obj = sf.stack[--sf.sp]; 391 if(sf.cbaseObj != null) 392 obj = sf.cbaseObj; 393 if (obj == null) { 394 sf.classObj[cmd[1]] = cmd[2]; 395 } else { 396 if (!("__methods" in obj)) 397 obj.__methods = {}; 398 obj.__methods[cmd[1]] = cmd[2]; 399 } 400 opcode[ip] = null; 401 opcode[ip - 1] = null; 402 break; 403 case "defineclass" : 404 var parentClass = sf.stack[--sf.sp]; 405 var isRedefine = (parentClass == false); 406 if(parentClass == null) 407 parentClass = this.classes.Object; 408 var cbaseObj = sf.stack[--sf.sp]; 409 if(cmd[3] == 0) { 410 // Search predefined class 411 var newClass = this.getConstant(sf, sf.classObj, cmd[1]); 412 if(newClass == null || isRedefine) { 413 // Create class object 414 var newClass = { 415 __className : cmd[1], 416 __parentClass : parentClass, 417 __constantVars : {}, 418 __classVars : {} 419 }; 420 this.classes[cmd[1]] = newClass; 421 // Puts the className to CONSTANT 422 this.setConstant(sf, sf.classObj, cmd[1], newClass); 423 } 424 // Run the class definition 425 this.runOpcode(cmd[2], newClass, null, null, [], sf, false, null); 426 } else if(cmd[3] == 1) { 427 // Object-Specific Classes 428 if(cbaseObj == null || typeof(cbaseObj) != "object") 429 throw "Not supported Object-Specific Classes on Primitive Object" 430 // Run the class definition 431 this.runOpcode(cmd[2], cbaseObj.__className, null, null, [], sf, false, cbaseObj); 432 } else if(cmd[3] == 2) { 433 // TODO 434 throw "Not implemented"; 435 } 436 break; 437 case "postexe" : 438 this.endBlocks.push(cmd[1]); 439 break; 440 case "nop" : 441 break; 442 case "reput" : 443 break; 444 default : 445 throw "[mainLoop] Unknown opcode : " + cmd[0]; 446 } 447 } 448 }, 449 450 /** 451 * Invoke the method 452 * @param {Object} recver 453 * @param {String} methodName 454 * @param {Array} args 455 * @param {HotRuby.StackFrame} sf 456 * @param {Number} type VM_CALL_ARGS_SPLAT_BIT, ... 457 * @param {boolean} invokeSuper 458 */ 459 invokeMethod : function(recver, methodName, args, sf, type, invokeSuper) { 460 var recverClassName = this.getClassName(recver); 461 var invokeClassName = recverClassName; 462 var invokeMethodName = methodName; 463 var func = null; 464 465 if (invokeSuper) { 466 var searchClass = this.classes[recverClassName]; 467 while (func == null) { 468 // Search Parent class 469 if (!("__parentClass" in searchClass)) break; 470 searchClass = searchClass.__parentClass; 471 invokeClassName = searchClass.__className; 472 473 // Search method in class 474 func = searchClass[methodName]; 475 } 476 } else { 477 // Search method in object 478 if (recver != null && recver.__methods != null) { 479 func = recver.__methods[methodName]; 480 } 481 if (func == null) { 482 var searchClass = this.classes[recverClassName]; 483 while (true) { 484 // Search method in class 485 func = searchClass[methodName]; 486 if (func != null) break; 487 488 if (methodName == "new") { 489 func = searchClass["initialize"]; 490 if (func != null) { 491 invokeMethodName = "initialize"; 492 break; 493 } 494 } 495 496 // Search Parent class 497 if ("__parentClass" in searchClass) { 498 searchClass = searchClass.__parentClass; 499 if(searchClass == null) { 500 func = null; 501 break; 502 } 503 invokeClassName = searchClass.__className; 504 continue; 505 } 506 break; 507 } 508 } 509 } 510 if (func == null) { 511 if (invokeSuper) { 512 sf.stack[sf.sp++] = null; 513 return; 514 } 515 if (methodName != "new") { 516 throw "[invokeMethod] Undefined function : " + methodName; 517 } 518 } 519 520 if (methodName == "new") { 521 // Create instance 522 var newObj = { 523 __className : recverClassName, 524 __instanceVars : {} 525 }; 526 sf.stack[sf.sp++] = newObj; 527 if (func == null) return; 528 529 recver = newObj; 530 } 531 532 // Splat array args 533 if (type & HotRuby.VM_CALL_ARGS_SPLAT_BIT) { 534 args = args.concat(args.pop()); 535 } 536 537 // Exec method 538 switch (typeof(func)) { 539 case "function" : 540 sf.stack[sf.sp++] = func.call(this, recver, args, sf); 541 break; 542 case "object" : 543 this.runOpcode(func, this.classes[invokeClassName], 544 invokeMethodName, recver, args, sf, false, sf.cbaseObj); 545 break; 546 default : 547 throw "[invokeMethod] Unknown function type : " + typeof(func); 548 } 549 550 // Returned value of initialize() is unnecessally at new() 551 if (methodName == "new") { 552 sf.sp--; 553 } 554 }, 555 556 /** 557 * Set the Constant 558 * @param {HotRuby.StackFrame} sf 559 * @param {Object} classObj 560 * @param {String} constName 561 * @param constValue 562 * @private 563 */ 564 setConstant : function(sf, classObj, constName, constValue) { 565 if (classObj == null) { 566 classObj = sf.classObj; 567 } else if (classObj == false) { 568 // TODO 569 throw "[setConstant] Not implemented"; 570 } 571 // Const in <global> belongs to Object 572 if(classObj.__className == "<global>") 573 classObj = this.classes.Object; 574 classObj.__constantVars[constName] = constValue; 575 }, 576 577 /** 578 * Get the constant 579 * @param {HotRuby.StackFrame} sf 580 * @param {Object} classObj 581 * @param {String} constName 582 * @return constant value 583 * @private 584 */ 585 getConstant : function(sf, classObj, constName) { 586 if (classObj == null) { 587 var isFound = false; 588 // Search outer(parentStackFrame) 589 for (var checkSF = sf;!isFound; checkSF = checkSF.parentStackFrame) { 590 if (checkSF.classObj.__className == "<global>") { 591 break; 592 } 593 if (constName in checkSF.classObj.__constantVars) { 594 classObj = checkSF.classObj; 595 isFound = true; 596 } 597 } 598 // Search parent class 599 if (!isFound) { 600 for (classObj = sf.classObj;classObj.__className != "<global>";) { 601 if (constName in classObj.__constantVars) { 602 isFound = true; 603 break; 604 } 605 classObj = classObj.__parentClass; 606 } 607 } 608 // Search in Object class 609 if (!isFound) { 610 classObj = this.classes.Object; 611 } 612 } else if (classObj == false) { 613 // TODO 614 throw "[setConstant] Not implemented"; 615 } 616 if (classObj == null) 617 throw "[getConstant] Cannot find constant : " + constName; 618 // Const in <global> belongs to Object 619 if (classObj.__className == "<global>") 620 classObj = this.classes.Object; 621 return classObj.__constantVars[constName]; 622 }, 623 624 /** 625 * Returns class name from object. 626 * @param obj 627 * @return {String} 628 */ 629 getClassName : function(obj) { 630 if (obj == null) 631 return "<global>"; 632 switch (typeof(obj)) { 633 case "object" : 634 return obj.__className; 635 case "number" : 636 return "Float"; 637 case "string" : 638 return "String"; 639 case "boolean" : 640 return obj ? "TrueClass" : "FalseClass"; 641 default : 642 throw "[getClassName] unknown type : " + typeof(obj); 643 } 644 }, 645 646 /** 647 * JavaScript String -> Ruby String 648 * @param {String} str 649 * @return {String} 650 */ 651 createRubyString : function(str) { 652 str.__className = "String"; 653 str.__parentClass = this.classes.Object; 654 return str; 655 }, 656 657 /** 658 * opcode -> Ruby Proc 659 * @param {Array} opcode 660 * @param {HotRuby.StackFrame} sf 661 * @return {Object} Proc 662 */ 663 createRubyProc : function(opcode, sf) { 664 return { 665 __opcode : opcode, 666 __className : "Proc", 667 __parentClass : this.classes.Object, 668 __parentStackFrame : sf 669 }; 670 }, 671 672 /** 673 * JavaScript Array -> Ruby Array 674 * @param {Array} ary 675 * @return {Array} 676 */ 677 createRubyArray : function(ary) { 678 ary.__className = "Array"; 679 ary.__parentClass = this.classes.Object; 680 return ary; 681 }, 682 683 /** 684 * JavaScript Array -> Ruby Hash 685 * @param {Array} ary 686 * @return {Object} 687 */ 688 createRubyHash : function(ary) { 689 var hash = { 690 __className : "Hash", 691 __parentClass : this.classes.Object, 692 __instanceVars : { 693 length : ary.length / 2 694 } 695 }; 696 for (var i = 0;i < ary.length; i += 2) { 697 hash[ary[i]] = ary[i + 1]; 698 } 699 return hash; 700 }, 701 702 /** 703 * Creates Ruby Range 704 * @param {Number} last 705 * @param {Number} first 706 * @param {boolean} exclude_end 707 */ 708 createRubyRange : function(last, first, exclude_end) { 709 return { 710 __className : "Range", 711 __parentClass : this.classes.Object, 712 __instanceVars : { 713 first : first, 714 last : last, 715 exclude_end : exclude_end 716 } 717 }; 718 }, 719 720 /** 721 * Print to debug dom. 722 * @param {String} str 723 */ 724 printDebug : function(str) { 725 var div = document.createElement("div"); 726 var text = document.createTextNode(str); 727 div.appendChild(text); 728 this.debugDom.appendChild(div); 729 }, 730 731 /** 732 * Search <script type="text/ruby"></script> and run. 733 * @param {String} url Ruby compiler url 734 */ 735 runFromScriptTag : function(url) { 736 var ary = document.getElementsByTagName("script"); 737 for(var i=0; i < ary.length; i++) { 738 var hoge = ary[i].type; 739 if(ary[i].type == "text/ruby") { 740 this.compileAndRun(url, ary[i].text); 741 break; 742 } 743 } 744 }, 745 746 /** 747 * Send the source to server and run. 748 * @param {String} url Ruby compiler url 749 * @param {src} Ruby source 750 */ 751 compileAndRun : function(url, src) { 752 Ext.lib.Ajax.request( 753 "POST", 754 url, 755 { 756 success: function(response) { 757 if(response.responseText.length == 0) { 758 alert("Compile failed"); 759 } else { 760 this.run(eval("(" + response.responseText + ")")); 761 } 762 }, 763 failure: function(response) { 764 alert("Compile failed"); 765 }, 766 scope: this 767 }, 768 "src=" + encodeURIComponent(src) 769 ); 770 } 771 }; 772 773 // Consts 774 /** @memberof HotRuby */ 775 HotRuby.VM_CALL_ARGS_SPLAT_BIT = 2; 776 /** @memberof HotRuby */ 777 HotRuby.VM_CALL_ARGS_BLOCKARG_BIT = 4; 778 /** @memberof HotRuby */ 779 HotRuby.VM_CALL_FCALL_BIT = 8; 780 /** @memberof HotRuby */ 781 HotRuby.VM_CALL_VCALL_BIT = 16; 782