From 0ba3c5d516b1a0b67cff6d5f4b4583284086dd4b Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Thu, 2 Mar 2017 21:15:42 +0100 Subject: load, luaL_loadbuffer(x), luaL_loadstring, lua_replace, lua_isnone, lua_isnoneornil, lua_setupvalue --- README.md | 18 ++++++------- src/lapi.js | 56 +++++++++++++++++++++++++++++++++++++++ src/lauxlib.js | 21 +++++++++++++++ src/lbaselib.js | 79 +++++++++++++++++++++++++++++++++++++++++++++++------- src/lcode.js | 2 +- src/ldo.js | 2 +- src/lvm.js | 2 +- tests/load.js | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 240 insertions(+), 22 deletions(-) create mode 100644 tests/load.js 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 -- cgit v1.2.3-70-g09d2