diff options
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | src/lopcodes.js | 110 | ||||
-rw-r--r-- | src/lundump.js | 4 | ||||
-rw-r--r-- | src/lvm.js | 91 | ||||
-rw-r--r-- | tests/lvm.js | 50 |
5 files changed, 182 insertions, 77 deletions
@@ -36,8 +36,8 @@ - [ ] OP_LEN - [ ] OP_CONCAT - [x] OP_JMP - - [ ] OP_EQ - - [ ] OP_LT + - [x] OP_EQ + - [x] OP_LT - [x] OP_LE - [ ] OP_TEST - [ ] OP_TESTSET diff --git a/src/lopcodes.js b/src/lopcodes.js index 8df67a3..05eb0d5 100644 --- a/src/lopcodes.js +++ b/src/lopcodes.js @@ -2,69 +2,53 @@ "use strict"; const OpCodes = [ - "OP_MOVE", /* A B R(A) := R(B) */ - "OP_LOADK", /* A Bx R(A) := Kst(Bx) */ - "OP_LOADKX", /* A R(A) := Kst(extra arg) */ - "OP_LOADBOOL", /* A B C R(A) := (Bool)B; if (C) pc++ */ - "OP_LOADNIL", /* A B R(A), R(A+1), ..., R(A+B) := nil */ - "OP_GETUPVAL", /* A B R(A) := UpValue[B] */ - - "OP_GETTABUP", /* A B C R(A) := UpValue[B][RK(C)] */ - "OP_GETTABLE", /* A B C R(A) := R(B)[RK(C)] */ - - "OP_SETTABUP", /* A B C UpValue[A][RK(B)] := RK(C) */ - "OP_SETUPVAL", /* A B UpValue[B] := R(A) */ - "OP_SETTABLE", /* A B C R(A)[RK(B)] := RK(C) */ - - "OP_NEWTABLE", /* A B C R(A) := {} (size = B,C) */ - - "OP_SELF", /* A B C R(A+1) := R(B); R(A) := R(B)[RK(C)] */ - - "OP_ADD", /* A B C R(A) := RK(B) + RK(C) */ - "OP_SUB", /* A B C R(A) := RK(B) - RK(C) */ - "OP_MUL", /* A B C R(A) := RK(B) * RK(C) */ - "OP_MOD", /* A B C R(A) := RK(B) % RK(C) */ - "OP_POW", /* A B C R(A) := RK(B) ^ RK(C) */ - "OP_DIV", /* A B C R(A) := RK(B) / RK(C) */ - "OP_IDIV", /* A B C R(A) := RK(B) // RK(C) */ - "OP_BAND", /* A B C R(A) := RK(B) & RK(C) */ - "OP_BOR", /* A B C R(A) := RK(B) | RK(C) */ - "OP_BXOR", /* A B C R(A) := RK(B) ~ RK(C) */ - "OP_SHL", /* A B C R(A) := RK(B) << RK(C) */ - "OP_SHR", /* A B C R(A) := RK(B) >> RK(C) */ - "OP_UNM", /* A B R(A) := -R(B) */ - "OP_BNOT", /* A B R(A) := ~R(B) */ - "OP_NOT", /* A B R(A) := not R(B) */ - "OP_LEN", /* A B R(A) := length of R(B) */ - - "OP_CONCAT", /* A B C R(A) := R(B).. ... ..R(C) */ - - "OP_JMP", /* A sBx pc+=sBx; if (A) close all upvalues >= R(A - 1) */ - "OP_EQ", /* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */ - "OP_LT", /* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */ - "OP_LE", /* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */ - - "OP_TEST", /* A C if not (R(A) <=> C) then pc++ */ - "OP_TESTSET", /* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */ - - "OP_CALL", /* A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */ - "OP_TAILCALL", /* A B C return R(A)(R(A+1), ... ,R(A+B-1)) */ - "OP_RETURN", /* A B return R(A), ... ,R(A+B-2) (see note) */ - - "OP_FORLOOP", /* A sBx R(A)+=R(A+2); - if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }*/ - "OP_FORPREP", /* A sBx R(A)-=R(A+2); pc+=sBx */ - - "OP_TFORCALL", /* A C R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2)); */ - "OP_TFORLOOP", /* A sBx if R(A+1) ~= nil then { R(A)=R(A+1); pc += sBx }*/ - - "OP_SETLIST", /* A B C R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B */ - - "OP_CLOSURE", /* A Bx R(A) := closure(KPROTO[Bx]) */ - - "OP_VARARG", /* A B R(A), R(A+1), ..., R(A+B-2) = vararg */ - - "OP_EXTRAARG" /* Ax extra (larger) argument for previous opcode */ + "OP_MOVE", + "OP_LOADK", + "OP_LOADKX", + "OP_LOADBOOL", + "OP_LOADNIL", + "OP_GETUPVAL", + "OP_GETTABUP", + "OP_GETTABLE", + "OP_SETTABUP", + "OP_SETUPVAL", + "OP_SETTABLE", + "OP_NEWTABLE", + "OP_SELF", + "OP_ADD", + "OP_SUB", + "OP_MUL", + "OP_MOD", + "OP_POW", + "OP_DIV", + "OP_IDIV", + "OP_BAND", + "OP_BOR", + "OP_BXOR", + "OP_SHL", + "OP_SHR", + "OP_UNM", + "OP_BNOT", + "OP_NOT", + "OP_LEN", + "OP_CONCAT", + "OP_JMP", + "OP_EQ", + "OP_LT", + "OP_LE", + "OP_TEST", + "OP_TESTSET", + "OP_CALL", + "OP_TAILCALL", + "OP_RETURN", + "OP_FORLOOP", + "OP_FORPREP", + "OP_TFORCALL", + "OP_TFORLOOP", + "OP_SETLIST", + "OP_CLOSURE", + "OP_VARARG", + "OP_EXTRAARG" ]; const SIZE_C = 9; diff --git a/src/lundump.js b/src/lundump.js index f2f91f1..b104e32 100644 --- a/src/lundump.js +++ b/src/lundump.js @@ -83,7 +83,7 @@ class BytecodeParser { readString(n) { let size = typeof n !== 'undefined' ? n : this.readByte() - 1; - if (size === 0xFF) // TODO test + if (size === 0xFF) // TODO: test this.offset += this.size_tSize; if (size === 0) { @@ -131,7 +131,7 @@ class BytecodeParser { C: (ins >> o.POS_C) & p.MASK1(o.SIZE_C, 0), Bx: (ins >> o.POS_Bx) & p.MASK1(o.SIZE_Bx, 0), Ax: (ins >> o.POS_Ax) & p.MASK1(o.SIZE_Ax, 0), - sBx: (ins >> o.POS_Bx) & p.MASK1(o.SIZE_Bx, 0) - o.MAXARG_sBx + sBx: ((ins >> o.POS_Bx) & p.MASK1(o.SIZE_Bx, 0)) - o.MAXARG_sBx }; console.log(` [${i}] Op: ${o.OpCodes[f.code[i].opcode]} A: ${f.code[i].A} B: ${f.code[i].B} C: ${f.code[i].C} Ax: ${f.code[i].Ax} Bx: ${f.code[i].Bx} sBx: ${f.code[i].sBx}`); @@ -53,7 +53,7 @@ class LuaVM { return new TValue(CT.LUA_TNUMFLT, v.value); if (v.type === CT.LUA_TSHRSTR || v.type === CT.LUA_TLNGSTR) - return new TValue(CT.LUA_TNUMFLT, parseFloat(v.value)); // TODO 0x or other exotic form + return new TValue(CT.LUA_TNUMFLT, parseFloat(v.value)); // TODO: 0x or other exotic form return false; } @@ -347,9 +347,19 @@ class LuaVM { break; } case "OP_EQ": { + if (LuaVM.luaV_equalobj(this.RKB(base, k, i), this.RKC(base, k, i)) !== i.A) + ci.pcOff++; + else + this.donextjump(ci); + base = ci.u.l.base; break; } case "OP_LT": { + if (LuaVM.luaV_lessthan(this.RKB(base, k, i), this.RKC(base, k, i)) !== i.A) + ci.pcOff++; + else + this.donextjump(ci); + base = ci.u.l.base; break; } case "OP_LE": { @@ -397,7 +407,7 @@ class LuaVM { let ofunc = oci.func; let ofuncOff = oci.funcOff; let lim = nci.u.l.base + nfunc.p.numparams; - // TODO close upvalues ? + // TODO: close upvalues ? for (let aux = 0; nfuncOff + aux < lim; aux++) L.stack[ofuncOff + aux] = L.stack[nfuncOff + aux]; @@ -631,7 +641,7 @@ class LuaVM { dojump(ci, i, e) { let a = i.A; - // TODO if (a != 0) luaF_close(L, ci.u.l.base + a - 1); + // TODO: if (a != 0) luaF_close(L, ci.u.l.base + a - 1); ci.pcOff += i.sBx + e; } @@ -644,19 +654,82 @@ class LuaVM { return LuaVM.LEnum(l, r); else if (l.ttisstring() && r.ttisstring()) return LuaVM.l_strcmp(l, r) <= 0; - // TODO metatable - // else if (l.metatable.__le || r.metatable.__le) - // return l.metatable.__le ? l.metatable.__le(l, r) : r.metatable.__le(l, r); - // else { + // TODO: metatable + // else if (l.metatable.__le || r.metatable.__le) { + // let res = l.metatable.__le ? l.metatable.__le(l, r) : r.metatable.__le(l, r); + // if (res >= 0) + // return res; + // } else { // L.ci.callstatus |= lstate.CIST_LEQ; // let res = l.metatable.__lt ? l.metatable.__lt(r, l) : r.metatable.__lt(r, l); // L.ci.callstatus ^= lstate.CIST_LEQ; // if (res < 0) - // throw new Error("attempt to compare two string values"); + // throw new Error("attempt to compare ..."); // return !res; // } } + static luaV_lessthan(l, r) { + if (l.ttisnumber() && r.ttisnumber()) + return LuaVM.LTnum(l, r); + else if (l.ttisstring() && r.ttisstring()) + return LuaVM.l_strcmp(l, r) < 0; + // TODO: metatable + // else if (l.metatable.__lt || r.metatable.__lt) { + // let res = l.metatable.__lt ? l.metatable.__lt(l, r) : r.metatable.__lt(l, r); + // if (res < 0) + // throw new Error("attempt to compare ...") + // return res; + // } + } + + static luaV_equalobj(t1, t2) { + if (t1.ttype() !== t2.ttype()) { /* not the same variant? */ + if (t1.ttnov() !== t2.ttnov() || t1.ttnov() !== CT.LUA_NUMBER) + return 0; /* only numbers can be equal with different variants */ + else { /* two numbers with different variants */ + /* compare them as integers */ + return Math.floor(t1.value) === Math.floor(t2.value) // TODO: tointeger + } + } + + /* values have same type and same variant */ + switch(t1.ttype()) { + case CT.LUA_TNIL: + return 1; + case CT.LUA_TNUMINT: + case CT.LUA_TNUMFLT: + case CT.LUA_TBOOLEAN: + case CT.LUA_TLIGHTUSERDATA: + case CT.LUA_TLCF: + case CT.LUA_TSHRSTR: + case CT.LUA_TLNGSTR: + return t1.value === t2.value ? 1 : 0; + case CT.LUA_TUSERDATA: + case CT.LUA_TTABLE: + if (t1 === t2) return 1; + // TODO: __eq + default: + return t1.value === t2.value ? 1 : 0; + } + } + + static LTnum(l, r) { + if (l.ttisinteger()) { + if (r.ttisinteger()) + return l.value < r.value ? 1 : 0; + else + return LuaVM.LTintfloat(r.value, l.value); + } else { + if (r.ttisfloat()) + return l.value < r.value ? 1 : 0; + else if (isNan(l.value)) + return 0; + else + return !LuaVM.LEintfloat(r.value, l.value); + } + } + static LEnum(l, r) { if (l.ttisinteger()) { if (r.ttisinteger()) @@ -684,7 +757,7 @@ class LuaVM { } static l_strcmp(ls, rs) { - // TODO lvm.c:248 static int l_strcmp (const TString *ls, const TString *rs) + // TODO: lvm.c:248 static int l_strcmp (const TString *ls, const TString *rs) return ls.value === rs.value ? 0 : (ls.value < rs.value ? -1 : 1); } diff --git a/tests/lvm.js b/tests/lvm.js index f826091..e2454eb 100644 --- a/tests/lvm.js +++ b/tests/lvm.js @@ -275,7 +275,7 @@ test('VARARG', function (t) { test('LE, JMP', function (t) { let luaCode = ` - local a, b = 1, 2 + local a, b = 1, 1 return a >= b `, vm; @@ -291,7 +291,55 @@ test('LE, JMP', function (t) { t.strictEqual( vm.L.stack[vm.L.top - 1].value, + true, + "Program output is correct" + ); +}); + + +test('LT', function (t) { + let luaCode = ` + local a, b = 1, 1 + + return 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.strictEqual( + vm.L.stack[vm.L.top - 1].value, false, "Program output is correct" ); +}); + + +test('EQ', function (t) { + let luaCode = ` + local a, b = 1, 1 + + return 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.strictEqual( + vm.L.stack[vm.L.top - 1].value, + true, + "Program output is correct" + ); });
\ No newline at end of file |