diff options
Diffstat (limited to 'src')
| -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 | 
4 files changed, 342 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  | 
