From 675b7642a9d624fa013416438ccca48ab6448ce6 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Fri, 3 Feb 2017 14:17:39 +0100 Subject: Binary/Unary operators --- luac.out | Bin 221 -> 241 bytes src/lvm.js | 292 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- tests/lvm.js | 53 ++++++++++- 3 files changed, 329 insertions(+), 16 deletions(-) diff --git a/luac.out b/luac.out index eb528fa..f5b0ee0 100644 Binary files a/luac.out and b/luac.out 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 -- cgit v1.2.3-54-g00ecf