summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--luac.outbin221 -> 241 bytes
-rw-r--r--src/lvm.js292
-rw-r--r--tests/lvm.js53
3 files changed, 329 insertions, 16 deletions
diff --git a/luac.out b/luac.out
index eb528fa..f5b0ee0 100644
--- a/luac.out
+++ b/luac.out
Binary files differ
diff --git a/src/lvm.js b/src/lvm.js
index 964abe7..0c52eaf 100644
--- a/src/lvm.js
+++ b/src/lvm.js
@@ -3,6 +3,7 @@
const BytecodeParser = require("./lundump.js");
const OC = require('./lopcodes.js');
+const CT = require('./lua.js').constant_types;
class LuaVM {
@@ -10,24 +11,46 @@ class LuaVM {
this.L = L;
}
- RA(base, a) {
- return base + a;
+ RA(base, i) {
+ return base + i.A;
}
- RB(base, opcode, b) {
- return base + b;
+ RB(base, i) {
+ return base + i.B;
}
- RC(base, c) {
- return base + c;
+ RC(base, i) {
+ return base + i.C;
}
- RKB(base, k, b) {
- return OC.ISK(b) ? k[OC.INDEXK(b)] : base + b;
+ RKB(base, k, i) {
+ return OC.ISK(b) ? k[OC.INDEXK(b)] : base + i.B;
}
- RKC(base, k, c) {
- return OC.ISK(c) ? k[OC.INDEXK(c)] : base + c;
+ RKC(base, k, i) {
+ return OC.ISK(c) ? k[OC.INDEXK(c)] : base + i.C;
+ }
+
+ static tonumber(v) {
+ if (v.type === CT.LUA_TNUMFLT)
+ return {
+ type: v.type,
+ value: v.value
+ };
+
+ if (v.type === CT.LUA_TNUMINT)
+ return {
+ type: CT.LUA_TNUMFLT,
+ value: v.value
+ };
+
+ if (v.type === CT.LUA_TSHRSTR || v.type === CT.LUA_TLNGSTR)
+ return {
+ type: CT.LUA_TNUMFLT,
+ value: parseFloat(v.value) // TODO 0x or other exotic form
+ };
+
+ return false;
}
execute() {
@@ -41,20 +64,36 @@ class LuaVM {
let base = ci.base;
let i = ci.savedpc[ci.pcOff++];
- let ra = this.RA(base, i.A);
-
+ let ra = this.RA(base, i);
+
+ var op1, op2, numberop1, numberop2, op, numberop;
switch (OC.OpCodes[i.opcode]) {
case "OP_MOVE":
- L.stack[ra] = L.stack[this.RB(base, i.opcode, i.B)];
+ L.stack[ra] = L.stack[this.RB(base, i)];
break;
case "OP_LOADK":
L.stack[ra] = k[i.Bx];
break;
case "OP_LOADKX":
+ assert(OC.OpCodes[ci.savedpc[ci.pcOff].opcode] === "OP_EXTRAARG");
+ L.stack[ra] = k[ci.savedpc[ci.pcOff++].Ax];
break;
case "OP_LOADBOOL":
+ L.stack[ra] = {
+ type: CT.LUA_TBOOLEAN,
+ value: i.B !== 0
+ };
+
+ if (i.C !== 0)
+ ci.pcOff++; /* skip next instruction (if C) */
+
break;
case "OP_LOADNIL":
+ for (let j = 0; j <= i.B; j++)
+ L.stack[ra + j] = {
+ type: CT.LUA_TNIL,
+ value: null
+ }
break;
case "OP_GETUPVAL":
break;
@@ -73,34 +112,261 @@ class LuaVM {
case "OP_SELF":
break;
case "OP_ADD":
+ op1 = k[i.B];
+ op2 = k[i.C];
+ numberop1 = LuaVM.tonumber(op1);
+ numberop2 = LuaVM.tonumber(op2);
+
+ if (op1.type === CT.LUA_TNUMINT && op2.type === CT.LUA_TNUMINT) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMINT,
+ value: k[i.B].value + k[i.C].value
+ };
+ } else if (numberop1 !== false && numberop2 !== false) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMFLT,
+ value: k[i.B].value + k[i.C].value
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_SUB":
+ op1 = k[i.B];
+ op2 = k[i.C];
+ numberop1 = LuaVM.tonumber(op1);
+ numberop2 = LuaVM.tonumber(op2);
+
+ if (op1.type === CT.LUA_TNUMINT && op2.type === CT.LUA_TNUMINT) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMINT,
+ value: k[i.B].value - k[i.C].value
+ };
+ } else if (numberop1 !== false && numberop2 !== false) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMFLT,
+ value: k[i.B].value - k[i.C].value
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_MUL":
+ op1 = k[i.B];
+ op2 = k[i.C];
+ numberop1 = LuaVM.tonumber(op1);
+ numberop2 = LuaVM.tonumber(op2);
+
+ if (op1.type === CT.LUA_TNUMINT && op2.type === CT.LUA_TNUMINT) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMINT,
+ value: k[i.B].value * k[i.C].value
+ };
+ } else if (numberop1 !== false && numberop2 !== false) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMFLT,
+ value: k[i.B].value * k[i.C].value
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_MOD":
+ op1 = k[i.B];
+ op2 = k[i.C];
+ numberop1 = LuaVM.tonumber(op1);
+ numberop2 = LuaVM.tonumber(op2);
+
+ if (op1.type === CT.LUA_TNUMINT && op2.type === CT.LUA_TNUMINT) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMINT,
+ value: k[i.B].value % k[i.C].value
+ };
+ } else if (numberop1 !== false && numberop2 !== false) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMFLT,
+ value: k[i.B].value % k[i.C].value
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_POW":
+ op1 = k[i.B];
+ op2 = k[i.C];
+ numberop1 = LuaVM.tonumber(op1);
+ numberop2 = LuaVM.tonumber(op2);
+
+ if (numberop1 !== false && numberop2 !== false) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMFLT,
+ value: Math.pow(k[i.B].value, k[i.C].value)
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_DIV":
+ op1 = k[i.B];
+ op2 = k[i.C];
+ numberop1 = LuaVM.tonumber(op1);
+ numberop2 = LuaVM.tonumber(op2);
+
+ if (numberop1 !== false && numberop2 !== false) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMFLT,
+ value: k[i.B].value / k[i.C].value
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_IDIV":
+ op1 = k[i.B];
+ op2 = k[i.C];
+ numberop1 = LuaVM.tonumber(op1);
+ numberop2 = LuaVM.tonumber(op2);
+
+ if (op1.type === CT.LUA_TNUMINT && op2.type === CT.LUA_TNUMINT) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMINT,
+ value: Math.floor(k[i.B].value / k[i.C].value)
+ };
+ } else if (numberop1 !== false && numberop2 !== false) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMFLT,
+ value: Math.floor(k[i.B].value / k[i.C].value)
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_BAND":
+ op1 = k[i.B];
+ op2 = k[i.C];
+ numberop1 = LuaVM.tonumber(op1);
+ numberop2 = LuaVM.tonumber(op2);
+
+ if (op1.type === CT.LUA_TNUMINT && op2.type === CT.LUA_TNUMINT) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMINT,
+ value: k[i.B].value & k[i.C].value
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_BOR":
+ op1 = k[i.B];
+ op2 = k[i.C];
+ numberop1 = LuaVM.tonumber(op1);
+ numberop2 = LuaVM.tonumber(op2);
+
+ if (op1.type === CT.LUA_TNUMINT && op2.type === CT.LUA_TNUMINT) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMINT,
+ value: k[i.B].value | k[i.C].value
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_BXOR":
+ op1 = k[i.B];
+ op2 = k[i.C];
+ numberop1 = LuaVM.tonumber(op1);
+ numberop2 = LuaVM.tonumber(op2);
+
+ if (op1.type === CT.LUA_TNUMINT && op2.type === CT.LUA_TNUMINT) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMINT,
+ value: k[i.B].value ^ k[i.C].value
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_SHL":
+ op1 = k[i.B];
+ op2 = k[i.C];
+ numberop1 = LuaVM.tonumber(op1);
+ numberop2 = LuaVM.tonumber(op2);
+
+ if (op1.type === CT.LUA_TNUMINT && op2.type === CT.LUA_TNUMINT) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMINT,
+ value: k[i.B].value << k[i.C].value
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_SHR":
+ op1 = k[i.B];
+ op2 = k[i.C];
+ numberop1 = LuaVM.tonumber(op1);
+ numberop2 = LuaVM.tonumber(op2);
+
+ if (op1.type === CT.LUA_TNUMINT && op2.type === CT.LUA_TNUMINT) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMINT,
+ value: k[i.B].value >> k[i.C].value
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_UNM":
+ op = L.stack[this.RB(base, i)];
+ numberop = LuaVM.tonumber(op);
+
+ if (op.type === CT.LUA_TNUMINT) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMINT,
+ value: -L.stack[this.RB(base, i)].value
+ };
+ } else if (numberop !== false) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMFLT,
+ value: -L.stack[this.RB(base, i)].value
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform unary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_BNOT":
+ op = L.stack[this.RB(base, i)];
+ numberop = LuaVM.tonumber(op);
+
+ if (op.type === CT.LUA_TNUMINT) {
+ L.stack[ra] = {
+ type: CT.LUA_TNUMINT,
+ value: ~L.stack[this.RB(base, i)].value
+ };
+ } else {
+ // Metamethod
+ throw new Error(`Can't perform unary operation on ${k[i.B].value} and ${k[i.C].value}`);
+ }
break;
case "OP_NOT":
+ op = L.stack[this.RB(base, i)];
+ L.stack[ra] = {
+ type: CT.LUA_TBOOLEAN,
+ value: !!((op.type === CT.LUA_TBOOLEAN && !op.value) || op.type === CT.LUA_TNIL)
+ }
break;
case "OP_LEN":
break;
diff --git a/tests/lvm.js b/tests/lvm.js
index ffd128d..d2c716a 100644
--- a/tests/lvm.js
+++ b/tests/lvm.js
@@ -73,18 +73,65 @@ test('MOV', function (t) {
return b
`, vm;
- t.plan(1);
+ t.plan(2);
t.comment("Running following code: \n" + luaCode);
- // t.doesNotThrow(function () {
+ t.doesNotThrow(function () {
vm = getVM(luaCode);
vm.execute();
- // }, "Program executed without errors");
+ }, "Program executed without errors");
t.strictEqual(
vm.L.stack[0].value,
"hello world",
"Program output is correct"
);
+});
+
+test('Binary op', function (t) {
+ let luaCode = `
+ local a = 5
+ local b = 10
+ return a + b, a - b, a * b, a / b, a % b, a^b, a // b, a & b, a | b, a ~ b, a << b, a >> b
+ `, vm;
+
+ t.plan(2);
+
+ t.comment("Running following code: \n" + luaCode);
+
+ t.doesNotThrow(function () {
+ vm = getVM(luaCode);
+ vm.execute();
+ }, "Program executed without errors");
+
+ t.deepEqual(
+ vm.L.stack.slice(0, 12).map(function (e) { return e.value; }),
+ [15, -5, 50, 0.5, 5, 9765625.0, 0, 0, 15, 15, 5120, 0],
+ "Program output is correct"
+ );
+});
+
+
+test('Unary op', function (t) {
+ let luaCode = `
+ local a = 5
+ local b = false
+ return -a, not b, ~a
+ `, vm;
+
+ t.plan(2);
+
+ t.comment("Running following code: \n" + luaCode);
+
+ t.doesNotThrow(function () {
+ vm = getVM(luaCode);
+ vm.execute();
+ }, "Program executed without errors");
+
+ t.deepEqual(
+ vm.L.stack.slice(0, 3).map(function (e) { return e.value; }),
+ [-5, true, -6],
+ "Program output is correct"
+ );
}); \ No newline at end of file