diff options
| -rw-r--r-- | src/lapi.js | 19 | ||||
| -rw-r--r-- | src/lauxlib.js | 87 | ||||
| -rw-r--r-- | src/linit.js | 6 | ||||
| -rw-r--r-- | src/ltablib.js | 92 | ||||
| -rw-r--r-- | src/lvm.js | 2 | ||||
| -rw-r--r-- | tests/ltablib.js | 47 | ||||
| -rw-r--r-- | tests/lvm.js | 6 | 
7 files changed, 232 insertions, 27 deletions
diff --git a/src/lapi.js b/src/lapi.js index c841ba2..6316326 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -205,7 +205,7 @@ const lua_pushinteger = function(L, n) {  const lua_pushlstring = function(L, s, len) { // TODO: embedded \0      assert(typeof s === "string"); -    assert(typeof n === "number"); +    assert(typeof len === "number");      let ts = len === 0 ? new TValue(CT.LUA_TLNGSTR, "") : new TValue(CT.LUA_TLNGSTR, s.substr(0, len));      L.stack[L.top++] = ts; @@ -538,6 +538,15 @@ const lua_stringtonumber = function(L, s) {      return s.length;  }; +// TODO: pisnum +const lua_tointegerx = function(L, idx) { +    let o = index2addr(L, idx); +    let res = lvm.tointeger(o); +    if (res === false) +        res = 0;  /* call to 'tointeger' may change 'n' even if it fails */ +    return res; +}; +  const f_call = function(L, ud) {      ldo.luaD_callnoyield(L, ud.funcOff, ud.nresults);  }; @@ -701,6 +710,12 @@ const lua_concat = function(L, n) {      }  }; +const lua_len = function(L, idx) { +    let t = index2addr(L, idx); +    lvm.luaV_objlen(L, L.top++, t); +    assert(L.top <= L.ci.top, "stack overflow"); +}; +  // This functions are only there for compatibility purposes  const lua_gc = function () {}; @@ -735,6 +750,7 @@ module.exports.lua_gettop          = lua_gettop;  module.exports.lua_insert          = lua_insert;  module.exports.lua_isstring        = lua_isstring;  module.exports.lua_istable         = lua_istable; +module.exports.lua_len             = lua_len;  module.exports.lua_load            = lua_load;  module.exports.lua_newtable        = lua_newtable;  module.exports.lua_next            = lua_next; @@ -771,6 +787,7 @@ module.exports.lua_status          = lua_status;  module.exports.lua_stringtonumber  = lua_stringtonumber;  module.exports.lua_toboolean       = lua_toboolean;  module.exports.lua_tointeger       = lua_tointeger; +module.exports.lua_tointegerx      = lua_tointegerx;  module.exports.lua_tolstring       = lua_tolstring;  module.exports.lua_tonumber        = lua_tonumber;  module.exports.lua_topointer       = lua_topointer; diff --git a/src/lauxlib.js b/src/lauxlib.js index a04f3be..ba720af 100644 --- a/src/lauxlib.js +++ b/src/lauxlib.js @@ -9,9 +9,16 @@ const lua    = require('./lua.js');  const ldebug = require('./ldebug.js');  const CT     = lua.constant_types; -const LUA_LOADED_TABLE = "_LOADED" +const LUA_LOADED_TABLE = "_LOADED"; +class luaL_Buffer { +    constructor(L) { +        this.L = L; +        this.b = ""; +    } +} +  /*  ** search for 'objidx' in table at index -1.  ** return 1 + string at top if find a good name. @@ -95,7 +102,7 @@ const typeerror = function(L, arg, tname) {      else          typearg = luaL_typename(L, arg); -    let msg = lua_pushstring(L, `${tname} expected, got ${typearg}`); +    let msg = lapi.lua_pushstring(L, `${tname} expected, got ${typearg}`);      return luaL_argerror(L, arg, msg);  }; @@ -183,6 +190,30 @@ const luaL_optinteger = function(L, arg, def) {      return luaL_opt(L, luaL_checkinteger, arg, def);  }; +const luaL_buffinit = function(L, B) { +    B.L = L; +    B.b = ""; +}; + +const luaL_addlstring = function(B, s) { +    B.b += s; +}; + +const luaL_addstring = luaL_addlstring; + +const luaL_pushresult = function(B) { +    let L = B.L; +    lapi.lua_pushstring(L, B.b); +}; + +const luaL_addvalue = function(B) { +    let L = B.L; +    let s = lapi.lua_tostring(L, -1); +    // TODO: buffonstack ? necessary ? +    luaL_addstring(B, s); +    lapi.lua_remove(L, -1); +}; +  const luaL_opt = function(L, f, n, d) {      return lapi.lua_type(L, n) <= 0 ? d : f(L, n);  }; @@ -210,6 +241,15 @@ const luaL_callmeta = function(L, obj, event) {      return true;  }; +const luaL_len = function(L, idx) { +    lapi.lua_len(L, idx); +    let l = lapi.lua_tointegerx(L, -1); +    if (l === false) +        luaL_error(L, "object length is not an integer"); +    lapi.lua_pop(L, 1);  /* remove object */ +    return l; +}; +  const luaL_tolstring = function(L, idx) {      if (luaL_callmeta(L, idx, "__tostring")) {          if (!lapi.lua_isstring(L, -1)) @@ -226,7 +266,7 @@ const luaL_tolstring = function(L, idx) {                  break;              default:                  let tt = luaL_getmetafield(L, idx, "__name"); -                let kind = tt === CT.LUA_TSTRING ? lua_tostring(L, -1) : luaL_typename(L, idx); +                let kind = tt === CT.LUA_TSTRING ? lapi.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); @@ -315,26 +355,33 @@ const luaL_newlib = function(L, l) {      luaL_setfuncs(L, l, 0);  }; -module.exports.luaL_newstate     = luaL_newstate; -module.exports.luaL_typename     = luaL_typename; +module.exports.LUA_LOADED_TABLE  = LUA_LOADED_TABLE; +module.exports.luaL_addlstring   = luaL_addlstring; +module.exports.luaL_addstring    = luaL_addstring; +module.exports.luaL_addvalue     = luaL_addvalue; +module.exports.luaL_argcheck     = luaL_argcheck; +module.exports.luaL_argerror     = luaL_argerror; +module.exports.luaL_Buffer       = luaL_Buffer; +module.exports.luaL_buffinit     = luaL_buffinit; +module.exports.luaL_callmeta     = luaL_callmeta;  module.exports.luaL_checkany     = luaL_checkany; +module.exports.luaL_checkinteger = luaL_checkinteger; +module.exports.luaL_checklstring = luaL_checklstring; +module.exports.luaL_checkstack   = luaL_checkstack;  module.exports.luaL_checktype    = luaL_checktype; -module.exports.luaL_callmeta     = luaL_callmeta; +module.exports.luaL_error        = luaL_error;  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; -module.exports.LUA_LOADED_TABLE  = LUA_LOADED_TABLE; -module.exports.luaL_tolstring    = luaL_tolstring; -module.exports.luaL_argcheck     = luaL_argcheck; -module.exports.luaL_checklstring = luaL_checklstring; +module.exports.luaL_len          = luaL_len; +module.exports.luaL_newlib       = luaL_newlib; +module.exports.luaL_newstate     = luaL_newstate; +module.exports.luaL_opt          = luaL_opt; +module.exports.luaL_optinteger   = luaL_optinteger;  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; -module.exports.luaL_error        = luaL_error; -module.exports.luaL_argerror     = luaL_argerror; -module.exports.luaL_newlib       = luaL_newlib;
\ No newline at end of file +module.exports.luaL_pushresult   = luaL_pushresult; +module.exports.luaL_requiref     = luaL_requiref; +module.exports.luaL_setfuncs     = luaL_setfuncs; +module.exports.luaL_tolstring    = luaL_tolstring; +module.exports.luaL_typename     = luaL_typename; +module.exports.luaL_where        = luaL_where;
\ No newline at end of file diff --git a/src/linit.js b/src/linit.js index d52b640..eb8caf2 100644 --- a/src/linit.js +++ b/src/linit.js @@ -8,10 +8,12 @@ const lauxlib  = require('./lauxlib.js');  const lualib   = require('./lualib.js');  const lbaselib = require('./lbaselib.js');  const lcorolib = require('./lcorolib.js'); +const ltablib  = require('./ltablib.js');  const loadedlibs = { -    [lualib.LUA_COLIBNAME]: lcorolib.luaopen_coroutine, -    "_G":                  lbaselib.luaopen_base +    [lualib.LUA_TABLIBNAME]: ltablib.luaopen_table, +    [lualib.LUA_COLIBNAME]:  lcorolib.luaopen_coroutine, +    "_G":                    lbaselib.luaopen_base  };  const luaL_openlibs = function(L) { diff --git a/src/ltablib.js b/src/ltablib.js new file mode 100644 index 0000000..8e1f758 --- /dev/null +++ b/src/ltablib.js @@ -0,0 +1,92 @@ +/* jshint esversion: 6 */ +"use strict"; + +const assert  = require('assert'); + +const lua     = require('./lua.js'); +const lapi    = require('./lapi.js'); +const lauxlib = require('./lauxlib.js'); +const lstate  = require('./lstate.js'); +const ldo     = require('./ldo.js'); +const ldebug  = require('./ldebug.js'); +const CT      = lua.constant_types; +const TS      = lua.thread_status; + + +/* +** Operations that an object must define to mimic a table +** (some functions only need some of them) +*/ +const TAB_R  = 1;               /* read */ +const TAB_W  = 2;               /* write */ +const TAB_L  = 4;               /* length */ +const TAB_RW = (TAB_R | TAB_W); /* read/write */ + +const checkfield = function(L, key, n) { +    lapi.lua_pushstring(L, key); +    return lapi.lua_rawget(L, -n) !== CT.LUA_TNIL; +}; + +/* +** Check that 'arg' either is a table or can behave like one (that is, +** has a metatable with the required metamethods) +*/ +const checktab = function(L, arg, what) { +    if (lapi.lua_type(L, arg) !== CT.LUA_TTABLE) {  /* is it not a table? */ +        let n = 1; +        if (lapi.lua_getmetatable(L, arg) &&  /* must have metatable */ +            (!(what & TAB_R) || checkfield(L, "__index", ++n)) && +            (!(what & TAB_W) || checkfield(L, "__newindex", ++n)) && +            (!(what & TAB_L) || checkfield(L, "__len", ++n))) { +            lapi.lua_pop(L, n);  /* pop metatable and tested metamethods */ +        } +        else +            lauxlib.luaL_checktype(L, arg, CT.LUA_TTABLE);  /* force an error */ +    } +}; + +const aux_getn = function(L, n, w) { +    checktab(L, n, w | TAB_L); +    lauxlib.luaL_len(L, n); +}; + +const addfield = function(L, b, i) { +    lapi.lua_geti(L, 1, i); +    if (!lapi.lua_isstring(L, -1)) +        lauxlib.luaL_error(L, `invalid value (${lauxlib.luaL_typename(L, -1)}) at index ${i} in table for 'concat'`); + +    lauxlib.luaL_addvalue(b); +}; + +const tconcat = function(L) { +    let last = aux_getn(L, 1, TAB_R); +    let sep = lauxlib.luaL_optlstring(L, 2, ""); +    let i = lauxlib.luaL_optinteger(L, 3, 1); +    last = lauxlib.luaL_optinteger(L, 4, last); + +    let b = new lauxlib.luaL_Buffer(); +    lauxlib.luaL_buffinit(L, b); + +    for (; i < last; i++) { +        addfield(L, b, i); +        lauxlib.luaL_addlstring(b, sep); +    } + +    if (i === last) +        addfield(L, b, i); + +    lauxlib.luaL_pushresult(b); + +    return 1; +}; + +const tab_funcs = { +    "concat": tconcat +}; + +const luaopen_table = function(L) { +    lauxlib.luaL_newlib(L, tab_funcs); +    return 1; +}; + +module.exports.luaopen_table = luaopen_table;
\ No newline at end of file @@ -915,7 +915,7 @@ const luaV_objlen = function(L, ra, rb) {          case CT.LUA_TTABLE: {              tm = ltm.luaT_gettmbyobj(L, rb, ltm.TMS.TM_LEN);              if (!tm.ttisnil()) break; -            L.stack[ra] = rb.luaH_getn(); +            L.stack[ra] = new TValue(CT.LUA_TNUMINT, rb.luaH_getn());              return;          }          case CT.LUA_TSHRSTR: diff --git a/tests/ltablib.js b/tests/ltablib.js new file mode 100644 index 0000000..22a6797 --- /dev/null +++ b/tests/ltablib.js @@ -0,0 +1,47 @@ +/*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 VM         = 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'); +const lstate     = require('../src/lstate.js'); +const CT         = lua.constant_types; + + +test('table.concat', function (t) { +    let luaCode = ` +        return table.concat({1, 2, 3, 4, 5, 6, 7}, ",", 3, 5) +    `, 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-table.concat"); + +        lapi.lua_call(L, 0, -1); + +    }, "JS Lua program ran without error"); + +    t.strictEqual( +        lapi.lua_tostring(L, -1), +        "3,4,5", +        "Correct element(s) on the stack" +    ); +});
\ No newline at end of file diff --git a/tests/lvm.js b/tests/lvm.js index 8517393..4f02ed5 100644 --- a/tests/lvm.js +++ b/tests/lvm.js @@ -758,19 +758,19 @@ test('LEN', function (t) {      }, "Program executed without errors");      t.strictEqual( -        L.stack[L.top - 1], +        L.stack[L.top - 1].value,          5,          "Program output is correct"      );      t.strictEqual( -        L.stack[L.top - 2], +        L.stack[L.top - 2].value,          3,          "Program output is correct"      );      t.strictEqual( -        L.stack[L.top - 3], +        L.stack[L.top - 3].value,          0,          "Program output is correct"      );  | 
