aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lapi.js159
-rw-r--r--src/lauxlib.js142
-rw-r--r--src/lbaselib.js56
-rw-r--r--src/linit.js22
-rw-r--r--tests/lapi.js14
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