From 8b9545c6b2a158d44e18dcaa1147cc1206eabfd3 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Thu, 23 Feb 2017 16:12:46 +0100 Subject: table.concat --- src/lapi.js | 19 +++++++++++- src/lauxlib.js | 87 +++++++++++++++++++++++++++++++++++++++++------------ src/linit.js | 6 ++-- src/ltablib.js | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lvm.js | 2 +- tests/ltablib.js | 47 +++++++++++++++++++++++++++++ tests/lvm.js | 6 ++-- 7 files changed, 232 insertions(+), 27 deletions(-) create mode 100644 src/ltablib.js create mode 100644 tests/ltablib.js 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 diff --git a/src/lvm.js b/src/lvm.js index 95e8cc4..16a3bd2 100644 --- a/src/lvm.js +++ b/src/lvm.js @@ -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" ); -- cgit v1.2.3-70-g09d2