diff options
| author | Benoit Giannangeli <benoit.giannangeli@boursorama.fr> | 2017-02-20 14:57:49 +0100 | 
|---|---|---|
| committer | Benoit Giannangeli <giann008@gmail.com> | 2017-02-20 21:37:27 +0100 | 
| commit | 7d58c3b7314e4a63591fa375546cfc76a042e644 (patch) | |
| tree | c51d390ee3830f924b2fb1e289b9461c8310d625 | |
| parent | 5860ec2bde3b220eff01b3bd1462e60905ef2fe9 (diff) | |
| download | fengari-7d58c3b7314e4a63591fa375546cfc76a042e644.tar.gz fengari-7d58c3b7314e4a63591fa375546cfc76a042e644.tar.bz2 fengari-7d58c3b7314e4a63591fa375546cfc76a042e644.zip | |
ldebug, lua_error, error
| -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  = "\"]"; -module.exports.LClosure = LClosure; -module.exports.CClosure = CClosure; -module.exports.TValue   = TValue; -module.exports.Table    = Table;
\ No newline at end of file +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; +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 | 
