lvm.js

1
var OP_MOVE = 0;
2
var OP_LOADK = 1;
3
var OP_LOADBOOL = 2;
4
var OP_LOADNIL = 3;
5
var OP_GETUPVAL = 4;
6
var OP_GETGLOBAL = 5;
7
var OP_GETTABLE = 6;
8
var OP_SETGLOBAL = 7;
9
var OP_SETUPVAL = 8;
10
var OP_SETTABLE = 9;
11
var OP_NEWTABLE = 10;
12
var OP_SELF = 11;
13
var OP_ADD = 12;
14
var OP_SUB = 13;
15
var OP_MUL = 14;
16
var OP_DIV = 15;
17
var OP_MOD = 16;
18
var OP_POW = 17;
19
var OP_UNM = 18;
20
var OP_NOT = 19;
21
var OP_LEN = 20;
22
var OP_CONCAT = 21;
23
var OP_JMP = 22;
24
var OP_EQ = 23;
25
var OP_LT = 24;
26
var OP_LE = 25;
27
var OP_TEST = 26;
28
var OP_TESTSET = 27;
29
var OP_CALL = 28;
30
var OP_TAILCALL = 29;
31
var OP_RETURN = 30;
32
var OP_FORLOOP = 31;
33
var OP_FORPREP = 32;
34
var OP_TFORLOOP = 33;
35
var OP_SETLIST = 34;
36
var OP_CLOSE = 35;
37
var OP_CLOSURE = 36;
38
var OP_VARARG = 37;
39
 
40
var debugMode = false;
41
 
42
function LValue(vm, type, value)
43
{
44
	this.vm = vm;
45
	this.type = type||"nil";
46
	this.value = value;
47
}
48
 
49
LValue.prototype = {
50
	call: function (args)
51
	{
52
		var ret = this.vm.call(this, args);
53
		if(typeof(ret) == "undefined")
54
			ret = [];
55
		return ret;
56
	},
57
	precall: function ()
58
	{
59
		if(this.type == "function")
60
			return this.value;
61
		else
62
			throw "Attempt to call a " + this.type + " value";
63
	},
64
	index: function (key, raw)
65
	{
66
		if(this.type == "table")
67
		{
68
			var val;
69
			if(key.value in this.value)
70
				return this.value[key.value];
71
			else if(raw != true && this.metatable && this.metatable.type != "nil")
72
			{
73
				var __index = this.metatable.index(this.vm.LValue("__index"));
74
				if(__index.type == "function")
75
				{
76
					return this.vm.LValue(__index.call([this, key])[0]);
77
				}
78
				else if(__index.type != "nil")
79
					return __index.index(key);
80
			}
81
			return this.vm.LValue(null);
82
		}
83
		else
84
			throw "Attempt to index a " + this.type + " value";
85
	},
86
	setIndex: function (key, value)
87
	{
88
		if(this.type == "table")
89
		{
90
			this.value[key.value] = value;
91
		}
92
		else
93
			throw "Attempt to index a " + this.type + " value";
94
	},
95
	setMetatable: function (metatable)
96
	{
97
		if(metatable.type == "table")
98
			this.metatable = metatable;
99
		else if(metatable.type == "nil")
100
			this.metatable = null;
101
		else
102
			throw "Attempt to set a "+metatable.type+" value as a metatable";
103
	},
104
	toString: function ()
105
	{
106
		switch(this.type)
107
		{
108
		case "nil":
109
			return "nil";
110
		default:
111
			return this.value.toString();
112
		}
113
	},
114
	truth: function ()
115
	{
116
		if(this.type == "nil" || (this.type == "boolean" && this.value == false))
117
			return false;
118
		return true;
119
	},
120
	add: function (op2)
121
	{
122
		var metamethod;
123
		var __add = this.vm.LValue("__add");
124
		if(this.metatable)
125
			metamethod = this.metatable.index(__add);
126
		if((!metamethod || metamethod.type == "nil") && op2.metatable)
127
			metamethod = op2.metatable.index(__add);
128
		if(metamethod && metamethod.type != "nil")
129
		{
130
			return metamethod.call([this, op2]);
131
		}
132
		else if((this.type == "number" || this.type == "string")
133
			&& (op2.type == "number" || op2.type == "string"))
134
		{
135
			// Plain addition
136
			return this.vm.LValue(parseFloat(this.value, 10) + parseFloat(op2.value, 10));
137
		}
138
		else
139
			throw "Attempt to perform arithmetic on a "+this.type+" and "+op2.type;
140
	},
141
	equals: function (op2)
142
	{
143
		if(this.type != op2.type)
144
			return false;
145
		if(this.value == op2.value)
146
			return true;
147
		var __eq = this.vm.LValue("__eq");
148
		if(this.metatable && op2.metatable)
149
		{
150
			var metamethod1 = this.metatable.index(__eq);
151
			var metamethod2 = op2.metatable.index(__eq);
152
			if(metamethod1.equals(metamethod2))
153
			{
154
				var result = metamethod1.call([this, op2]);
155
				return (result[0].type != "nil"
156
					&& (result[0].type != "boolean" || result[0].value == true)
157
				);
158
			}
159
		}
160
		return false;
161
	},
162
	len: function ()
163
	{
164
		switch(this.type)
165
		{
166
		case "string":
167
		case "table":
168
			return this.value.length;
169
		default:
170
			throw "attempt to get length of a "+this.type+" value";
171
		}
172
	}
173
};
174
 
175
function LBinaryChunk(vm, chunk, start, sourceName)
176
{
177
	this.chunk = chunk;
178
	this.pos = start||12;
179
	
180
	this.sourceName = this.readString();
181
	if(sourceName)
182
		this.sourceName = sourceName;
183
	this.lineDefined = this.readInt();
184
	this.lastLineDefined = this.readInt();
185
	this.numUpvalues = this.readByte();
186
	this.numParameters = this.readByte();
187
	this.isVararg = this.readByte();
188
	this.maxStackSize = this.readByte();
189
	
190
	this.instructions = [];
191
	
192
	this.numInstructions = this.readInt();
193
	for(var i=0;i<this.numInstructions;i++)
194
	{
195
		var ins = this.readInt();
196
		this.instructions.push([
197
			ins&0x3F, // Opcode
198
			(ins>>6)&0xFF, // Field A
199
			(ins>>23)&0x1FF, // Field B
200
			(ins>>14)&0x1FF // Field C
201
		]);
202
		if(debugMode)
203
		{
204
			var pi = this.instructions[this.instructions.length-1];
205
			//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));
206
		}
207
	}
208
	
209
	this.constants = [];
210
	
211
	this.numConstants = this.readInt();
212
	for(var i=0;i<this.numConstants;i++)
213
	{
214
		var type = this.readByte();
215
		switch(type)
216
		{
217
		case 0: // Nil
218
			this.constants.push(new LValue(vm, "nil", null));
219
			break;
220
		case 1: // Boolean
221
			this.constants.push(new LValue(vm, "boolean", this.readByte())); // FIXME type
222
			break;
223
		case 3: // Number
224
			this.constants.push(new LValue(vm, "number", this.readNumber()));
225
			break;
226
		case 4: // String
227
			this.constants.push(new LValue(vm, "string", this.readString()));
228
			break;
229
		default:
230
			throw "Invalid constant type "+type+" in bytecode";
231
		}
232
	}
233
	
234
	this.prototypes = [];
235
	
236
	this.numPrototypes = this.readInt();
237
	for(var i=0;i<this.numPrototypes;i++)
238
	{
239
		var p = new LBinaryChunk(vm, chunk, this.pos, this.sourceName);
240
		this.pos = p.pos;
241
		this.prototypes.push(p);
242
	}
243
	
244
	this.sourceLines = [];
245
	
246
	this.numSourceLines = this.readInt();
247
	for(var i=0;i<this.numSourceLines;i++)
248
	{
249
		this.sourceLines.push(this.readInt());
250
	}
251
	
252
	this.localList = [];
253
	this.numLocalList = this.readInt();
254
	for(var i=0;i<this.numLocalList;i++)
255
	{
256
		this.localList.push([this.readString(),this.readInt(),this.readInt()]);
257
	}
258
	
259
	this.upvalueList = [];
260
	this.numUpvalueList = this.readInt();
261
	for(var i=0;i<this.numUpvalueList;i++)
262
	{
263
		this.upvalueList.push(this.readString());
264
	}
265
	
266
	return this;
267
}
268
 
269
LBinaryChunk.prototype = {
270
	readBytes: function (n)
271
	{
272
		return this.chunk.slice(this.pos, this.pos+=n);
273
	},
274
	readByte: function ()
275
	{
276
		return this.readBytes(1).charCodeAt(0);
277
	},
278
	readInt: function ()
279
	{
280
		//FIXME: Endianness
281
		return this.readByte() | (this.readByte()<<8)
282
			| (this.readByte()<<16) | (this.readByte()<<24);
283
	},
284
	readString: function ()
285
	{
286
		var len = this.readInt();
287
		return this.readBytes(len).substring(0,len-1);
288
	},
289
	readNumber: function ()
290
	{
291
		//FIXME: Endianness
292
		var bytes = [this.readByte(),this.readByte(),this.readByte(),this.readByte(),
293
		             this.readByte(),this.readByte(),this.readByte(),this.readByte()].reverse();
294
		
295
		var sign = (bytes[0]>>7)&0x1;
296
		var exp = (bytes[0]&0x7F)<<4 | (bytes[1]&0xf0)>>4;
297
		
298
		var frac = ((bytes[1] & 0x0f) * Math.pow(2,48))
299
		          + (bytes[2] * Math.pow(2,40))
300
		          + (bytes[3] * Math.pow(2,32))
301
		          + (bytes[4] * Math.pow(2,24))
302
		          + (bytes[5] * Math.pow(2,16))
303
		          + (bytes[6] * Math.pow(2,8))
304
		          +  bytes[7];
305
		
306
		if(exp != 0x000 && exp != 0x7FF)
307
		{
308
			var n = (sign==1?-1:1)*Math.pow(2,exp-1023)*(1+(frac/0x10000000000000));
309
			return n;
310
		}
311
		else if(exp == 0x000)
312
		{
313
			return sign*0;
314
		}
315
		else
316
			return frac==0?sign*Infinity:NaN;
317
	}
318
};
319
 
320
function INS_OPCODE(ins)
321
{
322
	return ins[0];
323
}
324
 
325
function INS_A(ins)
326
{
327
	return ins[1];
328
}
329
 
330
function INS_B(ins)
331
{
332
	return ins[2];
333
}
334
 
335
function INS_C(ins)
336
{
337
	return ins[3];
338
}
339
 
340
function INS_Bx(ins)
341
{
342
	return ((INS_C(ins))|(INS_B(ins)<<9));
343
}
344
 
345
function INS_sBx(ins)
346
{
347
	return (INS_Bx(ins)-0x1FFFF);
348
}
349
 
350
function RK(frame, R)
351
{
352
	var keysource = (R&0x100)?frame.f.constants:frame.reg;
353
	return keysource[R&0xff];
354
}
355
 
356
function LFunction(vm, chunk, env)
357
{
358
	var F = function () {};
359
	F.prototype = chunk;
360
	var o = new F();
361
	o.vm = vm;
362
	o.environment = env;
363
	o.chunk = chunk;
364
	o.upvalues = [];
365
	return o;
366
}
367
 
368
function LVM()
369
{
370
	this.callstack = [];
371
	this.stack = [];
372
	return this;
373
}
374
 
375
LVM.prototype = {
376
	LValue: function (value)
377
	{
378
		switch(typeof(value))
379
		{
380
		case "number":
381
			return new LValue(this, "number", value);
382
		case "string":
383
			return new LValue(this, "string", value);
384
		case "function":
385
			return new LValue(this, "function", value);
386
		case "object":
387
			if(value == null)
388
				return new LValue(this, "nil", value);
389
			else
390
				return new LValue(this, "table", value);
391
		case "undefined":
392
			return new LValue(this, "nil", null);
393
		default:
394
			throw "Not able to convert type " +
395
				typeof(value)+" from Javascript to Lua";
396
		}
397
	},
398
	call: function (func, args)
399
	{
400
		var f = func.precall();
401
		if(typeof(f) == "function")
402
		{
403
			return f.apply(this, args);
404
		}
405
		else if(f.instructions)
406
		{
407
			var frame = {f:f,pc:0,entry:true};
408
			if(args)
409
				frame.reg = args.slice(0);
410
			else
411
				frame.reg = [];
412
			this.callstack.push(frame);
413
			for(var i=frame.reg.length;i<f.maxStackSize;i++)
414
				frame.reg[i] = this.LValue(null);
415
			return this.run(frame);
416
		}
417
		else
418
			throw "Attempt to call invalid function object: "+f.toString();
419
	},
420
	run: function(frame)
421
	{
422
		var instruction;
423
		while(this.callstack.length>0)
424
		{
425
			instruction = frame.f.instructions[frame.pc++];
426
			if(debugMode)
427
			{
428
				sys.puts("PC: "+(frame.pc-1)+" OP: "+instruction[0]);
429
				for(var i = 0; i < frame.reg.length; i++)
430
				{
431
					var entry = frame.reg[i];
432
					if(entry && entry.type)
433
						sys.puts("\t"+i+":\t("+entry.type+") "+entry.toString());
434
					else
435
						sys.puts("\t"+i+": "+entry);
436
				}
437
			}
438
			switch(INS_OPCODE(instruction))
439
			{
440
			case OP_MOVE:
441
				frame.reg[INS_A(instruction)] = frame.reg[INS_B(instruction)];
442
				break;
443
			case OP_LOADNIL:
444
				for(var i = INS_A(instruction);i<=INS_B(instruction);i++)
445
					frame.reg[i] = new LValue(this, "nil", null);
446
				break;
447
			case OP_LOADBOOL:
448
				frame.reg[INS_A(instruction)] = new LValue(this, "boolean", INS_B(instruction)!=0);
449
				if(INS_C(instruction)!=0)
450
					frame.pc++;
451
				break;
452
			case OP_GETUPVAL:
453
				var upvalue = frame.f.upvalues[INS_B(instruction)];
454
				frame.reg[INS_A(instruction)] = new LValue(this, upvalue.type, upvalue.value);
455
				break;
456
			case OP_GETGLOBAL:
457
				var name = frame.f.constants[INS_Bx(instruction)];
458
				frame.reg[INS_A(instruction)] = frame.f.environment.index(name);
459
				break;
460
			case OP_SETUPVAL:
461
				var reg = frame.reg[INS_A(instruction)];
462
				var upvalue = frame.f.upvalues[INS_B(instruction)];
463
				upvalue.type = reg.type;
464
				upvalue.value = reg.value;
465
				break;
466
			case OP_SETGLOBAL:
467
				var name = frame.f.constants[INS_Bx(instruction)];
468
				frame.f.environment.setIndex(name, frame.reg[instruction[1]]);
469
				break;
470
			case OP_LOADK:
471
				var constant = frame.f.constants[INS_Bx(instruction)];
472
				frame.reg[INS_A(instruction)] = new LValue(this, constant.type, constant.value);
473
				break;
474
			case OP_NEWTABLE:
475
				frame.reg[INS_A(instruction)] = new LValue(this, "table", []);
476
				break;
477
			case OP_GETTABLE:
478
				var C = INS_C(instruction);
479
				var value = frame.reg[INS_B(instruction)].index(RK(frame, C));
480
				frame.reg[INS_A(instruction)] = new LValue(this, value.type, value.value);
481
				break;
482
			case OP_SETTABLE:
483
				var C = INS_C(instruction);
484
				var B = INS_B(instruction);
485
				frame.reg[INS_A(instruction)].setIndex(RK(frame, B), RK(frame, C));
486
				break;
487
			case OP_VARARG:
488
				var A = INS_A(instruction);
489
				var prevframe = this.callstack[this.callstack.length-2];
490
				var base = frame.retAt+frame.f.numParameters;
491
				var available = (prevframe.reg.length - base) - 1;
492
				var wanted = INS_B(instruction)-1;
493
				if(wanted < 0)
494
					wanted = available;
495
				for(var i = 0; i<wanted; i++)
496
				{
497
					if(i<available)
498
						frame.reg[A+i] = prevframe.reg[base+i+1];
499
					else
500
						frame.reg[A+i] = new LValue(this, "nil", null);
501
				}
502
				break;
503
			case OP_TAILCALL:
504
				var f = frame.reg[INS_A(instruction)].precall();
505
				if(typeof(f) != "function") // Can't tail-call native functions
506
				{
507
					var A = INS_A(instruction), B = INS_B(instruction);
508
					var undefined, args;
509
					if(B != 1)
510
						args = frame.reg.slice(A+1, B==0?undefined:(A+B));
511
					else
512
						args = [];
513
					if(args.length > f.numParameters)
514
						args.length = f.numParameters;
515
					for(var i=args.length;i<f.maxStackSize;i++)
516
						args[i] = new LValue(this, "nil", null); // Patch frame for new function
517
					frame.f = f; frame.pc = 0; frame.reg = args;
518
					break;
519
				}
520
				// Fall through...
521
			case OP_CALL:
522
				var f = frame.reg[INS_A(instruction)].precall(); // return JS or LFunction
523
				var A = INS_A(instruction), B = INS_B(instruction), C = INS_C(instruction);
524
				var undefined;
525
				var args;
526
				if(B != 1)
527
					args = frame.reg.slice(A+1, B==0?undefined:(A+B));
528
				else
529
					args = [];
530
				if(B != 0)
531
					frame.reg.length = A+B;
532
				if(typeof(f) == "function")
533
				{
534
					// JS native function
535
					var ret = this.call(frame.reg[INS_A(instruction)], args);
536
					// Insert ret to reg starting at R(A), with C-1 limit
537
					var nresults = ret.length;
538
					var nexpected;
539
					if(C == 0)
540
					{
541
						nexpected = nresults;
542
						frame.reg = frame.reg.slice(0, A+nexpected);
543
					}
544
					else
545
						nexpected = C-1;
546
					for(var i = 0;;i++)
547
					{
548
						if(i < nresults)
549
							frame.reg[A+i] = ret[i];
550
						else if(i < nexpected)
551
							frame.reg[A+i] = new LValue(this, "nil", null);
552
						else
553
							break;
554
					}
555
				}
556
				else
557
				{
558
					if(args.length > f.numParameters)
559
						args.length = f.numParameters;
560
					for(var i=args.length;i<f.maxStackSize;i++)
561
						args[i] = new LValue(this, "nil", null);
562
					// Lua function
563
					frame = {f:f,pc:0,reg:args,
564
						retAt:INS_A(instruction),retCount:INS_C(instruction),
565
						entry:false};
566
					this.callstack.push(frame);
567
				}
568
				break;
569
			case OP_CLOSURE:
570
				var prototype_id = INS_Bx(instruction);
571
				var chunk = frame.f.chunk.prototypes[prototype_id];
572
				var f = new LFunction(this, chunk, frame.f.environment);
573
				frame.reg[INS_A(instruction)] = new LValue(this, "function", f);
574
				for(var i=0;i<chunk.numUpvalues;i++)
575
				{
576
					var upval_instruction = frame.f.instructions[frame.pc++];
577
					switch(INS_OPCODE(upval_instruction))
578
					{
579
					case OP_MOVE:
580
						f.upvalues[i] = frame.reg[INS_B(upval_instruction)];
581
						break;
582
					case OP_GETUPVAL:
583
						f.upvalues[i] = frame.f.upvalues[INS_B(upval_instruction)];
584
						break;
585
					default:
586
						throw "Invalid upvalue opcode following OP_CLOSURE";
587
					}
588
				}
589
				break;
590
			case OP_RETURN:
591
				var oldFrame = this.callstack.pop();
592
				frame = this.callstack[this.callstack.length-1];
593
				var rets;
594
				if(INS_B(instruction) == 0)
595
					rets = oldFrame.reg.slice(INS_A(instruction));
596
				else
597
					rets = oldFrame.reg.slice(INS_A(instruction),INS_A(instruction)+(INS_B(instruction)-1));
598
				if(!oldFrame.entry)
599
				{
600
					var i;
601
					for(i=0;(oldFrame.retCount == 0||i<(oldFrame.retCount-1))&&i<rets.length;i++)
602
						frame.reg[oldFrame.retAt+i] = rets[i];
603
					if(oldFrame.retAt+i<frame.reg.length)
604
						frame.reg.length = (oldFrame.retAt+i);
605
					if(i<oldFrame.retCount)
606
					{
607
						for(;i<oldFrame.retCount;i++)
608
							frame.reg[oldFrame.retAt+i] = new LValue(this, "nil", null);
609
					}
610
				}
611
				else
612
					return rets;
613
				break;
614
			case OP_CLOSE:
615
				// No-op, since we leave upvalue management to the GC
616
				break;
617
			case OP_SELF:
618
				var table = frame.reg[INS_B(instruction)];
619
				frame.reg[INS_A(instruction)+1] = table;
620
				var C = INS_C(instruction);
621
				frame.reg[INS_A(instruction)] = table.index(RK(frame, C));
622
				break;
623
			case OP_FORPREP:
624
				frame.pc+=(INS_sBx(instruction));
625
				var A = INS_A(instruction);
626
				frame.reg[A].value -= frame.reg[A+2].value;
627
				break;
628
			case OP_FORLOOP:
629
				var A = INS_A(instruction);
630
				var RA = frame.reg[A];
631
				RA.value += frame.reg[A+2].value;
632
				if(RA.value <= frame.reg[A+1].value)
633
				{
634
					frame.pc += INS_sBx(instruction);
635
					frame.reg[A+3] = new LValue(this, "number", RA.value);
636
				}
637
				break;
638
			case OP_TFORLOOP:
639
				var A = INS_A(instruction);
640
				var C = INS_C(instruction);
641
				var RA = frame.reg[A]; // Iterator function
642
				var rets = this.call(RA, [frame.reg[A+1], frame.reg[A+2]]);
643
				frame.reg.length = A+3;
644
				for(var i = 0; i<C; i++)
645
					frame.reg[A+3+i] = rets[i];
646
				if(frame.reg[A+3].type != "nil")
647
					frame.reg[A+2] = frame.reg[A+3];
648
				else
649
					frame.pc++; // Skip JMP to start
650
				continue;
651
			case OP_TEST:
652
				var RA = frame.reg[INS_A(instruction)];
653
				var RA_bool = RA.type == "nil" || (RA.type == "boolean" && RA.value == false);
654
				if(RA_bool == (INS_C(instruction)!=0))
655
					frame.pc++;
656
				break;
657
			case OP_TESTSET:
658
				var RB = frame.reg[INS_B(instruction)];
659
				var RB_bool = RB.type == "nil" || (RB.type == "boolean" && RB.value == false);
660
				if(RB_bool == (INS_C(instruction)!=0))
661
					frame.pc++;
662
				else
663
					frame.reg[INS_A(instruction)] = RB;
664
				break;
665
			case OP_JMP:
666
				frame.pc+=INS_sBx(instruction);
667
				break;
668
			case OP_CONCAT:
669
				var A = INS_A(instruction);
670
				var B = INS_B(instruction);
671
				var C = INS_C(instruction);
672
				var values = [];
673
				for(var i = B; i<=C; i++)
674
					values.push(frame.reg[i].value);
675
				frame.reg[A] = new LValue(this, "string", values.join(''));
676
				break;
677
			case OP_ADD:
678
				var RB = RK(frame, INS_B(instruction));
679
				var RC = RK(frame, INS_C(instruction));
680
				frame.reg[INS_A(instruction)] = RB.add(RC);
681
				break;
682
			case OP_SUB:
683
				var RB = RK(frame, INS_B(instruction));
684
				var RC = RK(frame, INS_C(instruction));
685
				frame.reg[INS_A(instruction)] = new LValue(this, "number", RB.value - RC.value);
686
				break;
687
			case OP_MUL:
688
				var RB = RK(frame, INS_B(instruction));
689
				var RC = RK(frame, INS_C(instruction));
690
				frame.reg[INS_A(instruction)] = new LValue(this, "number", RB.value * RC.value);
691
				break;
692
			case OP_DIV:
693
				var RB = RK(frame, INS_B(instruction));
694
				var RC = RK(frame, INS_C(instruction));
695
				frame.reg[INS_A(instruction)] = new LValue(this, "number", RB.value / RC.value);
696
				break;
697
			case OP_MOD:
698
				var RB = RK(frame, INS_B(instruction));
699
				var RC = RK(frame, INS_C(instruction));
700
				frame.reg[INS_A(instruction)] = new LValue(this, "number", RB.value % RC.value);
701
				break;
702
			case OP_POW:
703
				var RB = RK(frame, INS_B(instruction));
704
				var RC = RK(frame, INS_C(instruction));
705
				frame.reg[INS_A(instruction)] = new LValue(this, "number", Math.pow(RB.value, RC.value));
706
				break;
707
			case OP_UNM:
708
				var RB = frame.reg[INS_B(instruction)];
709
				frame.reg[INS_A(instruction)] = new LValue(this, "number", -RB.value);
710
				break;
711
			case OP_NOT:
712
				var RB = frame.reg[INS_B(instruction)];
713
				frame.reg[INS_A(instruction)] = new LValue(this, "boolean", !RB.truth());
714
				break;
715
			case OP_LEN:
716
				var RB = frame.reg[INS_B(instruction)];
717
				frame.reg[INS_A(instruction)] = new LValue(this, "number", RB.len());
718
				break;
719
			case OP_EQ:
720
				var A = INS_A(instruction);
721
				var RB = RK(frame, INS_B(instruction));
722
				var RC = RK(frame, INS_C(instruction));
723
				if(RB.equals(RC) != (A!=0))
724
					frame.pc++;
725
				break;
726
			case OP_LT:
727
				var A = INS_A(instruction);
728
				var RB = RK(frame, INS_B(instruction));
729
				var RC = RK(frame, INS_C(instruction));
730
				if(RB.value < RC.value != (A!=0))
731
					frame.pc++;
732
				break;
733
			case OP_LE:
734
				var A = INS_A(instruction);
735
				var RB = RK(frame, INS_B(instruction));
736
				var RC = RK(frame, INS_C(instruction));
737
				if(RB.value <= RC.value != (A!=0))
738
					frame.pc++;
739
				break;
740
			case OP_SETLIST:
741
				var A = INS_A(instruction);
742
				var RA = frame.reg[A];
743
				var B = INS_B(instruction);
744
				var C = INS_C(instruction);
745
				if(C == 0)
746
					throw "Dynamic table construction not yet implemented";
747
				// #define LFIELDS_PER_FLUSH 50 // Lua 5.1
748
				var baseindex = (C-1)*50;
749
				var index = new LValue(this, "number", 1);
750
				var lim = B>0?B:((frame.reg.length-baseindex)-2);
751
				for(var i = 1; i<=lim; index.value=(baseindex+(++i)))
752
					RA.setIndex(index, frame.reg[A+i]);
753
				break;
754
			default:
755
				throw "Unhandled opcode: "+INS_OPCODE(instruction);
756
			}
757
		}
758
	},
759
	registerLib: function (env, name, lib)
760
	{
761
		var t;
762
		if(name)
763
		{
764
			t = this.LValue([]); // Create env[name] and put fns in there
765
			env.setIndex(this.LValue(name), t);
766
		}
767
		else
768
			t = env; // Import directly into env
769
		
770
		for(var k in lib)
771
			t.setIndex(this.LValue(k), this.LValue(lib[k]));
772
	},
773
	loadstring: function (chunk, env)
774
	{
775
		var c = new LBinaryChunk(this, chunk);
776
		var f = new LFunction(this, c, env);
777
		return new LValue(this, "function", f);
778
	},
779
	traceback: function ()
780
	{
781
		var trace = [];
782
		for(var i=this.callstack.length-1; i>=0; i--)
783
		{
784
			var currframe = testvm.callstack[i];
785
			var currfunc = currframe.f;
786
			var sourceName = (currfunc.sourceName||"=?").substr(1);
787
			var line = "?";
788
			if(currfunc.sourceLines && currfunc.sourceLines[currframe.pc-1])
789
				line = currfunc.sourceLines[currframe.pc-1];
790
			trace.push({ sourceName: sourceName, line: line });
791
		}
792
		return trace;
793
	}
794
};
795
 
796
try{
797
var testvm = new LVM();
798
 
799
var fs=require("fs");
800
var sys=require("sys");
801
 
802
var _G = testvm.LValue([]);
803
 
804
// Standard library
805
 
806
var baselib = {
807
	error: function (message)
808
	{
809
		throw message.toString();
810
	},
811
	print: function ()
812
	{
813
		var args = Array.prototype.slice.call(arguments);
814
		sys.print(args[0].toString());
815
		for(var i = 1; i<args.length; i++)
816
			sys.print("\t"+args[i].toString());
817
		sys.print("\n");
818
		return [];
819
	},
820
	setmetatable: function (table, metatable)
821
	{
822
		if(arguments.length!=2)
823
			throw "setmetatable expects 2 arguments, got "+arguments.length;
824
 
825
		table.setMetatable(metatable);
826
		return [table];
827
	},
828
	type: function (o)
829
	{
830
		return [this.LValue(o.type)];
831
	},
832
	assert: function (expr, message)
833
	{
834
		if(!expr.truth())
835
			if(message && message.truth())
836
				throw message;
837
			else
838
				throw "assertion failed";
839
		return [expr];
840
	}
841
};
842
 
843
var math = {
844
	ldexp: function (m, e)
845
	{
846
		return [this.LValue(m.value*Math.pow(2, e.value))];
847
	},
848
	frexp: function (x)
849
	{
850
		var m = x.value;
851
		var e = 0;
852
		if(m != 0) {
853
			while (Math.abs(m) < 0.5) {
854
				m = m * 2;
855
				e = e - 1;
856
			}
857
			while (Math.abs(m) >= 1) {
858
				m = m / 2;
859
				e = e + 1;
860
			}
861
		}
862
		return [this.LValue(m), this.LValue(e)];
863
	},
864
	floor: function (x)
865
	{
866
		return [this.LValue(Math.floor(x.value))];
867
	}
868
};
869
 
870
var _patternClasses = {
871
	"a": "[A-Za-z]", "A": "[^A-Za-z]",
872
	"p": "[\x21-\x2f\x3a-\x40\x5b-\x60\x7b-\x7e]",
873
	"P": "[^\x21-\x2f\x3a-\x40\x5b-\x60\x7b-\x7e]",
874
	"s": "[\x09-\x0d\x20]", "S": "[^\x09-\x0d\x20]",
875
	"u": "[A-Z]", "U": "[^A-Z]",
876
	"d": "[0-9]", "D": "[^0-9]",
877
	"w": "[0-9A-Za-z]", "W": "[^0-9A-Za-z]",
878
	"x": "[0-9A-Fa-f]", "X": "[^0-9A-Fa-f]",
879
	"z": "[\x00]", "Z": "[^\x00]",
880
	"l": "[a-z]", "L": "[^a-z]",
881
	"c": "[\x00-\x1f\x7f]", "C": "[^\x00-\x1f\x7f]",
882
};
883
 
884
var _patternToRegExp = function (patt)
885
{
886
	var regexp = "";
887
	for(var i=0;i<patt.length;i++)
888
	{
889
		var c = patt[i];
890
		if(c == "%")
891
		{
892
			c = patt[++i];
893
			if(c == "b")
894
			{
895
				throw "%b not supported in patterns";
896
			}
897
			else if(c >= "0" && c <= "9")
898
			{
899
				regexp += ("\\"+c);
900
				continue;
901
			}
902
			else
903
			{
904
				var cls = _patternClasses[c];
905
				if(cls)
906
				{
907
					regexp += cls;
908
					continue;
909
				}
910
			}
911
		}
912
		else if(c == "\\" || c == "/")
913
			regexp += "\\"; // Escape escapes
914
		regexp += c;
915
	}
916
	return new RegExp(regexp, "g");
917
};
918
 
919
var string = {
920
	"char": function ()
921
	{
922
		var nArgs = arguments.length;
923
		if(nArgs < 1)
924
			throw "string.char(): Expects at least 1 parameter";
925
		var results = [];
926
		for(var i=0; i<nArgs; i++)
927
		{
928
			var code = arguments[i];
929
			if(code.type != "number")
930
				throw "string.char(): Argument #"+(i+1)+" expected number, got "+code.type;
931
			results.push(String.fromCharCode(code.value));
932
		}
933
		return [this.LValue(results.join(''))];
934
	},
935
	find: function (str, patt, init, plain)
936
	{
937
		if(arguments.length > 2)
938
			throw "string.find(): No more than the first 2 arguments supported";
939
		var re = _patternToRegExp(patt.value);
940
		var result = re.exec(str);
941
		if(!result)
942
			return [this.LValue(null)];
943
		var start = result.index+1;
944
		var end = start + result[0].length - 1;
945
		var ret = [this.LValue(start), this.LValue(end)];
946
		for(var i=1; i<result.length; i++)
947
			ret.push(this.LValue(result[i]));
948
		return ret;
949
	},
950
	format: function (format_)
951
	{
952
		var format = format_.value, result = "";
953
		var re = new RegExp("%([0-9. ]*)([a-zA-Z%])", "g");
954
		var match, currpos = 0, currparam = 1;
955
		while(match = re.exec(format))
956
		{
957
			result += format.substring(currpos, match.index);
958
			currpos = re.lastIndex;
959
			switch(match[2])
960
			{
961
			case "f": case "d":
962
				if(match[1].length>0)
963
					throw "string.format(): Number format modifers not yet implemented";
964
			case "s":
965
				result+=arguments[currparam++].value.toString();
966
			case "%":
967
				break;
968
			default:
969
				throw "string.format(): Format %"+match[2]+" not implemented";
970
			}
971
		}
972
		result += format.substring(currpos);
973
		return [this.LValue(result)];
974
	},
975
	gmatch: function (str, patt)
976
	{
977
		var re = _patternToRegExp(patt.value);
978
		var matches = str.value.match(re)||[];
979
		var curr = 0;
980
		var iter = function ()
981
		{
982
			return [this.LValue(matches[curr++])];
983
		};
984
		return [this.LValue(iter)];
985
	},
986
	sub: function (str, from, to)
987
	{
988
		var result;
989
		switch(arguments.length)
990
		{
991
		case 0:
992
		case 1:
993
			throw "string.sub(): Expected 2 or more arguments";
994
		case 2:
995
			result = str.value.substring(from.value);
996
		case 3:
997
			result = str.value.substring(from.value, to.value);
998
		}
999
		return [this.LValue(result)];
1000
	}
1001
};
1002
testvm.registerLib(_G, null, baselib);
1003
testvm.registerLib(_G, "math", math);
1004
testvm.registerLib(_G, "string", string);
1005
 
1006
// Metatable on environment to print out nil global accesses
1007
var mt = testvm.LValue([]);
1008
mt.setIndex(
1009
	testvm.LValue("__index"),
1010
	testvm.LValue(function (t, k) { sys.puts("Access of nil global: "+k); })
1011
);
1012
_G.setMetatable(mt);
1013
 
1014
 
1015
var f = testvm.loadstring(fs.readFileSync("luac.out", "binary"), _G);
1016
 
1017
var ret = testvm.call(f);
1018
if(ret)
1019
	sys.puts("Returned: "+sys.inspect(ret));
1020
 
1021
}
1022
catch(e)
1023
{
1024
	var trace = testvm.traceback();
1025
	var currframe = trace[0];
1026
	if(currframe)
1027
	{
1028
		sys.print("lvm.js: "+currframe.sourceName+":"+currframe.line+": ");
1029
	}
1030
	sys.puts(e);
1031
	if(typeof(e) == "object" && "stack" in e)
1032
		sys.puts(e.stack);
1033
	process.exit(1);
1034
}