aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Giannangeli <benoit.giannangeli@boursorama.fr>2017-02-20 14:57:49 +0100
committerBenoit Giannangeli <giann008@gmail.com>2017-02-20 21:37:27 +0100
commit7d58c3b7314e4a63591fa375546cfc76a042e644 (patch)
treec51d390ee3830f924b2fb1e289b9461c8310d625
parent5860ec2bde3b220eff01b3bd1462e60905ef2fe9 (diff)
downloadfengari-7d58c3b7314e4a63591fa375546cfc76a042e644.tar.gz
fengari-7d58c3b7314e4a63591fa375546cfc76a042e644.tar.bz2
fengari-7d58c3b7314e4a63591fa375546cfc76a042e644.zip
ldebug, lua_error, error
-rw-r--r--src/lapi.js40
-rw-r--r--src/lauxlib.js67
-rw-r--r--src/lbaselib.js33
-rw-r--r--src/ldebug.js409
-rw-r--r--src/ldo.js23
-rw-r--r--src/lobject.js46
-rw-r--r--src/lopcodes.js76
-rw-r--r--src/lua.js23
-rw-r--r--src/luaconf.js10
-rw-r--r--src/lvm.js3
-rw-r--r--tests/lbaselib.js53
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
diff --git a/src/ldo.js b/src/ldo.js
index 458ae46..04592e9 100644
--- a/src/ldo.js
+++ b/src/ldo.js
@@ -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
diff --git a/src/lua.js b/src/lua.js
index 6ebf250..358be64 100644
--- a/src/lua.js
+++ b/src/lua.js
@@ -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
diff --git a/src/lvm.js b/src/lvm.js
index a75e305..6bbe997 100644
--- a/src/lvm.js
+++ b/src/lvm.js
@@ -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