From 23d15010edc905a3b7c123e85ef6d1960d3da039 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 21 Feb 2017 08:46:48 +0100 Subject: luaG_ functions --- src/lauxlib.js | 34 ++++++++++++++++++- src/ldebug.js | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/lfunc.js | 29 ++++++++++++---- src/ltm.js | 16 +++++++++ src/lvm.js | 7 ++-- tests/ldebug.js | 42 +++++++++++++++++++++++ 6 files changed, 215 insertions(+), 14 deletions(-) create mode 100644 tests/ldebug.js diff --git a/src/lauxlib.js b/src/lauxlib.js index 16b480d..b879446 100644 --- a/src/lauxlib.js +++ b/src/lauxlib.js @@ -17,6 +17,26 @@ const panic = function(L) { throw new Error(msg); }; +// const luaL_argerror = function(L, arg, extramsg) { +// let ar = new lua.lua_Debug(); +// +// if (!lapi.lua_getstack(L, 0, ar)) /* no stack frame? */ +// return luaL_error(L, 'bad argument #%d (%s)', arg, extramsg); +// +// ldebug.lua_getinfo(L, 'n', ar); +// +// if (ar.namewhat === 'method') { +// arg--; /* do not count 'self' */ +// if (arg === 0) /* error is in the self argument itself? */ +// return luaL_error(L, "calling '%s' on bad self (%s)", ar.name, extramsg); +// } +// +// if (ar.name === null) +// ar.name = pushglobalfuncname(L, ar) ? lapi.lua_tostring(L, -1) : "?"; +// +// return luaL_error(L, "bad argument #%d to '%s' (%s)", arg, ar.name, extramsg); +// }; + const typeerror = function(L, arg, tname) { let typearg; if (luaL_getmetafield(L, arg, "__name") === CT.LUA_TSTRING) @@ -45,6 +65,17 @@ const luaL_where = function(L, level) { lapi.lua_pushstring(L, ""); }; +const luaL_error = function(L, fmt, ...args) { + let i = 0; + + // TODO: bypassing lua_pushvstring for now + lapi.lua_pushstring(L, fmt.replace(/(^%[sfIpdcU]|([^%])%[sfIpdcU])/g, function (m, p1, p2, off) { + return p2 ? p2 + args[i++] : args[i++]; + })); + + return lapi.lua_error(L); +}; + const tag_error = function(L, arg, tag) { typeerror(L, arg, lapi.lua_typename(L, tag)); }; @@ -252,4 +283,5 @@ 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 +module.exports.luaL_where = luaL_where; +module.exports.luaL_error = luaL_error; \ No newline at end of file diff --git a/src/ldebug.js b/src/ldebug.js index c6b6752..ecd612b 100644 --- a/src/ldebug.js +++ b/src/ldebug.js @@ -9,6 +9,7 @@ const lobject = require('./lobject.js'); const lstate = require('./lstate.js'); const luaconf = require('./luaconf.js'); const OC = require('./lopcodes.js'); +const lvm = require('./lvm.js'); const ltm = require('./ltm.js'); const lfunc = require('./lfunc.js'); const TMS = ltm.TMS; @@ -254,7 +255,7 @@ const findsetreg = function(p, lastpc, reg) { } default: if (OC.testAMode(i.opcode) && reg === a) - setreg= filterpc(pc, jmptarget); + setreg = filterpc(pc, jmptarget); break; } } @@ -392,6 +393,92 @@ const funcnamefromcode = function(L, ci) { return r; }; +const isinstack = function(L, ci, o) { + for (let i = ci.u.l.base; i < ci.top; i++) { + if (L.stack[i] === o) + return i; + } + + return false; +} + +/* +** Checks whether value 'o' came from an upvalue. (That can only happen +** with instructions OP_GETTABUP/OP_SETTABUP, which operate directly on +** upvalues.) +*/ +const getupvalname = function(L, ci, o, name) { + let c = ci.func; + for (let i = 0; i < c.nupvalues; i++) { + if (c.upvals[i].val(L) === o) { + return { + name: upvalname(c.p, i), + funcname: 'upvalue' + } + } + } + + return null; +}; + +const varinfo = function(L, o) { + let ci = L.ci; + let kind = null; + if (ci.callstatus & lstate.CIST_LUA) { + kind = getupvalname(L, ci, o); /* check whether 'o' is an upvalue */ + let stkid = isinstack(L, ci, o); + if (!kind && stkid) /* no? try a register */ + kind = getobjname(ci.func.p, ci.pcOff, stkid); + } + + return kind ? ` (${kind.funcname} '${kind.name}')` : ``; +}; + +const luaG_typeerror = function(L, o, op) { + let t = ltm.luaT_objtypename(L, o); + luaG_runerror(L, `attempt to ${op} a ${t} value${varinfo(L, o)}`); +}; + +const luaG_concaterror = function(L, p1, p2) { + if (p1.ttisstring() || p1.ttisnumber()) p1 = p2; + luaG_typeerror(L, p1, 'concatenate'); +}; + +/* +** Error when both values are convertible to numbers, but not to integers +*/ +const luaG_opinterror = function(L, p1, p2, msg) { + let temp = lvm.tonumber(p1); + if (temp !== false) + p2 = p1; + luaG_typeerror(L, p2, msg); +}; + +const luaG_ordererror = function(L, p1, p2) { + let t1 = ltm.luaT_objtypename(L, p1); + let t2 = ltm.luaT_objtypename(L, p2); + if (t1 === t2) + luaG_runerror(L, `attempt to compare two ${t1} values`); + else + luaG_runerror(L, `attempt to compare ${t1} with ${t2}`); +}; + +/* add src:line information to 'msg' */ +const luaG_addinfo = function(L, msg, src, line) { + let buff = '?'; + if (src) + buff = lobject.luaO_chunkid(src, luaconf.LUA_IDSIZE); + + return `${buff}:${line}: ${msg}`; +}; + +const luaG_runerror = function(L, msg) { + let ci = L.ci; + if (ci.callstatus & lstate.CIST_LUA) /* if Lua function, add source:line information */ + luaG_addinfo(L, msg, ci.func.p.source, currentline(ci)); + luaG_errormsg(L); +}; + const luaG_errormsg = function(L) { if (L.errfunc !== 0) { /* is there an error handling function? */ let errfunc = L.errfunc; @@ -404,6 +491,12 @@ const luaG_errormsg = function(L) { 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 +module.exports.lua_getstack = lua_getstack; +module.exports.lua_getinfo = lua_getinfo; +module.exports.luaG_errormsg = luaG_errormsg; +module.exports.luaG_addinfo = luaG_addinfo; +module.exports.luaG_runerror = luaG_runerror; +module.exports.luaG_typeerror = luaG_typeerror; +module.exports.luaG_concaterror = luaG_concaterror; +module.exports.luaG_opinterror = luaG_opinterror; +module.exports.luaG_ordererror = luaG_ordererror; \ No newline at end of file diff --git a/src/lfunc.js b/src/lfunc.js index c309147..7057f2d 100644 --- a/src/lfunc.js +++ b/src/lfunc.js @@ -99,9 +99,26 @@ const luaF_initupvals = function(L, cl) { } }; -module.exports.Proto = Proto; -module.exports.UpVal = UpVal; -module.exports.findupval = findupval; -module.exports.luaF_close = luaF_close; -module.exports.MAXUPVAL = 255; -module.exports.luaF_initupvals = luaF_initupvals; \ No newline at end of file +/* +** Look for n-th local variable at line 'line' in function 'func'. +** Returns null if not found. +*/ +const luaF_getlocalname = function(f, local_number, pc) { + for (let i = 0; i < f.locvars.length && f.locvars[i].startpc <= pc; i++) { + if (pc < f.locvars[i].endpc) { /* is variable active? */ + local_number--; + if (local_number == 0) + return f.locvars[i].varname; + } + } + return null; /* not found */ +} + + +module.exports.Proto = Proto; +module.exports.UpVal = UpVal; +module.exports.findupval = findupval; +module.exports.luaF_close = luaF_close; +module.exports.MAXUPVAL = 255; +module.exports.luaF_initupvals = luaF_initupvals; +module.exports.luaF_getlocalname = luaF_getlocalname \ No newline at end of file diff --git a/src/ltm.js b/src/ltm.js index 77d65e4..f163f44 100644 --- a/src/ltm.js +++ b/src/ltm.js @@ -65,6 +65,21 @@ const luaT_init = function(L) { } }; +/* +** Return the name of the type of an object. For tables and userdata +** with metatable, use their '__name' metafield, if present. +*/ +const luaT_objtypename = function(L, o) { + if ((o.ttistable() && o.metatable !== null) + || (o.ttisfulluserdata() && o.metatable !== null)) { + let name = o.__index(o, '__name'); + if (name.ttisstring()) + return name.value; + } + + return ttypename(o.ttnov()); +}; + const luaT_callTM = function(L, f, p1, p2, p3, hasres) { let result = p3; let func = L.top; @@ -131,4 +146,5 @@ module.exports.luaT_trybinTM = luaT_trybinTM; module.exports.luaT_callorderTM = luaT_callorderTM; module.exports.luaT_gettmbyobj = luaT_gettmbyobj; module.exports.luaT_init = luaT_init; +module.exports.luaT_objtypename = luaT_objtypename; module.exports.ttypename = ttypename; \ No newline at end of file diff --git a/src/lvm.js b/src/lvm.js index 6bbe997..97292cd 100644 --- a/src/lvm.js +++ b/src/lvm.js @@ -20,6 +20,7 @@ const llimit = require('./llimit.js'); const ldo = require('./ldo.js'); const ltm = require('./ltm.js'); const ltable = require('./ltable.js'); +const ldebug = require('./ldebug.js'); const TMS = ltm.TMS; const RA = function(L, base, i) { @@ -865,7 +866,7 @@ const luaV_objlen = function(L, ra, rb) { default: { tm = ltm.luaT_gettmbyobj(L, rb, TMS.TM_LEN); if (tm.ttisnil()) - throw new Error("attempt to get length"); // TODO: luaG_typeerror + ldebug.luaG_typeerror(L, rb, "get length of"); break; } } @@ -952,7 +953,7 @@ const luaV_finishget = function(L, t, key, val, slot, recur) { assert(!t.ttistable()); tm = ltm.luaT_gettmbyobj(L, t, TMS.TM_INDEX); if (tm.ttisnil()) - throw new Error(`attempt to index a ${tm.ttype()} value`); // TODO: luaG_typeerror + ldebug.luaG_typeerror(L, t, 'index'); } else { /* 't' is a table */ assert(slot.ttisnil()); tm = ltm.luaT_gettmbyobj(L, t, TMS.TM_INDEX); // TODO: fasttm @@ -1001,7 +1002,7 @@ const luaV_finishset = function(L, t, key, val, slot, recur) { } else { /* not a table; check metamethod */ tm = ltm.luaT_gettmbyobj(L, t, TMS.TM_NEWINDEX); if (tm.ttisnil()) - throw new Error(`attempt to index a ${tm.ttype()} value`); // TODO: luaG_typeerror + ldebug.luaG_typeerror(L, t, 'index'); } if (tm.ttisfunction()) { diff --git a/tests/ldebug.js b/tests/ldebug.js new file mode 100644 index 0000000..a6fa55f --- /dev/null +++ b/tests/ldebug.js @@ -0,0 +1,42 @@ +/*jshint esversion: 6 */ +"use strict"; + +const test = require('tape'); +const beautify = require('js-beautify').js_beautify; + +const tests = require("./tests.js"); +const getState = tests.getState; +const toByteCode = tests.toByteCode; + +const lvm = require("../src/lvm.js"); +const ldo = require("../src/ldo.js"); +const lapi = require("../src/lapi.js"); +const lauxlib = require("../src/lauxlib.js"); +const lua = require('../src/lua.js'); +const linit = require('../src/linit.js'); + +test('luaG_typeerror', function (t) { + let luaCode = ` + local a = true + return #a + `, L; + + t.plan(1); + + t.doesNotThrow(function () { + + let bc = toByteCode(luaCode).dataView; + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lapi.lua_load(L, bc, "test-typeerror"); + + lapi.lua_pcall(L, 0, -1, 0); + + }, "JS Lua program ran without error"); + + + console.log(lapi.lua_tostring(L, -1)); +}); \ No newline at end of file -- cgit v1.2.3-70-g09d2