summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md18
-rw-r--r--src/lapi.js56
-rw-r--r--src/lauxlib.js21
-rw-r--r--src/lbaselib.js79
-rw-r--r--src/lcode.js2
-rw-r--r--src/ldo.js2
-rw-r--r--src/lvm.js2
-rw-r--r--tests/load.js82
8 files changed, 240 insertions, 22 deletions
diff --git a/README.md b/README.md
index fbc41e7..7663ee8 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[![Build Status](https://travis-ci.com/giann/fengari.svg?token=PSYuVp8qrrdszprvDFz7&branch=master)](https://travis-ci.com/giann/fengari) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
+[![Build Status](https://travis-ci.org/giann/fengari.svg?branch=master)](https://travis-ci.org/giann/fengari) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
# fengari
🐺 φεγγάρι - A Lua VM written in JS ES6 targeting the browser
@@ -39,6 +39,8 @@
- [x] lua_gettop
- [x] lua_insert
- [x] lua_isinteger
+ - [x] lua_isnil
+ - [x] lua_isnone
- [x] lua_isnoneornil
- [x] lua_isnumber
- [x] lua_isstring
@@ -71,6 +73,7 @@
- [x] lua_rawlen
- [x] lua_rawset
- [x] lua_remove
+ - [x] lua_replace
- [x] lua_resume
- [x] lua_rotate
- [x] lua_setfield
@@ -79,6 +82,7 @@
- [x] lua_setmetatable
- [x] lua_settable
- [x] lua_settop
+ - [x] lua_setupvalue
- [x] lua_status
- [x] lua_stringtonumber
- [x] lua_toboolean
@@ -116,8 +120,6 @@
- [ ] lua_iscfunction
- [ ] lua_isfunction
- [ ] lua_islightuserdata
- - [ ] lua_isnil
- - [ ] lua_isnone
- [ ] lua_isthread
- [ ] lua_isuserdata
- [ ] lua_newuserdata
@@ -129,11 +131,9 @@
- [ ] lua_rawseti
- [ ] lua_rawsetp
- [ ] lua_register
- - [ ] lua_replace
- [ ] lua_setallocf
- [ ] lua_sethook
- [ ] lua_setlocal
- - [ ] lua_setupvalue
- [ ] lua_setuservalue
- [ ] lua_tocfunction
- [ ] lua_touserdata
@@ -157,6 +157,9 @@
- [x] luaL_getmetafield
- [x] luaL_getsubtable
- [x] luaL_len
+ - [x] luaL_loadbuffer
+ - [x] luaL_loadbufferx
+ - [x] luaL_loadstring
- [x] luaL_newlib
- [x] luaL_newstate
- [x] luaL_openlibs
@@ -182,11 +185,8 @@
- [ ] luaL_fileresult
- [ ] luaL_getmetatable
- [ ] luaL_gsub
- - [ ] luaL_loadbuffer
- - [ ] luaL_loadbufferx
- [ ] luaL_loadfile
- [ ] luaL_loadfilex
- - [ ] luaL_loadstring
- [ ] luaL_newlibtable
- [ ] luaL_newmetatable
- [ ] luaL_optnumber
@@ -202,10 +202,10 @@
- [ ] Standard library
- [ ] Base lib
- [x] ...
+ - [x] load
- [ ] require
- [ ] dofile
- [ ] loadfile
- - [ ] load
- [x] Coroutine
- [x] Table
- [x] Math
diff --git a/src/lapi.js b/src/lapi.js
index 72ff554..a11dbca 100644
--- a/src/lapi.js
+++ b/src/lapi.js
@@ -182,6 +182,11 @@ const lua_insert = function(L, idx) {
lua_rotate(L, idx, 1);
};
+const lua_replace = function(L, idx) {
+ lua_copy(L, -1, idx);
+ lua_pop(L, 1);
+};
+
/*
** push functions (JS -> stack)
*/
@@ -424,6 +429,45 @@ const lua_createtable = function(L, narray, nrec) {
assert(L.top <= L.ci.top, "stack overflow");
};
+const aux_upvalue = function(fi, n) {
+ switch(fi.ttype()) {
+ case CT.LUAT_TCCL: { /* C closure */
+ let f = fi.value;
+ if (!(1 <= n && n <= f.nupvalues)) return null;
+ return {
+ name: "",
+ val: f.upvalue[n-1]
+ };
+ }
+ case CT.LUA_TLCL: { /* Lua closure */
+ let f = fi.value;
+ let p = f.p;
+ if (!(1 <= n && n <= p.upvalues.length)) return null;
+ let name = p.upvalues[n-1].name;
+ return {
+ name: name ? name : "(*no name)",
+ val: f.upvals[n-1].val()
+ };
+ }
+ default: return null; /* not a closure */
+ }
+};
+
+const lua_setupvalue = function(L, funcindex, n) {
+ let fi = index2addr(L, funcindex);
+ assert(1 < L.top - L.ci.funcOff, "not enough elements in the stack");
+ let aux = aux_upvalue(fi, n);
+ let name = aux.name;
+ let val = aux.val;
+ if (name) {
+ L.top--;
+ // TODO: what if it's not a pure TValue (closure, table)
+ val.type = L.stack[L.top].type;
+ val.value = L.stack[L.top].value;
+ }
+ return name;
+};
+
const lua_newtable = function(L) {
lua_createtable(L, 0, 0);
};
@@ -595,6 +639,14 @@ const lua_typename = function(L, t) {
return ltm.ttypename(t);
};
+const lua_isnil = function(L, n) {
+ return lua_type(L, n) === CT.LUA_TNIL;
+};
+
+const lua_isnone = function(L, n) {
+ return lua_type(L, n) === CT.LUA_TNONE;
+};
+
const lua_isnoneornil = function(L, n) {
return lua_type(L, n) <= 0;
};
@@ -798,6 +850,8 @@ module.exports.lua_gettable = lua_gettable;
module.exports.lua_gettop = lua_gettop;
module.exports.lua_insert = lua_insert;
module.exports.lua_isinteger = lua_isinteger;
+module.exports.lua_isnil = lua_isnil;
+module.exports.lua_isnone = lua_isnone;
module.exports.lua_isnoneornil = lua_isnoneornil;
module.exports.lua_isnumber = lua_isnumber;
module.exports.lua_isstring = lua_isstring;
@@ -830,6 +884,7 @@ module.exports.lua_rawgeti = lua_rawgeti;
module.exports.lua_rawlen = lua_rawlen;
module.exports.lua_rawset = lua_rawset;
module.exports.lua_remove = lua_remove;
+module.exports.lua_replace = lua_replace;
module.exports.lua_rotate = lua_rotate;
module.exports.lua_setfield = lua_setfield;
module.exports.lua_setglobal = lua_setglobal;
@@ -837,6 +892,7 @@ module.exports.lua_seti = lua_seti;
module.exports.lua_setmetatable = lua_setmetatable;
module.exports.lua_settable = lua_settable;
module.exports.lua_settop = lua_settop;
+module.exports.lua_setupvalue = lua_setupvalue;
module.exports.lua_status = lua_status;
module.exports.lua_stringtonumber = lua_stringtonumber;
module.exports.lua_toboolean = lua_toboolean;
diff --git a/src/lauxlib.js b/src/lauxlib.js
index 728469a..d58a171 100644
--- a/src/lauxlib.js
+++ b/src/lauxlib.js
@@ -225,6 +225,24 @@ const luaL_opt = function(L, f, n, d) {
return lapi.lua_type(L, n) <= 0 ? d : f(L, n);
};
+const getS = function(L, ud) {
+ let s = ud.string;
+ ud.string = null;
+ return s;
+};
+
+const luaL_loadbufferx = function(L, buff, size, name, mode) {
+ return lapi.lua_load(L, getS, {string: buff}, name, mode);
+};
+
+const luaL_loadbuffer = function(L, s, sz, n) {
+ return luaL_loadbufferx(L, s, sz, n, null);
+};
+
+const luaL_loadstring = function(L, s) {
+ return luaL_loadbuffer(L, s, s.length, s);
+};
+
const luaL_getmetafield = function(L, obj, event) {
if (!lapi.lua_getmetatable(L, obj))
return CT.LUA_TNIL;
@@ -381,6 +399,9 @@ module.exports.luaL_error = luaL_error;
module.exports.luaL_getmetafield = luaL_getmetafield;
module.exports.luaL_getsubtable = luaL_getsubtable;
module.exports.luaL_len = luaL_len;
+module.exports.luaL_loadbuffer = luaL_loadbuffer;
+module.exports.luaL_loadbufferx = luaL_loadbufferx;
+module.exports.luaL_loadstring = luaL_loadstring;
module.exports.luaL_newlib = luaL_newlib;
module.exports.luaL_newstate = luaL_newstate;
module.exports.luaL_opt = luaL_opt;
diff --git a/src/lbaselib.js b/src/lbaselib.js
index 77638d0..0b5c2f7 100644
--- a/src/lbaselib.js
+++ b/src/lbaselib.js
@@ -251,26 +251,85 @@ const luaB_xpcall = function(L) {
return finishpcall(L, status, 2);
};
+const load_aux = function(L, status, envidx) {
+ if (status === TS.LUA_OK) {
+ if (envidx !== 0) { /* 'env' parameter? */
+ lapi.lua_pushvalue(L, envidx); /* environment for loaded function */
+ if (!lapi.lua_setupvalue(L, -2, 1)) /* set it as 1st upvalue */
+ lapi.lua_pop(L, 1); /* remove 'env' if not used by previous call */
+ }
+ return 1;
+ } else { /* error (message is on top of the stack) */
+ lapi.lua_pushnil(L);
+ lapi.lua_insert(L, -2); /* put before error message */
+ return 2; /* return nil plus error message */
+ }
+};
+
+/*
+** reserved slot, above all arguments, to hold a copy of the returned
+** string to avoid it being collected while parsed. 'load' has four
+** optional arguments (chunk, source name, mode, and environment).
+*/
+const RESERVEDSLOT = 5;
+
+/*
+** Reader for generic 'load' function: 'lua_load' uses the
+** stack for internal stuff, so the reader cannot change the
+** stack top. Instead, it keeps its resulting string in a
+** reserved slot inside the stack.
+*/
+const generic_reader = function(L, ud) {
+ lauxlib.luaL_checkstack(L, 2, "too many nested functions");
+ lapi.lua_pushvalue(L, 1); /* get function */
+ lapi.lua_call(L, 0, 1); /* call it */
+ if (lapi.lua_isnil(L, -1)) {
+ lapi.lua_pop(L, 1); /* pop result */
+ return null;
+ } else if (!lapi.lua_isstring(L, -1))
+ lauxlib.luaL_error(L, "reader function must return a string");
+ lapi.lua_replace(L, RESERVEDSLOT); /* save string in reserved slot */
+ return lapi.lua_tostring(L, RESERVEDSLOT);
+};
+
+const luaB_load = function(L) {
+ let s = lapi.lua_tostring(L, 1);
+ let mode = lauxlib.luaL_optstring(L, 3, "bt");
+ let env = !lapi.lua_isnone(L, 4) ? 4 : 0; /* 'env' index or 0 if no 'env' */
+ let status;
+ if (s !== null) { /* loading a string? */
+ let chunkname = lauxlib.luaL_optstring(L, 2, s);
+ status = lauxlib.luaL_loadbufferx(L, s, chunkname, mode);
+ } else { /* loading from a reader function */
+ let chunkname = lauxlib.luaL_optstring(L, 2, "=(load)");
+ lauxlib.luaL_checktype(L, 1, CT.LUA_TFUNCTION);
+ lapi.lua_settop(L, RESERVEDSLOT); /* create reserved slot */
+ status = lapi.lua_load(L, generic_reader, null, chunkname, mode);
+ }
+ return load_aux(L, status, env);
+};
+
const base_funcs = {
"collectgarbage": function () {},
"assert": luaB_assert,
- "print": luaB_print,
- "tostring": luaB_tostring,
- "tonumber": luaB_tonumber,
+ "error": luaB_error,
"getmetatable": luaB_getmetatable,
+ "ipairs": luaB_ipairs,
+ "load": luaB_load,
"next": luaB_next,
"pairs": luaB_pairs,
- "ipairs": luaB_ipairs,
- "select": luaB_select,
- "setmetatable": luaB_setmetatable,
+ "pcall": luaB_pcall,
+ "print": luaB_print,
"rawequal": luaB_rawequal,
+ "rawget": luaB_rawget,
"rawlen": luaB_rawlen,
"rawset": luaB_rawset,
- "rawget": luaB_rawget,
+ "select": luaB_select,
+ "setmetatable": luaB_setmetatable,
+ "tonumber": luaB_tonumber,
+ "tostring": luaB_tostring,
"type": luaB_type,
- "error": luaB_error,
- "pcall": luaB_pcall,
- "xpcall": luaB_xpcall,
+ "xpcall": luaB_xpcall
};
const luaopen_base = function(L) {
diff --git a/src/lcode.js b/src/lcode.js
index 9fb32e2..fedf1e2 100644
--- a/src/lcode.js
+++ b/src/lcode.js
@@ -359,7 +359,7 @@ const luaK_patchclose = function(fs, list, level) {
** line information. Return 'i' position.
*/
const luaK_code = function(fs, i) {
- console.log(`${i.opcode}\t${i.A}\t${i.B}\t${i.C}\t${i.Ax}\t${i.Bx}\t${i.sBx}`);
+ // console.log(`${i.opcode}\t${i.A}\t${i.B}\t${i.C}\t${i.Ax}\t${i.Bx}\t${i.sBx}`);
let f = fs.f;
dischargejpc(fs); /* 'pc' will change */
/* put new instruction in code array */
diff --git a/src/ldo.js b/src/ldo.js
index 668ea9b..f4d8336 100644
--- a/src/ldo.js
+++ b/src/ldo.js
@@ -521,7 +521,7 @@ class SParser {
}
const checkmode = function(L, mode, x) {
- if (mode && mode !== x) {
+ if (mode && mode.charAt(0) !== x.charAt(0)) {
lapi.lua_pushstring(L, `attempt to load a ${x} chunk (mode is '${mode}')`);
luaD_throw(L, TS.LUA_ERRSYNTAX);
}
diff --git a/src/lvm.js b/src/lvm.js
index 75fe44c..534ba7b 100644
--- a/src/lvm.js
+++ b/src/lvm.js
@@ -128,7 +128,7 @@ const luaV_execute = function(L) {
if (i.breakpoint) // TODO: remove, used until lapi
return;
- console.log(`> ${opcode}`);
+ // console.log(`> ${opcode}`);
switch (opcode) {
case "OP_MOVE": {
L.stack[ra] = L.stack[RB(L, base, i)];
diff --git a/tests/load.js b/tests/load.js
new file mode 100644
index 0000000..0f524f3
--- /dev/null
+++ b/tests/load.js
@@ -0,0 +1,82 @@
+"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 CT = lua.constant_types;
+
+
+test('luaL_loadstring', function (t) {
+ let luaCode = `
+ local a = "hello world"
+ return a
+ `, L;
+
+ t.plan(3);
+
+ t.doesNotThrow(function () {
+
+ L = lauxlib.luaL_newstate();
+
+ linit.luaL_openlibs(L);
+
+ lauxlib.luaL_loadstring(L, luaCode);
+
+ }, "Lua program loaded without error");
+
+ t.doesNotThrow(function () {
+
+ lapi.lua_call(L, 0, -1);
+
+ }, "Lua program ran without error");
+
+ t.strictEqual(
+ lapi.lua_tostring(L, -1),
+ "hello world",
+ "Correct element(s) on the stack"
+ );
+
+});
+
+
+test('load', function (t) {
+ let luaCode = `
+ local f = load("return 'js running lua running lua'")
+ return f()
+ `, L;
+
+ t.plan(3);
+
+ t.doesNotThrow(function () {
+
+ L = lauxlib.luaL_newstate();
+
+ linit.luaL_openlibs(L);
+
+ lauxlib.luaL_loadstring(L, luaCode);
+
+ }, "Lua program loaded without error");
+
+ t.doesNotThrow(function () {
+
+ lapi.lua_call(L, 0, -1);
+
+ }, "Lua program ran without error");
+
+ t.strictEqual(
+ lapi.lua_tostring(L, -1),
+ "js running lua running lua",
+ "Correct element(s) on the stack"
+ );
+
+}); \ No newline at end of file