Fri, 19 Nov 2010 03:48:41 +0000
Switch to sys.puts for all output for now, leaving browser compatibility for the moment.
var OP_MOVE = 0; var OP_LOADK = 1; var OP_LOADBOOL = 2; var OP_LOADNIL = 3; var OP_GETUPVAL = 4; var OP_GETGLOBAL = 5; var OP_GETTABLE = 6; var OP_SETGLOBAL = 7; var OP_SETUPVAL = 8; var OP_SETTABLE = 9; var OP_NEWTABLE = 10; var OP_SELF = 11; var OP_ADD = 12; var OP_SUB = 13; var OP_JMP = 22; var OP_LT = 24; var OP_TEST = 26; var OP_TESTSET = 27; var OP_CALL = 28; var OP_RETURN = 30; var OP_FORLOOP = 31; var OP_FORPREP = 32; var OP_CLOSURE = 36; var debugMode = false; function LValue(type, value) { this.type = type||"nil"; this.value = value; } LValue.prototype = { call: function (args) { var f = this.precall(); if(typeof(f) == "function") var ret = f.apply(this.vm, args?args.map(function (a) { return a.value; }):[]); else var ret = this.vm.call(f); if(typeof(ret) == "undefined") ret = []; return ret; }, precall: function () { if(this.type == "function") return this.value; else throw "Attempt to call a " + this.type + " value"; }, index: function (key, raw) { if(this.type == "table") { var val; if(key.value in this.value) return this.value[key.value]; else if(raw != true && this.metatable && this.metatable.type != "nil") { var __index = this.metatable.index(new LValue("string", "__index")); if(__index.type == "function") { return LValueFromValue(__index.call([this, key])[0]); } else if(__index.type != "nil") return __index.index(key); } return new LValue("nil", null); } else throw "Attempt to index a " + this.type + " value"; }, setIndex: function (key, value) { if(this.type == "table") { this.value[key.value] = value; } else throw "Attempt to index a " + this.type + " value"; }, setMetatable: function (metatable) { if(metatable.type == "table") this.metatable = metatable; else if(metatable.type == "nil") this.metatable = null; else throw "Attempt to set a "+metatable.type+" value as a metatable"; }, toString: function () { switch(this.type) { case "nil": return "nil"; default: return this.value.toString(); } } }; function LValueFromString(string) { return new LValue("string", string); } function LValueFromFunction(vm, func) { var val = new LValue("function", func); val.vm = vm; return val; } function LValueFromValue(value) { switch(typeof(value)) { case "number": return new LValue("number", value); case "string": return new LValueFromString(value); case "function": return new LValueFromFunction(value); case "object": if(value == null) return new LValue("nil", null); else return new LValue("table", value); case "undefined": return new LValue("nil", null); default: sys.puts( "Not able to convert type " + typeof(value)+" from Javascript to Lua: "+sys.inspect(value)); throw "Not able to convert type " + typeof(value)+" from Javascript to Lua"; } } function LBinaryChunk(chunk, start) { this.chunk = chunk; this.pos = start||12; this.sourceName = this.readString(); this.lineDefined = this.readInt(); this.lastLineDefined = this.readInt(); this.numUpvalues = this.readByte(); this.numParameters = this.readByte(); this.isVararg = this.readByte(); this.maxStackSize = this.readByte(); this.instructions = []; this.numInstructions = this.readInt(); for(var i=0;i<this.numInstructions;i++) { var ins = this.readInt(); this.instructions.push([ ins&0x3F, // Opcode (ins>>6)&0xFF, // Field A (ins>>23)&0x1FF, // Field B (ins>>14)&0x1FF // Field C ]); if(debugMode) { var pi = this.instructions[this.instructions.length-1]; sys.puts("Pos: "+(this.pos-4)+" Ins: "+ins+" OP: "+INS_OPCODE(pi)+" A: "+INS_A(pi)+" B: "+INS_B(pi)+" C: "+INS_C(pi)+" Bx: "+INS_Bx(pi)+" sBx: "+(INS_Bx(pi)-0x1FFFE)); } } this.constants = []; this.numConstants = this.readInt(); for(var i=0;i<this.numConstants;i++) { var type = this.readByte(); switch(type) { case 0: // Nil this.constants.push(new LValue("nil", null)); break; case 1: // Boolean this.constants.push(new LValue("boolean", this.readByte())); break; case 3: // Number this.constants.push(new LValue("number", this.readNumber())); break; case 4: // String this.constants.push(LValueFromString(this.readString())); break; default: throw "Invalid constant type "+type+" in bytecode"; } } this.prototypes = []; this.numPrototypes = this.readInt(); for(var i=0;i<this.numPrototypes;i++) { var p = new LBinaryChunk(chunk, this.pos); this.pos = p.pos; this.prototypes.push(p); } this.sourceLines = []; this.numSourceLines = this.readInt(); for(var i=0;i<this.numSourceLines;i++) { this.sourceLines.push(this.readInt()); } this.localList = []; this.numLocalList = this.readInt(); for(var i=0;i<this.numLocalList;i++) { this.localList.push([this.readString(),this.readInt(),this.readInt()]); } this.upvalueList = []; this.numUpvalueList = this.readInt(); for(var i=0;i<this.numUpvalueList;i++) { this.upvalueList.push(this.readString()); } return this; } LBinaryChunk.prototype = { readBytes: function (n) { return this.chunk.slice(this.pos, this.pos+=n); }, readByte: function () { return this.readBytes(1).charCodeAt(0); }, readInt: function () { //FIXME: Endianness return this.readByte() | (this.readByte()<<8) | (this.readByte()<<16) | (this.readByte()<<24); }, readString: function () { var len = this.readInt(); return this.readBytes(len).substring(0,len-1); }, readNumber: function () { //FIXME: Endianness var bytes = [this.readByte(),this.readByte(),this.readByte(),this.readByte(), this.readByte(),this.readByte(),this.readByte(),this.readByte()].reverse(); var sign = (bytes[0]>>7)&0x1; var exp = (bytes[0]&0x7F)<<4 | (bytes[1]&0xf0)>>4; var frac = ((bytes[1] & 0x0f) * Math.pow(2,48)) + (bytes[2] * Math.pow(2,40)) + (bytes[3] * Math.pow(2,32)) + (bytes[4] * Math.pow(2,24)) + (bytes[5] * Math.pow(2,16)) + (bytes[6] * Math.pow(2,8)) + bytes[7]; if(exp != 0x000 && exp != 0x7FF) { var n = (sign==1?-1:1)*Math.pow(2,exp-1023)*(1+(frac/0x10000000000000)); return n; } else if(exp == 0x000) { return sign*0; } else return frac==0?sign*Infinity:NaN; } }; function INS_OPCODE(ins) { return ins[0]; } function INS_A(ins) { return ins[1]; } function INS_B(ins) { return ins[2]; } function INS_C(ins) { return ins[3]; } function INS_Bx(ins) { return ((INS_C(ins))|(INS_B(ins)<<9)); } function INS_sBx(ins) { return (INS_Bx(ins)-0x1FFFF); } function LFunction(vm, chunk, env) { function F() {}; F.prototype = chunk; var o = new F(); o.vm = vm; o.environment = env; o.chunk = chunk; o.upvalues = []; return o; } function LVM() { this.callstack = []; this.stack = []; return this; } LVM.prototype = { call: function (lfFunction) { var frame = {f:lfFunction,pc:0,reg:[],entry:true}; this.callstack.push(frame); for(var i=0;i<lfFunction.maxStackSize;i++) frame.reg[i] = new LValue("nil", null); return this.run(frame); }, run: function(frame) { var instruction; while(this.callstack.length>0) { instruction = frame.f.instructions[frame.pc++]; if(debugMode) { sys.puts("PC: "+(frame.pc-1)+" OP: "+instruction[0]); sys.puts("STACK: "+sys.inspect(frame.reg)); } switch(INS_OPCODE(instruction)) { case OP_MOVE: frame.reg[INS_A(instruction)] = frame.reg[INS_B(instruction)]; break; case OP_LOADNIL: for(var i = INS_A(instruction);i<=INS_B(instruction);i++) frame.reg[i] = new LValue("nil", null); break; case OP_LOADBOOL: frame.reg[INS_A(instruction)] = new LValue("boolean", INS_B(instruction)!=0); if(INS_C(instruction)!=0) frame.pc++; break; case OP_GETUPVAL: frame.reg[INS_A(instruction)] = frame.f.upvalues[INS_B(instruction)]; break; case OP_GETGLOBAL: var name = frame.f.constants[INS_Bx(instruction)]; frame.reg[INS_A(instruction)] = frame.f.environment.index(name); break; case OP_SETUPVAL: var reg = frame.reg[INS_A(instruction)]; var upvalue = frame.f.upvalues[INS_B(instruction)]; upvalue.type = reg.type; upvalue.value = reg.value; break; case OP_SETGLOBAL: var name = frame.f.constants[INS_Bx(instruction)]; frame.f.environment.setIndex(name, frame.reg[instruction[1]]); break; case OP_LOADK: var constant = frame.f.constants[INS_Bx(instruction)]; frame.reg[INS_A(instruction)] = new LValue(constant.type, constant.value); break; case OP_NEWTABLE: frame.reg[INS_A(instruction)] = new LValue("table", {}); break; case OP_GETTABLE: var C = INS_C(instruction); var keysource = (C&256)?frame.f.constants:frame.reg; var key = keysource[C&0xff]; var value = frame.reg[INS_B(instruction)].index(key).value; frame.reg[INS_A(instruction)] = new LValueFromValue(value); break; case OP_SETTABLE: var C = INS_C(instruction); var valuesource = (C&256)?frame.f.constants:frame.reg; var value = valuesource[C&0xff]; var B = INS_B(instruction); var keysource = (B&256)?frame.f.constants:frame.reg; var key = keysource[B&0xff]; frame.reg[INS_A(instruction)].setIndex(key, value); break; case OP_CALL: var f = frame.reg[INS_A(instruction)].precall(); // return JS or LValue var A = INS_A(instruction), B = INS_B(instruction), C = INS_C(instruction); var undefined; var args = frame.reg.slice(A+1, B==0?undefined:(A+B)); for(var i=args.length+1;i<f.maxStackSize;i++) args[i] = new LValue("nil", null); if(typeof(f) == "function") { // JS native function var ret = f.apply(this, [args]); for(var i = 0; i < (C-1); i++) //FIXME: Handle C == 0 { if(i < ret && i < args.length) frame.reg[A+i] = args[i]; else frame.reg[A+i] = new LValue("nil", null); } } else { // Lua function frame = {f:f,pc:0,reg:args, retAt:INS_A(instruction),retCount:INS_C(instruction), entry:false}; this.callstack.push(frame); } break; case OP_CLOSURE: var prototype_id = INS_Bx(instruction); var chunk = frame.f.chunk.prototypes[prototype_id]; var f = new LFunction(this, chunk, frame.f.environment); frame.reg[INS_A(instruction)] = new LValue("function", f); for(var i=0;i<chunk.numUpvalues;i++) { var upval_instruction = frame.f.instructions[frame.pc++]; switch(INS_OPCODE(upval_instruction)) { case OP_MOVE: f.upvalues[i] = frame.reg[INS_B(upval_instruction)]; break; case OP_GETUPVAL: f.upvalues[i] = frame.f.upvalues[INS_B(upval_instruction)]; break; default: throw "Invalid upvalue opcode following OP_CLOSURE"; } } break; case OP_RETURN: var oldFrame = this.callstack.pop(); frame = this.callstack[this.callstack.length-1]; var rets; if(INS_B(instruction) == 0) rets = oldFrame.reg.slice(INS_A(instruction)); else rets = oldFrame.reg.slice(INS_A(instruction),INS_A(instruction)+(INS_B(instruction)-1)); if(!oldFrame.entry) { var i; for(i=0;(oldFrame.retCount == 0||i<(oldFrame.retCount-1))&&i<rets.length;i++) frame.reg[oldFrame.retAt+i] = rets[i]; if(oldFrame.retAt+i<frame.reg.length) frame.reg.slice(0,oldFrame.retAt+i); } else return rets; break; case OP_SELF: var table = frame.reg[INS_B(instruction)]; frame.reg[INS_A(instruction)+1] = table; var C = INS_C(instruction); var keysource = (C&256)?frame.f.constants:frame.reg; var key = keysource[C&0xff]; // FIXME: Should this be made an LValue? frame.reg[INS_A(instruction)] = table.index(key); break; case OP_FORPREP: frame.pc+=(INS_sBx(instruction)); var A = INS_A(instruction); frame.reg[A].value -= frame.reg[A+2].value; frame.reg[A+3] = new LValue("number", null); break; case OP_FORLOOP: var A = INS_A(instruction); var RA = frame.reg[A]; RA.value += frame.reg[A+2].value; if(RA.value <= frame.reg[A+1].value) { frame.pc += INS_sBx(instruction); frame.reg[A+3].value = RA.value; } break; case OP_TEST: var RA = frame.reg[INS_A(instruction)]; var RA_bool = RA.type == "nil" || (RA.type == "boolean" && RA.value == false); if(RA_bool == (INS_C(instruction)!=0)) frame.pc++; break; case OP_TESTSET: var RB = frame.reg[INS_B(instruction)]; var RB_bool = RB.type == "nil" || (RB.type == "boolean" && RB.value == false); if(RB_bool == (INS_C(instruction)!=0)) frame.pc++; else frame.reg[INS_A(instruction)] = RB; break; case OP_JMP: frame.pc+=INS_sBx(instruction); break; case OP_ADD: var RB = frame.reg[INS_B(instruction)]; var RC = frame.reg[INS_C(instruction)]; frame.reg[INS_A(instruction)] = new LValue("number", RB.value + RC.value); break; case OP_SUB: var RB = frame.reg[INS_B(instruction)]; var RC = frame.reg[INS_C(instruction)]; frame.reg[INS_A(instruction)] = new LValue("number", RB.value - RC.value); break; case OP_LT: var RB = frame.reg[INS_B(instruction)]; var RC = frame.reg[INS_C(instruction)]; if(RB.value < RC.value) frame.pc++; break; default: throw "Unhandled opcode: "+INS_OPCODE(instruction); } } } }; try{ var testvm = new LVM(); var fs=require("fs"); var sys=require("sys"); var c = new LBinaryChunk(fs.readFileSync("luac.out", "binary")); var default_environment = new LValue("table", {}); // Standard library var baselib = { print: function (args) { sys.print(args[0].toString()); for(var i = 1; i<args.length; i++) sys.print("\t"+args[i].toString()); sys.print("\n"); return 0; }, setmetatable: function (args) { if(args.length!=2) throw "setmetatable expects 2 arguments, got "+args.length; args[0].setMetatable(args[1]); return 1; } }; for(var name in baselib) { default_environment.setIndex(LValueFromString(name), LValueFromFunction(testvm, baselib[name])); } // Metatable on environment to print out nil global accesses var mt = new LValue("table", {}); mt.setIndex( LValueFromString("__index"), LValueFromFunction(testvm, function (t, k) { sys.puts("Access of nil global: "+k); }) ); default_environment.setMetatable(mt); var f = new LFunction(testvm, c, default_environment); var ret = testvm.call(f); if(ret) sys.puts("Returned: "+sys.inspect(ret)); } catch(e) { sys.puts("Error: " + e); if(typeof(e) == "object" && "stack" in e) sys.puts(e.stack); process.exit(1); }