From 9e08fa3a4b9f8848bb4eac3e745079282099a3d8 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 21 Mar 2017 15:29:53 +0100 Subject: loadfile --- README.md | 4 +- src/lauxlib.js | 129 ++++++++++++++++++++++++++++++++++++++++++++++-- src/lbaselib.js | 30 +++++++++++ src/ldebug.js | 2 +- src/lobject.js | 7 ++- tests/load.js | 33 +++++++++++++ tests/loadfile-test.lua | 1 + 7 files changed, 193 insertions(+), 13 deletions(-) create mode 100644 tests/loadfile-test.lua diff --git a/README.md b/README.md index 791a885..0cc0b09 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,6 @@ - [ ] luaL_fileresult - [ ] luaL_getmetatable - [ ] luaL_gsub - - [ ] luaL_loadfile - - [ ] luaL_loadfilex - [ ] luaL_newlibtable - [ ] luaL_newmetatable - [ ] luaL_optnumber @@ -87,8 +85,8 @@ - [ ] Base lib - [x] ... - [x] load + - [x] loadfile - [ ] dofile - - [ ] loadfile - [x] Coroutine - [x] Table - [x] Math diff --git a/src/lauxlib.js b/src/lauxlib.js index ac7ab00..dbdb251 100644 --- a/src/lauxlib.js +++ b/src/lauxlib.js @@ -3,11 +3,12 @@ const assert = require('assert'); -const lstate = require('./lstate.js'); -const lapi = require('./lapi.js'); -const lua = require('./lua.js'); -const ldebug = require('./ldebug.js'); -const CT = lua.constant_types; +const lstate = require('./lstate.js'); +const lapi = require('./lapi.js'); +const lua = require('./lua.js'); +const ldebug = require('./ldebug.js'); +const lobject = require('./lobject.js'); +const CT = lua.constant_types; const LUA_LOADED_TABLE = "_LOADED"; @@ -399,6 +400,124 @@ const luaL_newlib = function(L, l) { luaL_setfuncs(L, l, 0); }; +// Only with Node +if (typeof window === "undefined") { + const fs = require('fs'); + + class LoadF { + constructor() { + this.n = NaN; /* number of pre-read characters */ + this.f = null; /* file being read */ + this.buff = new Buffer(1024); /* area for reading file */ + this.pos = 0; /* current position in file */ + } + } + + const getF = function(L, ud) { + let lf = ud; + let bytes = 0; + if (lf.n > 0) { /* are there pre-read characters to be read? */ + lf.n = 0; /* no more pre-read characters */ + } else { /* read a block from file */ + lf.buff.fill(0); + bytes = fs.readSync(lf.f, lf.buff, 0, lf.buff.length, lf.pos); /* read block */ + lf.pos += bytes; + } + return bytes > 0 ? new lobject.TValue(0, lf.buff).jsstring() : null; // TODO: Here reading utf8 only + }; + + const errfile = function(L, what, fnameindex, error) { + let serr = error.message; + let filename = lapi.lua_tostring(L, fnameindex).slice(1); + lapi.lua_pushstring(L, `cannot ${what} ${filename}: ${serr}`); + lapi.lua_remove(L, fnameindex); + return lua.thread_status.LUA_ERRFILE; + }; + + const getc = function(lf) { + let b = new Buffer(1); + let bytes = fs.readSync(lf.f, b, 0, 1, lf.pos); + lf.pos += bytes; + return bytes > 0 ? b.readUInt8() : null; + }; + + const skipBOM = function(lf) { + let p = "\xEF\xBB\xBF"; /* UTF-8 BOM mark */ + lf.n = 0; + let c; + do { + c = getc(lf); + if (c === null || c !== p.charCodeAt(0)) return c; + p = p.slice(1); + lf.buff[lf.n++] = c; /* to be read by the parser */ + } while (p.length > 0); + lf.n = 0; /* prefix matched; discard it */ + return getc(lf); /* return next character */ + }; + + /* + ** reads the first character of file 'f' and skips an optional BOM mark + ** in its beginning plus its first line if it starts with '#'. Returns + ** true if it skipped the first line. In any case, '*cp' has the + ** first "valid" character of the file (after the optional BOM and + ** a first-line comment). + */ + const skipcomment = function(lf) { + let c = skipBOM(lf); + if (c === '#'.charCodeAt(0)) { /* first line is a comment (Unix exec. file)? */ + do { /* skip first line */ + c = getc(lf); + } while (c && c !== '\n'.charCodeAt(0)); + return getc(lf); /* skip end-of-line, if present */ + } else { + lf.pos--; + return false; + } + }; + + const luaL_loadfilex = function(L, filename, mode) { + let lf = new LoadF(); + let fnameindex = lapi.lua_gettop(L) + 1; /* index of filename on the stack */ + if (filename === null) { + lapi.lua_pushliteral(L, "=stdin"); + lf.f = process.stdin.fd; + } else { + lapi.lua_pushstring(L, `@${filename}`); + try { + lf.f = fs.openSync(filename, "r"); + } catch (e) { + return errfile(L, "open", fnameindex, e); + } + } + + try { + let c; + if ((c = skipcomment(lf))) /* read initial portion */ + lf.buff[lf.n++] = '\n'.charCodeAt(0); /* add line to correct line numbers */ + + if (c === lua.LUA_SIGNATURE.charCodeAt(0) && filename) { /* binary file? */ + // ... + } + + let status = lapi.lua_load(L, getF, lf, lapi.lua_tostring(L, -1), mode); + if (filename) fs.closeSync(lf.f); /* close file (even in case of errors) */ + lapi.lua_remove(L, fnameindex); + return status; + } catch (err) { + lapi.lua_settop(L, fnameindex); /* ignore results from 'lua_load' */ + return errfile(L, "read", fnameindex); + } + }; + + const luaL_loadfile = function(L, filename) { + return luaL_loadfilex(L, filename, null); + }; + + module.exports.luaL_loadfilex = luaL_loadfilex; + module.exports.luaL_loadfile = luaL_loadfile; + +} + module.exports.LUA_LOADED_TABLE = LUA_LOADED_TABLE; module.exports.luaL_Buffer = luaL_Buffer; module.exports.luaL_addchar = luaL_addchar; diff --git a/src/lbaselib.js b/src/lbaselib.js index 0b5c2f7..604a005 100644 --- a/src/lbaselib.js +++ b/src/lbaselib.js @@ -332,6 +332,36 @@ const base_funcs = { "xpcall": luaB_xpcall }; +// Only with Node +if (typeof window === "undefined") { + + 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 */ + } + }; + + const luaB_loadfile = function(L) { + let fname = lauxlib.luaL_optstring(L, 1, null); + let mode = lauxlib.luaL_optstring(L, 2, null); + let env = !lapi.lua_isnone(L, 3) ? 3 : 0; /* 'env' index or 0 if no 'env' */ + let status = lauxlib.luaL_loadfilex(L, fname, mode); + return load_aux(L, status, env); + }; + + base_funcs.loadfile = luaB_loadfile; + +} + const luaopen_base = function(L) { /* open lib into global table */ lapi.lua_pushglobaltable(L); diff --git a/src/ldebug.js b/src/ldebug.js index 2858267..529cb0c 100644 --- a/src/ldebug.js +++ b/src/ldebug.js @@ -431,7 +431,7 @@ const varinfo = function(L, o) { kind = getobjname(ci.func.p, ci.pcOff, stkid - ci.u.l.base); } - return kind ? ` (${kind.funcname} '${kind.name}')` : ``; + return kind ? ` (${kind.funcname} '${kind.name.jsstring()}')` : ``; }; const luaG_typeerror = function(L, o, op) { diff --git a/src/lobject.js b/src/lobject.js index dfe6337..8fc587d 100644 --- a/src/lobject.js +++ b/src/lobject.js @@ -119,7 +119,6 @@ class TValue { } jsstring() { - //return this.ttisstring() ? String.fromCharCode(...this.value) : null; let u0, u1, u2, u3, u4, u5; let idx = 0; @@ -273,17 +272,17 @@ const PRE = "[string \""; const POS = "\"]"; const luaO_chunkid = function(source, bufflen) { - source = source instanceof TValue ? source.value : source; + source = source instanceof TValue ? source.jsstring() : source; bufflen = bufflen instanceof TValue ? bufflen.value : bufflen; let l = source.length; let out = ""; - if (source[0] === '=') { /* 'literal' source */ + if (source.charAt(0) === '=') { /* 'literal' source */ if (l < bufflen) /* small enough? */ out = `${source.slice(1)}`; else { /* truncate it */ out += `${source.slice(1, bufflen)}`; } - } else if (source[0] === '@') { /* file name */ + } else if (source.charAt(0) === '@') { /* file name */ if (l <= bufflen) /* small enough? */ out = `${source.slice(1)}`; else { /* add '...' before rest of name */ diff --git a/tests/load.js b/tests/load.js index 86c3904..c7ea3f9 100644 --- a/tests/load.js +++ b/tests/load.js @@ -114,4 +114,37 @@ test('luaL_loadbuffer', function (t) { "Correct element(s) on the stack" ); +}); + +// TODO: test stdin +test('loadfile', function (t) { + let luaCode = ` + local f = loadfile("tests/loadfile-test.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), + "hello world", + "Correct element(s) on the stack" + ); + }); \ No newline at end of file diff --git a/tests/loadfile-test.lua b/tests/loadfile-test.lua new file mode 100644 index 0000000..838d20b --- /dev/null +++ b/tests/loadfile-test.lua @@ -0,0 +1 @@ +return "hello world" \ No newline at end of file -- cgit v1.2.3-70-g09d2