57 var val; |
58 var val; |
58 if(key.value in this.value) |
59 if(key.value in this.value) |
59 return this.value[key.value]; |
60 return this.value[key.value]; |
60 else if(raw != true && this.metatable && this.metatable.type != "nil") |
61 else if(raw != true && this.metatable && this.metatable.type != "nil") |
61 { |
62 { |
62 var __index = this.metatable.index(new LValue("string", "__index")); |
63 var __index = this.metatable.index(this.vm.LValue("__index")); |
63 if(__index.type == "function") |
64 if(__index.type == "function") |
64 { |
65 { |
65 return LValueFromValue(__index.call([this, key])[0]); |
66 return this.vm.LValue(__index.call([this, key])[0]); |
66 } |
67 } |
67 else if(__index.type != "nil") |
68 else if(__index.type != "nil") |
68 return __index.index(key); |
69 return __index.index(key); |
69 } |
70 } |
70 return new LValue("nil", null); |
71 return this.vm.LValue(null); |
71 } |
72 } |
72 else |
73 else |
73 throw "Attempt to index a " + this.type + " value"; |
74 throw "Attempt to index a " + this.type + " value"; |
74 }, |
75 }, |
75 setIndex: function (key, value) |
76 setIndex: function (key, value) |
101 } |
102 } |
102 }, |
103 }, |
103 add: function (op2) |
104 add: function (op2) |
104 { |
105 { |
105 var metamethod; |
106 var metamethod; |
106 var __add = LValueFromString("__add"); |
107 var __add = this.vm.LValue("__add"); |
107 if(this.metatable) |
108 if(this.metatable) |
108 metamethod = this.metatable.index(__add); |
109 metamethod = this.metatable.index(__add); |
109 if((!metamethod || metamethod.type == "nil") && op2.metatable) |
110 if((!metamethod || metamethod.type == "nil") && op2.metatable) |
110 metamethod = op2.metatable.index(__add); |
111 metamethod = op2.metatable.index(__add); |
111 if(metamethod && metamethod.type != "nil") |
112 if(metamethod && metamethod.type != "nil") |
120 else |
121 else |
121 throw "Attempt to perform arithmetic on a "+this.type+" and "+op2.type; |
122 throw "Attempt to perform arithmetic on a "+this.type+" and "+op2.type; |
122 } |
123 } |
123 }; |
124 }; |
124 |
125 |
125 function LValueFromString(string) |
126 function LBinaryChunk(vm, chunk, start) |
126 { |
|
127 return new LValue("string", string); |
|
128 } |
|
129 |
|
130 function LValueFromFunction(vm, func) |
|
131 { |
|
132 var val = new LValue("function", func); |
|
133 val.vm = vm; |
|
134 return val; |
|
135 } |
|
136 |
|
137 function LValueFromValue(value) |
|
138 { |
|
139 switch(typeof(value)) |
|
140 { |
|
141 case "number": |
|
142 return new LValue("number", value); |
|
143 case "string": |
|
144 return new LValueFromString(value); |
|
145 case "function": |
|
146 return new LValueFromFunction(value); |
|
147 case "object": |
|
148 if(value == null) |
|
149 return new LValue("nil", null); |
|
150 else |
|
151 return new LValue("table", value); |
|
152 case "undefined": |
|
153 return new LValue("nil", null); |
|
154 default: |
|
155 sys.puts( "Not able to convert type " + |
|
156 typeof(value)+" from Javascript to Lua: "+sys.inspect(value)); |
|
157 throw "Not able to convert type " + |
|
158 typeof(value)+" from Javascript to Lua"; |
|
159 } |
|
160 } |
|
161 |
|
162 function LBinaryChunk(chunk, start) |
|
163 { |
127 { |
164 this.chunk = chunk; |
128 this.chunk = chunk; |
165 this.pos = start||12; |
129 this.pos = start||12; |
166 |
130 |
167 this.sourceName = this.readString(); |
131 this.sourceName = this.readString(); |
198 { |
162 { |
199 var type = this.readByte(); |
163 var type = this.readByte(); |
200 switch(type) |
164 switch(type) |
201 { |
165 { |
202 case 0: // Nil |
166 case 0: // Nil |
203 this.constants.push(new LValue("nil", null)); |
167 this.constants.push(new LValue(vm, "nil", null)); |
204 break; |
168 break; |
205 case 1: // Boolean |
169 case 1: // Boolean |
206 this.constants.push(new LValue("boolean", this.readByte())); |
170 this.constants.push(new LValue(vm, "boolean", this.readByte())); // FIXME type |
207 break; |
171 break; |
208 case 3: // Number |
172 case 3: // Number |
209 this.constants.push(new LValue("number", this.readNumber())); |
173 this.constants.push(new LValue(vm, "number", this.readNumber())); |
210 break; |
174 break; |
211 case 4: // String |
175 case 4: // String |
212 this.constants.push(LValueFromString(this.readString())); |
176 this.constants.push(new LValue(vm, "string", this.readString())); |
213 break; |
177 break; |
214 default: |
178 default: |
215 throw "Invalid constant type "+type+" in bytecode"; |
179 throw "Invalid constant type "+type+" in bytecode"; |
216 } |
180 } |
217 } |
181 } |
219 this.prototypes = []; |
183 this.prototypes = []; |
220 |
184 |
221 this.numPrototypes = this.readInt(); |
185 this.numPrototypes = this.readInt(); |
222 for(var i=0;i<this.numPrototypes;i++) |
186 for(var i=0;i<this.numPrototypes;i++) |
223 { |
187 { |
224 var p = new LBinaryChunk(chunk, this.pos); |
188 var p = new LBinaryChunk(vm, chunk, this.pos); |
225 this.pos = p.pos; |
189 this.pos = p.pos; |
226 this.prototypes.push(p); |
190 this.prototypes.push(p); |
227 } |
191 } |
228 |
192 |
229 this.sourceLines = []; |
193 this.sourceLines = []; |
350 this.stack = []; |
314 this.stack = []; |
351 return this; |
315 return this; |
352 } |
316 } |
353 |
317 |
354 LVM.prototype = { |
318 LVM.prototype = { |
355 call: function (lfFunction) |
319 LValue: function (value) |
356 { |
320 { |
357 var frame = {f:lfFunction,pc:0,reg:[],entry:true}; |
321 switch(typeof(value)) |
358 this.callstack.push(frame); |
322 { |
359 for(var i=0;i<lfFunction.maxStackSize;i++) |
323 case "number": |
360 frame.reg[i] = new LValue("nil", null); |
324 return new LValue(this, "number", value); |
361 return this.run(frame); |
325 case "string": |
|
326 return new LValue(this, "string", value); |
|
327 case "function": |
|
328 return new LValue(this, "function", value); |
|
329 case "object": |
|
330 if(value == null) |
|
331 return new LValue(this, "nil", value); |
|
332 else |
|
333 return new LValue(this, "table", value); |
|
334 case "undefined": |
|
335 return new LValue(this, "nil", null); |
|
336 default: |
|
337 throw "Not able to convert type " + |
|
338 typeof(value)+" from Javascript to Lua"; |
|
339 } |
|
340 }, |
|
341 call: function (lfFunction, args) |
|
342 { |
|
343 if(typeof(lfFunction) == "function") |
|
344 { |
|
345 return lfFunction.apply(this, args); |
|
346 } |
|
347 else |
|
348 { |
|
349 var frame = {f:lfFunction,pc:0,entry:true}; |
|
350 if(args) |
|
351 frame.reg = args.slice(0); |
|
352 else |
|
353 frame.reg = []; |
|
354 this.callstack.push(frame); |
|
355 for(var i=0;i<lfFunction.maxStackSize;i++) |
|
356 frame.reg[i] = this.LValue(null); |
|
357 return this.run(frame); |
|
358 } |
362 }, |
359 }, |
363 run: function(frame) |
360 run: function(frame) |
364 { |
361 { |
365 var instruction; |
362 var instruction; |
366 while(this.callstack.length>0) |
363 while(this.callstack.length>0) |
376 case OP_MOVE: |
373 case OP_MOVE: |
377 frame.reg[INS_A(instruction)] = frame.reg[INS_B(instruction)]; |
374 frame.reg[INS_A(instruction)] = frame.reg[INS_B(instruction)]; |
378 break; |
375 break; |
379 case OP_LOADNIL: |
376 case OP_LOADNIL: |
380 for(var i = INS_A(instruction);i<=INS_B(instruction);i++) |
377 for(var i = INS_A(instruction);i<=INS_B(instruction);i++) |
381 frame.reg[i] = new LValue("nil", null); |
378 frame.reg[i] = new LValue(this, "nil", null); |
382 break; |
379 break; |
383 case OP_LOADBOOL: |
380 case OP_LOADBOOL: |
384 frame.reg[INS_A(instruction)] = new LValue("boolean", INS_B(instruction)!=0); |
381 frame.reg[INS_A(instruction)] = new LValue(this, "boolean", INS_B(instruction)!=0); |
385 if(INS_C(instruction)!=0) |
382 if(INS_C(instruction)!=0) |
386 frame.pc++; |
383 frame.pc++; |
387 break; |
384 break; |
388 case OP_GETUPVAL: |
385 case OP_GETUPVAL: |
389 frame.reg[INS_A(instruction)] = frame.f.upvalues[INS_B(instruction)]; |
386 frame.reg[INS_A(instruction)] = frame.f.upvalues[INS_B(instruction)]; |
402 var name = frame.f.constants[INS_Bx(instruction)]; |
399 var name = frame.f.constants[INS_Bx(instruction)]; |
403 frame.f.environment.setIndex(name, frame.reg[instruction[1]]); |
400 frame.f.environment.setIndex(name, frame.reg[instruction[1]]); |
404 break; |
401 break; |
405 case OP_LOADK: |
402 case OP_LOADK: |
406 var constant = frame.f.constants[INS_Bx(instruction)]; |
403 var constant = frame.f.constants[INS_Bx(instruction)]; |
407 frame.reg[INS_A(instruction)] = new LValue(constant.type, constant.value); |
404 frame.reg[INS_A(instruction)] = new LValue(this, constant.type, constant.value); |
408 break; |
405 break; |
409 case OP_NEWTABLE: |
406 case OP_NEWTABLE: |
410 frame.reg[INS_A(instruction)] = new LValue("table", {}); |
407 frame.reg[INS_A(instruction)] = new LValue(this, "table", {}); |
411 break; |
408 break; |
412 case OP_GETTABLE: |
409 case OP_GETTABLE: |
413 var C = INS_C(instruction); |
410 var C = INS_C(instruction); |
414 var keysource = (C&0x100)?frame.f.constants:frame.reg; |
411 var keysource = (C&0x100)?frame.f.constants:frame.reg; |
415 var key = keysource[C&0xff]; |
412 var key = keysource[C&0xff]; |
416 var value = frame.reg[INS_B(instruction)].index(key).value; |
413 var value = frame.reg[INS_B(instruction)].index(key).value; |
417 frame.reg[INS_A(instruction)] = new LValueFromValue(value); |
414 frame.reg[INS_A(instruction)] = this.LValue(value); |
418 break; |
415 break; |
419 case OP_SETTABLE: |
416 case OP_SETTABLE: |
420 var C = INS_C(instruction); |
417 var C = INS_C(instruction); |
421 var valuesource = (C&0x100)?frame.f.constants:frame.reg; |
418 var valuesource = (C&0x100)?frame.f.constants:frame.reg; |
422 var value = valuesource[C&0xff]; |
419 var value = valuesource[C&0xff]; |
430 var f = frame.reg[INS_A(instruction)].precall(); // return JS or LValue |
427 var f = frame.reg[INS_A(instruction)].precall(); // return JS or LValue |
431 var A = INS_A(instruction), B = INS_B(instruction), C = INS_C(instruction); |
428 var A = INS_A(instruction), B = INS_B(instruction), C = INS_C(instruction); |
432 var undefined; |
429 var undefined; |
433 var args = frame.reg.slice(A+1, B==0?undefined:(A+B)); |
430 var args = frame.reg.slice(A+1, B==0?undefined:(A+B)); |
434 for(var i=args.length+1;i<f.maxStackSize;i++) |
431 for(var i=args.length+1;i<f.maxStackSize;i++) |
435 args[i] = new LValue("nil", null); |
432 args[i] = new LValue(this, "nil", null); |
436 if(typeof(f) == "function") |
433 if(typeof(f) == "function") |
437 { |
434 { |
438 // JS native function |
435 // JS native function |
439 var ret = f.apply(this, [args]); |
436 var ret = this.call(f, args); |
440 for(var i = 0; i < (C-1); i++) //FIXME: Handle C == 0 |
437 for(var i = 0; i < (C-1); i++) //FIXME: Handle C == 0 |
441 { |
438 { |
442 if(i < ret && i < args.length) |
439 if(i < ret && i < args.length) |
443 frame.reg[A+i] = args[i]; |
440 frame.reg[A+i] = args[i]; |
444 else |
441 else |
445 frame.reg[A+i] = new LValue("nil", null); |
442 frame.reg[A+i] = new LValue(this, "nil", null); |
446 } |
443 } |
447 } |
444 } |
448 else |
445 else |
449 { |
446 { |
450 // Lua function |
447 // Lua function |
456 break; |
453 break; |
457 case OP_CLOSURE: |
454 case OP_CLOSURE: |
458 var prototype_id = INS_Bx(instruction); |
455 var prototype_id = INS_Bx(instruction); |
459 var chunk = frame.f.chunk.prototypes[prototype_id]; |
456 var chunk = frame.f.chunk.prototypes[prototype_id]; |
460 var f = new LFunction(this, chunk, frame.f.environment); |
457 var f = new LFunction(this, chunk, frame.f.environment); |
461 frame.reg[INS_A(instruction)] = new LValue("function", f); |
458 frame.reg[INS_A(instruction)] = new LValue(this, "function", f); |
462 for(var i=0;i<chunk.numUpvalues;i++) |
459 for(var i=0;i<chunk.numUpvalues;i++) |
463 { |
460 { |
464 var upval_instruction = frame.f.instructions[frame.pc++]; |
461 var upval_instruction = frame.f.instructions[frame.pc++]; |
465 switch(INS_OPCODE(upval_instruction)) |
462 switch(INS_OPCODE(upval_instruction)) |
466 { |
463 { |
504 break; |
501 break; |
505 case OP_FORPREP: |
502 case OP_FORPREP: |
506 frame.pc+=(INS_sBx(instruction)); |
503 frame.pc+=(INS_sBx(instruction)); |
507 var A = INS_A(instruction); |
504 var A = INS_A(instruction); |
508 frame.reg[A].value -= frame.reg[A+2].value; |
505 frame.reg[A].value -= frame.reg[A+2].value; |
509 frame.reg[A+3] = new LValue("number", null); |
506 frame.reg[A+3] = new LValue(this, "number", null); |
510 break; |
507 break; |
511 case OP_FORLOOP: |
508 case OP_FORLOOP: |
512 var A = INS_A(instruction); |
509 var A = INS_A(instruction); |
513 var RA = frame.reg[A]; |
510 var RA = frame.reg[A]; |
514 RA.value += frame.reg[A+2].value; |
511 RA.value += frame.reg[A+2].value; |
541 frame.reg[INS_A(instruction)] = RB.add(RC); |
538 frame.reg[INS_A(instruction)] = RB.add(RC); |
542 break; |
539 break; |
543 case OP_SUB: |
540 case OP_SUB: |
544 var RB = frame.reg[INS_B(instruction)]; |
541 var RB = frame.reg[INS_B(instruction)]; |
545 var RC = frame.reg[INS_C(instruction)]; |
542 var RC = frame.reg[INS_C(instruction)]; |
546 frame.reg[INS_A(instruction)] = new LValue("number", RB.value - RC.value); |
543 frame.reg[INS_A(instruction)] = new LValue(this, "number", RB.value - RC.value); |
547 break; |
544 break; |
548 case OP_LT: |
545 case OP_LT: |
549 var RB = frame.reg[INS_B(instruction)]; |
546 var RB = frame.reg[INS_B(instruction)]; |
550 var RC = frame.reg[INS_C(instruction)]; |
547 var RC = frame.reg[INS_C(instruction)]; |
551 if(RB.value < RC.value) |
548 if(RB.value < RC.value) |
561 try{ |
558 try{ |
562 var testvm = new LVM(); |
559 var testvm = new LVM(); |
563 |
560 |
564 var fs=require("fs"); |
561 var fs=require("fs"); |
565 var sys=require("sys"); |
562 var sys=require("sys"); |
566 var c = new LBinaryChunk(fs.readFileSync("luac.out", "binary")); |
563 var c = new LBinaryChunk(testvm, fs.readFileSync("luac.out", "binary")); |
567 |
564 |
568 var default_environment = new LValue("table", {}); |
565 var default_environment = testvm.LValue({}); |
569 |
566 |
570 // Standard library |
567 // Standard library |
571 |
568 |
572 var baselib = { |
569 var baselib = { |
573 print: function (args) |
570 print: function (args) |
587 } |
584 } |
588 }; |
585 }; |
589 |
586 |
590 for(var name in baselib) |
587 for(var name in baselib) |
591 { |
588 { |
592 default_environment.setIndex(LValueFromString(name), LValueFromFunction(testvm, baselib[name])); |
589 default_environment.setIndex(testvm.LValue(name), testvm.LValue(baselib[name])); |
593 } |
590 } |
594 |
591 |
595 // Metatable on environment to print out nil global accesses |
592 // Metatable on environment to print out nil global accesses |
596 var mt = new LValue("table", {}); |
593 var mt = testvm.LValue({}); |
597 mt.setIndex( |
594 mt.setIndex( |
598 LValueFromString("__index"), |
595 testvm.LValue("__index"), |
599 LValueFromFunction(testvm, function (t, k) { sys.puts("Access of nil global: "+k); }) |
596 testvm.LValue(function (t, k) { sys.puts("Access of nil global: "+k); }) |
600 ); |
597 ); |
601 default_environment.setMetatable(mt); |
598 default_environment.setMetatable(mt); |
602 |
599 |
603 |
600 |
604 var f = new LFunction(testvm, c, default_environment); |
601 var f = new LFunction(testvm, c, default_environment); |