From c428f1241ebd5194a37c37d9d5376b326b78ee37 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Wed, 15 Feb 2017 09:22:11 +0100 Subject: Implementing minimal path from main to luaV_execute of user script --- src/lapi.js | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ldo.js | 57 +++++++++++++-- src/lfunc.js | 3 +- src/lobject.js | 24 +++---- src/lstate.js | 14 +++- src/lua.js | 91 +++++++++++++++++++++++- src/lualib.js | 11 +++ src/lvm.js | 5 +- 8 files changed, 388 insertions(+), 31 deletions(-) create mode 100644 src/lapi.js create mode 100644 src/lualib.js diff --git a/src/lapi.js b/src/lapi.js new file mode 100644 index 0000000..eecf1ab --- /dev/null +++ b/src/lapi.js @@ -0,0 +1,214 @@ +/*jshint esversion: 6 */ +"use strict"; + +const assert = require('assert'); +const ldo = require('./ldo.js'); +const lobject = require('./lobject.js'); +const lfunc = require('./lfunc.js'); +const lua = require('./lua.js'); +const lstate = require('./lstate.js'); +const nil = ldo.nil; +const MAXUPVAL = lfunc.MAXUPVAL; +const CT = lua.constant_types; +const TS = lua.thread_status; +const l_isfalse = lobject.l_isfalse; +const TValue = lobject.TValue; +const CClosure = lobject.CClosure; + +// Return real index on stack +const index2addr = function(L, idx) { + let ci = L.ci; + if (idx > 0) { + let o = ci.funcOff + idx; + assert(idx <= ci.top - (ci.funcOff + 1), "unacceptable index"); + if (o >= L.top) return nil; + else return L.stack[o]; + } else if (idx < 0) // TODO: pseudo-indices + return nil; // TODO: G(L)->l_registry + else { /* upvalues */ + idx = -idx; + assert(idx <= MAXUPVAL + 1, "upvalue index too large"); + if (ci.func.ttislcf()) /* light C function? */ + return nil; /* it has no upvalues */ + else { + return idx <= ci.func.nupvalues ? ci.func.upvalue[idx - 1] : nil; + } + } + +}; + +const lua_pushvalue = function(L, idx) { + L.stack[L.top] = L.stack[index2addr(L, idx)]; + + L.top++; + assert(L.top <= L.ci.top, "stack overflow"); +}; + +/* +** push functions (JS -> stack) +*/ + +const lua_pushnil = function(L) { + L.stack[L.top] = nil; + + L.top++; + assert(L.top <= L.ci.top, "stack overflow"); +}; + +const lua_pushnumber = function(L, n) { + assert(typeof n === "number"); + + L.stack[L.top] = new TValue(CT.LUA_TNUMFLT, n); + + L.top++; + assert(L.top <= L.ci.top, "stack overflow"); +}; + +const lua_pushinteger = function(L, n) { + assert(typeof n === "number"); + + L.stack[L.top] = new TValue(CT.LUA_TNUMINT, n|0); + + L.top++; + assert(L.top <= L.ci.top, "stack overflow"); +}; + +const lua_pushlstring = function(L, s, len) { + assert(typeof s === "string"); + assert(typeof n === "number"); + + let ts = len === 0 ? new TValue(CT.LUA_TLNGSTR, "") : new TValue(CT.LUA_TLNGSTR, s.substr(0, len)); + L.stack[L.top] = ts; + + L.top++; + assert(L.top <= L.ci.top, "stack overflow"); + + return ts.value; +}; + +const lua_pushstring = function (L, s) { + assert(typeof s === "string"); + if (!s) + L.stack[L.top] = nil; + else { + let ts = new TValue(CT.LUA_TLNGSTR, s); + s = ts.value; + } + + L.top++; + assert(L.top <= L.ci.top, "stack overflow"); + + return s; +}; + +const lua_pushcclosure = function(L, fn, n) { + assert(typeof fn === "function"); + assert(typeof n === "number"); + + if (n === 0) + L.stack[L.top] = new TValue(CT.LUA_TLCF, fn); + else { + assert(n < L.top - L.ci.funcOff, "not enough elements in the stack"); + assert(n <= MAXUPVAL, "upvalue index too large"); + let cl = new CClosure(L, n, fn); + L.top -= n; + while (n--) { + cl.upvalue[n] = L.stack[L.top + n]; + } + L.stack[L.top] = cl; + } + + L.top++; + assert(L.top <= L.ci.top, "stack overflow"); +}; + +const lua_pushboolean = function(L, b) { + L.stack[L.top] = new TValue(CT.LUA_TBOOLEAN, b ? true : false); + + L.top++; + assert(L.top <= L.ci.top, "stack overflow"); +}; + +const lua_pushlightuserdata = function(L, p) { + assert(typeof p === "object"); + + L.stack[L.top] = new TValue(CT.LUA_TLIGHTUSERDATA, p); + + L.top++; + assert(L.top <= L.ci.top, "stack overflow"); +}; + + +/* +** access functions (stack -> JS) +*/ + +const lua_toboolean = function(L, idx) { + let o = L.stack[index2addr(L, idx)]; + return !l_isfalse(o); +}; + +const f_call = function(L, ud) { + ldo.luaD_callnoyield(L, ud.func, ud.nresults); +}; + + +/* +** 'load' and 'call' functions (run Lua code) +*/ + +const lua_pcallk = function(L, nargs, nresults, errfunc, ctx, k) { + assert(nargs + 1 < L.top - L.ci.func, "not enough elements in the stack"); + assert(L.status === TS.LUA_OK, "cannot do calls on non-normal thread"); + assert(nargs == lua.LUA_MULTRET || (L.ci.top - L.top >= nargs - nresults, "results from function overflow current stack size")); + + let c = { + func: null, + funcOff: NaN, + nresults: NaN + }; + let status; + let func; + + if (errfunc === 0) + func = 0; + else { + let o = L.stack[index2addr(L, errfunc)]; + // TODO: api_checkstackindex(L, errfunc, o); + func = errfunc; + } + + c.funcOff = L.top - (nargs + 1); /* function to be called */ + c.func = L.stack[c.funcOff]; + + if (k === null || L.nny > 0) { /* no continuation or no yieldable? */ + c.nresults = nresults; /* do a 'conventional' protected call */ + status = ldo.luaD_pcall(L, f_call, c, c.funcOff, c.func); + } else { /* prepare continuation (call is already protected by 'resume') */ + let ci = L.ci; + ci.u.c.k = k; /* prepare continuation (call is already protected by 'resume') */ + ci.u.c.ctx = ctx; /* prepare continuation (call is already protected by 'resume') */ + /* save information for error recovery */ + ci.extra = c.funcOff; + ci.u.c.old_errfunc = L.errfunc; + L.errfunc = c.func; + // TODO: setoah(ci->callstatus, L->allowhook); + ci.callstatus |= lstate.CIST_YPCALL; /* function can do error recovery */ + ldo.luaD_call(L, c.funcOff, nresults); /* do the call */ + ci.callstatus &= ~lstate.CIST_YPCALL; + L.errfunc = ci.u.c.old_errfunc; + status = TS.LUA_OK; + } + + if (nresults == lua.LUA_MULTRET && L.ci.top < L.top) + L.ci.top = L.top; + + return status; +}; + +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; diff --git a/src/ldo.js b/src/ldo.js index 63b949c..79b92ca 100644 --- a/src/ldo.js +++ b/src/ldo.js @@ -2,16 +2,17 @@ "use strict"; const lua = require('./lua.js'); -const CT = lua.constant_types; -const LUA_MULTRET = lua.LUA_MULTRET; const lobject = require('./lobject.js'); -const TValue = lobject.TValue; const lstate = require('./lstate.js'); -const CallInfo = lstate.CallInfo; const llimit = require('./llimit.js'); const ltm = require('./ltm.js'); -const TMS = ltm.TMS; const lvm = require('./lvm.js'); +const CT = lua.constant_types; +const TS = lua.thread_status; +const LUA_MULTRET = lua.LUA_MULTRET; +const TValue = lobject.TValue; +const CallInfo = lstate.CallInfo; +const TMS = ltm.TMS; const nil = new TValue(CT.LUA_TNIL, null); @@ -170,6 +171,49 @@ const luaD_call = function(L, off, nResults) { L.nCcalls--; }; +const luaD_rawrunprotected = function(L, f, ud) { + let oldnCcalls = L.nCcalls; + let lj = { // TODO: necessary when using try/catch ? (ldo.c:47-52) + status: TS.LUA_OK, + previous: L.errorJmp /* chain new error handler */ + }; + L.errorJmp = lj; + + try { + f(L, ud); + } catch (e) { + if (lj.status == 0) lj.status = -1; + } + + L.errorJmp = lj.previous; + L.nCcalls = oldnCcalls; + + return lj.status; + +}; + +const luaD_pcall = function(L, func, u, old_top, ef) { + let old_ci = L.ci; + // TODO: lu_byte old_allowhooks = L->allowhook; + let old_nny = L.nny; + let old_errfunc = L.errfunc; + L.errfunc = ef; + + status = luaD_rawrunprotected(L, func, u); + + if (status !== TS.LUA_OK) { + lfunc.luaF_close(L, old_top); + // TODO: seterrorobj(L, status, oldtop); + L.ci = old_ci; + // TODO: L->allowhook = old_allowhooks; + L.nny = old_nny; + } + + L.errfunc = old_errfunc; + + return status; +}; + /* ** Similar to 'luaD_call', but does not allow yields during the call */ @@ -187,4 +231,5 @@ module.exports.adjust_varargs = adjust_varargs; module.exports.tryfuncTM = tryfuncTM; module.exports.stackerror = stackerror; module.exports.luaD_call = luaD_call; -module.exports.luaD_callnoyield = luaD_callnoyield; \ No newline at end of file +module.exports.luaD_callnoyield = luaD_callnoyield; +module.exports.luaD_pcall = luaD_pcall; \ No newline at end of file diff --git a/src/lfunc.js b/src/lfunc.js index 1a1597a..f3e11d3 100644 --- a/src/lfunc.js +++ b/src/lfunc.js @@ -91,4 +91,5 @@ const luaF_close = function(L, level) { module.exports.Proto = Proto; module.exports.UpVal = UpVal; module.exports.findupval = findupval; -module.exports.luaF_close = luaF_close; \ No newline at end of file +module.exports.luaF_close = luaF_close; +module.exports.MAXUPVAL = 255; \ No newline at end of file diff --git a/src/lobject.js b/src/lobject.js index 725f63d..5860f67 100644 --- a/src/lobject.js +++ b/src/lobject.js @@ -189,28 +189,22 @@ class LClosure extends TValue { } -class TString extends TValue { - - constructor(string) { - super(CT.LUA_TSTRING, string); - } - -} +class CClosure extends TValue { + constructor(n, f) { + super(CT.LUA_TCCL, null); -class Userdata extends TValue { - - constructor(jsObject) { - super(CT.LUA_TUSERDATA, jsObject); + this.f = f; + this.nupvalues = n; + this.upvalue = new Array(n); - this.metatable = null; + this.value = this; } } module.exports.LClosure = LClosure; +module.exports.CClosure = CClosure; module.exports.TValue = TValue; -module.exports.Table = Table; -module.exports.TString = TString; -module.exports.Userdata = Userdata; \ No newline at end of file +module.exports.Table = Table; \ No newline at end of file diff --git a/src/lstate.js b/src/lstate.js index ed299b5..84aefc0 100644 --- a/src/lstate.js +++ b/src/lstate.js @@ -1,7 +1,9 @@ /*jshint esversion: 6 */ "use strict"; -const LUA_MULTRET = require('./lua.js').LUA_MULTRET; +const lua = require('./lua.js'); +const LUA_MULTRET = lua.LUA_MULTRET; +const TS = lua.thread_status; const Table = require('./lobject.js').Table; class CallInfo { @@ -14,9 +16,14 @@ class CallInfo { this.next = next; this.pcOff = 0; this.u = { - l: { - base: base, + l: { /* only for Lua functions */ + base: base, /* base for this function */ savedpc: [] + }, + c: { /* only for JS functions */ + k: null, /* continuation in case of yields */ + old_errfunc: null, + ctx: null /* context info. in case of yields */ } }; this.nresults = 0; @@ -37,6 +44,7 @@ class lua_State { cl ]; this.openupval = []; + this.status = TS.LUA_OK; } } diff --git a/src/lua.js b/src/lua.js index 33cf623..a336bb0 100644 --- a/src/lua.js +++ b/src/lua.js @@ -1,6 +1,32 @@ /*jshint esversion: 6 */ "use strict"; +const assert = require('assert'); +const lualib = require('./lualib.js'); + +const LUA_VERSION_MAJOR = "5"; +const LUA_VERSION_MINOR = "3"; +const LUA_VERSION_NUM = 503; +const LUA_VERSION_RELEASE = "4"; + +const LUA_VERSION = "Lua " + LUA_VERSION_MAJOR + "." + LUA_VERSION_MINOR; +const LUA_RELEASE = LUA_VERSION + "." + LUA_VERSION_RELEASE; +const LUA_COPYRIGHT = LUA_RELEASE + " Copyright (C) 1994-2017 Lua.org, PUC-Rio"; +const LUA_AUTHORS = "R. Ierusalimschy, L. H. de Figueiredo, W. Celes"; + +const FENGARI_VERSION_MAJOR = "0"; +const FENGARI_VERSION_MINOR = "0"; +const FENGARI_VERSION_NUM = 1; +const FENGARI_VERSION_RELEASE = "1"; + +const FENGARI_VERSION = "Fengari " + FENGARI_VERSION_MAJOR + "." + FENGARI_VERSION_MINOR; +const FENGARI_RELEASE = FENGARI_VERSION + "." + FENGARI_VERSION_RELEASE; +const FENGARI_COPYRIGHT = FENGARI_RELEASE + " Copyright (C) 2017 BenoƮt Giannangeli\nBased on: " + LUA_COPYRIGHT; +const FENGARI_AUTHORS = "B. Giannangeli"; + +const LUA_INIT_VAR = "LUA_INIT"; +const LUA_INITVARVERSION = LUA_INIT_VAR + lualib.LUA_VERSUFFIX; + const thread_status = { LUA_OK: 0, LUA_YIELD: 1, @@ -35,6 +61,65 @@ constant_types.LUA_TLCL = constant_types.LUA_TFUNCTION | (0 << 4); /* Lua closu constant_types.LUA_TLCF = constant_types.LUA_TFUNCTION | (1 << 4); /* light C function */ constant_types.LUA_TCCL = constant_types.LUA_TFUNCTION | (2 << 4); /* C closure */ -module.exports.constant_types = constant_types; -module.exports.thread_status = thread_status; -module.exports.LUA_MULTRET = -1; \ No newline at end of file +const print_version = function() { + console.log(FENGARI_COPYRIGHT); +}; + + +const handle_script = function(L, args) { + // TODO: stdin + +}; + +const handle_luainit = function(L) { + // TODO: Should execute script in LUA_INIT_5_3 + return thread_status.LUA_OK; +}; + +/* +** Main body of stand-alone interpreter (to be called in protected mode). +** Reads the options and handles them all. +*/ +const pmain = function(L) { + // arguments are a userdata wrapping a plain JS object + let args = L.stack[1].value; // For now it should only hold a DataView containing bytecode + + // TODO: luaL_checkversion(L); + // TODO: LUA_NOENV + // TODO: luaL_openlibs(L); + // TODO: createargtable(L, argv, argc, script); + + if (!args.E) { + if (handle_luainit(L) != thread_status.LUA_OK) + return 0; /* error running LUA_INIT */ + } + + // TODO: runargs(L, argv, script) + if (args.script && handle_script(L, args) != thread_status.LUA_OK) + return 0; + + // TODO: doREPL(L); +}; + +module.exports.constant_types = constant_types; +module.exports.thread_status = thread_status; +module.exports.LUA_MULTRET = -1; +module.exports.print_version = print_version; +module.exports.LUA_VERSION_MAJOR = LUA_VERSION_MAJOR; +module.exports.LUA_VERSION_MINOR = LUA_VERSION_MINOR; +module.exports.LUA_VERSION_NUM = LUA_VERSION_NUM; +module.exports.LUA_VERSION_RELEASE = LUA_VERSION_RELEASE; +module.exports.LUA_VERSION = LUA_VERSION; +module.exports.LUA_RELEASE = LUA_RELEASE; +module.exports.LUA_COPYRIGHT = LUA_COPYRIGHT; +module.exports.LUA_AUTHORS = LUA_AUTHORS; +module.exports.FENGARI_VERSION_MAJOR = FENGARI_VERSION_MAJOR; +module.exports.FENGARI_VERSION_MINOR = FENGARI_VERSION_MINOR; +module.exports.FENGARI_VERSION_NUM = FENGARI_VERSION_NUM; +module.exports.FENGARI_VERSION_RELEASE = FENGARI_VERSION_RELEASE; +module.exports.FENGARI_VERSION = FENGARI_VERSION; +module.exports.FENGARI_RELEASE = FENGARI_RELEASE; +module.exports.FENGARI_COPYRIGHT = FENGARI_COPYRIGHT; +module.exports.FENGARI_AUTHORS = FENGARI_AUTHORS; +module.exports.LUA_INIT_VAR = LUA_INIT_VAR; +module.exports.LUA_INITVARVERSION = LUA_INITVARVERSION; \ No newline at end of file diff --git a/src/lualib.js b/src/lualib.js new file mode 100644 index 0000000..1eff812 --- /dev/null +++ b/src/lualib.js @@ -0,0 +1,11 @@ +/*jshint esversion: 6 */ +"use strict"; + +const assert = require('assert'); +const lua = require('./lua.js'); + + +const LUA_VERSUFFIX = "_" + lua.LUA_VERSION_MAJOR + "_" + lua.LUA_VERSION_MINOR; + + +module.exports.LUA_VERSUFFIX = LUA_VERSUFFIX; \ No newline at end of file diff --git a/src/lvm.js b/src/lvm.js index 07a9f96..5ba568d 100644 --- a/src/lvm.js +++ b/src/lvm.js @@ -11,7 +11,6 @@ const LUA_MULTRET = lua.LUA_MULTRET; const lobject = require('./lobject.js'); const TValue = lobject.TValue; const Table = lobject.Table; -const TString = lobject.TString; const LClosure = lobject.LClosure; const lfunc = require('./lfunc.js'); const UpVal = lfunc.UpVal; @@ -879,7 +878,7 @@ const tostring = function(L, i) { let str = `${o.value}`; if (o.ttisstring() || (o.ttisnumber() && !isNaN(parseFloat(`${str}`)))) { - L.stack[i] = new TString(str); + L.stack[i] = new TValue(CT.LUA_TLNGSTR, str); return true; } @@ -914,7 +913,7 @@ const luaV_concat = function(L, total) { tl += l; } - let ts = new TString(""); + let ts = new TValue(CT.LUA_TLNGSTR, ""); for (let i = n; i > 0; i--) { ts.value = `${ts.value}${L.stack[top - i].value}`; } -- cgit v1.2.3-54-g00ecf