diff options
-rw-r--r-- | src/lapi.js | 159 | ||||
-rw-r--r-- | src/lauxlib.js | 142 | ||||
-rw-r--r-- | src/lbaselib.js | 56 | ||||
-rw-r--r-- | src/linit.js | 22 | ||||
-rw-r--r-- | tests/lapi.js | 14 |
5 files changed, 356 insertions, 37 deletions
diff --git a/src/lapi.js b/src/lapi.js index 1113eec..b5a8fab 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -8,6 +8,7 @@ const lobject = require('./lobject.js'); const ltm = require('./ltm.js'); const lfunc = require('./lfunc.js'); const lua = require('./lua.js'); +const luaconf = require('./luaconf.js'); const lstate = require('./lstate.js'); const lvm = require('./lvm.js'); const lundump = require('./lundump.js'); @@ -52,10 +53,23 @@ const index2addr = function(L, idx) { } }; +const lua_checkstack = function(L, n) { + return L.stack.length < luaconf.LUAI_MAXSTACK; +}; + /* ** basic stack manipulation */ +/* +** convert an acceptable stack index into an absolute index +*/ +const lua_absindex = function(L, idx) { + return (idx > 0 || idx > lua.LUA_REGISTRYINDEX) + ? idx + : L.top - L.ci.funcOff + idx; +}; + const lua_gettop = function(L) { return L.top - 1; }; @@ -83,6 +97,37 @@ const lua_pop = function(L, n) { lua_settop(L, -n - 1); } +const reverse = function(L, from, to) { + for (; from < to; from++, to --) { + let temp = L.stack[from]; + L.stack[from] = L.stack[to]; + L.stack[to] = temp; + } +}; + +/* +** Let x = AB, where A is a prefix of length 'n'. Then, +** rotate x n == BA. But BA == (A^r . B^r)^r. +*/ +const lua_rotate = function(L, idx, n) { + let t = L.stack[L.top - 1]; + let p = index2addr(L, idx); + + assert(!p.ttisnil() && idx > 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 : idx - n - 1; /* end of prefix */ + + reverse(L, idx, m); + reverse(L, m + 1, L.top - 1); + reverse(L, idx, L.top - 1); +}; + +const lua_remove = function(L, idx) { + lua_rotate(L, idx, -1); + lua_pop(L, 1); +}; + /* ** push functions (JS -> stack) */ @@ -137,6 +182,8 @@ const lua_pushstring = function (L, s) { return s; }; +const lua_pushliteral = lua_pushstring; + const lua_pushcclosure = function(L, fn, n) { assert(typeof fn === "function"); assert(typeof n === "number"); @@ -183,6 +230,10 @@ const lua_pushlightuserdata = function(L, p) { assert(L.top <= L.ci.top, "stack overflow"); }; +const lua_pushglobaltable = function(L) { + lua_rawgeti(L, lua.LUA_REGISTRYINDEX, lua.LUA_RIDX_GLOBALS); +}; + /* ** set functions (stack -> Lua) */ @@ -218,11 +269,36 @@ const lua_settable = function(L, idx) { L.top -= 2; }; +const lua_setfield = function(L, idx, k) { + auxsetstr(L, index2addr(L, idx), k) +}; /* ** get functions (Lua -> stack) */ +const lua_rawgeti = function(L, idx, n) { + let t = index2addr(L, idx); + + assert(t.ttistable(), "table expected"); + + L.stack[L.top++] = t.__index(t, n); + + assert(L.top <= L.ci.top, "stack overflow"); + + return L.stack[L.top - 1].ttnov(); +}; + +const lua_rawget = function(L, idx) { + let t = index2addr(L, idx); + + assert(t.ttistable(t), "table expected"); + + L.stack[L.top - 1] = t.__index(t, L.stack[L.top - 1]); + + return L.stack[L.top - 1].ttnov(); +}; + // narray and nrec are mostly useless for this implementation const lua_createtable = function(L, narray, nrec) { let t = new lobject.Table(); @@ -244,7 +320,6 @@ const lua_gettable = function(L, idx) { return L.stack[L.top - 1].ttnov(); }; - /* ** access functions (stack -> JS) */ @@ -293,6 +368,11 @@ const lua_istable = function(L, idx) { return index2addr(L, idx).ttistable(); }; +const lua_isstring = function(L, idx) { + let o = index2addr(L, idx); + return o.ttisstring() || o.ttisnumber(); +} + /* ** 'load' and 'call' functions (run Lua code) */ @@ -391,36 +471,47 @@ const lua_pcall = function(L, n, r, f) { return lua_pcallk(L, n, r, f, 0, null); } -module.exports.lua_pushvalue = lua_pushvalue; -module.exports.lua_pushnil = lua_pushnil; -module.exports.lua_pushnumber = lua_pushnumber; -module.exports.lua_pushinteger = lua_pushinteger; -module.exports.lua_pushlstring = lua_pushlstring; -module.exports.lua_pushstring = lua_pushstring; -module.exports.lua_pushboolean = lua_pushboolean; -module.exports.lua_pushcclosure = lua_pushcclosure; -module.exports.lua_pushcfunction = lua_pushcfunction; -module.exports.lua_pushjsclosure = lua_pushjsclosure; -module.exports.lua_pushjsfunction = lua_pushjsfunction; -module.exports.lua_version = lua_version; -module.exports.lua_atpanic = lua_atpanic; -module.exports.lua_gettop = lua_gettop; -module.exports.lua_typename = lua_typename; -module.exports.lua_type = lua_type; -module.exports.lua_tonumber = lua_tonumber; -module.exports.lua_tointeger = lua_tointeger; -module.exports.lua_toboolean = lua_toboolean; -module.exports.lua_tolstring = lua_tolstring; -module.exports.lua_tostring = lua_tostring; -module.exports.lua_load = lua_load; -module.exports.lua_callk = lua_callk; -module.exports.lua_call = lua_call; -module.exports.lua_pcallk = lua_pcallk; -module.exports.lua_pcall = lua_pcall; -module.exports.lua_pop = lua_pop; -module.exports.lua_setglobal = lua_setglobal; -module.exports.lua_istable = lua_istable; -module.exports.lua_createtable = lua_createtable; -module.exports.lua_newtable = lua_newtable; -module.exports.lua_settable = lua_settable; -module.exports.lua_gettable = lua_gettable;
\ No newline at end of file +module.exports.lua_pushvalue = lua_pushvalue; +module.exports.lua_pushnil = lua_pushnil; +module.exports.lua_pushnumber = lua_pushnumber; +module.exports.lua_pushinteger = lua_pushinteger; +module.exports.lua_pushlstring = lua_pushlstring; +module.exports.lua_pushstring = lua_pushstring; +module.exports.lua_pushliteral = lua_pushliteral; +module.exports.lua_pushboolean = lua_pushboolean; +module.exports.lua_pushcclosure = lua_pushcclosure; +module.exports.lua_pushcfunction = lua_pushcfunction; +module.exports.lua_pushjsclosure = lua_pushjsclosure; +module.exports.lua_pushjsfunction = lua_pushjsfunction; +module.exports.lua_version = lua_version; +module.exports.lua_atpanic = lua_atpanic; +module.exports.lua_gettop = lua_gettop; +module.exports.lua_typename = lua_typename; +module.exports.lua_type = lua_type; +module.exports.lua_tonumber = lua_tonumber; +module.exports.lua_tointeger = lua_tointeger; +module.exports.lua_toboolean = lua_toboolean; +module.exports.lua_tolstring = lua_tolstring; +module.exports.lua_tostring = lua_tostring; +module.exports.lua_load = lua_load; +module.exports.lua_callk = lua_callk; +module.exports.lua_call = lua_call; +module.exports.lua_pcallk = lua_pcallk; +module.exports.lua_pcall = lua_pcall; +module.exports.lua_pop = lua_pop; +module.exports.lua_setglobal = lua_setglobal; +module.exports.lua_istable = lua_istable; +module.exports.lua_createtable = lua_createtable; +module.exports.lua_newtable = lua_newtable; +module.exports.lua_settable = lua_settable; +module.exports.lua_gettable = lua_gettable; +module.exports.lua_absindex = lua_absindex; +module.exports.index2addr = index2addr; +module.exports.lua_rawget = lua_rawget; +module.exports.lua_isstring = lua_isstring; +module.exports.lua_rotate = lua_rotate; +module.exports.lua_remove = lua_remove; +module.exports.lua_checkstack = lua_checkstack; +module.exports.lua_rawgeti = lua_rawgeti; +module.exports.lua_pushglobaltable = lua_pushglobaltable; +module.exports.lua_setfield = lua_setfield;
\ No newline at end of file diff --git a/src/lauxlib.js b/src/lauxlib.js index 6cc3d3c..af09df7 100644 --- a/src/lauxlib.js +++ b/src/lauxlib.js @@ -5,6 +5,8 @@ const assert = require('assert'); const lstate = require('./lstate.js'); const lapi = require('./lapi.js'); +const lua = require('./lua.js'); +const CT = lua.constant_types; const panic = function(L) { console.log(`PANIC: unprotected error in call to Lua API (...)`); @@ -20,8 +22,142 @@ const luaL_newstate = function() { const luaL_typename = function(L, i) { return lapi.lua_typename(L, lapi.lua_type(L, i)); -} +}; + +const luaL_checkany = function(L, arg) { + if (lapi.lua_type(L, arg) === CT.LUA_TNONE) + throw new Error("value expected"); // TODO: luaL_argerror(L, arg, "value expected"); +}; + +const luaL_getmetafield = function(L, obj, event) { + if (!lapi.lua_getmetatable(L, obj)) + return CT.LUA_TNIL; + else { + lapi.lua_pushstring(L, event); + let tt = lapi.lua_rawget(L, -2); + if (tt === CT.LUA_TNIL) + lapi.lua_pop(L, 2); + return tt; + } +}; + +const luaL_callmeta = function(L, obj, event) { + obj = lapi.lua_absindex(L, obj); + if (luaL_getmetafield(L, obj, event) === CT.LUA_TNIL) + return false; + + lapi.lua_pushvalue(L, obj); + lapi.lua_call(L, 1, 1); + + return true; +}; +const luaL_tolstring = function(L, idx, len) { + if (luaL_callmeta(L, idx, "__tostring")) { + if (!lapi.lua_isstring(L, -1)) + throw new Error("'__tostring' must return a string"); // TODO: luaL_error + } else { + switch(lapi.lua_type(L, idx)) { + case CT.LUA_TNUMBER: + case CT.LUA_TSTRING: + case CT.LUA_TBOOLEAN: + lapi.lua_pushstring(L, `${lapi.index2addr(L, idx).value}`); + break; + case CT.LUA_TNIL: + lapi.lua_pushstring(L, `nil`); + break; + default: + let tt = luaL_getmetafield(L, idx, "__name"); + let kind = tt === CT.LUA_TSTRING ? lua_tostring(L, -1) : luaL_typename(L, idx); + lapi.lua_pushstring(L, `${kind}`); // We can't print memory address in JS + if (tt !== CT.LUA_TNIL) + lapi.lua_remove(L, -2); + break; + } + } + + return lapi.lua_tolstring(L, -1, len); +}; + +/* +** Stripped-down 'require': After checking "loaded" table, calls 'openf' +** to open a module, registers the result in 'package.loaded' table and, +** if 'glb' is true, also registers the result in the global table. +** Leaves resulting module on the top. +*/ +const luaL_requiref = function(L, modname, openf, glb) { + luaL_getsubtable(L, lua.LUA_REGISTRYINDEX, lua.LUA_LOADED_TABLE); + lapi.lua_getfield(L, -1, modname); /* LOADED[modname] */ + if (!lapi.lua_toboolean(L, -1)) { /* package not already loaded? */ + lapi.lua_pop(L, 1); /* remove field */ + lapi.lua_pushcfunction(L, openf); + lapi.lua_pushstring(L, modname); /* argument to open function */ + lapi.lua_call(L, 1, 1); /* call 'openf' to open module */ + lapi.lua_pushvalue(L, -1); /* make copy of module (call result) */ + lapi.lua_setfield(L, -3, modname); /* LOADED[modname] = module */ + } + lapi.lua_remove(L, -2); /* remove LOADED table */ + if (glb) { + lua_pushvalue(L, -1); /* copy of module */ + lua_setglobal(L, modname); /* _G[modname] = module */ + } +}; + +/* +** ensure that stack[idx][fname] has a table and push that table +** into the stack +*/ +const luaL_getsubtable = function(L, idx, fname) { + if (lapi.lua_getfield(L, idx, fname) === CT.LUA_TTABLE) + return true; /* table already there */ + else { + lapi.lua_pop(L, 1); /* remove previous result */ + idx = lapi.lua_absindex(L, idx); + lapi.lua_newtable(L); + lapi.lua_pushvalue(L, -1); /* copy to be left at top */ + lapi.lua_setfield(L, idx, fname); /* assign new table to field */ + return false; /* false, because did not find table there */ + } +}; + +/* +** set functions from list 'l' into table at top - 'nup'; each +** function gets the 'nup' elements at the top as upvalues. +** Returns with only the table at the stack. +*/ +const luaL_setfuncs = function(L, l, nup) { + luaL_checkstack(L, nup, "too many upvalues"); + for (lib in l) { /* fill the table with given functions */ + for (let i = 0; i < nup; i++) /* copy upvalues to the top */ + lapi.lua_pushvalue(L, -nup); + lapi.lua_pushcclosure(L, l[lib], nup); /* closure with those upvalues */ + lapi.lua_setfield(L, -(nup + 2), lib); + } + lapi.lua_pop(l, nup); /* remove upvalues */ +}; + +/* +** Ensures the stack has at least 'space' extra slots, raising an error +** if it cannot fulfill the request. (The error handling needs a few +** extra slots to format the error message. In case of an error without +** this extra space, Lua will generate the same 'stack overflow' error, +** but without 'msg'.) +*/ +const luaL_checkstack = function(L, space, msg) { + if (!lapi.luaL_checkstack(L, space)) { + if (msg) + throw new Error(L, `stack overflow (${msg})`); + else + throw new Error(L, 'stack overflow'); // TODO: luaL_error + } +}; -module.exports.luaL_newstate = luaL_newstate; -module.exports.luaL_typename = luaL_typename;
\ No newline at end of file +module.exports.luaL_newstate = luaL_newstate; +module.exports.luaL_typename = luaL_typename; +module.exports.luaL_checkany = luaL_checkany; +module.exports.luaL_callmeta = luaL_callmeta; +module.exports.luaL_getmetafield = luaL_getmetafield; +module.exports.luaL_requiref = luaL_requiref; +module.exports.luaL_getsubtable = luaL_getsubtable; +module.exports.luaL_setfuncs = luaL_setfuncs; +module.exports.luaL_checkstack = luaL_checkstack;
\ No newline at end of file diff --git a/src/lbaselib.js b/src/lbaselib.js new file mode 100644 index 0000000..d28a3d5 --- /dev/null +++ b/src/lbaselib.js @@ -0,0 +1,56 @@ +/* jshint esversion: 6 */ +"use strict"; + +const assert = require('assert'); + +const lapi = require('./lapi.js'); +const lauxlib = require('./lauxlib.js'); + +const luaB_print = function(L) { + let n = lapi.lua_gettop(L); /* number of arguments */ + let str = ""; + + lapi.lua_getglobal(L, "tostring"); + for (let i = 1; i <= n; i++) { + lapi.lua_pushvalue(L, -1); /* function to be called */ + lapi.lua_pushvalue(L, i); /* value to print */ + lapi.lua_call(L, 1, 1); + s = lapi.lua_tolstring(L, -1, null); + if (s === null) + throw new Error("'tostring' must return a string to 'print"); + if (i > 1) s = `\t${s}`; + str = `${str}${s}`; + lapi.lua_pop(L, 1); + } + + console.log(str); + return 0; +}; + +const luaB_tostring = function(L) { + lauxlib.luaL_checkany(L, 1); + lauxlib.luaL_tolstring(L, 1, null); + + return true; +}; + +const base_funcs = { + "print": luaB_print, + "tostring": luaB_tostring, +}; + +const luaopen_base = function(L) { + /* open lib into global table */ + lapi.lua_pushglobaltable(L); + lauxlib.luaL_setfuncs(L, base_funcs, 0); + /* set global _G */ + lapi.lua_pushvalue(L, -1); + lapi.lua_setfield(L, -2, "_G"); + /* set global _VERSION */ + lapi.lua_pushliteral(L, lua.LUA_VERSION); + lapi.lua_setfield(L, -2, "_VERSION"); + return true; +}; + +module.exports.luaB_tostring = luaB_tostring; +module.exports.luaB_print = luaB_print; diff --git a/src/linit.js b/src/linit.js new file mode 100644 index 0000000..2747fc2 --- /dev/null +++ b/src/linit.js @@ -0,0 +1,22 @@ +/* jshint esversion: 6 */ +"use strict"; + +const assert = require('assert'); + +const lapi = require('./lapi.js'); +const lauxlib = require('./lauxlib.js'); +const lbaselib = require('./lbaselib.js'); + +const loadedlibs = { + "_G" = luaopen_base +}; + +const luaL_openlibs = function(L) { + /* "require" functions from 'loadedlibs' and set results to global table */ + for (lib in loadedlibs) { + lauxlib.luaL_requiref(L, lib, loadedlibs[lib], 1); + lapi.lua_pop(L, 1); /* remove lib */ + } +} + +module.exports.luaL_openlibs = luaL_openlibs;
\ No newline at end of file diff --git a/tests/lapi.js b/tests/lapi.js index 8a1db31..2f5f459 100644 --- a/tests/lapi.js +++ b/tests/lapi.js @@ -561,4 +561,18 @@ test('lua_settable, lua_gettable', function (t) { "value", "Correct element(s) on the stack" ); +}); + + +test('print', function (t) { + let luaCode = ` + print("hello world"); + `, L; + + t.plan(1); + + t.doesNotThrow(function () { + L = getState(luaCode); + lapi.lua_call(L, 0, -1); + }, "Program executed without errors"); });
\ No newline at end of file |