diff options
-rw-r--r-- | src/lapi.js | 40 | ||||
-rw-r--r-- | src/lauxlib.js | 67 | ||||
-rw-r--r-- | src/lbaselib.js | 33 | ||||
-rw-r--r-- | src/ldebug.js | 409 | ||||
-rw-r--r-- | src/ldo.js | 23 | ||||
-rw-r--r-- | src/lobject.js | 46 | ||||
-rw-r--r-- | src/lopcodes.js | 76 | ||||
-rw-r--r-- | src/lua.js | 23 | ||||
-rw-r--r-- | src/luaconf.js | 10 | ||||
-rw-r--r-- | src/lvm.js | 3 | ||||
-rw-r--r-- | tests/lbaselib.js | 53 |
11 files changed, 751 insertions, 32 deletions
diff --git a/src/lapi.js b/src/lapi.js index bb6c224..907d2b1 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -12,6 +12,7 @@ const luaconf = require('./luaconf.js'); const lstate = require('./lstate.js'); const lvm = require('./lvm.js'); const lundump = require('./lundump.js'); +const ldebug = require('./ldebug.js'); const MAXUPVAL = lfunc.MAXUPVAL; const CT = lua.constant_types; const TS = lua.thread_status; @@ -409,18 +410,16 @@ const lua_toboolean = function(L, idx) { return !o.l_isfalse(); }; -const lua_tolstring = function(L, idx, len) { +const lua_tolstring = function(L, idx) { let o = index2addr(L, idx); if (!o.ttisstring() && !o.ttisnumber()) return null; - return len !== null ? `${o.value}`.substr(0, len) : `${o.value}`; + return `${o.value}`; }; -const lua_tostring = function(L, idx) { - return lua_tolstring(L, idx, null); -}; +const lua_tostring = lua_tolstring; const lua_tointeger = function(L, idx) { return lvm.tointeger(index2addr(L, idx)) @@ -530,7 +529,7 @@ const lua_pcallk = function(L, nargs, nresults, errfunc, ctx, k) { if (k === null || L.nny > 0) { /* no continuation or no yieldable? */ c.nresults = nresults; /* do a 'conventional' protected call */ - status = ldo.luaD_pcall(L, f_call, c, c.funcOff, c.func); + status = ldo.luaD_pcall(L, f_call, c, c.funcOff, func); } else { /* prepare continuation (call is already protected by 'resume') */ let ci = L.ci; ci.u.c.k = k; /* prepare continuation (call is already protected by 'resume') */ @@ -538,7 +537,7 @@ const lua_pcallk = function(L, nargs, nresults, errfunc, ctx, k) { /* save information for error recovery */ ci.extra = c.funcOff; ci.u.c.old_errfunc = L.errfunc; - L.errfunc = c.func; + L.errfunc = func; // TODO: setoah(ci->callstatus, L->allowhook); ci.callstatus |= lstate.CIST_YPCALL; /* function can do error recovery */ ldo.luaD_call(L, c.funcOff, nresults); /* do the call */ @@ -555,7 +554,28 @@ const lua_pcallk = function(L, nargs, nresults, errfunc, ctx, k) { const lua_pcall = function(L, n, r, f) { return lua_pcallk(L, n, r, f, 0, null); -} +}; + + +/* +** miscellaneous functions +*/ + +const lua_error = function(L) { + assert(1 < L.top - L.ci.funcOff, "not enough elements in the stack"); + ldebug.luaG_errormsg(L); +}; + +const lua_concat = function(L, n) { + assert(n < L.top - L.ci.funcOff, "not enough elements in the stack"); + if (n >= 2) + lvm.luaV_concat(L, n); + else if (n === 0) { + L.stack[L.top++] = new TValue("", CT.LUA_TLNGSTR); + assert(L.top <= L.ci.top, "stack overflow"); + } +}; + module.exports.lua_pushvalue = lua_pushvalue; module.exports.lua_pushnil = lua_pushnil; @@ -607,4 +627,6 @@ module.exports.lua_getglobal = lua_getglobal; module.exports.lua_getmetatable = lua_getmetatable; module.exports.lua_setmetatable = lua_setmetatable; module.exports.lua_settop = lua_settop; -module.exports.lua_rawequal = lua_rawequal;
\ No newline at end of file +module.exports.lua_rawequal = lua_rawequal; +module.exports.lua_concat = lua_concat; +module.exports.lua_error = lua_error;
\ No newline at end of file diff --git a/src/lauxlib.js b/src/lauxlib.js index a1f9d64..16b480d 100644 --- a/src/lauxlib.js +++ b/src/lauxlib.js @@ -6,13 +6,15 @@ const assert = require('assert'); const lstate = require('./lstate.js'); const lapi = require('./lapi.js'); const lua = require('./lua.js'); +const ldebug = require('./ldebug.js'); const CT = lua.constant_types; const LUA_LOADED_TABLE = "_LOADED" const panic = function(L) { - console.log(`PANIC: unprotected error in call to Lua API (...)`); - return 0; + let msg = `PANIC: unprotected error in call to Lua API (${lapi.lua_tostring(L, -1)})`; + console.error(msg); + throw new Error(msg); }; const typeerror = function(L, arg, tname) { @@ -31,6 +33,18 @@ const typeerror = function(L, arg, tname) { // return luaL_argerror(L, arg, msg); }; +const luaL_where = function(L, level) { + let ar = new lua.lua_Debug(); + if (ldebug.lua_getstack(L, level, ar)) { + ldebug.lua_getinfo(L, "Sl", ar); + if (ar.currentline > 0) { + lapi.lua_pushstring(L, `${ar.short_src}:${ar.currentline}:`); + return; + } + } + lapi.lua_pushstring(L, ""); +}; + const tag_error = function(L, arg, tag) { typeerror(L, arg, lapi.lua_typename(L, tag)); }; @@ -60,6 +74,42 @@ const luaL_checktype = function(L, arg, t) { tag_error(L, arg, t); }; +const luaL_checklstring = function(L, arg) { + let s = lapi.lua_tolstring(L, arg); + if (!s) tag_error(L, arg, CT.LUA_TSTRING); + return s; +}; + +const luaL_optlstring = function(L, arg, def) { + if (lapi.lua_type(L, arg) <= 0) { + return def; + } else return luaL_checklstring(L, arg); +}; + +const luaL_optstring = luaL_optlstring; + +const interror = function(L, arg) { + if (lapi.lua_isnumber(L, arg)) + throw new Error("number has no integer representation"); + else + tag_error(L, arg, CT.LUA_TNUMBER); +}; + +const luaL_checkinteger = function(L, arg) { + let d = lapi.lua_tointeger(L, arg); + if (d === false) + interror(L, arg); + return d; +}; + +const luaL_optinteger = function(L, arg, def) { + return luaL_opt(L, luaL_checkinteger, arg, def); +}; + +const luaL_opt = function(L, f, n, d) { + return lapi.lua_type(L, n) <= 0 ? d : f(L, n); +}; + const luaL_getmetafield = function(L, obj, event) { if (!lapi.lua_getmetatable(L, obj)) return CT.LUA_TNIL; @@ -83,7 +133,7 @@ const luaL_callmeta = function(L, obj, event) { return true; }; -const luaL_tolstring = function(L, idx, len) { +const luaL_tolstring = function(L, idx) { if (luaL_callmeta(L, idx, "__tostring")) { if (!lapi.lua_isstring(L, -1)) throw new Error("'__tostring' must return a string"); // TODO: luaL_error @@ -107,7 +157,7 @@ const luaL_tolstring = function(L, idx, len) { } } - return lapi.lua_tolstring(L, -1, len); + return lapi.lua_tolstring(L, -1); }; /* @@ -195,4 +245,11 @@ module.exports.luaL_setfuncs = luaL_setfuncs; module.exports.luaL_checkstack = luaL_checkstack; module.exports.LUA_LOADED_TABLE = LUA_LOADED_TABLE; module.exports.luaL_tolstring = luaL_tolstring; -module.exports.luaL_argcheck = luaL_argcheck;
\ No newline at end of file +module.exports.luaL_argcheck = luaL_argcheck; +module.exports.luaL_checklstring = luaL_checklstring; +module.exports.luaL_optlstring = luaL_optlstring; +module.exports.luaL_optstring = luaL_optstring; +module.exports.luaL_checkinteger = luaL_checkinteger; +module.exports.luaL_optinteger = luaL_optinteger; +module.exports.luaL_opt = luaL_opt; +module.exports.luaL_where = luaL_where;
\ No newline at end of file diff --git a/src/lbaselib.js b/src/lbaselib.js index db6e61d..16c63bf 100644 --- a/src/lbaselib.js +++ b/src/lbaselib.js @@ -17,7 +17,7 @@ const luaB_print = function(L) { lapi.lua_pushvalue(L, -1); /* function to be called */ lapi.lua_pushvalue(L, i); /* value to print */ lapi.lua_call(L, 1, 1); - let s = lapi.lua_tolstring(L, -1, null); + let s = lapi.lua_tolstring(L, -1); if (s === null) throw new Error("'tostring' must return a string to 'print"); if (i > 1) s = `\t${s}`; @@ -31,7 +31,7 @@ const luaB_print = function(L) { const luaB_tostring = function(L) { lauxlib.luaL_checkany(L, 1); - lauxlib.luaL_tolstring(L, 1, null); + lauxlib.luaL_tolstring(L, 1); return 1; }; @@ -88,15 +88,28 @@ const luaB_type = function(L) { return 1; }; +const luaB_error = function(L) { + let level = lauxlib.luaL_optinteger(L, 2, 1); + lapi.lua_settop(L, 1); + if (lapi.lua_type(L, 1) === CT.LUA_TSTRING && level > 0) { + lauxlib.luaL_where(L, level); /* add extra information */ + lapi.lua_pushvalue(L, 1); + lapi.lua_concat(L, 2); + } + return lapi.lua_error(L); +}; + const base_funcs = { - "print": luaB_print, - "tostring": luaB_tostring, - "getmetatable": luaB_getmetatable, - "setmetatable": luaB_setmetatable, - "rawequal": luaB_rawequal, - "rawset": luaB_rawset, - "rawget": luaB_rawget, - "type": luaB_type + "collectgarbage": function () {}, + "print": luaB_print, + "tostring": luaB_tostring, + "getmetatable": luaB_getmetatable, + "setmetatable": luaB_setmetatable, + "rawequal": luaB_rawequal, + "rawset": luaB_rawset, + "rawget": luaB_rawget, + "type": luaB_type, + "error": luaB_error }; const luaopen_base = function(L) { diff --git a/src/ldebug.js b/src/ldebug.js new file mode 100644 index 0000000..c6b6752 --- /dev/null +++ b/src/ldebug.js @@ -0,0 +1,409 @@ +/*jshint esversion: 6 */ +"use strict"; + +const assert = require('assert'); + +const lua = require('./lua.js'); +const ldo = require('./ldo.js'); +const lobject = require('./lobject.js'); +const lstate = require('./lstate.js'); +const luaconf = require('./luaconf.js'); +const OC = require('./lopcodes.js'); +const ltm = require('./ltm.js'); +const lfunc = require('./lfunc.js'); +const TMS = ltm.TMS; +const TValue = lobject.TValue; +const Table = lobject.Table; +const CT = lua.constant_types; +const TS = lua.thread_status; + +const currentline = function(ci) { + return ci.func.p.lineinfo ? ci.func.p.lineinfo[ci.pcOff] : -1; +}; + +/* +** If function yielded, its 'func' can be in the 'extra' field. The +** next function restores 'func' to its correct value for debugging +** purposes. (It exchanges 'func' and 'extra'; so, when called again, +** after debugging, it also "re-restores" ** 'func' to its altered value. +*/ +const swapextra = function(L) { + if (L.status === TS.LUA_YIELD) { + let ci = L.ci; /* get function that yielded */ + let temp = ci.funcOff; /* exchange its 'func' and 'extra' values */ + ci.func = L.stack[ci.extra]; + ci.funcOff = ci.extra; + ci.extra = temp; + } +}; + +const lua_getstack = function(L, level, ar) { + let ci; + let status; + if (level < 0) return 0; /* invalid (negative) level */ + for (ci = L.ci; level > 0 && ci !== L.base_ci; ci = ci.previous) + level--; + if (level === 0 && ci !== L.base_ci) { /* level found? */ + status = 1; + ar.i_ci = ci; + } else + status = 0; /* no such level */ + return status; +}; + +const upvalname = function(p, uv) { + assert(uv < p.upvalues.length); + let s = p.upvalues[uv].name; + if (s === null) return "?"; + return s; +}; + +const funcinfo = function(ar, cl) { + if (cl === null || cl.type == CT.LUA_TCCL) { + ar.source = "=[JS]"; + ar.linedefined = -1; + ar.lastlinedefined = -1; + ar.what = "J"; + } else { + let p = cl.p; + ar.source = p.source ? p.source : "=?"; + ar.linedefined = p.linedefined; + ar.lastlinedefined = p.lastlinedefined; + ar.what = ar.linedefined === 0 ? "main" : "Lua"; + } + + ar.short_src = lobject.luaO_chunkid(ar.source, luaconf.LUA_IDSIZE); +}; + +const collectvalidlines = function(L, f) { + if (f === null || f.c.type == CT.LUA_TCCL) { + L.stack[L.top++] = ldo.nil; + assert(L.top <= L.ci.top, "stack overflow"); + } else { + let lineinfo = f.l.p.lineinfo; + let t = new Table(); + L.stack[L.top++] = t; + assert(L.top <= L.ci.top, "stack overflow"); + let v = new TValue(true, CT.LUA_TBOOLEAN); + for (let i = 0; i < f.l.p.length; i++) + t.__newindex(t, lineinfo[i], v); + } +}; + +const getfuncname = function(L, ci) { + let r = { + name: null, + funcname: null + }; + if (ci === null) + return null; + else if (ci.callstatus & lstate.CIST_FIN) { /* is this a finalizer? */ + r.name = "__gc"; + r.funcname = "metamethod"; /* report it as such */ + return r; + } + /* calling function is a known Lua function? */ + else if (!(ci.callstatus & lstate.CIST_TAIL) && ci.previous.callstatus & lstate.CIST_LUA) + return funcnamefromcode(L, ci.previous); + else return null; /* no way to find a name */ +}; + +const auxgetinfo = function(L, what, ar, f, ci) { + let status = 1; + for (; what.length > 0; what = what.slice(1)) { + switch (what[0]) { + case 'S': { + funcinfo(ar, f); + break; + } + case 'l': { + ar.currentline = ci && ci.callstatus & lstate.CIST_LUA ? currentline(ci) : -1; + break; + } + case 'u': { + ar.nups = f === null ? 0 : f.c.nupvalues; + if (f === null || f.c.type == CT.LUA_TCCL) { + ar.isvararg = true; + ar.nparams = 0; + } else { + ar.isvararg = f.l.p.is_vararg; + ar.nparams = f.l.p.numparams; + } + break; + } + case 't': { + ar.istailcall = ci ? ci.callstatus & lstate.CIST_TAIL : 0; + break; + } + case 'n': { + ar.namewhat = getfuncname(L, ci, ar.name); + if (ar.namewhat === null) { + ar.namewhat = ""; + ar.name = null; + } + break; + } + case 'L': + case 'f': /* handled by lua_getinfo */ + break; + default: status = 0; /* invalid option */ + } + } + + return status; +}; + +const lua_getinfo = function(L, what, ar) { + let status, cl, ci, func, funcOff; + swapextra(L); + if (what[0] === '>') { + ci = null; + funcOff = L.top - 1; + func = L.stack[funcOff]; + assert(L, func.ttisfunction(), "function expected"); + what = what.slice(1); /* skip the '>' */ + L.top--; /* pop function */ + } else { + ci = ar.i_ci; + func = ci.func; + funcOff = ci.funcOff; + assert(ci.func.ttisfunction()); + } + + cl = func.ttisclosure() ? func : null; + status = auxgetinfo(L, what, ar, cl, ci); + if (what.indexOf('f') >= 0) { + L.stack[L.top++] = func; + assert(L.top <= L.ci.top, "stack overflow"); + } + + swapextra(L); + if (what.indexOf('L') >= 0) + collectvalidlines(L, cl); + + return status; +}; + +const kname = function(p, pc, c) { + let r = { + name: null, + funcname: null + }; + + if (OC.ISK(c)) { /* is 'c' a constant? */ + let kvalue = p.k[OC.INDEXK(c)]; + if (kvalue.ttisstring()) { /* literal constant? */ + r.name = kvalue.value; /* it is its own name */ + return r; + } + /* else no reasonable name found */ + } else { /* 'c' is a register */ + let what = getobjname(p, pc, c); /* search for 'c' */ + if (what && what.name[0] === 'c') { + return what; + } + /* else no reasonable name found */ + } + r.name = "?"; + return r; /* no reasonable name found */ +}; + +const filterpc = function(pc, jmptarget) { + if (pc < jmptarget) /* is code conditional (inside a jump)? */ + return -1; /* cannot know who sets that register */ + else return pc; /* current position sets that register */ +}; + +/* +** try to find last instruction before 'lastpc' that modified register 'reg' +*/ +const findsetreg = function(p, lastpc, reg) { + let setreg = -1; /* keep last instruction that changed 'reg' */ + let jmptarget = 0; /* any code before this address is conditional */ + for (let pc = 0; pc < lastpc; pc++) { + let i = p.code[pc]; + let op = OC.OpCodes[i.opcode]; + let a = i.A; + switch (op) { + case 'OP_LOADNIL': { + let b = i.B; + if (a <= reg && reg <= a + b) /* set registers from 'a' to 'a+b' */ + setreg = filterpc(pc, jmptarget); + break; + } + case 'OP_TFORCALL': { + if (reg >= a + 2) /* affect all regs above its base */ + setreg = filterpc(pc, jmptarget); + break; + } + case 'OP_CALL': + case 'OP_TAILCALL': { + if (reg >= a) /* affect all registers above base */ + setreg = filterpc(pc, jmptarget); + break; + } + case 'OP_JMP': { + let b = i.sBx; + let dest = pc + 1 + b; + /* jump is forward and do not skip 'lastpc'? */ + if (pc < dest && dest <= lastpc) { + if (dest > jmptarget) + jmptarget = dest; /* update 'jmptarget' */ + } + break; + } + default: + if (OC.testAMode(i.opcode) && reg === a) + setreg= filterpc(pc, jmptarget); + break; + } + } + + return setreg; +}; + + +const getobjname = function(p, lastpc, reg) { + let r = { + name: lfunc.luaF_getlocalname(p, reg + 1, lastpc), + funcname: null + }; + + if (r.name) { /* is a local? */ + r.funcname = "local"; + return r; + } + + /* else try symbolic execution */ + let pc = findsetreg(p, lastpc, reg); + if (pc !== -1) { /* could find instruction? */ + let i = p.code[pc]; + let op = OC.OpCodes[i.opcode]; + switch (op) { + case 'OP_MOVE': { + let b = i.B; /* move from 'b' to 'a' */ + if (b < i.A) + return getobjname(p, pc, b); /* get name for 'b' */ + break; + } + case 'OP_GETTABUP': + case 'OP_GETTABLE': { + let k = i.C; /* key index */ + let t = i.B; /* table index */ + let vn = op === 'OP_GETTABLE' ? lfunc.luaF_getlocalname(p, t + 1, pc) : upvalname(p, t); + r.name = kname(p, pc, k); + r.funcname = vn && vn === "_ENV" ? "global" : "field"; + return r; + } + case 'OP_GETUPVAL': { + r.name = upvalname(p, i.B); + r.funcname = "upvalue"; + return r; + } + case 'OP_LOADK': + case 'OP_LOADKX': { + let b = op === 'OP_LOADK' ? i.Bx : p.code[pc + 1].Ax; + if (p.k[b].ttisstring()) { + r.name = p.k[b].value; + r.funcname = "constant"; + return r; + } + break; + } + case 'OP_SELF': { + let k = i.C; + r.name = kname(p, pc, k); + r.funcname = "method"; + return r; + } + default: break; + } + } + + return null; +}; + +/* +** Try to find a name for a function based on the code that called it. +** (Only works when function was called by a Lua function.) +** Returns what the name is (e.g., "for iterator", "method", +** "metamethod") and sets '*name' to point to the name. +*/ +const funcnamefromcode = function(L, ci) { + let r = { + name: null, + funcname: null + }; + + let tm = 0; /* (initial value avoids warnings) */ + let p = ci.func.p; /* calling function */ + let pc = ci.pcOff; /* calling instruction index */ + let i = p.code[pc]; /* calling instruction */ + + if (ci.callstatus & lstate.CIST_HOOKED) { + r.name = "?"; + r.funcname = "hook"; + return r; + } + + switch (OC.OpCodes[i.opcode]) { + case 'OP_CALL': + case 'OP_TAILCALL': + return getobjname(p, pc, i.A); /* get function name */ + case 'OP_TFORCALL': + r.name = "for iterator"; + r.funcname = "for iterator"; + return r; + /* other instructions can do calls through metamethods */ + case 'OP_SELF': + case 'OP_GETTABUP': + case 'OP_GETTABLE': + tm = TMS.TM_INDEX; + break; + case 'OP_SETTABUP': + case 'OP_SETTABLE': + tm = TMS.TM_NEWINDEX; + break; + case 'OP_ADD': tm = TMS.OP_ADD; break; + case 'OP_SUB': tm = TMS.OP_SUB; break; + case 'OP_MUL': tm = TMS.OP_MUL; break; + case 'OP_MOD': tm = TMS.OP_MOD; break; + case 'OP_POW': tm = TMS.OP_POW; break; + case 'OP_DIV': tm = TMS.OP_DIV; break; + case 'OP_IDIV': tm = TMS.OP_IDI; break; + case 'OP_BAND': tm = TMS.OP_BAN; break; + case 'OP_BOR': tm = TMS.OP_BOR; break; + case 'OP_BXOR': tm = TMS.OP_BXO; break; + case 'OP_SHL': tm = TMS.OP_SHL; break; + case 'OP_SHR': tm = TMS.OP_SHR; break; + case 'OP_UNM': tm = TMS.TM_UNM; break; + case 'OP_BNOT': tm = TMS.TM_BNOT; break; + case 'OP_LEN': tm = TMS.TM_LEN; break; + case 'OP_CONCAT': tm = TMS.TM_CONCAT; break; + case 'OP_EQ': tm = TMS.TM_EQ; break; + case 'OP_LT': tm = TMS.TM_LT; break; + case 'OP_LE': tm = TMS.TM_LE; break; + default: + return null; /* cannot find a reasonable name */ + } + + r.name = L.l_G.tmname[tm]; + r.funcname = "metamethod"; + return r; +}; + +const luaG_errormsg = function(L) { + if (L.errfunc !== 0) { /* is there an error handling function? */ + let errfunc = L.errfunc; + L.stack[L.top] = L.stack[L.top - 1]; + L.stack[L.top - 1] = errfunc; + L.top++; + ldo.luaD_callnoyield(L, L.top - 2, 1); + } + + ldo.luaD_throw(L, TS.LUA_ERRRUN); +}; + +module.exports.lua_getstack = lua_getstack; +module.exports.lua_getinfo = lua_getinfo; +module.exports.luaG_errormsg = luaG_errormsg;
\ No newline at end of file @@ -227,6 +227,28 @@ const luaD_call = function(L, off, nResults) { L.nCcalls--; }; +const luaD_throw = function(L, errcode) { + if (L.errorJmp) { /* thread has an error handler? */ + L.errorJmp.status = errcode; /* set status */ + throw L.errorJmp; + } else { /* thread has no error handler */ + let g = L.l_G; + L.status = errcode; /* mark it as dead */ + if (g.mainthread.errorJmp) { /* main thread has a handler? */ + g.mainthread.stack[g.mainthread.top++] = L.stack[L.top - 1]; /* copy error obj. */ + luaD_throw(g.mainthread, errcode); /* re-throw in main thread */ + } else { /* no handler at all; abort */ + if (g.panic) { /* panic function? */ + seterrorobj(L, errcode, L.top); /* assume EXTRA_STACK */ + if (L.ci.top < L.top) + L.ci.top = L.top; /* pushing msg. can break this invariant */ + g.panic(L); /* call panic function (last chance to jump out) */ + } + throw new Error(`Aborted ${errcode}`); + } + } +}; + const luaD_rawrunprotected = function(L, f, ud) { let oldnCcalls = L.nCcalls; let lj = { // TODO: necessary when using try/catch ? (ldo.c:47-52) @@ -309,5 +331,6 @@ module.exports.stackerror = stackerror; module.exports.luaD_call = luaD_call; module.exports.luaD_callnoyield = luaD_callnoyield; module.exports.luaD_pcall = luaD_pcall; +module.exports.luaD_throw = luaD_throw; module.exports.luaD_rawrunprotected = luaD_rawrunprotected; module.exports.luaD_protectedparser = luaD_protectedparser;
\ No newline at end of file diff --git a/src/lobject.js b/src/lobject.js index 3a519c9..0a0b615 100644 --- a/src/lobject.js +++ b/src/lobject.js @@ -203,8 +203,46 @@ class CClosure extends TValue { } +const RETS = "..."; +const PRE = "[string \""; +const POS = "\"]"; + +const luaO_chunkid = function(source, bufflen) { + let l = source.length; + let out = ""; + if (source[0] === '=') { /* 'literal' source */ + if (l < bufflen) /* small enough? */ + out = `${source.slice(1)}`; + else { /* truncate it */ + out += `${source.slice(1, bufflen)}`; + } + } else if (source[0] === '@') { /* file name */ + if (l <= bufflen) /* small enough? */ + out = `${source.slice(1)}`; + else { /* add '...' before rest of name */ + bufflen -= RETS.length; + out = `${RETS}${source.slice(1, l - bufflen)}`; + } + } else { /* string; format as [string "source"] */ + let nli = source.indexOf('\n'); /* find first new line (if any) */ + let nl = nli ? source.slice(nli) : null; + out = `${PRE}`; /* add prefix */ + bufflen -= PRE.length - RETS.length; - POS.length + 1; /* save space for prefix+suffix+'\0' */ + if (l < bufflen && nl === null) { /* small one-line source? */ + out += `${source}`; /* keep it */ + } else { + if (nl !== null) l = nl.length - source.length; /* stop at first newline */ + if (l > bufflen) l = bufflen; + out += `${source}${RETS}`; + } + out += POS; + } + + return out; +}; -module.exports.LClosure = LClosure; -module.exports.CClosure = CClosure; -module.exports.TValue = TValue; -module.exports.Table = Table;
\ No newline at end of file +module.exports.LClosure = LClosure; +module.exports.CClosure = CClosure; +module.exports.TValue = TValue; +module.exports.Table = Table; +module.exports.luaO_chunkid = luaO_chunkid;
\ No newline at end of file diff --git a/src/lopcodes.js b/src/lopcodes.js index 1350468..5723abb 100644 --- a/src/lopcodes.js +++ b/src/lopcodes.js @@ -51,6 +51,79 @@ const OpCodes = [ "OP_EXTRAARG" ]; +/* +** masks for instruction properties. The format is: +** bits 0-1: op mode +** bits 2-3: C arg mode +** bits 4-5: B arg mode +** bit 6: instruction set register A +** bit 7: operator is a test (next instruction must be a jump) +*/ +const OpArgN = 0; /* argument is not used */ +const OpArgU = 1; /* argument is used */ +const OpArgR = 2; /* argument is a register or a jump offset */ +const OpArgK = 3; /* argument is a constant or register/constant */ + +/* basic instruction format */ +const iABC = 0; +const iABx = 1; +const iAsBx = 2; +const iAx = 3; + +const luaP_opmodes = [ + 0 << 7 | 1 << 6 | OpArgR << 4 | OpArgN << 2 | iABC, /* OP_MOVE */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgN << 2 | iABx, /* OP_LOADK */ + 0 << 7 | 1 << 6 | OpArgN << 4 | OpArgN << 2 | iABx, /* OP_LOADKX */ + 0 << 7 | 1 << 6 | OpArgU << 4 | OpArgU << 2 | iABC, /* OP_LOADBOOL */ + 0 << 7 | 1 << 6 | OpArgU << 4 | OpArgN << 2 | iABC, /* OP_LOADNIL */ + 0 << 7 | 1 << 6 | OpArgU << 4 | OpArgN << 2 | iABC, /* OP_GETUPVAL */ + 0 << 7 | 1 << 6 | OpArgU << 4 | OpArgK << 2 | iABC, /* OP_GETTABUP */ + 0 << 7 | 1 << 6 | OpArgR << 4 | OpArgK << 2 | iABC, /* OP_GETTABLE */ + 0 << 7 | 0 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_SETTABUP */ + 0 << 7 | 0 << 6 | OpArgU << 4 | OpArgN << 2 | iABC, /* OP_SETUPVAL */ + 0 << 7 | 0 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_SETTABLE */ + 0 << 7 | 1 << 6 | OpArgU << 4 | OpArgU << 2 | iABC, /* OP_NEWTABLE */ + 0 << 7 | 1 << 6 | OpArgR << 4 | OpArgK << 2 | iABC, /* OP_SELF */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_ADD */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_SUB */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_MUL */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_MOD */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_POW */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_DIV */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_IDIV */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_BAND */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_BOR */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_BXOR */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_SHL */ + 0 << 7 | 1 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_SHR */ + 0 << 7 | 1 << 6 | OpArgR << 4 | OpArgN << 2 | iABC, /* OP_UNM */ + 0 << 7 | 1 << 6 | OpArgR << 4 | OpArgN << 2 | iABC, /* OP_BNOT */ + 0 << 7 | 1 << 6 | OpArgR << 4 | OpArgN << 2 | iABC, /* OP_NOT */ + 0 << 7 | 1 << 6 | OpArgR << 4 | OpArgN << 2 | iABC, /* OP_LEN */ + 0 << 7 | 1 << 6 | OpArgR << 4 | OpArgR << 2 | iABC, /* OP_CONCAT */ + 0 << 7 | 0 << 6 | OpArgR << 4 | OpArgN << 2 | iAsBx, /* OP_JMP */ + 1 << 7 | 0 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_EQ */ + 1 << 7 | 0 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_LT */ + 1 << 7 | 0 << 6 | OpArgK << 4 | OpArgK << 2 | iABC, /* OP_LE */ + 1 << 7 | 0 << 6 | OpArgN << 4 | OpArgU << 2 | iABC, /* OP_TEST */ + 1 << 7 | 1 << 6 | OpArgR << 4 | OpArgU << 2 | iABC, /* OP_TESTSET */ + 0 << 7 | 1 << 6 | OpArgU << 4 | OpArgU << 2 | iABC, /* OP_CALL */ + 0 << 7 | 1 << 6 | OpArgU << 4 | OpArgU << 2 | iABC, /* OP_TAILCALL */ + 0 << 7 | 0 << 6 | OpArgU << 4 | OpArgN << 2 | iABC, /* OP_RETURN */ + 0 << 7 | 1 << 6 | OpArgR << 4 | OpArgN << 2 | iAsBx, /* OP_FORLOOP */ + 0 << 7 | 1 << 6 | OpArgR << 4 | OpArgN << 2 | iAsBx, /* OP_FORPREP */ + 0 << 7 | 0 << 6 | OpArgN << 4 | OpArgU << 2 | iABC, /* OP_TFORCALL */ + 0 << 7 | 1 << 6 | OpArgR << 4 | OpArgN << 2 | iAsBx, /* OP_TFORLOOP */ + 0 << 7 | 0 << 6 | OpArgU << 4 | OpArgU << 2 | iABC, /* OP_SETLIST */ + 0 << 7 | 1 << 6 | OpArgU << 4 | OpArgN << 2 | iABx, /* OP_CLOSURE */ + 0 << 7 | 1 << 6 | OpArgU << 4 | OpArgN << 2 | iABC, /* OP_VARARG */ + 0 << 7 | 0 << 6 | OpArgU << 4 | OpArgU << 2 | iAx /* OP_EXTRAARG */ +]; + +const testAMode = function(m) { + return luaP_opmodes[m] & (1 << 6); +}; + const SIZE_C = 9; const SIZE_B = 9; const SIZE_Bx = (SIZE_C + SIZE_B); @@ -105,4 +178,5 @@ module.exports.MAXARG_C = MAXARG_C; module.exports.BITRK = BITRK; module.exports.ISK = ISK; module.exports.INDEXK = INDEXK; -module.exports.LFIELDS_PER_FLUSH = LFIELDS_PER_FLUSH;
\ No newline at end of file +module.exports.LFIELDS_PER_FLUSH = LFIELDS_PER_FLUSH; +module.exports.testAMode = testAMode;
\ No newline at end of file @@ -80,6 +80,29 @@ const print_version = function() { console.log(FENGARI_COPYRIGHT); }; +class lua_Debug { + + constructor() { + // int event; + // const char *name; /* (n) */ + // const char *namewhat; /* (n) 'global', 'local', 'field', 'method' */ + // const char *what; /* (S) 'Lua', 'C', 'main', 'tail' */ + // const char *source; /* (S) */ + // int currentline; /* (l) */ + // int linedefined; /* (S) */ + // int lastlinedefined; /* (S) */ + // unsigned char nups; /* (u) number of upvalues */ + // unsigned char nparams;/* (u) number of parameters */ + // char isvararg; /* (u) */ + // char istailcall; /* (t) */ + // char short_src[LUA_IDSIZE]; /* (S) */ + /* private part */ + // struct CallInfo *i_ci; /* active function */ + } + +} + +module.exports.lua_Debug = lua_Debug; module.exports.constant_types = constant_types; module.exports.thread_status = thread_status; module.exports.LUA_MULTRET = -1; diff --git a/src/luaconf.js b/src/luaconf.js index b0456e3..c415a65 100644 --- a/src/luaconf.js +++ b/src/luaconf.js @@ -9,4 +9,12 @@ */ const LUAI_MAXSTACK = 1000000; -module.exports.LUAI_MAXSTACK = LUAI_MAXSTACK;
\ No newline at end of file +/* +@@ LUA_IDSIZE gives the maximum size for the description of the source +@@ of a function in debug information. +** CHANGE it if you want a different size. +*/ +const LUA_IDSIZE = 60 + +module.exports.LUAI_MAXSTACK = LUAI_MAXSTACK; +module.exports.LUA_IDSIZE = LUA_IDSIZE;
\ No newline at end of file @@ -1036,4 +1036,5 @@ module.exports.l_strcmp = l_strcmp; module.exports.luaV_objlen = luaV_objlen; module.exports.luaV_finishset = luaV_finishset; module.exports.gettable = gettable; -module.exports.settable = settable;
\ No newline at end of file +module.exports.settable = settable; +module.exports.luaV_concat = luaV_concat;
\ No newline at end of file diff --git a/tests/lbaselib.js b/tests/lbaselib.js index 87009ca..b55afde 100644 --- a/tests/lbaselib.js +++ b/tests/lbaselib.js @@ -34,7 +34,7 @@ test('print', function (t) { lapi.lua_load(L, bc, "test-print"); - lapi.lua_call(L, 0, 1); + lapi.lua_call(L, 0, -1); }, "JS Lua program ran without error"); }); @@ -239,4 +239,55 @@ test('type', function (t) { "nil", "Correct element(s) on the stack" ); +}); + + +test('error', function (t) { + let luaCode = ` + error("you fucked up") + `, L; + + t.plan(1); + + t.throws(function () { + + let bc = toByteCode(luaCode).dataView; + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lapi.lua_load(L, bc, "test-error"); + + lapi.lua_call(L, 0, -1); + + }, "JS Lua program ran without error"); +}); + + +test('error, protected', function (t) { + let luaCode = ` + error("you fucked up") + `, L; + + t.plan(2); + + t.doesNotThrow(function () { + + let bc = toByteCode(luaCode).dataView; + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lapi.lua_load(L, bc, "test-error"); + + lapi.lua_pcall(L, 0, -1, 0); + + }, "JS Lua program ran without error"); + + t.ok( + lapi.lua_tostring(L, -1).endsWith("you fucked up"), + "Error is on the stack" + ) });
\ No newline at end of file |