aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lapi.js19
-rw-r--r--src/lauxlib.js87
-rw-r--r--src/linit.js6
-rw-r--r--src/ltablib.js92
-rw-r--r--src/lvm.js2
-rw-r--r--tests/ltablib.js47
-rw-r--r--tests/lvm.js6
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
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"
);