From 653f4a07af6506e0b8ef71e8957976b9559f67e4 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 21 Feb 2017 15:24:37 +0100 Subject: pcall, xpcall --- README.md | 4 ++-- src/lapi.js | 42 ++++++++++++++++++++++++++++----- src/lbaselib.js | 44 +++++++++++++++++++++++++++++++++- tests/lbaselib.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index dd2ba4f..a7d0124 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ - [x] lua_istable - [x] lua_remove - [x] lua_rotate + - [x] lua_insert - [ ] lua_arith - [ ] lua_close - [ ] lua_compare @@ -91,7 +92,6 @@ - [ ] lua_getstack - [ ] lua_getupvalue - [ ] lua_getuservalue - - [ ] lua_insert - [ ] lua_isboolean - [ ] lua_iscfunction - [ ] lua_isfunction @@ -209,6 +209,7 @@ - [x] rawget - [x] type - [x] error + - [x] pcall - [ ] assert - [ ] collectgarbage - [ ] dofile @@ -218,7 +219,6 @@ - [ ] loadstring - [ ] next - [ ] pairs - - [ ] pcall - [ ] rawlen - [ ] select - [ ] tonumber diff --git a/src/lapi.js b/src/lapi.js index 907d2b1..9059ee9 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -30,7 +30,7 @@ const lua_atpanic = function(L, panicf) { return old; }; -// Return real index on stack +// Return value for idx on stack const index2addr = function(L, idx) { let ci = L.ci; if (idx > 0) { @@ -54,6 +54,30 @@ const index2addr = function(L, idx) { } }; +// Like index2addr but returns the index on stack +const index2addr_ = function(L, idx) { + let ci = L.ci; + if (idx > 0) { + let o = ci.funcOff + idx; + assert(idx <= ci.top - (ci.funcOff + 1), "unacceptable index"); + if (o >= L.top) return null; + else return o; + } else if (idx > lua.LUA_REGISTRYINDEX) { + assert(idx !== 0 && -idx <= L.top, "invalid index"); + return L.top + idx; + } else if (idx === lua.LUA_REGISTRYINDEX) { + return null; + } else { /* upvalues */ + idx = lua.LUA_REGISTRYINDEX - idx; + assert(idx <= MAXUPVAL + 1, "upvalue index too large"); + if (ci.func.ttislcf()) /* light C function? */ + return null; /* it has no upvalues */ + else { + return idx <= ci.func.nupvalues ? idx - 1 : null; + } + } +}; + const lua_checkstack = function(L, n) { return L.stack.length < luaconf.LUAI_MAXSTACK; }; @@ -99,7 +123,7 @@ const lua_pop = function(L, n) { } const reverse = function(L, from, to) { - for (; from < to; from++, to --) { + for (; from < to; from++, to--) { let temp = L.stack[from]; L.stack[from] = L.stack[to]; L.stack[to] = temp; @@ -113,15 +137,16 @@ const reverse = function(L, from, to) { const lua_rotate = function(L, idx, n) { let t = L.stack[L.top - 1]; let p = index2addr(L, idx); + let pIdx = index2addr_(L, idx); assert(!p.ttisnil() && idx > lua.LUA_REGISTRYINDEX, "index not in the stack"); assert((n >= 0 ? n : -n) <= (L.top - idx), "invalid 'n'"); - let m = n >= 0 ? L.top - 1 - n : L.top + idx - n - 1; /* end of prefix */ + let m = n >= 0 ? L.top - 1 - n : pIdx - n - 1; /* end of prefix */ - reverse(L, L.top + idx, m); + reverse(L, pIdx, m); reverse(L, m + 1, L.top - 1); - reverse(L, L.top + idx, L.top - 1); + reverse(L, pIdx, L.top - 1); }; const lua_remove = function(L, idx) { @@ -129,6 +154,10 @@ const lua_remove = function(L, idx) { lua_pop(L, 1); }; +const lua_insert = function(L, idx) { + lua_rotate(L, idx, 1); +}; + /* ** push functions (JS -> stack) */ @@ -629,4 +658,5 @@ module.exports.lua_setmetatable = lua_setmetatable; module.exports.lua_settop = lua_settop; module.exports.lua_rawequal = lua_rawequal; module.exports.lua_concat = lua_concat; -module.exports.lua_error = lua_error; \ No newline at end of file +module.exports.lua_error = lua_error; +module.exports.lua_insert = lua_insert; \ No newline at end of file diff --git a/src/lbaselib.js b/src/lbaselib.js index 205d57a..ac0ac4c 100644 --- a/src/lbaselib.js +++ b/src/lbaselib.js @@ -7,6 +7,7 @@ const lua = require('./lua.js'); const lapi = require('./lapi.js'); const lauxlib = require('./lauxlib.js'); const CT = lua.constant_types; +const TS = lua.thread_status; const luaB_print = function(L) { let n = lapi.lua_gettop(L); /* number of arguments */ @@ -99,6 +100,45 @@ const luaB_error = function(L) { return lapi.lua_error(L); }; +/* +** Continuation function for 'pcall' and 'xpcall'. Both functions +** already pushed a 'true' before doing the call, so in case of success +** 'finishpcall' only has to return everything in the stack minus +** 'extra' values (where 'extra' is exactly the number of items to be +** ignored). +*/ +const finishpcall = function(L, status, extra) { + if (status !== TS.LUA_OK && status !== TS.LUA_YIELD) { /* error? */ + lapi.lua_pushboolean(L, 0); /* first result (false) */ + lapi.lua_pushvalue(L, -2); /* error message */ + return 2; /* return false, msg */ + } else + return lapi.lua_gettop(L) - extra; +}; + +const luaB_pcall = function(L) { + lauxlib.luaL_checkany(L, 1); + lapi.lua_pushboolean(L, 1); /* first result if no errors */ + lapi.lua_insert(L, 1); /* put it in place */ + let status = lapi.lua_pcallk(L, lapi.lua_gettop(L) - 2, lua.LUA_MULTRET, 0, 0, finishpcall); + return finishpcall(L, status, 0); +}; + +/* +** Do a protected call with error handling. After 'lua_rotate', the +** stack will have ; so, the function passes +** 2 to 'finishpcall' to skip the 2 first values when returning results. +*/ +const luaB_xpcall = function(L) { + let n = lapi.lua_gettop(L); + lauxlib.luaL_checktype(L, 2, CT.LUA_TFUNCTION); + lapi.lua_pushboolean(L, 1); + lapi.lua_pushvalue(L, 1); + lapi.lua_rotate(L, 3, 2); + let status = lapi.lua_pcallk(L, n - 2, lua.LUA_MULTRET, 2, 2, finishpcall); + return finishpcall(L, status, 2); +}; + const base_funcs = { "collectgarbage": function () {}, "print": luaB_print, @@ -109,7 +149,9 @@ const base_funcs = { "rawset": luaB_rawset, "rawget": luaB_rawget, "type": luaB_type, - "error": luaB_error + "error": luaB_error, + "pcall": luaB_pcall, + "xpcall": luaB_xpcall, }; const luaopen_base = function(L) { diff --git a/tests/lbaselib.js b/tests/lbaselib.js index b55afde..cca3ebe 100644 --- a/tests/lbaselib.js +++ b/tests/lbaselib.js @@ -290,4 +290,74 @@ test('error, protected', function (t) { lapi.lua_tostring(L, -1).endsWith("you fucked up"), "Error is on the stack" ) +}); + + +test('pcall', function (t) { + let luaCode = ` + local willFail = function () + error("you fucked up") + end + + return pcall(willFail) + `, 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-pcall"); + + lapi.lua_call(L, 0, -1); + + }, "JS Lua program ran without error"); + + t.ok( + lapi.lua_tostring(L, -1).endsWith("you fucked up"), + "Error is on the stack" + ) +}); + + +test('xpcall', function (t) { + let luaCode = ` + local willFail = function () + error("you fucked up") + end + + local msgh = function (err) + return "Something's wrong: " .. err + end + + return xpcall(willFail, msgh) + `, 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-pcall"); + + lapi.lua_call(L, 0, -1); + + }, "JS Lua program ran without error"); + + console.log(lapi.lua_tostring(L, -1)); + + // t.ok( + // lapi.lua_tostring(L, -1).endsWith("you fucked up"), + // "Error is on the stack" + // ) }); \ No newline at end of file -- cgit v1.2.3-70-g09d2