From 5a47025d64f013975051473c1115ff70c0281785 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Mon, 27 Feb 2017 08:46:56 +0100 Subject: lexing --- README.md | 1 + src/ljstype.js | 29 +++ src/llex.js | 596 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lobject.js | 133 ++++++++++++- 4 files changed, 750 insertions(+), 9 deletions(-) create mode 100644 src/ljstype.js create mode 100644 src/llex.js diff --git a/README.md b/README.md index 8468a25..837e792 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,7 @@ - [ ] Standard library - [ ] Base lib - [x] ... + - [ ] require - [ ] dofile - [ ] loadfile - [ ] load diff --git a/src/ljstype.js b/src/ljstype.js new file mode 100644 index 0000000..52ac153 --- /dev/null +++ b/src/ljstype.js @@ -0,0 +1,29 @@ +/* jshint esversion: 6 */ +"use strict"; + +const assert = require('assert'); + +const lisdigit = function(c) { + return /^\d$/.test(c.charAt(0)); + +const lisxdigit = function(c) { + return /^[0-9a-fA-F]$/.test(c.charAt(0)); +}; + +const lisspace = function(c) { + return /^\s$/.test(c.charAt(0)); +}; + +const lislalpha = function(c) { + return /^[_a-zA-z]$/.test(c.charAt(0)); +}; + +const lislanum = function(c) { + return /^[_a-zA-z0-9]$/.test(c.charAt(0)); +}; + +module.exports.lisdigit = lisdigit; +module.exports.lislalpha = lislalpha; +module.exports.lislanum = lislanum; +module.exports.lisspace = lisspace; +module.exports.lisxdigit = lisxdigit; \ No newline at end of file diff --git a/src/llex.js b/src/llex.js new file mode 100644 index 0000000..4c85f65 --- /dev/null +++ b/src/llex.js @@ -0,0 +1,596 @@ +/* jshint esversion: 6 */ +"use strict"; + +const assert = require('assert'); + +const lapi = require('./lapi.js'); +const ldebug = require('./ldebug.js'); +const ldo = require('./ldo.js'); +const lua = require('./lua.js'); +const lobject = require('./lobject'); +const ljstype = require('./ljstype'); +const TValue = lobject.TValue; +const CT = lua.constant_types; +const TS = lua.thread_status; + +const FIRST_RESERVED = 257; + +const RESERVED = { + /* terminal symbols denoted by reserved words */ + TK_AND: FIRST_RESERVED, + TK_BREAK: FIRST_RESERVED + 1, + TK_DO: FIRST_RESERVED + 2, + TK_ELSE: FIRST_RESERVED + 3, + TK_ELSEIF: FIRST_RESERVED + 4, + TK_END: FIRST_RESERVED + 5, + TK_FALSE: FIRST_RESERVED + 6, + TK_FOR: FIRST_RESERVED + 8, + TK_FUNCTION: FIRST_RESERVED + 10, + TK_GOTO: FIRST_RESERVED + 11, + TK_IF: FIRST_RESERVED + 12, + TK_IN: FIRST_RESERVED + 13, + TK_LOCAL: FIRST_RESERVED + 14, + TK_NIL: FIRST_RESERVED + 15, + TK_NOT: FIRST_RESERVED + 16, + TK_OR: FIRST_RESERVED + 17, + TK_REPEAT: FIRST_RESERVED + 18, + TK_RETURN: FIRST_RESERVED + 19, + TK_THEN: FIRST_RESERVED + 20, + TK_TRUE: FIRST_RESERVED + 21, + TK_UNTIL: FIRST_RESERVED + 22, + TK_WHILE: FIRST_RESERVED + 23, + /* other terminal symbols */ + TK_IDIV: FIRST_RESERVED + 24, + TK_CONCAT: FIRST_RESERVED + 25, + TK_DOTS: FIRST_RESERVED + 26, + TK_EQ: FIRST_RESERVED + 27, + TK_GE: FIRST_RESERVED + 28, + TK_LE: FIRST_RESERVED + 29, + TK_NE: FIRST_RESERVED + 30, + TK_SHL: FIRST_RESERVED + 31, + TK_SHR: FIRST_RESERVED + 32, + TK_DBCOLON: FIRST_RESERVED + 33, + TK_EOS: FIRST_RESERVED + 34, + TK_FLT: FIRST_RESERVED + 35, + TK_INT: FIRST_RESERVED + 36, + TK_NAME: FIRST_RESERVED + 37, + TK_STRING: FIRST_RESERVED + 38 +}; + +const R = RESERVED; + +const reserved_keywords = [ + "and", "break", "do", "else", "elseif", + "end", "false", "for", "function", "goto", "if", + "in", "local", "nil", "not", "or", "repeat", + "return", "then", "true", "until", "while" +]; + +const reserved_keywords_tokens = [ + R.TK_AND, + R.TK_BREAK, + R.TK_DO, + R.TK_ELSE, + R.TK_ELSEIF, + R.TK_END, + R.TK_FALSE, + R.TK_FOR, + R.TK_FUNCTION, + R.TK_GOTO, + R.TK_IF, + R.TK_IN, + R.TK_LOCAL, + R.TK_NIL, + R.TK_NOT, + R.TK_OR, + R.TK_REPEAT, + R.TK_RETURN, + R.TK_THEN, + R.TK_TRUE, + R.TK_UNTIL, + R.TK_WHILE, +]; + +const luaX_tokens = [ + "and", "break", "do", "else", "elseif", + "end", "false", "for", "function", "goto", "if", + "in", "local", "nil", "not", "or", "repeat", + "return", "then", "true", "until", "while", + "//", "..", "...", "==", ">=", "<=", "~=", + "<<", ">>", "::", "", + "", "", "", "" +]; + +const NUM_RESERVED = Object.keys(RESERVED).length; + +class Buffer { + constructor(string) { + this.buffer = string ? string.split('') : []; + this.n = this.buffer.length; + this.off = 0; + } +} + +class SemInfo { + constructor() { + this.r = NaN; + this.i = NaN; + this.ts = null; + } +} + +class Token { + constructor() { + this.token = NaN; + this.seminfo = null; + } +} + +/* state of the lexer plus state of the parser when shared by all + functions */ +class LexState { + constructor() { + this.current = NaN; /* current character (charint) */ + this.linenumber = NaN; /* input line counter */ + this.lastline = NaN; /* line of last token 'consumed' */ + this.t = null; /* current token */ + this.lookahead = null; /* look ahead token */ + this.fs = null; /* current function (parser) */ + this.L = null; + this.z = new Buffer(); + this.buff = new Buffer(); /* buffer for tokens */ + this.h = null; /* to avoid collection/reuse strings */ + this.dyd = null; /* dynamic structures used by the parser */ + this.source = null; /* current source name */ + this.envn = null; /* environment variable name */ + } +} + +const save = function(ls, c) { + let b = ls.buff; + if (b.n + 1 > b.buffer.length) { + if (b.buffer.length >= Number.MAX_SAFE_INTEGER/2) + lexerror(ls, "lexical element too long", 0); + } + b.buffer[b.n++] = c; +}; + +const luaX_token2str = function(ls, token) { + if (token < FIRST_RESERVED) { /* single-byte symbols? */ + return lapi.lua_pushstring(ls.L, `'%{String.fromCharCode(token)}'`); + } else { + let s = luaX_tokens[token - FIRST_RESERVED]; + if (token < R.TK_EOS) /* fixed format (symbols and reserved words)? */ + return lapi.lua_pushstring(ls.L, `'${s}'`); + else /* names, strings, and numerals */ + return s; + } +}; + +const currIsNewline = function(ls) { + return ls.current === '\n' || ls.current === '\r'; +}; + +const next = function(ls) { + ls.current = ls.z.n-- > 0 ? ls.z.buffer[ls.z.off++] : -1; +}; + +const save_and_next = function(ls) { + save(ls, ls.current); + next(ls); +}; + +/* +** increment line number and skips newline sequence (any of +** \n, \r, \n\r, or \r\n) +*/ +const inclinenumber = function(ls) { + let old = ls.current; + assert(currIsNewline(ls)); + next(ls); /* skip '\n' or '\r' */ + if (currIsNewline(ls) && ls.current !== old) + next(ls); /* skip '\n\r' or '\r\n' */ + if (++ls.linenumber >= Number.MAX_SAFE_INTEGER) + lexerror(ls, "chunk has too many lines", 0); +}; + +const luaX_setinput = function(L, ls, z, source, firstchar) { + ls.t.token = 0; + ls.L = L; + ls.current = firstchar; + ls.lookahead.token = R.TK_EOS; + ls.z = z; + ls.fs = null; + ls.linenumber = 1; + ls.lastline = 1; + ls.source = source; + ls.envn = new TValue(CT.LUA_TLNGSTR, "_ENV"); +}; + +const check_next1 = function(ls, c) { + if (ls.current === c) { + next(ls); + return true; + } + + return false; +}; + +/* +** Check whether current char is in set 'set' (with two chars) and +** saves it +*/ +const check_next2 = function(ls, set) { + if (ls.current === set.charAt(0) || ls.current === set.charAt(1)) { + save_and_next(ls); + return true; + } + + return false; +}; + +const read_numeral = function(ls, seminfo) { + let expo = "Ee"; + let first = ls.current; + assert(ljstype.lisdigit(ls.current)); + save_and_next(ls); + if (first === '0' && check_next2(ls, "xX")) /* hexadecimal? */ + expo = "Pp"; + + for (;;) { + if (check_next2(ls, expo)) /* exponent part? */ + check_next2(ls, "-+"); /* optional exponent sign */ + if (ljstype.lisxdigit(ls.current)) + save_and_next(ls); + else if (ls.current === '.') + save_and_next(ls); + else break; + } + + save(ls, '\0'); + + let obj = lobject.luaO_str2num(ls.buff.buffer); + if (obj === false) /* format error? */ + lexerror(ls, "malformed number", R.TK_FLT); + if (obj.ttisinteger()) { + seminfo.i = obj.value; + return R.TK_INT; + } else { + assert(obj.ttisfloat()); + seminfo.r = obj.value; + return R.TK_FLT; + } +}; + +const txtToken = function(ls, token) { + switch (token) { + case R.TK_NAME: case R.TK_STRING: + case R.TK_FLT: case R.TK_INT: + save(ls, '\0'); + return lapi.lua_pushstring(ls.L, `'${ls.buff.buffer}'`); + default: + return luaX_token2str(ls, token); + } +}; + +const lexerror = function(ls, msg, token) { + msg = ldebug.luaG_addinfo(ls.L, msg, ls.source, ls.linenumber); + if (token) + lapi.lua_pushstring(ls.L, `${msg} near ${txtToken(ls, token)}`); + ldo.luaD_throw(ls.L, TS.LUA_ERRSYNTAX); +}; + +/* +** skip a sequence '[=*[' or ']=*]'; if sequence is well formed, return +** its number of '='s; otherwise, return a negative number (-1 iff there +** are no '='s after initial bracket) +*/ +const skip_sep = function(ls) { + let count = 0; + let s = ls.current; + assert(s === '[' || s === ']'); + save_and_next(ls); + while (ls.current === '=') { + save_and_next(ls); + count++; + } + return ls.current === s ? count : (-count) - 1; +}; + +const read_long_string = function(ls, seminfo, sep) { + let line = ls.linenumber; /* initial line (for error message) */ + save_and_next(ls); /* skip 2nd '[' */ + + if (currIsNewline(ls)) /* string starts with a newline? */ + inclinenumber(ls); /* skip it */ + + let skip = false; + for (; !skip ;) { + switch (ls.current) { + case -1: { /* error */ + let what = seminfo ? "string" : "comment"; + let msg = lapi.lua_pushstring(ls.L, `unfinished long ${what} (starting at line ${line})`); + lexerror(ls, msg, R.TK_EOS); + break; + } + case ']': { + if (skip_sep(ls) === sep) { + save_and_next(ls); /* skip 2nd ']' */ + skip = true; + } + break; + } + case '\n': case '\r': { + save(ls, '\n'); + inclinenumber(ls); + if (!seminfo) ls.buff.n = 0; + break; + } + default: { + if (seminfo) save_and_next(ls); + else next(ls); + } + } + } + + if (seminfo) + seminfo.ts = new TValue(CT.LUA_TLNGSTR, ls.buff.buffer.slice(2 + sep).join('')); +}; + +const esccheck = function(ls, c, msg) { + if (!c) { + if (ls.current !== -1) + save_and_next(ls); /* add current to buffer for error message */ + lexerror(ls, msg, R.TK_STRING); + } +}; + +const gethexa = function(ls) { + save_and_next(ls); + esccheck(ls, ljstype.lisxdigit(ls.current), "hexadecimal digit expected"); + return lobject.luaO_hexavalue(ls.current); +}; + +const readhexaesc = function(ls) { + let r = gethexa(ls); + r = (r << 4) + gethexa(ls); + ls.buff.n -= 2; /* remove saved chars from buffer */ + return r; +}; + +const readutf8desc = function(ls) { + let i = 4; /* chars to be removed: '\', 'u', '{', and first digit */ + save_and_next(ls); /* skip 'u' */ + esccheck(ls, ls.current === '{', "missing '{'"); + let r = gethexa(ls); /* must have at least one digit */ + + save_and_next(ls); + while (ljstype.lisxdigit(ls.current)) { + i++; + r = (r << 4) + lobject.luaO_hexavalue(ls.current); + esccheck(ls, r <= 0x10FFFF, "UTF-8 value too large"); + save_and_next(ls); + } + esccheck(ls, ls.current === '}', "missing '}'"); + next(ls); /* skip '}' */ + ls.buff.n -= i; /* remove saved chars from buffer */ + return r; +}; + +const utf8esc = function(ls) { + let buff = new Array(lobject.UTF8BUFFSZ); + let n = lobject.luaO_utf8esc(buff, readutf8desc(ls)); + for (; n > 0; n--) /* add 'buff' to string */ + save(ls, buff[lobject.UTF8BUFFSZ - n]); +}; + +const readdecesc = function(ls) { + let r = 0; /* result accumulator */ + let i; + for (i = 0; i < 3 && ljstype.lisdigit(ls.current); i++) { /* read up to 3 digits */ + r = 10 * r + parseInt(ls.current); + save_and_next(ls); + } + esccheck(ls, r <= 255, "decimal escape too large"); + ls.buff.n -= i; /* remove read digits from buffer */ + return r; +}; + +const read_string = function(ls, del, seminfo) { + save_and_next(ls); /* keep delimiter (for error messages) */ + + while (ls.current !== del) { + switch (ls.current) { + case -1: + lexerror(ls, "unfinished string", R.TK_EOS); + break; + case '\n': + case '\r': + lexerror(ls, "unfinished string", R.TK_STRING); + break; + case '\\': { /* escape sequences */ + save_and_next(ls); /* keep '\\' for error messages */ + let will; + let c; + switch(ls.current) { + case 'a': c = '\a'; will = 'read_save'; break; + case 'b': c = '\b'; will = 'read_save'; break; + case 'f': c = '\f'; will = 'read_save'; break; + case 'n': c = '\n'; will = 'read_save'; break; + case 'r': c = '\r'; will = 'read_save'; break; + case 't': c = '\t'; will = 'read_save'; break; + case 'v': c = '\v'; will = 'read_save'; break; + case 'x': c = readhexaesc(ls); will = 'read_save'; break; + case 'u': utf8esc(ls); will = 'read_save'; break; + case '\n': case '\r': + inclinenumber(ls); c = '\n'; will = 'read_save'; break; + case '\\': case '\"': case '\'': + c = ls.current; will = 'read_save'; break; + case -1: will = 'read_save'; break; /* will raise an error next loop */ + case 'z': { /* zap following span of spaces */ + ls.buff.n -= 1; /* remove '\\' */ + next(ls); /* skip the 'z' */ + while (ljstype.lisspace(ls.current)) { + if (currIsNewline(ls)) inclinenumber(ls); + else next(ls); + } + will = 'no_save'; break; + } + default: { + esccheck(ls, ljstype.lisdigit(ls.current), "invalid escape sequence"); + c = readdecesc(ls); /* digital escape '\ddd' */ + will = 'only_save'; break; + } + } + + if (will === 'read_save') + next(ls); + else if (will === 'only_save') { + ls.buff.n -= 1; /* remove '\\' */ + save(ls, c); + } else if (will === 'no_save') + break; + } + default: + save_and_next(ls); + } + } + save_and_next(ls); /* skip delimiter */ + seminfo.ts = new TValue(CT.LUA_TLNGSTR, ls.buff.buffer.slice(1).join('')); +}; + +const llex = function(ls, seminfo) { + ls.buff.n = 0; + + for (;;) { + switch (ls.current) { + case '\n': case '\r': { /* line breaks */ + inclinenumber(ls); + break; + } + case ' ': case '\f': case '\t': case '\v': { /* spaces */ + next(ls); + break; + } + case '-': { /* '-' or '--' (comment) */ + next(ls); + if (ls.current !== '-') return '-'; + /* else is a comment */ + next(ls); + if (ls.current === '[') { /* long comment? */ + let sep = skip_sep(ls); + ls.buff.n = 0; /* 'skip_sep' may dirty the buffer */ + if (sep >= 0) { + read_long_string(ls, null, sep); /* skip long comment */ + ls.buff.n = 0; /* previous call may dirty the buff. */ + break; + } + } + + /* else short comment */ + while (!currIsNewline(ls) && ls.current !== -1) + next(ls); /* skip until end of line (or end of file) */ + break; + } + case '[': { /* long string or simply '[' */ + let sep = skip_sep(ls); + if (sep.charCodeAt(0) >= 0) { + read_long_string(ls, seminfo, sep); + return R.TK_STRING; + } else if (sep !== -1) /* '[=...' missing second bracket */ + lexerror(ls, "invalid long string delimiter", R.TK_STRING); + return '['; + } + case '=': { + next(ls); + if (check_next1(ls, '=')) return R.TK_EQ; + else return '='; + } + case '<': { + next(ls); + if (check_next1(ls, '=')) return R.TK_LE; + else if (check_next1(ls, '<')) return R.TK_SHL; + else return '<'; + } + case '>': { + next(ls); + if (check_next1(ls, '=')) return R.TK_GE; + else if (check_next1(ls, '>')) return R.TK_SHR; + else return '>'; + } + case '/': { + next(ls); + if (check_next1(ls, '/')) return R.TK_IDIV; + else return '/'; + } + case '~': { + next(ls); + if (check_next1(ls, '=')) return R.TK_NE; + else return '~'; + } + case ':': { + next(ls); + if (check_next1(ls, ':')) return R.TK_DBCOLON; + else return ':'; + } + case '"': case '\'': { /* short literal strings */ + read_string(ls, ls.current, seminfo); + return R.TK_STRING; + } + case '.': { /* '.', '..', '...', or number */ + save_and_next(ls); + if (check_next1(ls, '.')) { + if (check_next1(ls, '.')) + return R.TK_DOTS; /* '...' */ + else return R.TK_CONCAT; /* '..' */ + } + else if (!ljstype.lisdigit(ls.current)) return '.'; + else return read_numeral(ls, seminfo); + } + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': { + return read_numeral(ls, seminfo); + } + case -1: { + return R.TK_EOS; + } + default: { + if (ljstype.lislalpha(ls.current)) { /* identifier or reserved word? */ + do { + save_and_next(ls); + } while (ljstype.lislalnum(ls.current)); + + let ts = new TValue(CT.LUA_TLNGSTR, ls.buff.buffer.join('')); + seminfo.ts = ts; + if (reserved_keywords.indexOf(ts.value) >= 0) /* reserved word? */ + return reserved_keywords_tokens[reserved_keywords.indexOf(ts.value)]; + else + return R.TK_NAME; + } else { /* single-char tokens (+ - / ...) */ + let c = ls.current; + next(ls); + return c; + } + } + } + } +}; + +const luaX_next = function(ls) { + ls.lastline = ls.linenumber; + if (ls.lookahead.token !== R.TK_EOS) { /* is there a look-ahead token? */ + ls.t = ls.lookahead; /* use this one */ + ls.lookahead.token = R.TK_EOS; /* and discharge it */ + } else + ls.t.token = llex(ls, ls.t.seminfo); /* read next token */ +}; + +const luaX_lookahead = function(ls) { + assert(ls.lookahead.token === R.TK_EOS); + ls.lookahead.token = llex(ls. ls.lookahead.seminfo); + return ls.lookahead.token; +}; + +module.exports.luaX_lookahead = luaX_lookahead; +module.exports.luaX_next = luaX_next; +module.exports.luaX_setinput = luaX_setinput; \ No newline at end of file diff --git a/src/lobject.js b/src/lobject.js index 086af00..93e23c2 100644 --- a/src/lobject.js +++ b/src/lobject.js @@ -1,8 +1,11 @@ -/*jshint esversion: 6 */ + /*jshint esversion: 6 */ "use strict"; -const CT = require('./lua.js').constant_types; -const UpVal = require('./lfunc.js').UpVal; +const assert = require('assert'); + +const ljstype = require('./ljstype.js'); +const CT = require('./lua.js').constant_types; +const UpVal = require('./lfunc.js').UpVal; class TValue { @@ -223,7 +226,7 @@ const luaO_chunkid = function(source, bufflen) { let nli = source.indexOf('\n'); /* find first new line (if any) */ let nl = nli ? source.slice(nli) : null; out = `${PRE}`; /* add prefix */ - bufflen -= PRE.length - RETS.length; - POS.length + 1; /* save space for prefix+suffix+'\0' */ + bufflen -= PRE.length + RETS.length + POS.length + 1; /* save space for prefix+suffix+'\0' */ if (l < bufflen && nl === null) { /* small one-line source? */ out += `${source}`; /* keep it */ } else { @@ -237,8 +240,120 @@ const luaO_chunkid = function(source, bufflen) { return out; }; -module.exports.LClosure = LClosure; -module.exports.CClosure = CClosure; -module.exports.TValue = TValue; -module.exports.Table = Table; -module.exports.luaO_chunkid = luaO_chunkid; \ No newline at end of file +const luaO_hexavalue = function(c) { + if (ljstype.lisdigit(c)) return c.charCodeAt(0) - '0'.charCodeAt(0); + else return (c.toLowerCase().charCodeAt(0) - 'a'.charCodeAt(0)) + 10; +}; + +const UTF8BUFFSZ = 8; + +const luaO_utf8desc = function(buff, x) { + let n = 1; /* number of bytes put in buffer (backwards) */ + assert(x <= 0x10FFFF); + if (x < 0x80) /* ascii? */ + buff[UTF8BUFFSZ - 1] = String.fromCharCode(x); + else { /* need continuation bytes */ + let mfb = 0x3f; /* maximum that fits in first byte */ + do { + buff[UTF8BUFFSZ - (n++)] = String.fromCharCode(0x80 | (x & 0x3f)); + x >>= 6; /* remove added bits */ + mfb >>= 1; /* now there is one less bit available in first byte */ + } while (x > mfb); /* still needs continuation byte? */ + buff[UTF8BUFFSZ - n] = String.fromCharCode((~mfb << 1) | x); /* add first byte */ + } + return n; +}; + +const l_str2dloc = function(s, result, mode) { + result[0] = mode === 'x' ? parseInt(s, '16') : parseInt(s); + if (isNaN(result[0])) return null; /* nothing recognized? */ + while (ljstype.lisspace(result[0])) result[0] = result[0].slice(1); /* skip trailing spaces */ + return result[0] === '\0' ? result : null; /* OK if no trailing characters */ +}; + +const l_str2d = function(s) { + let result = [null, null]; + let pidx = /[.xXnN]/g.exec(s).index; + let pmode = pidx ? s.slice(pidx) : null; + let mode = pmode ? pmode.toLowerCase() : 0; + if (mode === 'n') /* reject 'inf' and 'nan' */ + return null; + let end = l_str2dloc(s, result, mode)[0]; /* try to convert */ + if (end === null) { /* failed? may be a different locale */ + throw new Error("Locale not available to handle number"); // TODO + } + return [end, result[1]]; +}; + +const MAXBY10 = Number.MAX_SAFE_INTEGER / 10; +const MAXLASTD = Number.MAX_SAFE_INTEGER % 10; + +const l_str2int = function(s) { + let result = [null, null]; + let a = 0; + let empty = true; + let neg; + + while (ljstype.lisspace(s.charAt(0))) s = s.slice(1); /* skip initial spaces */ + neg = s.charAt(0) === '-'; + + if (neg || s.charAt(0) === '+') + s = s.slice(1); + + if (s[0] === '0' && (s[1] === 'x' || s[1] === 'X')) { /* hex? */ + s = s.slice(2); /* skip '0x' */ + + for (; ljstype.lisxdigit(s); s = s.slice(1)) { + a = a * 16 + luaO_hexavalue(s); + empty = false; + } + } else { /* decimal */ + for (; ljstype.lisdigit(s); s = s.slice(1)) { + let d = parseInt(s.charAt(0)); + if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg)) /* overflow? */ + return null; /* do not accept it (as integer) */ + a = a * 10 + d; + empty = false; + } + } + + while (ljstype.lisspace(s.charAt(0))) s = s.slice(1); /* skip trailing spaces */ + + if (empty || s.charAt(0) !== "") return null; /* something wrong in the numeral */ + else { + result[1] = neg ? -a : a; + result[0] = s; + return result; + } +}; + +const luaO_str2num = function(s) { + let s2i = l_str2int(s); + let e = s2i[0]; + let i = s2i[1]; + + let o; + if (e !== null) /* try as an integer */ + o = new TValue(CT.LUA_TNUMINT, i); + else { /* else try as a float */ + s2i = l_str2d(s); + e = s2i[0]; + i = s2i[1]; + + if (e !== null) { + o = new TValue(CT.LUA_TNUMFLT, i); + } else + return false; /* conversion failed */ + return (e - s) + 1; // TODO: wrong + } +}; + +module.exports.CClosure = CClosure; +module.exports.LClosure = LClosure; +module.exports.TValue = TValue; +module.exports.Table = Table; +module.exports.UTF8BUFFSZ = UTF8BUFFSZ; +module.exports.luaO_chunkid = luaO_chunkid; +module.exports.luaO_hexavalue = luaO_hexavalue; +module.exports.luaO_str2num = luaO_str2num; +module.exports.luaO_utf8desc = luaO_utf8desc; \ No newline at end of file -- cgit v1.2.3-54-g00ecf From 6beeccbd0a859f3a9d1be4142d16a3d11ac30743 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Mon, 27 Feb 2017 15:08:09 +0100 Subject: Basic lexing tests --- src/lfunc.js | 18 ++++++-- src/ljstype.js | 13 +++--- src/llex.js | 133 ++++++++++++++++++++++++++------------------------------- src/lparser.js | 15 +++++++ tests/llex.js | 50 ++++++++++++++++++++++ 5 files changed, 148 insertions(+), 81 deletions(-) create mode 100644 src/lparser.js create mode 100644 tests/llex.js diff --git a/src/lfunc.js b/src/lfunc.js index 3b3c8ce..fde910b 100644 --- a/src/lfunc.js +++ b/src/lfunc.js @@ -1,6 +1,8 @@ /*jshint esversion: 6 */ "use strict"; -const assert = require('assert'); +const assert = require('assert'); + +const lobject = require('./lobject.js'); class Proto { @@ -53,6 +55,15 @@ class UpVal { } +const luaF_newLclosure = function(L, n) { + let c = new lobject.LClosure(); + c.p = null; + c.nupvalues = n; + while (n--) c.upvals[n] = null; + return c; +}; + + const findupval = function(L, level) { let pp = L.openupval; @@ -116,10 +127,11 @@ const luaF_getlocalname = function(f, local_number, pc) { } +module.exports.MAXUPVAL = 255; module.exports.Proto = Proto; module.exports.UpVal = UpVal; module.exports.findupval = findupval; module.exports.luaF_close = luaF_close; -module.exports.MAXUPVAL = 255; +module.exports.luaF_getlocalname = luaF_getlocalname module.exports.luaF_initupvals = luaF_initupvals; -module.exports.luaF_getlocalname = luaF_getlocalname \ No newline at end of file +module.exports.luaF_newLclosure = luaF_newLclosure; \ No newline at end of file diff --git a/src/ljstype.js b/src/ljstype.js index 52ac153..192db6f 100644 --- a/src/ljstype.js +++ b/src/ljstype.js @@ -5,6 +5,7 @@ const assert = require('assert'); const lisdigit = function(c) { return /^\d$/.test(c.charAt(0)); +}; const lisxdigit = function(c) { return /^[0-9a-fA-F]$/.test(c.charAt(0)); @@ -18,12 +19,12 @@ const lislalpha = function(c) { return /^[_a-zA-z]$/.test(c.charAt(0)); }; -const lislanum = function(c) { +const lislalnum = function(c) { return /^[_a-zA-z0-9]$/.test(c.charAt(0)); }; -module.exports.lisdigit = lisdigit; -module.exports.lislalpha = lislalpha; -module.exports.lislanum = lislanum; -module.exports.lisspace = lisspace; -module.exports.lisxdigit = lisxdigit; \ No newline at end of file +module.exports.lisdigit = lisdigit; +module.exports.lislalnum = lislalnum; +module.exports.lislalpha = lislalpha; +module.exports.lisspace = lisspace; +module.exports.lisxdigit = lisxdigit; \ No newline at end of file diff --git a/src/llex.js b/src/llex.js index 4c85f65..1adac0a 100644 --- a/src/llex.js +++ b/src/llex.js @@ -4,11 +4,12 @@ const assert = require('assert'); const lapi = require('./lapi.js'); +const lauxlib = require('./lauxlib.js'); const ldebug = require('./ldebug.js'); const ldo = require('./ldo.js'); -const lua = require('./lua.js'); -const lobject = require('./lobject'); const ljstype = require('./ljstype'); +const lobject = require('./lobject'); +const lua = require('./lua.js'); const TValue = lobject.TValue; const CT = lua.constant_types; const TS = lua.thread_status; @@ -24,73 +25,41 @@ const RESERVED = { TK_ELSEIF: FIRST_RESERVED + 4, TK_END: FIRST_RESERVED + 5, TK_FALSE: FIRST_RESERVED + 6, - TK_FOR: FIRST_RESERVED + 8, - TK_FUNCTION: FIRST_RESERVED + 10, - TK_GOTO: FIRST_RESERVED + 11, - TK_IF: FIRST_RESERVED + 12, - TK_IN: FIRST_RESERVED + 13, - TK_LOCAL: FIRST_RESERVED + 14, - TK_NIL: FIRST_RESERVED + 15, - TK_NOT: FIRST_RESERVED + 16, - TK_OR: FIRST_RESERVED + 17, - TK_REPEAT: FIRST_RESERVED + 18, - TK_RETURN: FIRST_RESERVED + 19, - TK_THEN: FIRST_RESERVED + 20, - TK_TRUE: FIRST_RESERVED + 21, - TK_UNTIL: FIRST_RESERVED + 22, - TK_WHILE: FIRST_RESERVED + 23, + TK_FOR: FIRST_RESERVED + 7, + TK_FUNCTION: FIRST_RESERVED + 8, + TK_GOTO: FIRST_RESERVED + 9, + TK_IF: FIRST_RESERVED + 10, + TK_IN: FIRST_RESERVED + 11, + TK_LOCAL: FIRST_RESERVED + 12, + TK_NIL: FIRST_RESERVED + 13, + TK_NOT: FIRST_RESERVED + 14, + TK_OR: FIRST_RESERVED + 15, + TK_REPEAT: FIRST_RESERVED + 16, + TK_RETURN: FIRST_RESERVED + 17, + TK_THEN: FIRST_RESERVED + 18, + TK_TRUE: FIRST_RESERVED + 19, + TK_UNTIL: FIRST_RESERVED + 20, + TK_WHILE: FIRST_RESERVED + 21, /* other terminal symbols */ - TK_IDIV: FIRST_RESERVED + 24, - TK_CONCAT: FIRST_RESERVED + 25, - TK_DOTS: FIRST_RESERVED + 26, - TK_EQ: FIRST_RESERVED + 27, - TK_GE: FIRST_RESERVED + 28, - TK_LE: FIRST_RESERVED + 29, - TK_NE: FIRST_RESERVED + 30, - TK_SHL: FIRST_RESERVED + 31, - TK_SHR: FIRST_RESERVED + 32, - TK_DBCOLON: FIRST_RESERVED + 33, - TK_EOS: FIRST_RESERVED + 34, - TK_FLT: FIRST_RESERVED + 35, - TK_INT: FIRST_RESERVED + 36, - TK_NAME: FIRST_RESERVED + 37, - TK_STRING: FIRST_RESERVED + 38 + TK_IDIV: FIRST_RESERVED + 22, + TK_CONCAT: FIRST_RESERVED + 23, + TK_DOTS: FIRST_RESERVED + 24, + TK_EQ: FIRST_RESERVED + 25, + TK_GE: FIRST_RESERVED + 26, + TK_LE: FIRST_RESERVED + 27, + TK_NE: FIRST_RESERVED + 28, + TK_SHL: FIRST_RESERVED + 29, + TK_SHR: FIRST_RESERVED + 30, + TK_DBCOLON: FIRST_RESERVED + 31, + TK_EOS: FIRST_RESERVED + 32, + TK_FLT: FIRST_RESERVED + 33, + TK_INT: FIRST_RESERVED + 34, + TK_NAME: FIRST_RESERVED + 35, + TK_STRING: FIRST_RESERVED + 36 }; const R = RESERVED; -const reserved_keywords = [ - "and", "break", "do", "else", "elseif", - "end", "false", "for", "function", "goto", "if", - "in", "local", "nil", "not", "or", "repeat", - "return", "then", "true", "until", "while" -]; - -const reserved_keywords_tokens = [ - R.TK_AND, - R.TK_BREAK, - R.TK_DO, - R.TK_ELSE, - R.TK_ELSEIF, - R.TK_END, - R.TK_FALSE, - R.TK_FOR, - R.TK_FUNCTION, - R.TK_GOTO, - R.TK_IF, - R.TK_IN, - R.TK_LOCAL, - R.TK_NIL, - R.TK_NOT, - R.TK_OR, - R.TK_REPEAT, - R.TK_RETURN, - R.TK_THEN, - R.TK_TRUE, - R.TK_UNTIL, - R.TK_WHILE, -]; - const luaX_tokens = [ "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", @@ -103,7 +72,7 @@ const luaX_tokens = [ const NUM_RESERVED = Object.keys(RESERVED).length; -class Buffer { +class MBuffer { constructor(string) { this.buffer = string ? string.split('') : []; this.n = this.buffer.length; @@ -137,8 +106,8 @@ class LexState { this.lookahead = null; /* look ahead token */ this.fs = null; /* current function (parser) */ this.L = null; - this.z = new Buffer(); - this.buff = new Buffer(); /* buffer for tokens */ + this.z = new MBuffer(); + this.buff = new MBuffer(); /* buffer for tokens */ this.h = null; /* to avoid collection/reuse strings */ this.dyd = null; /* dynamic structures used by the parser */ this.source = null; /* current source name */ @@ -195,10 +164,24 @@ const inclinenumber = function(ls) { }; const luaX_setinput = function(L, ls, z, source, firstchar) { - ls.t.token = 0; + ls.t = { + token: 0, + seminfo: { + i: NaN, + r: NaN, + ts: null + } + }; ls.L = L; ls.current = firstchar; - ls.lookahead.token = R.TK_EOS; + ls.lookahead = { + token: R.TK_EOS, + seminfo: { + i: NaN, + r: NaN, + ts: null + } + }; ls.z = z; ls.fs = null; ls.linenumber = 1; @@ -562,8 +545,9 @@ const llex = function(ls, seminfo) { let ts = new TValue(CT.LUA_TLNGSTR, ls.buff.buffer.join('')); seminfo.ts = ts; - if (reserved_keywords.indexOf(ts.value) >= 0) /* reserved word? */ - return reserved_keywords_tokens[reserved_keywords.indexOf(ts.value)]; + let kidx = luaX_tokens.slice(0, 22).indexOf(ts.value) + if (kidx >= 0) /* reserved word? */ + return kidx + FIRST_RESERVED; else return R.TK_NAME; } else { /* single-char tokens (+ - / ...) */ @@ -591,6 +575,11 @@ const luaX_lookahead = function(ls) { return ls.lookahead.token; }; +module.exports.FIRST_RESERVED = FIRST_RESERVED; +module.exports.LexState = LexState; module.exports.luaX_lookahead = luaX_lookahead; module.exports.luaX_next = luaX_next; -module.exports.luaX_setinput = luaX_setinput; \ No newline at end of file +module.exports.luaX_setinput = luaX_setinput; +module.exports.MBuffer = MBuffer; +module.exports.RESERVED = RESERVED; +module.exports.luaX_tokens = luaX_tokens; \ No newline at end of file diff --git a/src/lparser.js b/src/lparser.js new file mode 100644 index 0000000..a82548c --- /dev/null +++ b/src/lparser.js @@ -0,0 +1,15 @@ +/* jshint esversion: 6 */ +"use strict"; + +const assert = require('assert'); + +const llex = require('./llex.js'); +const lfunc = require('./lfunc.js'); + + +const luaY_parser = function(L, z, buff, dyd, name, firstchar) { + // TODO ... +}; + + +module.exports.luaY_parser = luaY_parser; \ No newline at end of file diff --git a/tests/llex.js b/tests/llex.js new file mode 100644 index 0000000..3c05366 --- /dev/null +++ b/tests/llex.js @@ -0,0 +1,50 @@ +/*jshint esversion: 6 */ +"use strict"; + +const test = require('tape'); +const beautify = require('js-beautify').js_beautify; + +const tests = require("./tests.js"); + +const lapi = require("../src/lapi.js"); +const lauxlib = require("../src/lauxlib.js"); +const llex = require("../src/llex.js"); +const lua = require('../src/lua.js'); +const R = llex.RESERVED; + + +test('basic lexing', function (t) { + let luaCode = ` + return "hello lex !" + `, L; + + t.plan(2); + + let readTokens = []; + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + let ls = new llex.LexState(); + llex.luaX_setinput(L, ls, new llex.MBuffer(luaCode), luaCode, luaCode.charAt(0)); + + llex.luaX_next(ls); + + while (ls.t.token !== R.TK_EOS) { + console.log(llex.luaX_tokens[ls.t.token - llex.FIRST_RESERVED]); + + readTokens.push(ls.t.token); + llex.luaX_next(ls); + } + + + }, "JS Lua program ran without error"); + + t.deepEqual( + readTokens, + [R.TK_RETURN, R.TK_STRING], + "Correct tokens found" + ) + +}); \ No newline at end of file -- cgit v1.2.3-54-g00ecf From c1cf887d702a36729bab0257247066745223cf6e Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Mon, 27 Feb 2017 15:35:50 +0100 Subject: TK_LOCAL, TK_NAME, TK_INT --- src/llex.js | 10 ++++++++-- src/lobject.js | 26 ++++++++++++-------------- tests/llex.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/src/llex.js b/src/llex.js index 1adac0a..479fe48 100644 --- a/src/llex.js +++ b/src/llex.js @@ -250,7 +250,7 @@ const txtToken = function(ls, token) { case R.TK_NAME: case R.TK_STRING: case R.TK_FLT: case R.TK_INT: save(ls, '\0'); - return lapi.lua_pushstring(ls.L, `'${ls.buff.buffer}'`); + return lapi.lua_pushstring(ls.L, `'${ls.buff.buffer.join('')}'`); default: return luaX_token2str(ls, token); } @@ -306,7 +306,10 @@ const read_long_string = function(ls, seminfo, sep) { case '\n': case '\r': { save(ls, '\n'); inclinenumber(ls); - if (!seminfo) ls.buff.n = 0; + if (!seminfo) { + ls.buff.n = 0; + ls.buff.buffer = []; + } break; } default: { @@ -444,6 +447,7 @@ const read_string = function(ls, del, seminfo) { const llex = function(ls, seminfo) { ls.buff.n = 0; + ls.buff.buffer = []; for (;;) { switch (ls.current) { @@ -463,9 +467,11 @@ const llex = function(ls, seminfo) { if (ls.current === '[') { /* long comment? */ let sep = skip_sep(ls); ls.buff.n = 0; /* 'skip_sep' may dirty the buffer */ + ls.buff.buffer = []; if (sep >= 0) { read_long_string(ls, null, sep); /* skip long comment */ ls.buff.n = 0; /* previous call may dirty the buff. */ + ls.buff.buffer = []; break; } } diff --git a/src/lobject.js b/src/lobject.js index 93e23c2..f6c6ef0 100644 --- a/src/lobject.js +++ b/src/lobject.js @@ -294,22 +294,22 @@ const l_str2int = function(s) { let empty = true; let neg; - while (ljstype.lisspace(s.charAt(0))) s = s.slice(1); /* skip initial spaces */ - neg = s.charAt(0) === '-'; + while (ljstype.lisspace(s[0])) s = s.slice(1); /* skip initial spaces */ + neg = s[0] === '-'; - if (neg || s.charAt(0) === '+') + if (neg || s[0] === '+') s = s.slice(1); if (s[0] === '0' && (s[1] === 'x' || s[1] === 'X')) { /* hex? */ s = s.slice(2); /* skip '0x' */ - for (; ljstype.lisxdigit(s); s = s.slice(1)) { + for (; ljstype.lisxdigit(s[0]); s = s.slice(1)) { a = a * 16 + luaO_hexavalue(s); empty = false; } } else { /* decimal */ - for (; ljstype.lisdigit(s); s = s.slice(1)) { - let d = parseInt(s.charAt(0)); + for (; ljstype.lisdigit(s[0]); s = s.slice(1)) { + let d = parseInt(s[0]); if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg)) /* overflow? */ return null; /* do not accept it (as integer) */ a = a * 10 + d; @@ -317,9 +317,9 @@ const l_str2int = function(s) { } } - while (ljstype.lisspace(s.charAt(0))) s = s.slice(1); /* skip trailing spaces */ + while (ljstype.lisspace(s[0])) s = s.slice(1); /* skip trailing spaces */ - if (empty || s.charAt(0) !== "") return null; /* something wrong in the numeral */ + if (empty)/* TODO: || s[0] !== "") */ return null; /* something wrong in the numeral */ else { result[1] = neg ? -a : a; result[0] = s; @@ -332,19 +332,17 @@ const luaO_str2num = function(s) { let e = s2i[0]; let i = s2i[1]; - let o; - if (e !== null) /* try as an integer */ - o = new TValue(CT.LUA_TNUMINT, i); - else { /* else try as a float */ + if (e !== null) { /* try as an integer */ + return new TValue(CT.LUA_TNUMINT, i);; + } else { /* else try as a float */ s2i = l_str2d(s); e = s2i[0]; i = s2i[1]; if (e !== null) { - o = new TValue(CT.LUA_TNUMFLT, i); + return new TValue(CT.LUA_TNUMFLT, i); } else return false; /* conversion failed */ - return (e - s) + 1; // TODO: wrong } }; diff --git a/tests/llex.js b/tests/llex.js index 3c05366..725d3f8 100644 --- a/tests/llex.js +++ b/tests/llex.js @@ -13,7 +13,7 @@ const lua = require('../src/lua.js'); const R = llex.RESERVED; -test('basic lexing', function (t) { +test('basic lexing: TK_RETURN, TK_STRING', function (t) { let luaCode = ` return "hello lex !" `, L; @@ -32,7 +32,7 @@ test('basic lexing', function (t) { llex.luaX_next(ls); while (ls.t.token !== R.TK_EOS) { - console.log(llex.luaX_tokens[ls.t.token - llex.FIRST_RESERVED]); + // console.log(llex.luaX_tokens[ls.t.token - llex.FIRST_RESERVED]); readTokens.push(ls.t.token); llex.luaX_next(ls); @@ -47,4 +47,52 @@ test('basic lexing', function (t) { "Correct tokens found" ) +}); + + +test('TK_LOCAL, TK_NAME, TK_INT', function (t) { + let luaCode = ` + local f = testing(aBunch) + return "of" + end + + return f("things") + 12 + `, L; + + t.plan(2); + + let readTokens = []; + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + let ls = new llex.LexState(); + llex.luaX_setinput(L, ls, new llex.MBuffer(luaCode), luaCode, luaCode.charAt(0)); + + llex.luaX_next(ls); + + while (ls.t.token !== R.TK_EOS) { + // console.log(ls.t.token >= llex.FIRST_RESERVED ? + // llex.luaX_tokens[ls.t.token - llex.FIRST_RESERVED] + // : ls.t.token); + + readTokens.push(ls.t.token); + llex.luaX_next(ls); + } + + + }, "JS Lua program ran without error"); + + t.deepEqual( + readTokens, + [ + R.TK_LOCAL, R.TK_NAME, '=', R.TK_NAME, '(', R.TK_NAME, ')', + R.TK_RETURN, R.TK_STRING, + R.TK_END, + R.TK_RETURN, R.TK_NAME, '(', R.TK_STRING, ')', '+', R.TK_INT + ], + "Correct tokens found" + ) + }); \ No newline at end of file -- cgit v1.2.3-54-g00ecf From 8285c78283f922e5b5569ce5c28fb25074db0af3 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Mon, 27 Feb 2017 21:49:27 +0100 Subject: parsing --- src/lcode.js | 223 ++++++++++++++++++++++++++++++++++++ src/llex.js | 35 ++++-- src/lopcodes.js | 214 +++++++++++++++++++++++++++++++---- src/lparser.js | 346 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 779 insertions(+), 39 deletions(-) create mode 100644 src/lcode.js diff --git a/src/lcode.js b/src/lcode.js new file mode 100644 index 0000000..695c7dc --- /dev/null +++ b/src/lcode.js @@ -0,0 +1,223 @@ +/* jshint esversion: 6 */ +"use strict"; + +const assert = require('assert'); + +const lopcode = require('./lopcode.js'); +const llex = require('./llex.js'); +const lcode = require('./lcode.js'); +const OpCodesI = lopcode.OpCodesI; + +/* +** Marks the end of a patch list. It is an invalid value both as an absolute +** address, and as a list link (would link an element to itself). +*/ +const NO_JUMP = -1; + +/* +** Gets the destination address of a jump instruction. Used to traverse +** a list of jumps. +*/ +const getjump = function(fs, pc) { + let offset = fs.f.code[pc].sBx; + if (offset === NO_JUMP) /* point to itself represents end of list */ + return NO_JUMP; /* end of list */ + else + return pc + 1 + offset; /* turn offset into absolute position */ +}; + +/* +** Fix jump instruction at position 'pc' to jump to 'dest'. +** (Jump addresses are relative in Lua) +*/ +const fixjump = function(fs, pc, dest) { + let jmp = fs.f.code[pc]; + let offset = dest - (pc + 1); + assert(dest !== NO_JUMP); + if (Math.abs(offset) > lopcode.MAXARG_sBx) + llex.luaX_syntaxerror(fs.ls, "control structure too long"); + lopcode.SETARG_sBx(jmp, offset); +}; + +/* +** Concatenate jump-list 'l2' into jump-list 'l1' +*/ +const luaK_concat = function(fs, l1, l2) { + if (l2 === NO_JUMP) return; /* nothing to concatenate? */ + else if (l1 === NO_JUMP) /* no original list? */ + l1[0] = l2; + else { + let list = l1[0]; + let next = getjump(fs, list); + while (next !== NO_JUMP) { /* find last element */ + list = next; + next = getjump(fs, list); + } + fixjump(fs, list, l2); + } +}; + +/* +** Create a jump instruction and return its position, so its destination +** can be fixed later (with 'fixjump'). If there are jumps to +** this position (kept in 'jpc'), link them all together so that +** 'patchlistaux' will fix all them directly to the final destination. +*/ +const luaK_jump = function (fs) { + let jpc = fs.jpc; /* save list of jumps to here */ + fs.jpc = NO_JUMP; /* no more jumps to here */ + let j = luaK_codeAsBx(fs, OpCodesI.OP_JMP, 0, NO_JUMP); + luaK_concat(fs, j, jpc); /* keep them on hold */ + return j; +}; + +/* +** Code a 'return' instruction +*/ +const luaK_ret = function(fs, first, nret) { + luaK_codeABC(fs, OpCodesI.OP_RETURN, first, nret + 1, 0); +}; + +/* +** returns current 'pc' and marks it as a jump target (to avoid wrong +** optimizations with consecutive instructions not in the same basic block). +*/ +const luaK_getlabel = function(fs) { + fs.lasttarget = fs.pc; + return fs.pc; +}; + +/* +** Returns the position of the instruction "controlling" a given +** jump (that is, its condition), or the jump itself if it is +** unconditional. +*/ +const getjumpcontrol = function(fs, pc) { + if (pc >= 1 && lopcode.testTMode(fs.f.code[pc - 1].opcode)) + return fs.f.code[pc - 1]; + else + return fs.f.code[pc]; +}; + +/* +** Patch destination register for a TESTSET instruction. +** If instruction in position 'node' is not a TESTSET, return 0 ("fails"). +** Otherwise, if 'reg' is not 'NO_REG', set it as the destination +** register. Otherwise, change instruction to a simple 'TEST' (produces +** no register value) +*/ +const patchtestreg = function(fs, node, reg) { + let i = getjumpcontrol(fs, node); + if (i.opcode !== OpCodesI.OP_TESTSET) + return false; /* cannot patch other instructions */ + if (reg !== lopcode.NO_REG && reg !== i.B) + lopcode.SETARG_A(i, reg); + else { + /* no register to put value or register already has the value; + change instruction to simple test */ + i = lopcode.CREATE_ABC(OpCodesI.OP_TEST, i.B, 0, i.C); + } + return true; +}; + +/* +** Traverse a list of tests, patching their destination address and +** registers: tests producing values jump to 'vtarget' (and put their +** values in 'reg'), other tests jump to 'dtarget'. +*/ +const patchlistaux = function(fs, list, vtarget, reg, dtarget) { + while (list !== NO_JUMP) { + let next = getjump(fs, list); + if (patchtestreg(fs, list, reg)) + fixjump(fs, list, vtarget); + else + fixjump(fs, list, dtarget); /* jump to default target */ + list = next; + } +}; + +/* +** Add elements in 'list' to list of pending jumps to "here" +** (current position) +*/ +const luaK_patchtohere = function(fs, list) { + luaK_getlabel(fs); /* mark "here" as a jump target */ + luaK_concat(fs, fs.jpc, list); +}; + +/* +** Path all jumps in 'list' to jump to 'target'. +** (The assert means that we cannot fix a jump to a forward address +** because we only know addresses once code is generated.) +*/ +const luaK_patchlist = function(fs, list, target) { + if (target === fs.pc) /* 'target' is current position? */ + luaK_patchtohere(fs, list); /* add list to pending jumps */ + else { + assert(target < fs.pc); + patchlistaux(fs, list, target, lopcode.NO_REG, target); + } +}; + +/* +** Path all jumps in 'list' to close upvalues up to given 'level' +** (The assertion checks that jumps either were closing nothing +** or were closing higher levels, from inner blocks.) +*/ +const luaK_patchclose = function(fs, list, level) { + level++; /* argument is +1 to reserve 0 as non-op */ + for (; list !== NO_JUMP; list = getjump(fs, list)) { + let ins = fs.f.code[list]; + assert(ins.opcode === OpCodesI.OP_JMP && (ins.A === 0 || i.A >= level); + lopcode.SETARG_A(ins, level); + } +}; + +/* +** Emit instruction 'i', checking for array sizes and saving also its +** line information. Return 'i' position. +*/ +const luaK_code = function(fs, i) { + let f = fs.f; + lcode.dischargejpc(fs); /* 'pc' will change */ + /* put new instruction in code array */ + f.code[fs.pc] = i; + f.lineinfo[fs.pc] = fs.ls.lastline; + return fs.pc++; +}; + +/* +** Format and emit an 'iABC' instruction. (Assertions check consistency +** of parameters versus opcode.) +*/ +const luaK_codeABC = function(fs, o, a, b, c) { + assert(lopcode.getOpMode(o) == lopcode.iABC); + assert(lopcode.getBMode(o) != lopcode.OpArgN || b === 0); + assert(lopcode.getCMode(o) != lopcode.OpArgN || c === 0); + assert(a <= lopcode.MAXARG_A && b <= lopcode.MAXARG_B && c <= lopcode.MAXARG_C); + return luaK_code(fs, lopcode.CREATE_ABC(o, a, b, c)); +}; + +/* +** Format and emit an 'iABx' instruction. +*/ +const luaK_codeABx = function(fs, o, a, bc) { + lua_assert(lopcode.getOpMode(o) == lopcode.iABx || getOpMode(o) == lopcode.iAsBx); + lua_assert(lopcode.getCMode(o) == lopcode.OpArgN); + lua_assert(a <= lopcode.MAXARG_A && bc <= lopcode.MAXARG_Bx); + return luaK_code(fs, lopcode.CREATE_ABx(o, a, bc)); +}; + +const luaK_codeAsBx = function(fs,o,A,sBx) { + return luaK_codeABx(fs, o, A, (sBx) + lopcode.MAXARG_sBx); +}; + +module.exports.NO_JUMP = NO_JUMP; +module.exports.luaK_codeABx = luaK_codeABx; +module.exports.luaK_codeAsBx = luaK_codeAsBx; +module.exports.luaK_getlabel = luaK_getlabel; +module.exports.luaK_jump = luaK_jump; +module.exports.luaK_patchclose = luaK_patchclose; +module.exports.luaK_patchlist = luaK_patchlist; +module.exports.luaK_patchtohere = luaK_patchtohere; +module.exports.luaK_ret = luaK_ret; \ No newline at end of file diff --git a/src/llex.js b/src/llex.js index 479fe48..1992856 100644 --- a/src/llex.js +++ b/src/llex.js @@ -126,11 +126,11 @@ const save = function(ls, c) { const luaX_token2str = function(ls, token) { if (token < FIRST_RESERVED) { /* single-byte symbols? */ - return lapi.lua_pushstring(ls.L, `'%{String.fromCharCode(token)}'`); + return `'${String.fromCharCode(token)}'`; } else { let s = luaX_tokens[token - FIRST_RESERVED]; if (token < R.TK_EOS) /* fixed format (symbols and reserved words)? */ - return lapi.lua_pushstring(ls.L, `'${s}'`); + return `'${s}'`; else /* names, strings, and numerals */ return s; } @@ -250,7 +250,7 @@ const txtToken = function(ls, token) { case R.TK_NAME: case R.TK_STRING: case R.TK_FLT: case R.TK_INT: save(ls, '\0'); - return lapi.lua_pushstring(ls.L, `'${ls.buff.buffer.join('')}'`); + return `'${ls.buff.buffer.join('')}'`; default: return luaX_token2str(ls, token); } @@ -263,6 +263,10 @@ const lexerror = function(ls, msg, token) { ldo.luaD_throw(ls.L, TS.LUA_ERRSYNTAX); }; +const luaX_syntaxerror = function(ls, msg) { + lexerror(ls, msg, ls.t.token); +} + /* ** skip a sequence '[=*[' or ']=*]'; if sequence is well formed, return ** its number of '='s; otherwise, return a negative number (-1 iff there @@ -292,7 +296,7 @@ const read_long_string = function(ls, seminfo, sep) { switch (ls.current) { case -1: { /* error */ let what = seminfo ? "string" : "comment"; - let msg = lapi.lua_pushstring(ls.L, `unfinished long ${what} (starting at line ${line})`); + let msg = `unfinished long ${what} (starting at line ${line})`; lexerror(ls, msg, R.TK_EOS); break; } @@ -445,6 +449,10 @@ const read_string = function(ls, del, seminfo) { seminfo.ts = new TValue(CT.LUA_TLNGSTR, ls.buff.buffer.slice(1).join('')); }; +const isreserved = function(w) { + return luaX_tokens.slice(0, 22).indexOf(w) >= 0; +}; + const llex = function(ls, seminfo) { ls.buff.n = 0; ls.buff.buffer = []; @@ -581,11 +589,14 @@ const luaX_lookahead = function(ls) { return ls.lookahead.token; }; -module.exports.FIRST_RESERVED = FIRST_RESERVED; -module.exports.LexState = LexState; -module.exports.luaX_lookahead = luaX_lookahead; -module.exports.luaX_next = luaX_next; -module.exports.luaX_setinput = luaX_setinput; -module.exports.MBuffer = MBuffer; -module.exports.RESERVED = RESERVED; -module.exports.luaX_tokens = luaX_tokens; \ No newline at end of file +module.exports.FIRST_RESERVED = FIRST_RESERVED; +module.exports.LexState = LexState; +module.exports.MBuffer = MBuffer; +module.exports.RESERVED = RESERVED; +module.exports.isreserved = isreserved; +module.exports.luaX_lookahead = luaX_lookahead; +module.exports.luaX_next = luaX_next; +module.exports.luaX_setinput = luaX_setinput; +module.exports.luaX_syntaxerror = luaX_syntaxerror; +module.exports.luaX_token2str = luaX_token2str; +module.exports.luaX_tokens = luaX_tokens; \ No newline at end of file diff --git a/src/lopcodes.js b/src/lopcodes.js index 5723abb..d83897c 100644 --- a/src/lopcodes.js +++ b/src/lopcodes.js @@ -51,6 +51,56 @@ const OpCodes = [ "OP_EXTRAARG" ]; +const OpCodesI = { + OP_MOVE: 0, + OP_LOADK: 1, + OP_LOADKX: 2, + OP_LOADBOOL: 3, + OP_LOADNIL: 4, + OP_GETUPVAL: 5, + OP_GETTABUP: 6, + OP_GETTABLE: 7, + OP_SETTABUP: 8, + OP_SETUPVAL: 9, + OP_SETTABLE: 10, + OP_NEWTABLE: 11, + OP_SELF: 12, + OP_ADD: 13, + OP_SUB: 14, + OP_MUL: 15, + OP_MOD: 16, + OP_POW: 17, + OP_DIV: 18, + OP_IDIV: 19, + OP_BAND: 20, + OP_BOR: 21, + OP_BXOR: 22, + OP_SHL: 23, + OP_SHR: 24, + OP_UNM: 25, + OP_BNOT: 26, + OP_NOT: 27, + OP_LEN: 28, + OP_CONCAT: 29, + OP_JMP: 30, + OP_EQ: 31, + OP_LT: 32, + OP_LE: 33, + OP_TEST: 34, + OP_TESTSET: 35, + OP_CALL: 36, + OP_TAILCALL: 37, + OP_RETURN: 38, + OP_FORLOOP: 39, + OP_FORPREP: 40, + OP_TFORCALL: 41, + OP_TFORLOOP: 42, + OP_SETLIST: 43, + OP_CLOSURE: 44, + OP_VARARG: 45, + OP_EXTRAARG: 46 +}; + /* ** masks for instruction properties. The format is: ** bits 0-1: op mode @@ -120,10 +170,26 @@ const luaP_opmodes = [ 0 << 7 | 0 << 6 | OpArgU << 4 | OpArgU << 2 | iAx /* OP_EXTRAARG */ ]; +const getOpMode = function(m) { + return luaP_opmodes[m] & 3; +}; + +const getBMode = function(m) { + return (luaP_opmodes[m] >> 4) & 3; +}; + +const getCMode = function(m) { + return (luaP_opmodes[m] >> 2) & 3; +}; + const testAMode = function(m) { return luaP_opmodes[m] & (1 << 6); }; +const testTMode = function(m) { + return luaP_opmodes[m] & (1 << 7); +}; + const SIZE_C = 9; const SIZE_B = 9; const SIZE_Bx = (SIZE_C + SIZE_B); @@ -145,6 +211,11 @@ const MAXARG_C = ((1 << SIZE_C) - 1); const BITRK = (1 << (SIZE_B - 1)); +/* +** invalid register that fits in 8 bits +*/ +const NO_REG = MAXARG_A; + const ISK = function (x) { return x & BITRK; }; @@ -153,30 +224,125 @@ const INDEXK = function (r) { return r & ~BITRK; }; +/* creates a mask with 'n' 1 bits at position 'p' */ +const MASK1 = function(n, p) { + return ((~((~0)<<(n)))<<(p)); +}; + +/* creates a mask with 'n' 0 bits at position 'p' */ +const MASK0 = function(n, p) { + return (~MASK1(n, p)); +}; + +const setarg = function(i, v, pos, size) { + i.code = (i.code & MASK0(size, pos)) | ((v << pos) & MASK1(size, pos)); + fullins(i); +}; + +const SETARG_A = function(i,v) { + setarg(i, v, POS_A, SIZE_A); +}; + +const SETARG_B = function(i,v) { + setarg(i, v, POS_B, SIZE_B); +}; + +const SETARG_C = function(i,v) { + setarg(i, v, POS_C, SIZE_C); +}; + +const SETARG_Bx = function(i,v) { + setarg(i, v, POS_Bx, SIZE_Bx); +}; + +const SETARG_Ax = function(i,v) { + setarg(i, v, POS_Ax, SIZE_Ax); +}; + +const SETARG_sBx = function(i, b) { + SETARG_Bx(i, b + MAXARG_sBx); +}; + +/* +** Pre-calculate all possible part of the instruction +*/ +const fullins = function(ins) { + if (typeof ins === "integer") { + return { + code: ins, + opcode: (ins >> POS_OP) & MASK1(SIZE_OP, 0), + A: (ins >> POS_A) & MASK1(SIZE_A, 0), + B: (ins >> POS_B) & MASK1(SIZE_B, 0), + C: (ins >> POS_C) & MASK1(SIZE_C, 0), + Bx: (ins >> POS_Bx) & MASK1(SIZE_Bx, 0), + Ax: (ins >> POS_Ax) & MASK1(SIZE_Ax, 0), + sBx: ((ins >> POS_Bx) & MASK1(SIZE_Bx, 0)) - MAXARG_sBx + }; + } else { + let i = ins.code; + ins.opcode = (i >> POS_OP) & MASK1(SIZE_OP, 0), + ins.A = (i >> POS_A) & MASK1(SIZE_A, 0), + ins.B = (i >> POS_B) & MASK1(SIZE_B, 0), + ins.C = (i >> POS_C) & MASK1(SIZE_C, 0), + ins.Bx = (i >> POS_Bx) & MASK1(SIZE_Bx, 0), + ins.Ax = (i >> POS_Ax) & MASK1(SIZE_Ax, 0), + ins.sBx = ((i >> POS_Bx) & MASK1(SIZE_Bx, 0)) - MAXARG_sBx + return ins; + } +}; + +const CREATE_ABC = function(o, a, b, c) { + return fullins(o << POS_OP | a << POS_A | b << POS_B | c << POS_C); +}; + +const CREATE_ABx = function(o, a, bc) { + return fullins(o << POS_OP | a << POS_A | bc << POS_Bx); +}; + +const CREATE_Ax = function(o a) { + return fullins(o << POS_OP | a << POS_Ax); +}; + /* number of list items to accumulate before a SETLIST instruction */ const LFIELDS_PER_FLUSH = 50; -module.exports.OpCodes = OpCodes; -module.exports.SIZE_C = SIZE_C; -module.exports.SIZE_B = SIZE_B; -module.exports.SIZE_Bx = SIZE_Bx; -module.exports.SIZE_A = SIZE_A; -module.exports.SIZE_Ax = SIZE_Ax; -module.exports.SIZE_OP = SIZE_OP; -module.exports.POS_OP = POS_OP; -module.exports.POS_A = POS_A; -module.exports.POS_C = POS_C; -module.exports.POS_B = POS_B; -module.exports.POS_Bx = POS_Bx; -module.exports.POS_Ax = POS_Ax; -module.exports.MAXARG_Bx = MAXARG_Bx; -module.exports.MAXARG_sBx = MAXARG_sBx; -module.exports.MAXARG_Ax = MAXARG_Ax; -module.exports.MAXARG_A = MAXARG_A; -module.exports.MAXARG_B = MAXARG_B; -module.exports.MAXARG_C = MAXARG_C; -module.exports.BITRK = BITRK; -module.exports.ISK = ISK; -module.exports.INDEXK = INDEXK; -module.exports.LFIELDS_PER_FLUSH = LFIELDS_PER_FLUSH; -module.exports.testAMode = testAMode; \ No newline at end of file +module.exports.BITRK = BITRK; +module.exports.CREATE_ABC = CREATE_ABC; +module.exports.CREATE_ABx = CREATE_ABx; +module.exports.CREATE_Ax = CREATE_Ax; +module.exports.INDEXK = INDEXK; +module.exports.ISK = ISK; +module.exports.LFIELDS_PER_FLUSH = LFIELDS_PER_FLUSH; +module.exports.MAXARG_A = MAXARG_A; +module.exports.MAXARG_Ax = MAXARG_Ax; +module.exports.MAXARG_B = MAXARG_B; +module.exports.MAXARG_Bx = MAXARG_Bx; +module.exports.MAXARG_C = MAXARG_C; +module.exports.MAXARG_sBx = MAXARG_sBx; +module.exports.NO_REG = NO_REG; +module.exports.OpCodes = OpCodes; +module.exports.OpCodesI = OpCodesI; +module.exports.POS_A = POS_A; +module.exports.POS_Ax = POS_Ax; +module.exports.POS_B = POS_B; +module.exports.POS_Bx = POS_Bx; +module.exports.POS_C = POS_C; +module.exports.POS_OP = POS_OP; +module.exports.SETARG_A = SETARG_A; +module.exports.SETARG_Ax = SETARG_Ax; +module.exports.SETARG_B = SETARG_B; +module.exports.SETARG_Bx = SETARG_Bx; +module.exports.SETARG_C = SETARG_C; +module.exports.SETARG_sBx = SETARG_sBx; +module.exports.SIZE_A = SIZE_A; +module.exports.SIZE_Ax = SIZE_Ax; +module.exports.SIZE_B = SIZE_B; +module.exports.SIZE_Bx = SIZE_Bx; +module.exports.SIZE_C = SIZE_C; +module.exports.SIZE_OP = SIZE_OP; +module.exports.fullins = fullins; +module.exports.getBMode = getBMode; +module.exports.getCMode = getCMode; +module.exports.getOpMode = getOpMode; +module.exports.testAMode = testAMode; +module.exports.testTMode = testTMode; \ No newline at end of file diff --git a/src/lparser.js b/src/lparser.js index a82548c..8f3a48d 100644 --- a/src/lparser.js +++ b/src/lparser.js @@ -3,12 +3,352 @@ const assert = require('assert'); -const llex = require('./llex.js'); -const lfunc = require('./lfunc.js'); +const lua = require('./lua.js'); +const llex = require('./llex.js'); +const lfunc = require('./lfunc.js'); +const lobject = require('./lobject.js'); +const lcode = require('./lcode.js'); +const R = llex.RESERVED; +const TValue = lobject.TValue; +const CT = lua.constants_type; +const Table = lobject.Table; +const Proto = lfunc.Proto; +const UpVal = lfunc.UpVal; +class BlockCnt { + constructor() { + this.previous = null; /* chain */ + this.firstlabel = NaN; /* index of first label in this block */ + this.firstgoto = NaN; /* index of first pending goto in this block */ + this.nactvar = NaN; /* # active locals outside the block */ + this.upval = NaN; /* true if some variable in the block is an upvalue */ + this.isloop = NaN; /* true if 'block' is a loop */ + } +} + +const expkind = { + VVOID: 0, /* when 'expdesc' describes the last expression a list, + this kind means an empty list (so, no expression) */ + VNIL: 1, /* constant nil */ + VTRUE: 2, /* constant true */ + VFALSE: 3, /* constant false */ + VK: 4, /* constant in 'k'; info = index of constant in 'k' */ + VKFLT: 5, /* floating constant; nval = numerical float value */ + VKINT: 6, /* integer constant; nval = numerical integer value */ + VNONRELOC: 7, /* expression has its value in a fixed register; + info = result register */ + VLOCAL: 8, /* local variable; info = local register */ + VUPVAL: 9, /* upvalue variable; info = index of upvalue in 'upvalues' */ + VINDEXED: 10, /* indexed variable; + ind.vt = whether 't' is register or upvalue; + ind.t = table register or upvalue; + ind.idx = key's R/K index */ + VJMP: 11, /* expression is a test/comparison; + info = pc of corresponding jump instruction */ + VRELOCABLE: 12, /* expression can put result in any register; + info = instruction pc */ + VCALL: 13, /* expression is a function call; info = instruction pc */ + VVARARG: 14 /* vararg expression; info = instruction pc */ +}; + +class expdesc { + constructor() { + this.k = NaN; + this.u = { + ival: NaN, /* for VKINT */ + nval: NaN, /* for VKFLT */ + info: NaN, /* for generic use */ + ind: { /* for indexed variables (VINDEXED) */ + idx: NaN, /* index (R/K) */ + t: NaN, /* table (register or upvalue) */ + vt: NaN /* whether 't' is register (VLOCAL) or upvalue (VUPVAL) */ + } + }; + this.t = NaN; /* patch list of 'exit when true' */ + this.f = NaN; /* patch list of 'exit when false' */ + } +} + +class FuncState { + constructor() { + this.f = null; /* current function header */ + this.prev = null; /* enclosing function */ + this.ls = null; /* lexical state */ + this.bl = null; /* chain of current blocks */ + this.pc = NaN; /* next position to code (equivalent to 'ncode') */ + this.lasttarget = NaN; /* 'label' of last 'jump label' */ + this.jpc = NaN; /* list of pending jumps to 'pc' */ + this.nk = NaN; /* number of elements in 'k' */ + this.np = NaN; /* number of elements in 'p' */ + this.firstlocal = NaN; /* index of first local var (in Dyndata array) */ + this.nlocvars = NaN; /* number of elements in 'f->locvars' */ + this.nactvar = NaN; /* number of active local variables */ + this.nups = NaN; /* number of upvalues */ + this.freereg = NaN; /* first free register */ + } +} + +const semerror = function(ls, msg) { + ls.t.token = 0; /* remove "near " from final message */ + llex.luaX_syntaxerror(ls, msg); +}; + +const error_expected = function(ls, token) { + llex.luaX_syntaxerror(ls, `${llex.luaX_token2str(ls, token)} expected`); +}; + +const errorlimit = function(fs, limit, what) { + let L = fs.ls.L; + let line = fs.f.linedefined; + let where = (line === 0) ? "main function" : `function at line ${line}`; + let msg = `too many ${what} (limit is ${limit}) in ${where}`; + llex.luaX_syntaxerror(fs.ls, msg); +}; + +const checklimit = function(fs, v, l, what) { + if (v > l) errorlimit(fs, l, what); +}; + +const check = function(ls, c) { + if (ls.t.token !== c) + error_expected(ls, c); +}; + +const init_exp = function(e, k, i) { + e.f = e.t = lcode.NO_JUMP; + e.k = k; + e.u.info = i; +}; + +const getlocvar = function(fs, i) { + let idx = fs.ls.dyd.actvar.arr[fs.firstlocal + i].idx; + assert(idx < fs.nlocvars); + return fs.f.locvars[idx]; +}; + +const removevars = function(fs, tolevel) { + fs.ls.dyd.actvar.n -= fs.nactvar - tolevel; + while (fs.nactvar > tolevel) + getlocvar(fs, --fs.nactvar).endpc = fs.pc; +}; + +const newupvalue = function(fs, name, v) { + let f = fs.f; + checklimit(fs, fs.nups + 1, lfunc.MAXUPVAL, "upvalues"); + f.upvalues[fs.nups] = new UpVal(fs.ls.L); + f.upvalues[fs.nups].instack = v.k === expkind.VLOCAL; + f.upvalues[fs.nups].idx = v.u.info; + f.upvalues[fs.nups].name = name; + return fs.nups++; +}; + +const closegoto = function(ls, g, label) { + let fs = ls.fs; + let gl = ls.dyd.gt; + let gt = gl.arr[g]; + assert(gt.name === label.name); + if (gt.nactvar < label.nactvar) { + let vname = getlocvar(fs, gt.nactvar).varname; + semerror(ls, ` at line ${gt.line} jumps into the scope of local '${vname}'`); + } + lcode.luaK_patchlist(fs, gt.pc, label.pc); + /* remove goto from pending list */ + for (let i = g; i < gl.n - 1; i++) + gl.arr[i] = gl.arr[i + 1]; + gl.n--; +}; + +/* +** try to close a goto with existing labels; this solves backward jumps +*/ +const findlabel = function(ls, g) { + let bl = ls.fs.bl; + let dyd = ls.dyd; + let gt = dyd.gt.arr[g]; + /* check labels in current block for a match */ + for (let i = bl.firstlabel; i < dyd.label.n; i++) { + let lb = dyd.label.arr[i]; + if (lb.name === gt.name) { /* correct label? */ + if (gt.nactvar > lb.nactvar && (bl.upval || dyd.label.n > bl.firstlabel)) + lcode.luaK_patchclose(ls.fs, gt.pc, lb.nactvar); + closegoto(ls, g, lb); /* close it */ + return true; + } + } + return false; /* label not found; cannot close goto */ +}; + +const newlabelentry = function(ls, l, name, line, pc) { + let n = l.n; + l.arr[n].name = name; + l.arr[n].line = line; + l.arr[n].nactvar = ls.fs.nactvar; + l.arr[n].pc = pc; + l.n = n + 1; + return n; +}; + +/* +** check whether new label 'lb' matches any pending gotos in current +** block; solves forward jumps +*/ +const findgotos = function(ls, lb) { + let gl = ls.dyd.gt; + let i = ls.fs.bl.firstgoto; + while (i < gl.n) { + if (gl.arr[i].name === lb.name) + closegoto(ls, i, lb); + else + i++; + } +}; + +/* +** export pending gotos to outer level, to check them against +** outer labels; if the block being exited has upvalues, and +** the goto exits the scope of any variable (which can be the +** upvalue), close those variables being exited. +*/ +const movegotosout = function(fs, bl) { + let i = bl.firstgoto; + let gl = fs.ls.dydy.gt; + /* correct pending gotos to current block and try to close it + with visible labels */ + while (i < gl.n) { + let gt = gl.arr[i]; + if (gt.nactvar > bl.nactvar) { + if (bl.upval) + lcode.luaK_patchclose(fs, gt.pc, bl.nactvar); + gt.nactvar = bl.nactvar; + } + if (!findlabel(fs.ls, i)) + i++; /* move to next one */ + } +}; + +const enterblock = function(fs, bl, isloop) { + bl.isloop = isloop; + bl.nactvar = fs.nactvar; + bl.firstlabel = fs.ls.dyd.label.n; + bl.firstgoto = fs.ls.dyd.gt.n; + bl.upval = 0; + bl.previous = fs.bl; + fs.bl = bl; + assert(fs.freereg === fs.nactvar); +}; + +/* +** create a label named 'break' to resolve break statements +*/ +const breaklabel = function(ls) { + let n = new TValue(CT.LUA_TLNGSTR, "break"); + let l = newlabelentry(ls, ls.dyd.label, n, 0, ls.fs.pc); + findgotos(ls, ls.dyd.label.arr[l]); +}; + +/* +** generates an error for an undefined 'goto'; choose appropriate +** message when label name is a reserved word (which can only be 'break') +*/ +const undefgoto = function(ls, gt) { + const msg = llex.isreserved(gt.name) + ? `<${gt.name}> at line ${gt.line} not inside a loop` + : `no visible label '${gt.name}' for at line ${gt.line}`; + semerror(ls, msg); +}; + +const open_func = function(ls, fs, bl) { + this.f = new Proto(); + fs.prev = ls.fs; /* linked list of funcstates */ + fs.ls = ls; + ls.fs = fs; + fs.pc = 0; + fs.lasttarget = 0; + fs.jpc = lcode.NO_JUMP; + fs.freereg = 0; + fs.nk = 0; + fs.np = 0; + fs.nups = 0; + fs.nlocvars = 0; + fs.nactvar = 0; + fs.firstlocal = ls.dyd.actvar.n; + fs.bl = null; + let f = new Proto(); + f = fs.f; + f.source = ls.source; + f.maxstacksize = 2; /* registers 0/1 are always valid */ + enterblock(fs, bl, false); +}; + +const leaveblock = function(fs) { + let bl = fs.bl; + let ls = fs.ls; + if (bl.previous && bl.upval) { + /* create a 'jump to here' to close upvalues */ + let j = lcode.luaK_jump(fs); + lcode.luaK_patchclose(fs, j , bl.nactvar); + lcode.luaK_patchtohere(fs, j); + } + + if (bl.isloop) + breaklabel(ls); /* close pending breaks */ + + fs.bl = bl.previous; + removevars(fs, bl.nactvar); + assert(bl.nactvar === fs.nactvar); + fs.freereg = fs.nactvar; /* free registers */ + ls.dyd.label.n = bl.firstlabel; /* remove local labels */ + if (bl.previous) /* inner block? */ + movegotosout(fs, bl); /* update pending gotos to outer block */ + else if (bl.firstgoto < ls.dyd.gt.n) /* pending gotos in outer block? */ + undefgoto(ls, ls.dyd.gt.arr[bl.firstgoto]); /* error */ +}; + +const close_func = function(ls) { + let L = ls.L; + let fs = ls.fs; + let f = fs.f; + lcode.luaK_ret(fs, 0, 0); /* final return */ + leaveblock(fs); + ls.fs = fs.prev; +}; + +/* +** compiles the main function, which is a regular vararg function with an +** upvalue named LUA_ENV +*/ +const mainfunc = function(ls, fs) { + let bl = new BlockCnt(); + let v = new expdesc(); + open_func(ls, fs, bl); + fs.f.is_vararg = true; /* main function is always declared vararg */ + init_exp(v, expkind.VLOCAL, 0); /* create and... */ + newupvalue(fs, ls.envn, v); /* ...set environment upvalue */ + llex.luaX_next(ls); /* read first token */ + statlist(ls); /* parse main body */ + check(ls, R.TK_EOS); + close_func(ls); +}; const luaY_parser = function(L, z, buff, dyd, name, firstchar) { - // TODO ... + let lexstate = new llex.LexState(); + let funcstate = new FuncState(); + let cl = lfunc.luaF_newLclosure(L, 1); /* create main closure */ + L.stack[L.top++] = cl; + lexstate.h = new Table(); /* create table for scanner */ + L.stack[L.top++] = lexstate.h; + funcstate.f = cl.p = new Proto(L); + funcstate.f.source = new TValue(CT.LUA_TLNGSTR, name); + lexstate.buff = buff; + lexstate.dyd = dyd; + dyd.actvar.n = dyd.gt.n = dyd.label.n = 0; + llex.luaX_setinput(L, lexstate, z, funcstate.f.source, firstchar); + mainfunc(lexstate, funcstate); + assert(!funcstate.prev && funcstate.nups === 1 && !lexstate.fs); + /* all scopes should be correctly finished */ + assert(dyd.actvar.n === 0 && dyd.gt.n === 0 && dyd.label.n === 0); + L.top--; /* remove scanner's table */ + return cl; /* closure is on the stack, too */ }; -- cgit v1.2.3-54-g00ecf From f37df0022d609443c5b3f222a85ee465d8211b88 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 28 Feb 2017 09:08:45 +0100 Subject: grammar --- src/lcode.js | 46 +++++- src/lobject.js | 28 +++- src/lparser.js | 476 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 531 insertions(+), 19 deletions(-) diff --git a/src/lcode.js b/src/lcode.js index 695c7dc..e64c0a9 100644 --- a/src/lcode.js +++ b/src/lcode.js @@ -14,6 +14,39 @@ const OpCodesI = lopcode.OpCodesI; */ const NO_JUMP = -1; +const BinOpr = { + OPR_ADD: 0, + OPR_SUB: 1, + OPR_MUL: 2, + OPR_MOD: 3, + OPR_POW: 4, + OPR_DIV: 5, + OPR_IDIV: 6, + OPR_BAND: 7, + OPR_BOR: 8, + OPR_BXOR: 9, + OPR_SHL: 10, + OPR_SHR: 11, + OPR_CONCAT: 12, + OPR_EQ: 13, + OPR_LT: 14, + OPR_LE: 15, + OPR_NE: 16, + OPR_GT: 17, + OPR_GE: 18, + OPR_AND: 19, + OPR_OR: 21, + OPR_NOBINOPR: 22 +}; + +const UnOpr = { + OPR_MINUS: 0, + OPR_BNOT: 1, + OPR_NOT: 2, + OPR_LEN: 3, + OPR_NOUNOPR: 4 +}; + /* ** Gets the destination address of a jump instruction. Used to traverse ** a list of jumps. @@ -45,9 +78,9 @@ const fixjump = function(fs, pc, dest) { const luaK_concat = function(fs, l1, l2) { if (l2 === NO_JUMP) return; /* nothing to concatenate? */ else if (l1 === NO_JUMP) /* no original list? */ - l1[0] = l2; + l1 = l2; else { - let list = l1[0]; + let list = l1; let next = getjump(fs, list); while (next !== NO_JUMP) { /* find last element */ list = next; @@ -55,6 +88,8 @@ const luaK_concat = function(fs, l1, l2) { } fixjump(fs, list, l2); } + + return l1; }; /* @@ -67,7 +102,7 @@ const luaK_jump = function (fs) { let jpc = fs.jpc; /* save list of jumps to here */ fs.jpc = NO_JUMP; /* no more jumps to here */ let j = luaK_codeAsBx(fs, OpCodesI.OP_JMP, 0, NO_JUMP); - luaK_concat(fs, j, jpc); /* keep them on hold */ + j = luaK_concat(fs, j, jpc); /* keep them on hold */ return j; }; @@ -142,7 +177,7 @@ const patchlistaux = function(fs, list, vtarget, reg, dtarget) { */ const luaK_patchtohere = function(fs, list) { luaK_getlabel(fs); /* mark "here" as a jump target */ - luaK_concat(fs, fs.jpc, list); + fs.jpc = luaK_concat(fs, fs.jpc, list); }; /* @@ -212,7 +247,10 @@ const luaK_codeAsBx = function(fs,o,A,sBx) { return luaK_codeABx(fs, o, A, (sBx) + lopcode.MAXARG_sBx); }; +module.exports.BinOpr = BinOpr; module.exports.NO_JUMP = NO_JUMP; +module.exports.UnOpr = UnOpr; +module.exports.luaK_codeABC = luaK_codeABC; module.exports.luaK_codeABx = luaK_codeABx; module.exports.luaK_codeAsBx = luaK_codeAsBx; module.exports.luaK_getlabel = luaK_getlabel; diff --git a/src/lobject.js b/src/lobject.js index f6c6ef0..4974373 100644 --- a/src/lobject.js +++ b/src/lobject.js @@ -346,12 +346,32 @@ const luaO_str2num = function(s) { } }; +/* +** converts an integer to a "floating point byte", represented as +** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if +** eeeee != 0 and (xxx) otherwise. +*/ +int luaO_int2fb (unsigned int x) { + int e = 0; /* exponent */ + if (x < 8) return x; + while (x >= (8 << 4)) { /* coarse steps */ + x = (x + 0xf) >> 4; /* x = ceil(x / 16) */ + e += 4; + } + while (x >= (8 << 1)) { /* fine steps */ + x = (x + 1) >> 1; /* x = ceil(x / 2) */ + e++; + } + return ((e+1) << 3) | (x - 8); +} + module.exports.CClosure = CClosure; module.exports.LClosure = LClosure; -module.exports.TValue = TValue; -module.exports.Table = Table; -module.exports.UTF8BUFFSZ = UTF8BUFFSZ; module.exports.luaO_chunkid = luaO_chunkid; module.exports.luaO_hexavalue = luaO_hexavalue; +module.exports.luaO_int2fb = luaO_int2fb; module.exports.luaO_str2num = luaO_str2num; -module.exports.luaO_utf8desc = luaO_utf8desc; \ No newline at end of file +module.exports.luaO_utf8desc = luaO_utf8desc; +module.exports.Table = Table; +module.exports.TValue = TValue; +module.exports.UTF8BUFFSZ = UTF8BUFFSZ; \ No newline at end of file diff --git a/src/lparser.js b/src/lparser.js index 8f3a48d..807e8e7 100644 --- a/src/lparser.js +++ b/src/lparser.js @@ -3,17 +3,22 @@ const assert = require('assert'); -const lua = require('./lua.js'); -const llex = require('./llex.js'); -const lfunc = require('./lfunc.js'); -const lobject = require('./lobject.js'); -const lcode = require('./lcode.js'); -const R = llex.RESERVED; -const TValue = lobject.TValue; -const CT = lua.constants_type; -const Table = lobject.Table; -const Proto = lfunc.Proto; -const UpVal = lfunc.UpVal; +const lcode = require('./lcode.js'); +const lfunc = require('./lfunc.js'); +const llex = require('./llex.js'); +const llimit = require('./llimit.js'); +const lobject = require('./lobject.js'); +const lopcode = require('./lopcode.js'); +const lua = require('./lua.js'); +const BinOpr = lcode.BinOpr; +const CT = lua.constants_type; +const OpCodesI = lopcode.OpCodesI; +const Proto = lfunc.Proto; +const R = llex.RESERVED; +const TValue = lobject.TValue; +const Table = lobject.Table; +const UnOpr = lcode.UnOpr; +const UpVal = lfunc.UpVal; class BlockCnt { constructor() { @@ -109,17 +114,61 @@ const checklimit = function(fs, v, l, what) { if (v > l) errorlimit(fs, l, what); }; +const testnext = function(ls, c) { + if (ls.t.token === c) { + llex.luaX_next(ls); + return true; + } + + return false; +}; + const check = function(ls, c) { if (ls.t.token !== c) error_expected(ls, c); }; +const checknext = function(ls, c) { + check(ls, c); + llex.luaX_next(ls); +}; + +const check_condition = function(ls, c, msg) { + if (!c) + llex.luaX_syntaxerror(ls, msg); +}; + +const check_match = function(ls, what, who, where) { + if (!testnext(ls, what)) { + if (where === ls.linenumber) + error_expected(ls, what); + else + llex.luaX_syntaxerror(ls, + `${llex.luaX_token2str(ls, what)} expected (to close ${llex.luaX_token2str(ls, who)} at line ${where}`); + } +}; + +const str_checkname = function(ls) { + check(ls, R.TK_NAME); + let ts = ls.t.seminfo.ts; + llex.luaX_next(ls); + return ts; +}; + const init_exp = function(e, k, i) { e.f = e.t = lcode.NO_JUMP; e.k = k; e.u.info = i; }; +const codestring = function(ls, e, s) { + init_exp(e, expkind.VK, lcode.luaK_stringK(ls.fs, s)); +}; + +const checkname = function(ls, e) { + codestring(ls, e, str_checkname(ls)); +}; + const getlocvar = function(fs, i) { let idx = fs.ls.dyd.actvar.arr[fs.firstlocal + i].idx; assert(idx < fs.nlocvars); @@ -142,6 +191,16 @@ const newupvalue = function(fs, name, v) { return fs.nups++; }; +const enterlevel = function(ls) { + let L = ls.L; + ++L.nCcalls; + checklimit(ls.fs, L.nCcalls, llimit.LUAI_MAXCCALLS, "JS levels"); +}; + +const leavelevel = function(ls) { + return ls.L.nCcalls--; +}; + const closegoto = function(ls, g, label) { let fs = ls.fs; let gl = ls.dyd.gt; @@ -313,6 +372,401 @@ const close_func = function(ls) { ls.fs = fs.prev; }; +/*============================================================*/ +/* GRAMMAR RULES */ +/*============================================================*/ + +const statlist = function(ls) { + /* statlist -> { stat [';'] } */ + while (!block_follow(ls, 1)) { + if (ls.t.token === R.TK_RETURN) { + statement(ls); + return; /* 'return' must be last statement */ + } + statement(ls); + } +}; + +const yindex = function(ls, v) { + /* index -> '[' expr ']' */ + llex.luaX_next(ls); /* skip the '[' */ + expr(ls, v); + lcode.luaK_exp2val(ls.fs, v); + checknext(ls, ']'); +}; + +/* +** {====================================================================== +** Rules for Constructors +** ======================================================================= +*/ + +class ConsControl { + constructor() { + this.v = new expdesc(); /* last list item read */ + this.t = new expdesc(); /* table descriptor */ + this.nh = NaN; /* total number of 'record' elements */ + this.na = NaN; /* total number of array elements */ + this.tostore = NaN; /* number of array elements pending to be stored */ + } +} + +const recfield = function(ls, cc) { + /* recfield -> (NAME | '['exp1']') = exp1 */ + let fs = ls.fs; + let reg = ls.fs.freereg; + let key = new expdesc(); + let val = new expdesc(); + + if (ls.t.token === R.TK_NAME) { + checklimit(fs, cc.nh, Number.MAX_SAFE_INTEGER, "items in a constructor"); + checkname(ls, key); + } else /* ls->t.token == '[' */ + yindex(ls, key); + cc.nh++; + checknext(ls, '='); + let rkkey = lcode.luaK_exp2RK(fs, key); + expr(ls, val); + lcode.luaK_codeABC(fs, OpCodesI.OP_SETTABLE, cc.t.u.info, rkkey, lcode.luaK_exp2RK(fs, val)); + fs.freereg = reg; /* free registers */ +}; + +const closelistfield = function(fs, cc) { + if (cc.v.k === expkind.VVOID) return; /* there is no list item */ + lcode.luaK_exp2nextreg(fs, cc.v); + cc.v.k = expkind.VVOID; + if (cc.tostore === lopcode.LFIELDS_PER_FLUSH) { + luaK_setlist(fs, cc.t.u.info, cc.na, cc.tostore); /* flush */ + cc.tostore = 0; /* no more items pending */ + } +}; + +const lastlistfield = function(fs, cc) { + if (cc.tostore === 0) return; + if (hasmultret(cc.v.k)) { + lcode.luaK_setmultret(fs, cc.v); + lcode.luaK_setlist(fs, cc.t.u.info, cc.na, lua.LUA_MULTRET); + cc.na--; /* do not count last expression (unknown number of elements) */ + } else { + if (cc.v.k !== expkind.VVOID) + lcode.luaK_exp2nextreg(fs, cc.v); + lcode.luaK_setlist(fs, cc.t.u.info, cc.na, cc.tostore); + } +}; + +const listfield = function(ls, cc) { + /* listfield -> exp */ + expr(ls, cc.v); + checklimit(ls.fs, cc.na, Number.MAX_SAFE_INTEGER, "items in a constructor"); + cc.na++; + cc.tostore++; +}; + +const field = function(ls, cc) { + /* field -> listfield | recfield */ + switch (ls.t.token) { + case R.TK_NAME: { /* may be 'listfield' or 'recfield' */ + if (llex.luaX_lookahead(ls) !== '=') /* expression? */ + listfield(ls, cc); + else + recfield(ls, cc); + break; + } + case '[': { + recfield(ls, cc); + break; + } + default: { + listfield(ls, cc); + break; + } + } +}; + +const constructor = function(ls, t) { + /* constructor -> '{' [ field { sep field } [sep] ] '}' + sep -> ',' | ';' */ + let fs = ls.fs; + let line = ls.linenumber; + let pc = lcode.luaK_codeABC(fs, OpCodesI.OP_NEWTABLE, 0, 0, 0); + let cc = new ConsControl(); + cc.na = cc.nh = cc.tostore = 0; + cc.t = t; + init_exp(t, expkind.VRELOCABLE, pc); + init_exp(cc.v, expkind.VVOID, 0); /* no value (yet) */ + lcode.luaK_exp2nextreg(ls.fs, t); /* fix it at stack top */ + checknext(ls, '{'); + do { + assert(cc.v.k === expkind.VVOID || cc.tostore > 0); + if (ls.t.token === '}') break; + closelistfield(fs, cc); + field(ls, cc); + } while (testnext(ls, ',') || testnext(ls, ';')); + check_match(ls, '}', '{', line); + lastlistfield(fs, cc); + lopcode.SETARG_B(fs.f.code[pc], lobject.luaO_int2fb(cc.na)); /* set initial array size */ + lopcode.SETARG_C(fs.f.code[pc], lobject.luaO_int2fb(cc.nh)); /* set initial table size */ +}; + +/* +** {====================================================================== +** Expression parsing +** ======================================================================= +*/ + +const simpleexp = function(ls, v) { + /* simpleexp -> FLT | INT | STRING | NIL | TRUE | FALSE | ... | + constructor | FUNCTION body | suffixedexp */ + switch (ls.t.token) { + case R.TK_FLT: { + init_exp(v, expkind.VFLT, 0); + v.u.nval = ls.t.seminfo.r; + break; + } + case R.TK_INT: { + init_exp(v, expkind.VKINT, 0); + v.u.ival = ls.t.seminfo.i; + break; + } + case R.TK_STRING: { + codestring(ls, v, ls.t.seminfo.ts); + break; + } + case R.TK_NIL: { + init_exp(v, expkind.VNIL, 0); + break; + } + case R.TK_TRUE: { + init_exp(v, expkind.VTRUE, 0); + break; + } + case R.TK_FALSE: { + init_exp(v, expkind.VFALSE, 0); + break; + } + case R.TK_DOTS: { /* vararg */ + let fs = ls.fs; + check_condition(ls, fs.f.is_vararg, "cannot use '...' outside a vararg function"); + init_exp(v, expkind.VVARARG, lcode.luaK_codeABC(fs, OpCodesI.OP_VARARG, 0, 1, 0)); + } + case '{': { /* constructor */ + constructor(ls, v); + return; + } + case R.TK_FUNCTION: { + llex.luaX_next(ls); + body(ls, v, 0, ls.linenumber); + return; + } + default: { + suffixedexp(ls, v); + return; + } + } + llex.luaX_next(ls); +}; + +const getunopr = function(op) { + switch (op) { + case R.TK_NOT: return UnOpr.OPR_NOT; + case '-': return UnOpr.OPR_MINUS; + case '~': return UnOpr.OPR_BNOT; + case '#': return UnOpr.OPR_LEN; + default: return UnOpr.OPR_NOUNOPR; + } +}; + +const getbinopr = function(op) { + switch (op) { + case '+': return BinOpr.OPR_ADD; + case '-': return BinOpr.OPR_SUB; + case '*': return BinOpr.OPR_MUL; + case '%': return BinOpr.OPR_MOD; + case '^': return BinOpr.OPR_POW; + case '/': return BinOpr.OPR_DIV; + case R.TK_IDIV: return BinOpr.OPR_IDIV; + case '&': return BinOpr.OPR_BAND; + case '|': return BinOpr.OPR_BOR; + case '~': return BinOpr.OPR_BXOR; + case R.TK_SHL: return BinOpr.OPR_SHL; + case R.TK_SHR: return BinOpr.OPR_SHR; + case R.TK_CONCAT: return BinOpr.OPR_CONCAT; + case R.TK_NE: return BinOpr.OPR_NE; + case R.TK_EQ: return BinOpr.OPR_EQ; + case '<': return BinOpr.OPR_LT; + case R.TK_LE: return BinOpr.OPR_LE; + case '>': return BinOpr.OPR_GT; + case R.TK_GE: return BinOpr.OPR_GE; + case R.TK_AND: return BinOpr.OPR_AND; + case R.TK_OR: return BinOpr.OPR_OR; + default: return BinOpr.OPR_NOBINOPR; + } +}; + +const priority = [ /* ORDER OPR */ + {10, 10}, {10, 10}, /* '+' '-' */ + {11, 11}, {11, 11}, /* '*' '%' */ + {14, 13}, /* '^' (right associative) */ + {11, 11}, {11, 11}, /* '/' '//' */ + {6, 6}, {4, 4}, {5, 5}, /* '&' '|' '~' */ + {7, 7}, {7, 7}, /* '<<' '>>' */ + {9, 8}, /* '..' (right associative) */ + {3, 3}, {3, 3}, {3, 3}, /* ==, <, <= */ + {3, 3}, {3, 3}, {3, 3}, /* ~=, >, >= */ + {2, 2}, {1, 1} /* and, or */ +]; + +const UNARY_PRIORITY = 12; + +/* +** subexpr -> (simpleexp | unop subexpr) { binop subexpr } +** where 'binop' is any binary operator with a priority higher than 'limit' +*/ +const subexpr = function(ls, v, limit) { + enterlevel(ls); + let uop = getunopr(ls.t.token); + if (uop !== UnOpr.OPR_NOUNOPR) { + let line = ls.linenumber; + llex.luaX_next(ls); + subexpr(ls, v, UNARY_PRIORITY); + lcode.luaK_prefix(ls.fs, uop, v, line); + } else + simpleexp(ls, v); + /* expand while operators have priorities higher than 'limit' */ + let op = getbinopr(ls.t.token); + while (op !== BinOpr.OPR_NOBINOPR && priority[op].left > limit) { + let v2 = new expdesc(); + let line = ls.linenumber; + llex.luaX_next(ls); + lcode.luaK_infix(ls.fs, op, v); + /* read sub-expression with higher priority */ + let nextop = subexpr(ls, v2, priority[op].right); + lcode.luaK_posfix(ls.fs, op, v, v2, line); + op = nextop; + } + leavelevel(ls); + return op; /* return first untreated operator */ +}; + +const expr = function(ls, v) { + subexpr(ls, v, 0); +} + +const test_then_block = function(ls, escapelist) { + /* test_then_block -> [IF | ELSEIF] cond THEN block */ + let bl = new BlockCnt(); + let fs = ls.fs; + let v = new expdesc(); + let jf; /* instruction to skip 'then' code (if condition is false) */ + + llex.luaX_next(ls); /* skip IF or ELSEIF */ + expr(ls, v); /* read condition */ + checknext(ls, R.TK_THEN); + + if (ls.t.token === R.TK_GOTO || ls.t.token === R.TK_BREAK) { + lcode.luaK_goiffalse(ls.fs, v); /* will jump to label if condition is true */ + enterblock(fs, bl, false); /* must enter block before 'goto' */ + gotostat(ls, v.t); /* handle goto/break */ + skipnoopstat(ls); /* skip other no-op statements */ + if (block_follow(ls, 0)) { /* 'goto' is the entire block? */ + leaveblock(fs); + return; /* and that is it */ + } else /* must skip over 'then' part if condition is false */ + jf = lcode.luaK_jump(fs); + } else { /* regular case (not goto/break) */ + lcode.luaK_goiftrue(ls.fs, v); /* skip over block if condition is false */ + enterblock(fs, bl, false); + jf = v.f; + } + + statlist(ls); /* 'then' part */ + leaveblock(fs); + if (ls.t.token === R.TK_ELSE || ls.t.token === R.TK_ELSEIF) /* followed by 'else'/'elseif'? */ + escapelist = lcode.luaK_concat(fs, escapelist, lcode.luaK_jump(fs)); /* must jump over it */ + lcode.luaK_patchtohere(fs, jf); +}; + +const ifstat = function(ls, line) { + /* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */ + let fs = ls.fs; + let escapelist = lcode.NO_JUMP; /* exit list for finished parts */ + test_then_block(ls, escapelist); /* IF cond THEN block */ + while (ls.t.token === R.TK_ELSEIF) + test_then_block(ls, escapelist); /* ELSEIF cond THEN block */ + if (testnext(ls, R.TK_ELSE)) + block(ls); /* 'else' part */ + check_match(ls, R.TK_END, R.TK_IF, line); + lcode.luaK_patchtohere(fs, escapelist); /* patch escape list to 'if' end */ +}; + +const statement = function(ls) { + let line = ls.linenumber; /* may be needed for error messages */ + enterlevel(ls); + switch(ls.t.token) { + case ';': { /* stat -> ';' (empty statement) */ + llex.luaX_next(ls); /* skip ';' */ + break; + } + case R.TK_IF: { /* stat -> ifstat */ + ifstat(ls, line); + break; + } + case R.TK_WHILE: { /* stat -> whilestat */ + whilestat(ls, line); + break; + } + case R.TK_DO: { /* stat -> DO block END */ + llex.luaX_next(ls); /* skip DO */ + block(ls); + check_match(ls, R.TK_END, R.TK_DO, line); + break; + } + case R.TK_FOR: { /* stat -> forstat */ + forstat(ls, line); + break; + } + case R.TK_REPEAT: { /* stat -> repeatstat */ + repeatstat(ls, line); + break; + } + case R.TK_FUNCTION: { /* stat -> funcstat */ + funcstat(ls, line); + break; + } + case R.TK_LOCAL: { /* stat -> localstat */ + llex.luaX_next(ls); /* skip LOCAL */ + if (testnext(ls, R.TK_FUNCTION)) /* local function? */ + localfunc(ls); + else + localstat(ls); + break; + } + case R.TK_DBCOLON: { /* stat -> label */ + llex.luaX_next(ls); /* skip double colon */ + labelstat(ls, str_checkname(ls), line); + break; + } + case R.TK_RETURN: { /* skip double colon */ + llex.luaX_next(ls); /* skip RETURN */ + retstat(ls); + break; + } + case R.TK_BREAK: /* stat -> breakstat */ + case R.TK_GOTO: { /* stat -> 'goto' NAME */ + gotostat(ls, lcode.luaK_jump(ls.fs)); + break; + } + default: { /* stat -> func | assignment */ + exprstat(ls); + break; + } + } + + assert(ls.fs.f.maxstacksize >= ls.fs.freereg && ls.fs.freereg >= ls.fs.nactvar); + ls.fs.freereg = ls.fs.nactvar; /* free registers */ + leavelevel(ls); +}; + /* ** compiles the main function, which is a regular vararg function with an ** upvalue named LUA_ENV -- cgit v1.2.3-54-g00ecf From 8d576081a53ee1a866491b423c5373951aa6a7c4 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 28 Feb 2017 11:29:37 +0100 Subject: jshint --- .jshintrc | 6 ++++++ src/lapi.js | 1 - src/lbaselib.js | 1 - src/lcode.js | 9 ++++----- src/lcorolib.js | 1 - src/linit.js | 1 - src/ljstype.js | 1 - src/llex.js | 3 +-- src/lmathlib.js | 1 - src/lparser.js | 42 +++++++++++++++++++++++++++++------------- src/ltablib.js | 1 - 11 files changed, 40 insertions(+), 27 deletions(-) create mode 100644 .jshintrc diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..e64eb7e --- /dev/null +++ b/.jshintrc @@ -0,0 +1,6 @@ +{ + "node": true, + "esversion": 6, + "indent": false, + "white": false +} \ No newline at end of file diff --git a/src/lapi.js b/src/lapi.js index d0c099f..c4a7bbe 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ "use strict"; const assert = require('assert'); diff --git a/src/lbaselib.js b/src/lbaselib.js index 012f5c3..b663b87 100644 --- a/src/lbaselib.js +++ b/src/lbaselib.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ "use strict"; const assert = require('assert'); diff --git a/src/lcode.js b/src/lcode.js index e64c0a9..9a5cda0 100644 --- a/src/lcode.js +++ b/src/lcode.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ "use strict"; const assert = require('assert'); @@ -203,7 +202,7 @@ const luaK_patchclose = function(fs, list, level) { level++; /* argument is +1 to reserve 0 as non-op */ for (; list !== NO_JUMP; list = getjump(fs, list)) { let ins = fs.f.code[list]; - assert(ins.opcode === OpCodesI.OP_JMP && (ins.A === 0 || i.A >= level); + assert(ins.opcode === OpCodesI.OP_JMP && (ins.A === 0 || ins.A >= level)); lopcode.SETARG_A(ins, level); } }; @@ -237,9 +236,9 @@ const luaK_codeABC = function(fs, o, a, b, c) { ** Format and emit an 'iABx' instruction. */ const luaK_codeABx = function(fs, o, a, bc) { - lua_assert(lopcode.getOpMode(o) == lopcode.iABx || getOpMode(o) == lopcode.iAsBx); - lua_assert(lopcode.getCMode(o) == lopcode.OpArgN); - lua_assert(a <= lopcode.MAXARG_A && bc <= lopcode.MAXARG_Bx); + assert(lopcode.getOpMode(o) == lopcode.iABx || lopcode.getOpMode(o) == lopcode.iAsBx); + assert(lopcode.getCMode(o) == lopcode.OpArgN); + assert(a <= lopcode.MAXARG_A && bc <= lopcode.MAXARG_Bx); return luaK_code(fs, lopcode.CREATE_ABx(o, a, bc)); }; diff --git a/src/lcorolib.js b/src/lcorolib.js index 05f3b00..b2d0de8 100644 --- a/src/lcorolib.js +++ b/src/lcorolib.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ "use strict"; const assert = require('assert'); diff --git a/src/linit.js b/src/linit.js index 36c0121..b926683 100644 --- a/src/linit.js +++ b/src/linit.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ "use strict"; const assert = require('assert'); diff --git a/src/ljstype.js b/src/ljstype.js index 192db6f..a92c8d0 100644 --- a/src/ljstype.js +++ b/src/ljstype.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ "use strict"; const assert = require('assert'); diff --git a/src/llex.js b/src/llex.js index 1992856..59bc4a4 100644 --- a/src/llex.js +++ b/src/llex.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ "use strict"; const assert = require('assert'); @@ -265,7 +264,7 @@ const lexerror = function(ls, msg, token) { const luaX_syntaxerror = function(ls, msg) { lexerror(ls, msg, ls.t.token); -} +}; /* ** skip a sequence '[=*[' or ']=*]'; if sequence is well formed, return diff --git a/src/lmathlib.js b/src/lmathlib.js index 54bdc46..55f8416 100644 --- a/src/lmathlib.js +++ b/src/lmathlib.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ "use strict"; const assert = require('assert'); diff --git a/src/lparser.js b/src/lparser.js index 807e8e7..a7e4209 100644 --- a/src/lparser.js +++ b/src/lparser.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ "use strict"; const assert = require('assert'); @@ -20,6 +19,12 @@ const Table = lobject.Table; const UnOpr = lcode.UnOpr; const UpVal = lfunc.UpVal; +const MAXVARS = 200; + +const hasmultret = function(k) { + return k == expkind.VCALL || k == expkind.VVARARG; +}; + class BlockCnt { constructor() { this.previous = null; /* chain */ @@ -376,6 +381,16 @@ const close_func = function(ls) { /* GRAMMAR RULES */ /*============================================================*/ +const block_follow = function(ls, withuntil) { + switch (ls.t.token) { + case R.TK_ELSE: case R.TK_ELSEIF: + case R.TK_END: case R.TK_EOS: + return true; + case R.TK_UNTIL: return withuntil; + default: return false; + } +}; + const statlist = function(ls) { /* statlist -> { stat [';'] } */ while (!block_follow(ls, 1)) { @@ -436,7 +451,7 @@ const closelistfield = function(fs, cc) { lcode.luaK_exp2nextreg(fs, cc.v); cc.v.k = expkind.VVOID; if (cc.tostore === lopcode.LFIELDS_PER_FLUSH) { - luaK_setlist(fs, cc.t.u.info, cc.na, cc.tostore); /* flush */ + lcode.luaK_setlist(fs, cc.t.u.info, cc.na, cc.tostore); /* flush */ cc.tostore = 0; /* no more items pending */ } }; @@ -548,6 +563,7 @@ const simpleexp = function(ls, v) { let fs = ls.fs; check_condition(ls, fs.f.is_vararg, "cannot use '...' outside a vararg function"); init_exp(v, expkind.VVARARG, lcode.luaK_codeABC(fs, OpCodesI.OP_VARARG, 0, 1, 0)); + break; } case '{': { /* constructor */ constructor(ls, v); @@ -604,16 +620,16 @@ const getbinopr = function(op) { }; const priority = [ /* ORDER OPR */ - {10, 10}, {10, 10}, /* '+' '-' */ - {11, 11}, {11, 11}, /* '*' '%' */ - {14, 13}, /* '^' (right associative) */ - {11, 11}, {11, 11}, /* '/' '//' */ - {6, 6}, {4, 4}, {5, 5}, /* '&' '|' '~' */ - {7, 7}, {7, 7}, /* '<<' '>>' */ - {9, 8}, /* '..' (right associative) */ - {3, 3}, {3, 3}, {3, 3}, /* ==, <, <= */ - {3, 3}, {3, 3}, {3, 3}, /* ~=, >, >= */ - {2, 2}, {1, 1} /* and, or */ + {left: 10, right: 10}, {left: 10, right: 10}, /* '+' '-' */ + {left: 11, right: 11}, {left: 11, right: 11}, /* '*' '%' */ + {left: 14, right: 13}, /* '^' (right associative) */ + {left: 11, right: 11}, {left: 11, right: 11}, /* '/' '//' */ + {left: 6, right: 6}, {left: 4, right: 4}, {left: 5, right: 5}, /* '&' '|' '~' */ + {left: 7, right: 7}, {left: 7, right: 7}, /* '<<' '>>' */ + {left: 9, right: 8}, /* '..' (right associative) */ + {left: 3, right: 3}, {left: 3, right: 3}, {left: 3, right: 3}, /* ==, <, <= */ + {left: 3, right: 3}, {left: 3, right: 3}, {left: 3, right: 3}, /* ~=, >, >= */ + {left: 2, right: 2}, {left: 1, right: 1} /* and, or */ ]; const UNARY_PRIORITY = 12; @@ -650,7 +666,7 @@ const subexpr = function(ls, v, limit) { const expr = function(ls, v) { subexpr(ls, v, 0); -} +}; const test_then_block = function(ls, escapelist) { /* test_then_block -> [IF | ELSEIF] cond THEN block */ diff --git a/src/ltablib.js b/src/ltablib.js index fa43523..a7963d3 100644 --- a/src/ltablib.js +++ b/src/ltablib.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ "use strict"; const assert = require('assert'); -- cgit v1.2.3-54-g00ecf From dfac155a3f8b650222fa784303e599f4e50abe67 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 28 Feb 2017 11:56:00 +0100 Subject: grammar --- src/lcode.js | 5 + src/lobject.js | 8 +- src/lparser.js | 685 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ todo-temp.md | 25 +++ 4 files changed, 719 insertions(+), 4 deletions(-) create mode 100644 todo-temp.md diff --git a/src/lcode.js b/src/lcode.js index 9a5cda0..a2dc43b 100644 --- a/src/lcode.js +++ b/src/lcode.js @@ -46,6 +46,10 @@ const UnOpr = { OPR_NOUNOPR: 4 }; +const getinstruction = function(fs, e) { + return fs.f.code[e.u.info]; +}; + /* ** Gets the destination address of a jump instruction. Used to traverse ** a list of jumps. @@ -249,6 +253,7 @@ const luaK_codeAsBx = function(fs,o,A,sBx) { module.exports.BinOpr = BinOpr; module.exports.NO_JUMP = NO_JUMP; module.exports.UnOpr = UnOpr; +module.exports.getinstruction = getinstruction; module.exports.luaK_codeABC = luaK_codeABC; module.exports.luaK_codeABx = luaK_codeABx; module.exports.luaK_codeAsBx = luaK_codeAsBx; diff --git a/src/lobject.js b/src/lobject.js index 4974373..1a0df1c 100644 --- a/src/lobject.js +++ b/src/lobject.js @@ -333,7 +333,7 @@ const luaO_str2num = function(s) { let i = s2i[1]; if (e !== null) { /* try as an integer */ - return new TValue(CT.LUA_TNUMINT, i);; + return new TValue(CT.LUA_TNUMINT, i); } else { /* else try as a float */ s2i = l_str2d(s); e = s2i[0]; @@ -351,8 +351,8 @@ const luaO_str2num = function(s) { ** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if ** eeeee != 0 and (xxx) otherwise. */ -int luaO_int2fb (unsigned int x) { - int e = 0; /* exponent */ +const luaO_int2fb = function(x) { + let e = 0; /* exponent */ if (x < 8) return x; while (x >= (8 << 4)) { /* coarse steps */ x = (x + 0xf) >> 4; /* x = ceil(x / 16) */ @@ -363,7 +363,7 @@ int luaO_int2fb (unsigned int x) { e++; } return ((e+1) << 3) | (x - 8); -} +}; module.exports.CClosure = CClosure; module.exports.LClosure = LClosure; diff --git a/src/lparser.js b/src/lparser.js index a7e4209..154310b 100644 --- a/src/lparser.js +++ b/src/lparser.js @@ -61,6 +61,10 @@ const expkind = { VVARARG: 14 /* vararg expression; info = instruction pc */ }; +const vkisvar = function(k) { + return expkind.VLOCAL <= k && k <= expkind.VINDEXED; +}; + class expdesc { constructor() { this.k = NaN; @@ -174,18 +178,53 @@ const checkname = function(ls, e) { codestring(ls, e, str_checkname(ls)); }; +const registerlocalvar = function(ls, varname) { + let fs = ls.fs; + let f = fs.f; + f.locvars[fs.nlocvars].varname = varname; + return fs.nlocvars++; +}; + +const new_localvar = function(ls, name) { + let fs = ls.fs; + let dyd = ls.dyd; + let reg = registerlocalvar(ls, name); + checklimit(fs, dyd.actvar.n + 1 - fs.firstlocal, MAXVARS, "local variables"); + dyd.actvar.arr[dyd.actvar.n++].idx = reg; +}; + +const new_localvarliteral = function(ls, name) { + new_localvar(ls, new TValue(CT.LUA_TLNGSTR, name)); +}; + const getlocvar = function(fs, i) { let idx = fs.ls.dyd.actvar.arr[fs.firstlocal + i].idx; assert(idx < fs.nlocvars); return fs.f.locvars[idx]; }; +const adjustlocalvars = function(ls, nvars) { + let fs = ls.fs; + fs.nactvar = fs.nactvar + nvars; + for (; nvars; nvars--) + getlocvar(fs, fs.nactvar - nvars).startpc = fs.pc; +}; + const removevars = function(fs, tolevel) { fs.ls.dyd.actvar.n -= fs.nactvar - tolevel; while (fs.nactvar > tolevel) getlocvar(fs, --fs.nactvar).endpc = fs.pc; }; +const searchupvalue = function(fs, name) { + let up = fs.f.upvalues; + for (let i = 0; i < fs.nups; i++) { + if (up[i].name === name) + return i; + } + return -1; /* not found */ +}; + const newupvalue = function(fs, name, v) { let f = fs.f; checklimit(fs, fs.nups + 1, lfunc.MAXUPVAL, "upvalues"); @@ -196,6 +235,86 @@ const newupvalue = function(fs, name, v) { return fs.nups++; }; +const searchvar = function(fs, n) { + for (let i = fs.nactvar - 1; i >= 0; i--) { + if (n === getlocvar(fs, i).varname) + return i; + } + + return -1; +}; + +/* +** Mark block where variable at given level was defined +** (to emit close instructions later). +*/ +const markupval = function(fs, level) { + let bl = fs.bl; + while (bl.nactvar > level) + bl = bl.previous; + bl.upval = 1; +}; + +/* +** Find variable with given name 'n'. If it is an upvalue, add this +** upvalue into all intermediate functions. +*/ +const singlevaraux = function(fs, n, vr, base) { + if (fs === null) /* no more levels? */ + init_exp(vr, expkind.VVOID, 0); /* default is global */ + else { + let v = searchvar(fs, n); /* look up locals at current level */ + if (v >= 0) { /* found? */ + init_exp(vr, expkind.VLOCAL, v); /* variable is local */ + if (!base) + markupval(fs, v); /* local will be used as an upval */ + } else { /* not found as local at current level; try upvalues */ + let idx = searchupvalue(fs, n); /* try existing upvalues */ + if (idx < 0) { /* not found? */ + singlevaraux(fs.prev, n, vr, 0); /* try upper levels */ + if (vr.k === expkind.VVOID) /* not found? */ + return; /* it is a global */ + /* else was LOCAL or UPVAL */ + idx = newupvalue(fs, n, vr); /* will be a new upvalue */ + } + init_exp(vr, expkind.VUPVAL, idx); /* new or old upvalue */ + } + } +}; + +const singlevar = function(ls, vr) { + let varname = str_checkname(ls); + let fs = ls.fs; + singlevaraux(fs, varname, vr, 1); + if (vr.k === expkind.VVOID) { /* global name? */ + let key = new expdesc(); + singlevaraux(fs, ls.envn, vr, 1); /* get environment variable */ + assert(vr.k !== expkind.VVOID); /* this one must exist */ + codestring(ls, key, varname); /* key is variable name */ + lcode.luaK_indexed(fs, vr, key); /* env[varname] */ + } +}; + +const adjust_assign = function(ls, nvars, nexps, e) { + let fs = ls.fs; + let extra = nvars - nexps; + if (hasmultret(e.k)) { + extra++; /* includes call itself */ + if (extra < 0) extra = 0; + lcode.luaK_setreturns(fs, e, extra); /* last exp. provides the difference */ + if (extra > 1) lcode.luaK_reserveregs(fs, extra - 1); + } else { + if (e.k !== expkind.VVOID) lcode.luaK_exp2nextreg(fs, e); /* close last expression */ + if (extra > 0) { + let reg = fs.freereg; + lcode.luaK_reserveregs(fs, extra); + lcode.luaK_nil(fs, reg, extra); + } + } + if (nexps > nvars) + ls.fs.freereg -= nexps - nvars; /* remove extra values */ +}; + const enterlevel = function(ls) { let L = ls.L; ++L.nCcalls; @@ -321,6 +440,27 @@ const undefgoto = function(ls, gt) { semerror(ls, msg); }; +/* +** adds a new prototype into list of prototypes +*/ +const addprototype = function(ls) { + let clp = new Proto(); + let L = ls.L; + let fs = ls.fs; + let f = fs.f; /* prototype of current function */ + f.p[fs.np++] = clp; + return clp; +}; + +/* +** codes instruction to create new closure in parent function. +*/ +const codeclosure = function(ls, v) { + let fs = ls.fs.prev; + init_exp(v, expkind.VRELOCABLE, lcode.luaK_codeABx(fs, OpCodesI.OP_CLOSURE, 0, fs.np -1)); + lcode.luaK_exp2nextreg(fs, v); /* fix it at the last register */ +}; + const open_func = function(ls, fs, bl) { this.f = new Proto(); fs.prev = ls.fs; /* linked list of funcstates */ @@ -402,6 +542,16 @@ const statlist = function(ls) { } }; +const fieldsel = function(ls, v) { + /* fieldsel -> ['.' | ':'] NAME */ + let fs = ls.fs; + let key = new expdesc(); + lcode.luaK_exp2anyregup(fs, v); + llex.luaX_next(ls); /* skip the dot or colon */ + checkname(ls, key); + lcode.luaK_indexed(fs, v, key); +}; + const yindex = function(ls, v) { /* index -> '[' expr ']' */ llex.luaX_next(ls); /* skip the '[' */ @@ -523,12 +673,176 @@ const constructor = function(ls, t) { lopcode.SETARG_C(fs.f.code[pc], lobject.luaO_int2fb(cc.nh)); /* set initial table size */ }; +/* }====================================================================== */ + +const parlist = function(ls) { + /* parlist -> [ param { ',' param } ] */ + let fs = ls.fs; + let f = fs.f; + let nparams = 0; + f.is_vararg = 0; + if (ls.t.token !== ')') { /* is 'parlist' not empty? */ + do { + switch (ls.t.token) { + case R.TK_NAME: { /* param -> NAME */ + new_localvar(ls, str_checkname(ls)); + nparams++; + break; + } + case R.TK_DOTS: { /* param -> '...' */ + llex.luaX_next(ls); + f.is_vararg = 1; /* declared vararg */ + break; + } + default: llex.luaX_syntaxerror(ls, " or '...' expected"); + } + } while(!f.is_vararg && testnext(ls, ',')); + } + adjustlocalvars(ls, nparams); + f.numparams = fs.nactvar; + lcode.luaK_reserveregs(fs, fs.nactvar); /* reserve register for parameters */ +}; + +const body = function(ls, e, ismethod, line) { + /* body -> '(' parlist ')' block END */ + let new_fs = new FuncState(); + let bl = new BlockCnt(); + new_fs.f = addprototype(ls); + new_fs.f.linedefined = line; + open_func(ls, new_fs, bl); + checknext(ls, '('); + if (ismethod) { + new_localvarliteral(ls, "self"); /* create 'self' parameter */ + adjustlocalvars(ls, 1); + } + parlist(ls); + checknext(ls, ')'); + statlist(ls); + new_fs.f.lastlinedefined = ls.linenumber; + check_match(ls, R.TK_END, R.TK_FUNCTION, line); + codeclosure(ls, e); + close_func(ls); +}; + +const explist = function(ls, v) { + /* explist -> expr { ',' expr } */ + let n = 1; /* at least one expression */ + expr(ls, v); + while (testnext(ls, ',')) { + lcode.luaK_exp2nextreg(ls.fs, v); + expr(ls, v); + n++; + } + return n; +}; + +const funcargs = function(ls, f, line) { + let fs = ls.fs; + let args = new expdesc(); + switch (ls.t.token) { + case '(': { /* funcargs -> '(' [ explist ] ')' */ + llex.luaX_next(ls); + if (ls.t.token === ')') /* arg list is empty? */ + args.k = expkind.VVOID; + else { + explist(ls, args); + lcode.luaK_setmultret(fs, args); + } + check_match(ls, ')', '(', line); + break; + } + case '{': { /* funcargs -> constructor */ + constructor(ls, args); + break; + } + case R.TK_STRING: { /* funcargs -> STRING */ + codestring(ls, args, ls.t.seminfo.ts); + llex.luaX_next(ls); /* must use 'seminfo' before 'next' */ + break; + } + default: { + llex.luaX_syntaxerror(ls, "function arguments expected"); + } + } + assert(f.k === expkind.VNONRELOC); + let nparams; + let base = f.u.info; /* base register for call */ + if (hasmultret(args.k)) + nparams = lua.LUA_MULTRET; /* open call */ + else { + if (args.k !== expkind.VVOID) + lcode.luaK_exp2nextreg(fs, args); /* close last argument */ + nparams = fs.freereg - (base+1); + } + init_exp(f, expkind.VCALL, lcode.luaK_codeABC(fs, OpCodesI.OP_CALL, base, nparams+1, 2)); + lcode.luaK_fixline(fs, line); + fs.freereg = base + 1; /* call remove function and arguments and leaves (unless changed) one result */ +}; + /* ** {====================================================================== ** Expression parsing ** ======================================================================= */ +const primaryexp = function(ls, v) { + /* primaryexp -> NAME | '(' expr ')' */ + switch (ls.t.token) { + case '(': { + let line = ls.linenumber; + llex.luaX_next(ls); + expr(ls, v); + check_match(ls, ')', '(', line); + lcode.luaK_dischargevars(ls.fs, v); + return; + } + case R.TK_NAME: { + singlevar(ls, v); + return; + } + default: { + llex.luaX_syntaxerror(ls, "unexpected symbol"); + } + } +}; + +const suffixedexp = function(ls, v) { + /* suffixedexp -> + primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */ + let fs = ls.fs; + let line = ls.linenumber; + primaryexp(ls, v); + for (;;) { + switch (ls.t.token) { + case '.': { /* fieldsel */ + fieldsel(ls, v); + break; + } + case '[': { /* '[' exp1 ']' */ + let key = new expdesc(); + lcode.luaK_exp2anyregup(fs, v); + yindex(ls, key); + lcode.luaK_indexed(fs, v, key); + break; + } + case ':': { /* ':' NAME funcargs */ + let key = new expdesc(); + llex.luaX_next(ls); + checkname(ls, key); + lcode.luaK_self(fs, v, key); + funcargs(ls, v, line); + break; + } + case '(': case R.TK_STRING: case '{': { /* funcargs */ + lcode.luaK_exp2nextreg(fs, v); + funcargs(ls, v, line); + break; + } + default: return; + } + } +}; + const simpleexp = function(ls, v) { /* simpleexp -> FLT | INT | STRING | NIL | TRUE | FALSE | ... | constructor | FUNCTION body | suffixedexp */ @@ -668,6 +982,278 @@ const expr = function(ls, v) { subexpr(ls, v, 0); }; +/* }==================================================================== */ + + + +/* +** {====================================================================== +** Rules for Statements +** ======================================================================= +*/ + +const block = function(ls) { + /* block -> statlist */ + let fs = ls.fs; + let bl = new BlockCnt(); + enterblock(fs, bl, 0); + statlist(ls); + leaveblock(fs); +}; + +/* +** structure to chain all variables in the left-hand side of an +** assignment +*/ +class LHS_assign { + constructor() { + this.prev = null; + this.v = new expdesc(); /* variable (global, local, upvalue, or indexed) */ + } +} + +/* +** check whether, in an assignment to an upvalue/local variable, the +** upvalue/local variable is begin used in a previous assignment to a +** table. If so, save original upvalue/local value in a safe place and +** use this safe copy in the previous assignment. +*/ +const check_conflict = function(ls, lh, v) { + let fs = ls.fs; + let extra = fs.freereg; /* eventual position to save local variable */ + let conflict = false; + for (; lh; lh = lh.prev) { /* check all previous assignments */ + if (lh.v.k === expkind.VINDEXED) { /* assigning to a table? */ + /* table is the upvalue/local being assigned now? */ + if (lh.v.u.ind.vt === v.k && lh.v.u.ind.t === v.u.info) { + conflict = true; + lh.v.u.ind.vt = expkind.VLOCAL; + lh.v.u.ind.t = extra; /* previous assignment will use safe copy */ + } + /* index is the local being assigned? (index cannot be upvalue) */ + if (v.k === expkind.VLOCAL && lh.v.u.ind.idx === v.u.info) { + conflict = true; + lh.v.u.ind.idx = extra; /* previous assignment will use safe copy */ + } + } + } + if (conflict) { + /* copy upvalue/local value to a temporary (in position 'extra') */ + let op = v.k === expkind.VLOCAL ? OpCodesI.OP_MOVE : OpCodesI.OP_GETUPVAL; + lcode.luaK_codeABC(fs, op, extra, v.u.info, 0); + lcode.luaK_reserveregs(fs, 1); + } +}; + +const assignment = function(ls, lh, nvars) { + let e = new expdesc(); + check_condition(ls, vkisvar(lh.v.k), "syntax error"); + if (testnext(ls, ',')) { /* assignment -> ',' suffixedexp assignment */ + let nv = new LHS_assign(); + nv.prev = lh; + suffixedexp(ls, nv.v); + if (nv.v.k !== expkind.VINDEXED) + check_conflict(ls, lh, nv.v); + checklimit(ls.fs, nvars + ls.L.nCcalls, llimit.LUAI_MAXCCALLS, "JS levels"); + assignment(ls, nv, nvars + 1); + } else { /* assignment -> '=' explist */ + checknext(ls, '='); + let nexps = explist(ls, e); + if (nexps !== nvars) + adjust_assign(ls, nvars, nexps, e); + else { + lcode.luaK_setoneret(ls.fs, e); /* close last expression */ + lcode.luaK_storevar(ls.fs, lh.v, e); + return; /* avoid default */ + } + } + init_exp(e, expkind.VNONRELOC, ls.fs.freereg-1); /* default assignment */ + lcode.luaK_storevar(ls.fs, lh.v, e); +}; + +const cond = function(ls) { + /* cond -> exp */ + let v = expdesc(); + expr(ls, v); /* read condition */ + if (v.k === expkind.VNIL) v.k = expkind.VFALSE; /* 'falses' are all equal here */ + lcode.luaK_goiftrue(ls.fs, v); + return v; +}; + +const gotostat = function(ls, pc) { + let line = ls.linenumber; + let label; + if (testnext(ls, R.TK_GOTO)) + label = str_checkname(ls); + else { + llex.luaX_next(ls); /* skip break */ + label = new TValue(CT.LUA_TLNGSTR, "break"); + } + let g = newlabelentry(ls, ls.dyd.gt, label, line, pc); + findlabel(ls, g); /* close it if label already defined */ +}; + +/* check for repeated labels on the same block */ +const checkrepeated = function(fs, ll, label) { + for (let i = fs.bl.firstlabel; i < ll.n; i++) { + if (label === ll.arr[i].name) { + semerror(fs.ls, `label '${label}' already defined on line ${ll.arr[i].line}`); + } + } +}; + +/* skip no-op statements */ +const skipnoopstat = function(ls) { + while (ls.t.token === ';' || ls.t.token === R.TK_DBCOLON) + statement(ls); +}; + +const labelstat = function(ls, label, line) { + /* label -> '::' NAME '::' */ + let fs = ls.fs; + let ll = ls.dyd.label; + let l; /* index of new label being created */ + checkrepeated(fs, ll, label); /* check for repeated labels */ + checknext(ls, R.TK_DBCOLON); /* skip double colon */ + /* create new entry for this label */ + l = newlabelentry(ls, ll, label, line, lcode.luaK_getlabel(fs)); + skipnoopstat(ls); /* skip other no-op statements */ + if (block_follow(ls, 0)) { /* label is last no-op statement in the block? */ + /* assume that locals are already out of scope */ + ll.arr[l].nactvar = fs.bl.nactvar; + } + findgotos(ls, ll.arr[l]); +}; + +const whilestat = function(ls, line) { + /* whilestat -> WHILE cond DO block END */ + let fs = ls.fs; + let bl = new BlockCnt(); + llex.luaX_next(ls); /* skip WHILE */ + let whileinit = lcode.luaK_getlabel(fs); + let condexit = cond(ls); + enterblock(fs, bl, 1); + checknext(ls, R.TK_DO); + block(ls); + lcode.luaK_jumpto(fs, whileinit); + check_match(ls, R.TK_END, R.TK_WHILE, line); + leaveblock(fs); + lcode.luaK_patchtohere(fs, condexit); /* false conditions finish the loop */ +}; + +const repeatstat = function(ls, line) { + /* repeatstat -> REPEAT block UNTIL cond */ + let fs = ls.fs; + let repeat_init = lcode.luaK_getlabel(fs); + let bl1 = new BlockCnt(); + let bl2 = new BlockCnt(); + enterblock(fs, bl1, 1); /* loop block */ + enterblock(fs, bl2, 0); /* scope block */ + llex.luaX_next(ls); /* skip REPEAT */ + statlist(ls); + check_match(ls, R.TK_UNTIL, R.TK_REPEAT, line); + let condexit = cond(ls); /* read condition (inside scope block) */ + if (bl2.upval) /* upvalues? */ + lcode.luaK_patchclose(fs, condexit, bl2.nactvar); + leaveblock(fs); /* finish scope */ + lcode.luaK_patchlist(fs, condexit, repeat_init); /* close the loop */ + leaveblock(fs); /* finish loop */ +}; + +const exp1 = function(ls) { + let e = new expdesc(); + expr(ls, e); + lcode.luaK_exp2nextreg(ls.fs, e); + assert(e.k === expkind.VNONRELOC); + let reg = e.u.info; + return reg; +}; + +const forbody = function(ls, base, line, nvars, isnum) { + /* forbody -> DO block */ + let bl = new BlockCnt(); + let fs = ls.fs; + let endfor; + adjustlocalvars(ls, 3); /* control variables */ + checknext(ls, R.TK_DO); + let prep = isnum ? lcode.luaK_codeAsBx(fs, OpCodesI.OP_FORPREP, base, lcode.NO_JUMP) : lcode.luaK_jump(fs); + enterblock(fs, bl, 0); /* scope for declared variables */ + adjustlocalvars(ls, nvars); + lcode.luaK_reserveregs(fs, nvars); + block(ls); + leaveblock(fs); /* end of scope for declared variables */ + lcode.luaK_patchtohere(fs, prep); + if (isnum) /* end of scope for declared variables */ + endfor = lcode.luaK_codeAsBx(fs, OpCodesI.OP_FORLOOP, base, lcode.NO_JUMP); + else { /* generic for */ + lcode.luaK_codeABC(fs, OpCodesI.OP_TFORLOOP, base + 2, lcode.NO_JUMP); + lcode.luaK_fixline(fs, line); + endfor = lcode.luaK_codeAsBx(fs, OpCodesI.OP_TFORLOOP, base + 2, lcode.NO_JUMP); + } + lcode.luaK_patchlist(fs, endfor, prep + 1); + lcode.luaK_fixline(fs, line); +}; + +const fornum = function(ls, varname, line) { + /* fornum -> NAME = exp1,exp1[,exp1] forbody */ + let fs = ls.fs; + let base = fs.freereg; + new_localvarliteral(ls, "(for index)"); + new_localvarliteral(ls, "(for limit)"); + new_localvarliteral(ls, "(for step)"); + new_localvar(ls, varname); + checknext(ls, '='); + exp1(ls); /* initial value */ + checknext(ls, ','); + exp1(ls); /* limit */ + if (testnext(ls, ',')) + exp1(ls); /* optional step */ + else { /* default step = 1 */ + lcode.luaK_codek(fs, fs.freereg, lcode.luaK_intK(fs, 1)); + lcode.luaK_reserveregs(fs, 1); + } + forbody(ls, base, line, 1, 1); +}; + +const forlist = function(ls, indexname) { + /* forlist -> NAME {,NAME} IN explist forbody */ + let fs = ls.fs; + let e = new expdesc(); + let nvars = 4; /* gen, state, control, plus at least one declared var */ + let base = fs.freereg(); + /* create control variables */ + new_localvarliteral(ls, "(for generator)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for control)"); + /* create declared variables */ + new_localvar(ls, indexname); + while (testnext(ls, ',')) { + new_localvar(ls, str_checkname(ls)); + nvars++; + } + checknext(ls, R.TK_IN); + let line = ls.linenumber; + adjust_assign(ls, 3, explist(ls, e), e); + lcode.luaK_checkstack(fs, 3); /* extra space to call generator */ + forbody(ls, base, line, nvars - 3, 0); +}; + +const forstat = function(ls, line) { + /* forstat -> FOR (fornum | forlist) END */ + let fs = ls.fs; + let bl = new BlockCnt(); + enterblock(fs, bl, 1); /* scope for loop and control variables */ + llex.luaX_next(ls); /* skip 'for' */ + let varname = str_checkname(ls); /* first variable name */ + switch (ls.t.token) { + case '=': fornum(ls, varname, line); break; + case ',': case R.TK_IN: forlist(ls, varname); break; + default: llex.luaX_syntaxerror(ls, "'=' or 'in' expected"); + } + check_match(ls, R.TK_END, R.TK_FOR, line); + leaveblock(fs); /* loop scope ('break' jumps to this point) */ +}; + const test_then_block = function(ls, escapelist) { /* test_then_block -> [IF | ELSEIF] cond THEN block */ let bl = new BlockCnt(); @@ -715,6 +1301,105 @@ const ifstat = function(ls, line) { lcode.luaK_patchtohere(fs, escapelist); /* patch escape list to 'if' end */ }; +const localfunc = function(ls) { + let b = new expdesc(); + let fs = ls.fs; + new_localvar(ls, str_checkname(ls)); /* new local variable */ + adjustlocalvars(ls, 1); /* enter its scope */ + body(ls, b, 0, ls.linenumber); /* function created in next register */ + /* debug information will only see the variable after this point! */ + getlocvar(fs, b.u.info).startpc = fs.pc; +}; + +const localstat = function(ls) { + /* stat -> LOCAL NAME {',' NAME} ['=' explist] */ + let nvars = 0; + let nexps; + let e = new expdesc(); + do { + new_localvar(ls, str_checkname(ls)); + nvars++; + } while (testnext(ls, ',')); + if (testnext(ls, '=')) + nexps = explist(ls, e); + else { + e.k = expkind.VVOID; + nexps = 0; + } + adjust_assign(ls, nvars, nexps, e); + adjustlocalvars(ls, nvars); +}; + +const funcname = function(ls, v) { + /* funcname -> NAME {fieldsel} [':' NAME] */ + let ismethod = 0; + singlevar(ls, v); + while (ls.t.token == '.') + fieldsel(ls, v); + if (ls.t.token == ':') { + ismethod = 1; + fieldsel(ls, v); + } + return ismethod; +}; + +const funcstat = function(ls, line) { + /* funcstat -> FUNCTION funcname body */ + let v = new expdesc(); + let b = new expdesc(); + llex.luaX_next(ls); /* skip FUNCTION */ + let ismethod = funcname(ls, v); + body(ls, b, ismethod, line); + lcode.luaK_storevar(ls.fs, v, b); + lcode.luaK_fixline(ls.fs, line); /* definition "happens" in the first line */ +}; + +const exprstat= function(ls) { + /* stat -> func | assignment */ + let fs = ls.fs; + let v = new LHS_assign(); + suffixedexp(ls, v.v); + if (ls.t.token === '=' || ls.t.token === ',') { /* stat . assignment ? */ + v.prev = null; + assignment(ls, v, 1); + } + else { /* stat -> func */ + check_condition(ls, v.v.k === expkind.VCALL, "syntax error"); + lopcode.SETARG_C(lcode.getinstruction(fs, v.v), 1); /* call statement uses no results */ + } +}; + +const retstat = function(ls) { + /* stat -> RETURN [explist] [';'] */ + let fs = ls.fs; + let e = new expdesc(); + let first, nret; /* registers with returned values */ + if (block_follow(ls, 1) || ls.t.token === ';') + first = nret = 0; /* return no values */ + else { + nret = explist(ls, e); /* optional return values */ + if (hasmultret(e.k)) { + lcode.luaK_setmultret(fs, e); + if (e.k === expkind.VCALL && nret === 1) { /* tail call? */ + lopcode.SET_OPCODE(lcode.getinstruction(fs, e), OpCodesI.OP_TAILCALL); + assert(lcode.getinstruction(fs, e).A === fs.nactvar); + } + first = fs.nactvar; + nret = lua.LUA_MULTRET; /* return all values */ + } else { + if (nret === 1) /* only one single value? */ + first = lcode.luaK_exp2anyreg(fs, e); + else { + lcode.luaK_exp2nextreg(fs, e); /* values must go to the stack */ + first = fs.nactvar; /* return all active values */ + assert(nret == fs.freereg - first); + } + } + } + lcode.luaK_ret(fs, first, nret); + testnext(ls, ';'); /* skip optional semicolon */ +}; + const statement = function(ls) { let line = ls.linenumber; /* may be needed for error messages */ enterlevel(ls); diff --git a/todo-temp.md b/todo-temp.md new file mode 100644 index 0000000..d86e75c --- /dev/null +++ b/todo-temp.md @@ -0,0 +1,25 @@ +- [ ] luaK_checkstack +- [ ] luaK_codek +- [ ] luaK_dischargevars +- [ ] luaK_exp2RK +- [ ] luaK_exp2anyreg +- [ ] luaK_exp2anyregup +- [ ] luaK_exp2nextreg +- [ ] luaK_exp2val +- [ ] luaK_fixline +- [ ] luaK_goiffalse +- [ ] luaK_goiftrue +- [ ] luaK_indexed +- [ ] luaK_infix +- [ ] luaK_intK +- [ ] luaK_jumpto +- [ ] luaK_posfix +- [ ] luaK_prefix +- [ ] luaK_reserveregs +- [ ] luaK_self +- [ ] luaK_setlist +- [ ] luaK_setmultret +- [ ] luaK_setoneret +- [ ] luaK_setreturns +- [ ] luaK_storevar +- [ ] luaK_stringK \ No newline at end of file -- cgit v1.2.3-54-g00ecf From 74dda64eab7951da520dc451a1f3bbb8c7d62706 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 28 Feb 2017 21:15:16 +0100 Subject: Bytecode generation --- src/lapi.js | 8 +- src/lbaselib.js | 2 +- src/lcode.js | 1001 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/ldebug.js | 6 +- src/ldo.js | 2 +- src/lfunc.js | 2 +- src/lobject.js | 91 ++++- src/lopcodes.js | 2 +- src/lparser.js | 18 +- src/lstate.js | 2 +- src/lua.js | 33 ++ src/lvm.js | 65 ++-- tests/llex.js | 4 +- todo-temp.md | 25 -- 14 files changed, 1160 insertions(+), 101 deletions(-) delete mode 100644 todo-temp.md diff --git a/src/lapi.js b/src/lapi.js index c4a7bbe..9a05638 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -149,7 +149,7 @@ const reverse = function(L, from, to) { /* ** Let x = AB, where A is a prefix of length 'n'. Then, -** rotate x n == BA. But BA == (A^r . B^r)^r. +** 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]; @@ -586,7 +586,7 @@ const f_call = function(L, ud) { const lua_type = function(L, idx) { let o = index2addr(L, idx); - return o.ttnov(); // TODO: isvalid ? luaO_nilobject != nil tvalue ? + return o.ttnov(); // TODO: isvalid ? luaO_nilobject !== nil tvalue ? }; const lua_typename = function(L, t) { @@ -662,7 +662,7 @@ const lua_callk = function(L, nargs, nresults, ctx, k) { ldo.luaD_callnoyield(L, func, nresults); } - if (nresults == lua.LUA_MULTRET && L.ci.top < L.top) + if (nresults === lua.LUA_MULTRET && L.ci.top < L.top) L.ci.top = L.top; }; @@ -713,7 +713,7 @@ const lua_pcallk = function(L, nargs, nresults, errfunc, ctx, k) { status = TS.LUA_OK; } - if (nresults == lua.LUA_MULTRET && L.ci.top < L.top) + if (nresults === lua.LUA_MULTRET && L.ci.top < L.top) L.ci.top = L.top; return status; diff --git a/src/lbaselib.js b/src/lbaselib.js index b663b87..77638d0 100644 --- a/src/lbaselib.js +++ b/src/lbaselib.js @@ -90,7 +90,7 @@ const luaB_rawset = function(L) { const luaB_type = function(L) { let t = lapi.lua_type(L, 1); - lauxlib.luaL_argcheck(L, t != CT.LUA_TNONE, 1, "value expected"); + lauxlib.luaL_argcheck(L, t !== CT.LUA_TNONE, 1, "value expected"); lapi.lua_pushstring(L, lapi.lua_typename(L, t)); return 1; }; diff --git a/src/lcode.js b/src/lcode.js index a2dc43b..c2134ff 100644 --- a/src/lcode.js +++ b/src/lcode.js @@ -2,10 +2,19 @@ const assert = require('assert'); -const lopcode = require('./lopcode.js'); const llex = require('./llex.js'); -const lcode = require('./lcode.js'); +const llimit = require('./llimit.js'); +const lobject = require('./lobject.js'); +const lopcode = require('./lopcode.js'); +const lparser = require('./lparser.js'); +const lua = require('./lua.js'); +const lvm = require('./lvm.js'); +const CT = lua.constants_type; const OpCodesI = lopcode.OpCodesI; +const TValue = lobject.TValue; + +/* Maximum number of registers in a Lua function (must fit in 8 bits) */ +const MAXREGS = 255; /* ** Marks the end of a patch list. It is an invalid value both as an absolute @@ -46,6 +55,62 @@ const UnOpr = { OPR_NOUNOPR: 4 }; +const hasjumps = function(e) { + return e.t !== e.f; +}; + +/* +** If expression is a numeric constant, fills 'v' with its value +** and returns true. Otherwise, returns false. +*/ +const tonumeral = function(e, v) { + let ek = lparser.expkind; + if (hasjumps(e)) + return false; /* not a numeral */ + switch (e.k) { + case ek.VKINT: + if (v) { + v.type = CT.LUA_TNUMINT; + v.value = e.u.ival; + } + return true; + case ek.VKFLT: + if (v) { + v.type = CT.LUA_TNUMFLT; + v.value = e.u.nval; + } + return true; + default: return false; + } +}; + +/* +** Create a OP_LOADNIL instruction, but try to optimize: if the previous +** instruction is also OP_LOADNIL and ranges are compatible, adjust +** range of previous instruction instead of emitting a new one. (For +** instance, 'local a; local b' will generate a single opcode.) +*/ +const luaK_nil = function(fs, from, n) { + let previous; + let l = from + n - 1; /* last register to set nil */ + if (fs.pc > fs.lasttarget) { /* no jumps to current position? */ + previous = fs.f.code[fs.pc-1]; + if (previous.opcode === OpCodesI.OP_LOADNIL) { /* previous is LOADNIL? */ + let pfrom = previous.A; /* get previous range */ + let pl = pfrom + previous.B; + if ((pfrom <= from && from <= pl + 1) || + (from <= pfrom && pfrom <= l + 1)) { /* can connect both? */ + if (pfrom < from) from = pfrom; /* from = min(from, pfrom) */ + if (pl > l) l = pl; /* l = max(l, pl) */ + lopcode.SETARG_A(previous, from); + lopcode.SETARG_B(previous, l - from); + return; + } + } /* else go through */ + } + luaK_codeABC(fs, OpCodesI.OP_LOADNIL, from, n - 1, 0); /* else no optimization */ +}; + const getinstruction = function(fs, e) { return fs.f.code[e.u.info]; }; @@ -109,6 +174,10 @@ const luaK_jump = function (fs) { return j; }; +const luaK_jumpto = function(fs, t) { + return luaK_patchlist(fs, luaK_jump(fs), t); +}; + /* ** Code a 'return' instruction */ @@ -116,6 +185,15 @@ const luaK_ret = function(fs, first, nret) { luaK_codeABC(fs, OpCodesI.OP_RETURN, first, nret + 1, 0); }; +/* +** Code a "conditional jump", that is, a test or comparison opcode +** followed by a jump. Return jump position. +*/ +const condjump = function(fs, op, A, B, C) { + luaK_codeABC(fs, op, A, B, C); + return luaK_jump(fs); +}; + /* ** returns current 'pc' and marks it as a jump target (to avoid wrong ** optimizations with consecutive instructions not in the same basic block). @@ -158,6 +236,14 @@ const patchtestreg = function(fs, node, reg) { return true; }; +/* +** Traverse a list of tests ensuring no one produces a value +*/ +const removevalues = function(fs, list) { + for (; list !== NO_JUMP; list = getjump(fs, list)) + patchtestreg(fs, list, lopcode.NO_REG); +}; + /* ** Traverse a list of tests, patching their destination address and ** registers: tests producing values jump to 'vtarget' (and put their @@ -174,6 +260,16 @@ const patchlistaux = function(fs, list, vtarget, reg, dtarget) { } }; +/* +** Ensure all pending jumps to current position are fixed (jumping +** to current position with no values) and reset list of pending +** jumps +*/ +const dischargejpc = function(fs) { + patchlistaux(fs, fs.jpc, fs.pc, lopcode.NO_REG, fs.pc); + fs.jpc = NO_JUMP; +}; + /* ** Add elements in 'list' to list of pending jumps to "here" ** (current position) @@ -217,7 +313,7 @@ const luaK_patchclose = function(fs, list, level) { */ const luaK_code = function(fs, i) { let f = fs.f; - lcode.dischargejpc(fs); /* 'pc' will change */ + dischargejpc(fs); /* 'pc' will change */ /* put new instruction in code array */ f.code[fs.pc] = i; f.lineinfo[fs.pc] = fs.ls.lastline; @@ -229,9 +325,9 @@ const luaK_code = function(fs, i) { ** of parameters versus opcode.) */ const luaK_codeABC = function(fs, o, a, b, c) { - assert(lopcode.getOpMode(o) == lopcode.iABC); - assert(lopcode.getBMode(o) != lopcode.OpArgN || b === 0); - assert(lopcode.getCMode(o) != lopcode.OpArgN || c === 0); + assert(lopcode.getOpMode(o) === lopcode.iABC); + assert(lopcode.getBMode(o) !== lopcode.OpArgN || b === 0); + assert(lopcode.getCMode(o) !== lopcode.OpArgN || c === 0); assert(a <= lopcode.MAXARG_A && b <= lopcode.MAXARG_B && c <= lopcode.MAXARG_C); return luaK_code(fs, lopcode.CREATE_ABC(o, a, b, c)); }; @@ -240,8 +336,8 @@ const luaK_codeABC = function(fs, o, a, b, c) { ** Format and emit an 'iABx' instruction. */ const luaK_codeABx = function(fs, o, a, bc) { - assert(lopcode.getOpMode(o) == lopcode.iABx || lopcode.getOpMode(o) == lopcode.iAsBx); - assert(lopcode.getCMode(o) == lopcode.OpArgN); + assert(lopcode.getOpMode(o) === lopcode.iABx || lopcode.getOpMode(o) === lopcode.iAsBx); + assert(lopcode.getCMode(o) === lopcode.OpArgN); assert(a <= lopcode.MAXARG_A && bc <= lopcode.MAXARG_Bx); return luaK_code(fs, lopcode.CREATE_ABx(o, a, bc)); }; @@ -250,16 +346,879 @@ const luaK_codeAsBx = function(fs,o,A,sBx) { return luaK_codeABx(fs, o, A, (sBx) + lopcode.MAXARG_sBx); }; -module.exports.BinOpr = BinOpr; -module.exports.NO_JUMP = NO_JUMP; -module.exports.UnOpr = UnOpr; -module.exports.getinstruction = getinstruction; -module.exports.luaK_codeABC = luaK_codeABC; -module.exports.luaK_codeABx = luaK_codeABx; -module.exports.luaK_codeAsBx = luaK_codeAsBx; -module.exports.luaK_getlabel = luaK_getlabel; -module.exports.luaK_jump = luaK_jump; -module.exports.luaK_patchclose = luaK_patchclose; -module.exports.luaK_patchlist = luaK_patchlist; -module.exports.luaK_patchtohere = luaK_patchtohere; -module.exports.luaK_ret = luaK_ret; \ No newline at end of file +/* +** Emit an "extra argument" instruction (format 'iAx') +*/ +const codeextraarg = function(fs, a) { + assert(a <= lopcode.MAXARG_Ax); + return luaK_code(fs, lopcode.CREATE_Ax(OpCodesI.OP_EXTRAARG, a)); +}; + +/* +** Emit a "load constant" instruction, using either 'OP_LOADK' +** (if constant index 'k' fits in 18 bits) or an 'OP_LOADKX' +** instruction with "extra argument". +*/ +const luaK_codek = function(fs, reg, k) { + if (k <= lopcode.MAXARG_Bx) + return luaK_codeABx(fs, OpCodesI.OP_LOADK, reg, k); + else { + let p = luaK_codeABx(fs, OpCodesI.OP_LOADKX, reg, 0); + codeextraarg(fs, k); + return p; + } +}; + +/* +** Check register-stack level, keeping track of its maximum size +** in field 'maxstacksize' +*/ +const luaK_checkstack = function(fs, n) { + let newstack = fs.freereg + n; + if (newstack > fs.f.maxstacksize) { + if (newstack >= MAXREGS) + llex.luaX_syntaxerror(fs.ls, "function or expression needs to many registers"); + fs.f.maxstacksize = newstack; + } +}; + +/* +** Reserve 'n' registers in register stack +*/ +const luaK_reserveregs = function(fs, n) { + luaK_checkstack(fs, n); + fs.freereg += n; +}; + +/* +** Free register 'reg', if it is neither a constant index nor +** a local variable. +*/ +const freereg = function(fs, reg) { + if (!lopcode.ISK(reg) && reg >= fs.nactvar) { + fs.freereg--; + assert(reg === fs.freereg); + } +}; + +/* +** Free register used by expression 'e' (if any) +*/ +const freeexp = function(fs, e) { + if (e.k === lparser.expkind.VNONRELOC) + freereg(fs, e.u.info); +}; + +/* +** Free registers used by expressions 'e1' and 'e2' (if any) in proper +** order. +*/ +const freeexps = function(fs, e1, e2) { + let r1 = (e1.k === lparser.expkind.VNONRELOC) ? e1.u.info : -1; + let r2 = (e2.k === lparser.expkind.VNONRELOC) ? e2.u.info : -1; + if (r1 > r2) { + freereg(fs, r1); + freereg(fs, r2); + } + else { + freereg(fs, r2); + freereg(fs, r1); + } +}; + + +/* +** Add constant 'v' to prototype's list of constants (field 'k'). +** Use scanner's table to cache position of constants in constant list +** and try to reuse constants. Because some values should not be used +** as keys (nil cannot be a key, integer keys can collapse with float +** keys), the caller must provide a useful 'key' for indexing the cache. +*/ +const addk = function(fs, key, v) { + let f = fs.f; + let idx = fs.ls.h.__index(fs.ls.h, key); /* index scanner table */ + if (idx) { /* is there an index there? */ + /* correct value? (warning: must distinguish floats from integers!) */ + if (f.k[idx].ttype() === v.ttype() && f.k[idx].value === v.value) + return idx; /* reuse index */ + } + /* constant not found; create a new entry */ + let k = fs.nk; + fs.ls.h.__newindex(fs.ls.h, key, k); + f.k[k] = v; + fs.nk++; + return k; +}; + +/* +** Add a string to list of constants and return its index. +*/ +const luaK_stringK = function(fs, s) { + let o = new TValue(CT.LUA_TLNGSTR, s); + return addk(fs, o, o); /* use string itself as key */ +}; + + +/* +** Add an integer to list of constants and return its index. +** Integers use userdata as keys to avoid collision with floats with +** same value; conversion to 'void*' is used only for hashing, so there +** are no "precision" problems. +*/ +const luaK_intK = function(fs, n) { + let k = new TValue(CT.LUA_TLNGSTR, `n`); + let o = new TValue(CT.LUA_TNUMINT, n); + return addk(fs, k, o); +}; + +/* +** Add a float to list of constants and return its index. +*/ +const luaK_numberK = function(fs, r) { + let o = new TValue(CT.LUA_TNUMFLT, r); + return addk(fs, o, o); /* use number itself as key */ +}; + + +/* +** Add a boolean to list of constants and return its index. +*/ +const boolK = function(fs, b) { + let o = new TValue(CT.LUA_TBOOLEAN, b); + return addk(fs, o, o); /* use boolean itself as key */ +}; + + +/* +** Add nil to list of constants and return its index. +*/ +const nilK = function(fs) { + let o = new TValue(CT.LUA_TNIL, null); + return addk(fs, o, o); +}; + +/* +** Fix an expression to return the number of results 'nresults'. +** Either 'e' is a multi-ret expression (function call or vararg) +** or 'nresults' is LUA_MULTRET (as any expression can satisfy that). +*/ +const luaK_setreturns = function(fs, e, nresults) { + let ek = lparser.expkind; + if (e.k === ek.VCALL) { /* expression is an open function call? */ + lopcode.SETARG_C(getinstruction(fs, e), nresults + 1); + } + else if (e.k === ek.VVARARG) { + let pc = getinstruction(fs, e); + lopcode.SETARG_B(pc, nresults + 1); + lopcode.SETARG_A(pc, fs.freereg); + luaK_reserveregs(fs, 1); + } + else assert(nresults === lua.LUA_MULTRET); +}; + +const luaK_setmultret = function(fs, e) { + luaK_setreturns(fs, e, lua.LUA_MULTRET); +}; + +/* +** Fix an expression to return one result. +** If expression is not a multi-ret expression (function call or +** vararg), it already returns one result, so nothing needs to be done. +** Function calls become VNONRELOC expressions (as its result comes +** fixed in the base register of the call), while vararg expressions +** become VRELOCABLE (as OP_VARARG puts its results where it wants). +** (Calls are created returning one result, so that does not need +** to be fixed.) +*/ +const luaK_setoneret = function(fs, e) { + let ek = lparser.expkind; + if (e.k === ek.VCALL) { /* expression is an open function call? */ + /* already returns 1 value */ + assert(getinstruction(fs, e).C === 2); + e.k = ek.VNONRELOC; /* result has fixed position */ + e.u.info = getinstruction(fs, e).A; + } else if (e.k === ek.VVARARG) { + lopcode.SETARG_B(getinstruction(fs, e), 2); + e.k = ek.VRELOCABLE; /* can relocate its simple result */ + } +}; + +/* +** Ensure that expression 'e' is not a variable. +*/ +const luaK_dischargevars = function(fs, e) { + let ek = lparser.expkind; + + switch (e.k) { + case ek.VLOCAL: { /* already in a register */ + e.k = ek.VNONRELOC; /* becomes a non-relocatable value */ + break; + } + case ek.VUPVAL: { /* move value to some (pending) register */ + e.u.info = luaK_codeABC(fs, OpCodesI.OP_GETUPVAL, 0, e.u.info, 0); + e.k = ek.VRELOCABLE; + break; + } + case ek.VINDEXED: { + let op; + freereg(fs, e.u.ind.idx); + if (e.u.ind.vt === ek.VLOCAL) { /* is 't' in a register? */ + freereg(fs, e.u.ind.t); + op = OpCodesI.OP_GETTABLE; + } else { + assert(e.u.ind.vt === ek.VUPVAL); + op = OpCodesI.OP_GETTABUP; /* 't' is in an upvalue */ + } + e.u.info = luaK_codeABC(fs, op, 0, e.u.ind.t, e.u.ind.idx); + e.k = OpCodesI.VRELOCABLE; + break; + } + case ek.VVARARG: case ek.VCALL: { + luaK_setoneret(fs, e); + break; + } + default: break; /* there is one value available (somewhere) */ + } +}; + +const code_loadbool = function(fs, A, b, jump) { + luaK_getlabel(fs); /* those instructions may be jump targets */ + return luaK_codeABC(fs, OpCodesI.OP_LOADBOOL, A, b, jump); +}; + +/* +** Ensures expression value is in register 'reg' (and therefore +** 'e' will become a non-relocatable expression). +*/ +const discharge2reg = function(fs, e, reg) { + let ek = lparser.expkind; + luaK_dischargevars(fs, e); + switch (e.k) { + case ek.VNIL: { + luaK_nil(fs, reg, 1); + break; + } + case ek.VFALSE: case ek.VTRUE: { + luaK_codeABC(fs, OpCodesI.OP_LOADBOOL, reg, e.k === ek.VTRUE, 0); + break; + } + case ek.VK: { + luaK_codek(fs, reg, e.u.info); + break; + } + case ek.VKFLT: { + luaK_codek(fs, reg, luaK_numberK(fs, e.u.nval)); + break; + } + case ek.VKINT: { + luaK_codek(fs, reg, luaK_intK(fs, e.u.ival)); + break; + } + case ek.VRELOCABLE: { + let pc = getinstruction(fs, e); + lopcode.SETARG_A(pc, reg); /* instruction will put result in 'reg' */ + break; + } + case ek.VNONRELOC: { + if (reg !== e.u.info) + luaK_codeABC(fs, OpCodesI.OP_MOVE, reg, e.u.info, 0); + break; + } + default: { + assert(e.k === ek.VJMP); + return; /* nothing to do... */ + } + } + e.u.info = reg; + e.k = ek.VNONRELOC; +}; + +/* +** Ensures expression value is in any register. +*/ +const discharge2anyreg = function(fs, e) { + if (e.k !== lparser.expkind.VNONRELOC) { /* no fixed register yet? */ + luaK_reserveregs(fs, 1); /* get a register */ + discharge2reg(fs, e, fs.freereg-1); /* put value there */ + } +}; + +/* +** check whether list has any jump that do not produce a value +** or produce an inverted value +*/ +const need_value = function(fs, list) { + for (; list !== NO_JUMP; list = getjump(fs, list)) { + let i = getjumpcontrol(fs, list); + if (i.opcode !== OpCodesI.OP_TESTSET) return true; + } + return false; /* not found */ +}; + +/* +** Ensures final expression result (including results from its jump +** lists) is in register 'reg'. +** If expression has jumps, need to patch these jumps either to +** its final position or to "load" instructions (for those tests +** that do not produce values). +*/ +const exp2reg = function(fs, e, reg) { + let ek = lparser.expkind; + discharge2reg(fs, e, reg); + if (e.k === ek.VJMP) /* expression itself is a test? */ + e.t = luaK_concat(fs, e.t, e.u.info); /* put this jump in 't' list */ + if (hasjumps(e)) { + let final; /* position after whole expression */ + let p_f = NO_JUMP; /* position of an eventual LOAD false */ + let p_t = NO_JUMP; /* position of an eventual LOAD true */ + if (need_value(fs, e.t) || need_value(fs, e.f)) { + let fj = (e.k === ek.VJMP) ? NO_JUMP : luaK_jump(fs); + p_f = code_loadbool(fs, reg, 0, 1); + p_t = code_loadbool(fs, reg, 1, 0); + luaK_patchtohere(fs, fj); + } + final = luaK_getlabel(fs); + patchlistaux(fs, e.f, final, reg, p_f); + patchlistaux(fs, e.t, final, reg, p_t); + } + e.f = e.t = NO_JUMP; + e.u.info = reg; + e.k = ek.VNONRELOC; +}; + +/* +** Ensures final expression result (including results from its jump +** lists) is in next available register. +*/ +const luaK_exp2nextreg = function(fs, e) { + luaK_dischargevars(fs, e); + freeexp(fs, e); + luaK_reserveregs(fs, 1); + exp2reg(fs, e, fs.freereg - 1); +}; + + +/* +** Ensures final expression result (including results from its jump +** lists) is in some (any) register and return that register. +*/ +const luaK_exp2anyreg = function(fs, e) { + luaK_dischargevars(fs, e); + if (e.k === lparser.expkind.VNONRELOC) { /* expression already has a register? */ + if (!hasjumps(e)) /* no jumps? */ + return e.u.info; /* result is already in a register */ + if (e.u.info >= fs.nactvar) { /* reg. is not a local? */ + exp2reg(fs, e, e.u.info); /* put final result in it */ + return e.u.info; + } + } + luaK_exp2nextreg(fs, e); /* otherwise, use next available register */ + return e.u.info; +}; + +/* +** Ensures final expression result is either in a register or in an +** upvalue. +*/ +const luaK_exp2anyregup = function(fs, e) { + if (e.k !== lparser.expkind.VUPVAL || hasjumps(e)) + luaK_exp2anyreg(fs, e); +}; + +/* +** Ensures final expression result is either in a register or it is +** a constant. +*/ +const luaK_exp2val = function(fs, e) { + if (hasjumps(e)) + luaK_exp2anyreg(fs, e); + else + luaK_dischargevars(fs, e); +}; + +/* +** Ensures final expression result is in a valid R/K index +** (that is, it is either in a register or in 'k' with an index +** in the range of R/K indices). +** Returns R/K index. +*/ +const luaK_exp2RK = function(fs, e) { + let ek = lparser.expkind; + let vk = false; + luaK_exp2val(fs, e); + switch (e.k) { /* move constants to 'k' */ + case ek.VTRUE: e.u.info = boolK(fs, 1); vk = true; break; + case ek.VFALSE: e.u.info = boolK(fs, 0); vk = true; break; + case ek.VNIL: e.u.info = nilK(fs); vk = true; break; + case ek.VKINT: e.u.info = luaK_intK(fs, e.u.ival); vk = true; break; + case ek.VKFLT: e.u.info = luaK_numberK(fs, e.u.nval); vk = true; break; + case ek.VK: vk = true; break; + default: break; + } + + if (vk) { + e.k = ek.VK; + if (e.u.info <= lopcode.MAXINDEXRK) /* constant fits in 'argC'? */ + return lopcode.RKASK(e.u.info); + } + + /* not a constant in the right range: put it in a register */ + return luaK_exp2anyreg(fs, e); +}; + +/* +** Generate code to store result of expression 'ex' into variable 'var'. +*/ +const luaK_storevar = function(fs, vr, ex) { + let ek = lparser.expkind; + switch (vr.k) { + case ek.VLOCAL: { + freeexp(fs, ex); + exp2reg(fs, ex, vr.u.info); /* compute 'ex' into proper place */ + return; + } + case ek.VUPVAL: { + let e = luaK_exp2anyreg(fs, ex); + luaK_codeABC(fs, OpCodesI.OP_SETUPVAL, e, vr.u.info, 0); + break; + } + case ek.VINDEXED: { + let op = (vr.u.ind.vt === ek.VLOCAL) ? OpCodesI.OP_SETTABLE : OpCodesI.OP_SETTABUP; + let e = luaK_exp2RK(fs, ex); + luaK_codeABC(fs, op, vr.u.ind.t, vr.u.ind.idx, e); + break; + } + } + freeexp(fs, ex); +}; + + +/* +** Emit SELF instruction (convert expression 'e' into 'e:key(e,'). +*/ +const luaK_self = function(fs, e, key) { + luaK_exp2anyreg(fs, e); + let ereg = e.u.info; /* register where 'e' was placed */ + freeexp(fs, e); + e.u.info = fs.freereg; /* base register for op_self */ + e.k = lparser.expkind.VNONRELOC; /* self expression has a fixed register */ + luaK_reserveregs(fs, 2); /* function and 'self' produced by op_self */ + luaK_codeABC(fs, OpCodesI.OP_SELF, e.u.info, ereg, luaK_exp2RK(fs, key)); + freeexp(fs, key); +}; + +/* +** Negate condition 'e' (where 'e' is a comparison). +*/ +const negatecondition = function(fs, e) { + let pc = getjumpcontrol(fs, e.u.info); + assert(lopcode.testTMode(pc.opcode) && pc.opcode !== OpCodesI.OP_TESTSET && pc.opcode !== OpCodesI.OP_TEST); + lopcode.SETARG_A(pc, !(pc.A)); +}; + +/* +** Emit instruction to jump if 'e' is 'cond' (that is, if 'cond' +** is true, code will jump if 'e' is true.) Return jump position. +** Optimize when 'e' is 'not' something, inverting the condition +** and removing the 'not'. +*/ +const jumponcond = function(fs, e, cond) { + if (e.k === lparser.expkind.VRELOCABLE) { + let ie = getinstruction(fs, e); + if (ie.opcode === OpCodesI.OP_NOT) { + fs.pc--; /* remove previous OP_NOT */ + return condjump(fs, OpCodesI.OP_TEST, ie.B, 0, !cond); + } + /* else go through */ + } + discharge2anyreg(fs, e); + freeexp(fs, e); + return condjump(fs, OpCodesI.OP_TESTSET, lopcode.NO_REG, e.u.info, cond); +}; + +/* +** Emit code to go through if 'e' is true, jump otherwise. +*/ +const luaK_goiftrue = function(fs, e) { + let ek = lparser.expkind; + let pc; /* pc of new jump */ + luaK_dischargevars(fs, e); + switch (e.k) { + case ek.VJMP: { /* condition? */ + negatecondition(fs, e); /* jump when it is false */ + pc = e.u.info; /* save jump position */ + break; + } + case ek.VK: case ek.VKFLT: case ek.VKINT: case ek.VTRUE: { + pc = NO_JUMP; /* always true; do nothing */ + break; + } + default: { + pc = jumponcond(fs, e, 0); /* jump when false */ + break; + } + } + e.f = luaK_concat(fs, e.f, pc); /* insert new jump in false list */ + luaK_patchtohere(fs, e.t); /* true list jumps to here (to go through) */ + e.t = NO_JUMP; +}; + +/* +** Emit code to go through if 'e' is false, jump otherwise. +*/ +const luaK_goiffalse = function(fs, e) { + let ek = lparser.expkind; + let pc; /* pc of new jump */ + luaK_dischargevars(fs, e); + switch (e.k) { + case ek.VJMP: { + pc = e.u.info; /* already jump if true */ + break; + } + case ek.VNIL: case ek.VFALSE: { + pc = NO_JUMP; /* always false; do nothing */ + break; + } + default: { + pc = jumponcond(fs, e, 1); /* jump if true */ + break; + } + } + e.t = luaK_concat(fs, e.t, pc); /* insert new jump in 't' list */ + luaK_patchtohere(fs, e.f); /* false list jumps to here (to go through) */ + e.f = NO_JUMP; +}; + +/* +** Code 'not e', doing constant folding. +*/ +const codenot = function(fs, e) { + let ek = lparser.expkind; + luaK_dischargevars(fs, e); + switch (e.k) { + case ek.VNIL: case ek.VFALSE: { + e.k = ek.VTRUE; /* true === not nil === not false */ + break; + } + case ek.VK: case ek.VKFLT: case ek.VKINT: case ek.VTRUE: { + e.k = ek.VFALSE; /* false === not "x" === not 0.5 === not 1 === not true */ + break; + } + case ek.VJMP: { + negatecondition(fs, e); + break; + } + case ek.VRELOCABLE: + case ek.VNONRELOC: { + discharge2anyreg(fs, e); + freeexp(fs, e); + e.u.info = luaK_codeABC(fs, OpCodesI.OP_NOT, 0, e.u.info, 0); + e.k = ek.VRELOCABLE; + break; + } + } + /* interchange true and false lists */ + { let temp = e.f; e.f = e.t; e.t = temp; } + removevalues(fs, e.f); /* values are useless when negated */ + removevalues(fs, e.t); +}; + +/* +** Create expression 't[k]'. 't' must have its final result already in a +** register or upvalue. +*/ +const luaK_indexed = function(fs, t, k) { + let ek = lparser.expkind; + assert(!hasjumps(t) && (lparser.vkisinreg(t.k) || t.k === ek.VUPVAL)); + t.u.ind.t = t.u.info; /* register or upvalue index */ + t.u.ind.idx = luaK_exp2RK(fs, k); /* R/K index for key */ + t.u.ind.vt = (t.k === ek.VUPVAL) ? ek.VUPVAL : ek.VLOCAL; + t.k = ek.VINDEXED; +}; + +/* +** Return false if folding can raise an error. +** Bitwise operations need operands convertible to integers; division +** operations cannot have 0 as divisor. +*/ +const validop = function(op, v1, v2) { + switch (op) { + case lua.LUA_OPBAND: case lua.LUA_OPBOR: case lua.LUA_OPBXOR: + case lua.LUA_OPSHL: case lua.LUA_OPSHR: case lua.LUA_OPBNOT: { /* conversion errors */ + return (lvm.tointeger(v1) && lvm.tointeger(v2)); + } + case lua.LUA_OPDIV: case lua.LUA_OPIDIV: case lua.LUA_OPMOD: /* division by 0 */ + return (v2.value !== 0); + default: return 1; /* everything else is valid */ + } +}; + +/* +** Try to "constant-fold" an operation; return 1 iff successful. +** (In this case, 'e1' has the final result.) +*/ +const constfolding = function(fs, op, e1, e2) { + let ek = lparser.expkind; + let v1 = new TValue(); + let v2 = new TValue(); + let res = new TValue(); + if (!tonumeral(e1, v1) || !tonumeral(e2, v2) || !validop(op, v1, v2)) + return 0; /* non-numeric operands or not safe to fold */ + lobject.luaO_arith(fs.ls.L, op, v1, v2, res); /* does operation */ + if (res.ttisinteger()) { + e1.k = ek.VKINT; + e1.u.ival = res.value; + } + else { /* folds neither NaN nor 0.0 (to avoid problems with -0.0) */ + let n = res.value; + if (isNaN(n) || n === 0) + return false; + e1.k = ek.VKFLT; + e1.u.nval = n; + } + return true; +}; + +/* +** Emit code for unary expressions that "produce values" +** (everything but 'not'). +** Expression to produce final result will be encoded in 'e'. +*/ +const codeunexpval = function(fs, op, e, line) { + let r = luaK_exp2anyreg(fs, e); /* opcodes operate only on registers */ + freeexp(fs, e); + e.u.info = luaK_codeABC(fs, op, 0, r, 0); /* generate opcode */ + e.k = lparser.expkind.VRELOCABLE; /* all those operations are relocatable */ + luaK_fixline(fs, line); +}; + +/* +** Emit code for binary expressions that "produce values" +** (everything but logical operators 'and'/'or' and comparison +** operators). +** Expression to produce final result will be encoded in 'e1'. +** Because 'luaK_exp2RK' can free registers, its calls must be +** in "stack order" (that is, first on 'e2', which may have more +** recent registers to be released). +*/ +const codebinexpval = function(fs, op, e1, e2, line) { + let rk2 = luaK_exp2RK(fs, e2); /* both operands are "RK" */ + let rk1 = luaK_exp2RK(fs, e1); + freeexps(fs, e1, e2); + e1.u.info = luaK_codeABC(fs, op, 0, rk1, rk2); /* generate opcode */ + e1.k = lparser.expkind.VRELOCABLE; /* all those operations are relocatable */ + luaK_fixline(fs, line); +}; + + +/* +** Emit code for comparisons. +** 'e1' was already put in R/K form by 'luaK_infix'. +*/ +const codecomp = function(fs, opr, e1, e2) { + let ek = lparser.expkind; + let rk1 = (e1.k === ek.VK) ? lopcode.RKASK(e1.u.info) : llimit.check_exp(e1.k === ek.VNONRELOC, e1.u.info); + let rk2 = luaK_exp2RK(fs, e2); + freeexps(fs, e1, e2); + switch (opr) { + case BinOpr.OPR_NE: { /* '(a ~= b)' ==> 'not (a === b)' */ + e1.u.info = condjump(fs, OpCodesI.OP_EQ, 0, rk1, rk2); + break; + } + case BinOpr.OPR_GT: case BinOpr.OPR_GE: { + /* '(a > b)' ==> '(b < a)'; '(a >= b)' ==> '(b <= a)' */ + let op = (opr - BinOpr.OPR_NE) + OpCodesI.OP_EQ; + e1.u.info = condjump(fs, op, 1, rk2, rk1); /* invert operands */ + break; + } + default: { /* '==', '<', '<=' use their own opcodes */ + let op = (opr - BinOpr.OPR_EQ) + OpCodesI.OP_EQ; + e1.u.info = condjump(fs, op, 1, rk1, rk2); + break; + } + } + e1.k = ek.VJMP; +}; + +/* +** Apply prefix operation 'op' to expression 'e'. +*/ +const luaK_prefix = function(fs, op, e, line) { + let ef = new lparser.expdesc(); + ef.k = lparser.expkind.VKINT; + ef.u.ival = e.u.nval = e.u.info = 0; + e.t = NO_JUMP; + e.f = NO_JUMP; + switch (op) { + case UnOpr.OPR_MINUS: case UnOpr.OPR_BNOT: /* use 'ef' as fake 2nd operand */ + if (constfolding(fs, op + lua.LUA_OPUNM, e, ef)) + break; + /* FALLTHROUGH */ + case UnOpr.OPR_LEN: + codeunexpval(fs, op + UnOpr.OP_UNM, e, line); + break; + case UnOpr.OPR_NOT: codenot(fs, e); break; + } +}; + +/* +** Process 1st operand 'v' of binary operation 'op' before reading +** 2nd operand. +*/ +const luaK_infix = function(fs, op, v) { + switch (op) { + case BinOpr.OPR_AND: { + luaK_goiftrue(fs, v); /* go ahead only if 'v' is true */ + break; + } + case BinOpr.OPR_OR: { + luaK_goiffalse(fs, v); /* go ahead only if 'v' is false */ + break; + } + case BinOpr.OPR_CONCAT: { + luaK_exp2nextreg(fs, v); /* operand must be on the 'stack' */ + break; + } + case BinOpr.OPR_ADD: case BinOpr.OPR_SUB: + case BinOpr.OPR_MUL: case BinOpr.OPR_DIV: case BinOpr.OPR_IDIV: + case BinOpr.OPR_MOD: case BinOpr.OPR_POW: + case BinOpr.OPR_BAND: case BinOpr.OPR_BOR: case BinOpr.OPR_BXOR: + case BinOpr.OPR_SHL: case BinOpr.OPR_SHR: { + if (!tonumeral(v, null)) + luaK_exp2RK(fs, v); + /* else keep numeral, which may be folded with 2nd operand */ + break; + } + default: { + luaK_exp2RK(fs, v); + break; + } + } +}; + +/* +** Finalize code for binary operation, after reading 2nd operand. +** For '(a .. b .. c)' (which is '(a .. (b .. c))', because +** concatenation is right associative), merge second CONCAT into first +** one. +*/ +const luaK_posfix = function(fs, op, e1, e2, line) { + let ek = lparser.expkind; + switch (op) { + case BinOpr.OPR_AND: { + assert(e1.t === NO_JUMP); /* list closed by 'luK_infix' */ + luaK_dischargevars(fs, e2); + e2.f = luaK_concat(fs, e2.f, e1.f); + // WARN: *e1 = *e2; + break; + } + case BinOpr.OPR_OR: { + assert(e1.f === NO_JUMP); /* list closed by 'luK_infix' */ + luaK_dischargevars(fs, e2); + e2.t = luaK_concat(fs, e2.t, e1.t); + // WARN: *e1 = *e2; + break; + } + case BinOpr.OPR_CONCAT: { + let ins = getinstruction(fs, e2); + luaK_exp2val(fs, e2); + if (e2.k === ek.VRELOCABLE && ins.opcode === OpCodesI.OP_CONCAT) { + assert(e1.u.info === ins.B - 1); + freeexp(fs, e1); + lopcode.SETARG_B(ins, e1.u.info); + e1.k = ek.VRELOCABLE; e1.u.info = e2.u.info; + } + else { + luaK_exp2nextreg(fs, e2); /* operand must be on the 'stack' */ + codebinexpval(fs, OpCodesI.OP_CONCAT, e1, e2, line); + } + break; + } + case BinOpr.OPR_ADD: case BinOpr.OPR_SUB: case BinOpr.OPR_MUL: case BinOpr.OPR_DIV: + case BinOpr.OPR_IDIV: case BinOpr.OPR_MOD: case BinOpr.OPR_POW: + case BinOpr.OPR_BAND: case BinOpr.OPR_BOR: case BinOpr.OPR_BXOR: + case BinOpr.OPR_SHL: case BinOpr.OPR_SHR: { + if (!constfolding(fs, op + lua.LUA_OPADD, e1, e2)) + codebinexpval(fs, op + OpCodesI.OP_ADD, e1, e2, line); + break; + } + case BinOpr.OPR_EQ: case BinOpr.OPR_LT: case BinOpr.OPR_LE: + case BinOpr.OPR_NE: case BinOpr.OPR_GT: case BinOpr.OPR_GE: { + codecomp(fs, op, e1, e2); + break; + } + } +}; + +/* +** Change line information associated with current position. +*/ +const luaK_fixline = function(fs, line) { + fs.f.lineinfo[fs.pc - 1] = line; +}; + +/* +** Emit a SETLIST instruction. +** 'base' is register that keeps table; +** 'nelems' is #table plus those to be stored now; +** 'tostore' is number of values (in registers 'base + 1',...) to add to +** table (or LUA_MULTRET to add up to stack top). +*/ +const luaK_setlist = function(fs, base, nelems, tostore) { + let c = (nelems - 1)/lopcode.LFIELDS_PER_FLUSH + 1; + let b = (tostore === lua.LUA_MULTRET) ? 0 : tostore; + assert(tostore !== 0 && tostore <= lopcode.LFIELDS_PER_FLUSH); + if (c <= lopcode.MAXARG_C) + luaK_codeABC(fs, OpCodesI.OP_SETLIST, base, b, c); + else if (c <= lopcode.MAXARG_Ax) { + luaK_codeABC(fs, OpCodesI.OP_SETLIST, base, b, 0); + codeextraarg(fs, c); + } + else + llex.luaX_syntaxerror(fs.ls, "constructor too long"); + fs.freereg = base + 1; /* free registers with list values */ +}; + + +module.exports.BinOpr = BinOpr; +module.exports.NO_JUMP = NO_JUMP; +module.exports.UnOpr = UnOpr; +module.exports.getinstruction = getinstruction; +module.exports.luaK_checkstack = luaK_checkstack; +module.exports.luaK_code = luaK_code; +module.exports.luaK_codeABC = luaK_codeABC; +module.exports.luaK_codeABx = luaK_codeABx; +module.exports.luaK_codeAsBx = luaK_codeAsBx; +module.exports.luaK_codek = luaK_codek; +module.exports.luaK_concat = luaK_concat; +module.exports.luaK_dischargevars = luaK_dischargevars; +module.exports.luaK_exp2RK = luaK_exp2RK; +module.exports.luaK_exp2anyreg = luaK_exp2anyreg; +module.exports.luaK_exp2anyregup = luaK_exp2anyregup; +module.exports.luaK_exp2nextreg = luaK_exp2nextreg; +module.exports.luaK_exp2val = luaK_exp2val; +module.exports.luaK_fixline = luaK_fixline; +module.exports.luaK_getlabel = luaK_getlabel; +module.exports.luaK_goiffalse = luaK_goiffalse; +module.exports.luaK_goiftrue = luaK_goiftrue; +module.exports.luaK_indexed = luaK_indexed; +module.exports.luaK_infix = luaK_infix; +module.exports.luaK_intK = luaK_intK; +module.exports.luaK_jump = luaK_jump; +module.exports.luaK_jumpto = luaK_jumpto; +module.exports.luaK_nil = luaK_nil; +module.exports.luaK_numberK = luaK_numberK; +module.exports.luaK_patchclose = luaK_patchclose; +module.exports.luaK_patchlist = luaK_patchlist; +module.exports.luaK_patchtohere = luaK_patchtohere; +module.exports.luaK_posfix = luaK_posfix; +module.exports.luaK_prefix = luaK_prefix; +module.exports.luaK_reserveregs = luaK_reserveregs; +module.exports.luaK_ret = luaK_ret; +module.exports.luaK_self = luaK_self; +module.exports.luaK_setlist = luaK_setlist; +module.exports.luaK_setmultret = luaK_setmultret; +module.exports.luaK_setoneret = luaK_setoneret; +module.exports.luaK_setreturns = luaK_setreturns; +module.exports.luaK_storevar = luaK_storevar; +module.exports.luaK_stringK = luaK_stringK; \ No newline at end of file diff --git a/src/ldebug.js b/src/ldebug.js index f6d0633..091b896 100644 --- a/src/ldebug.js +++ b/src/ldebug.js @@ -60,7 +60,7 @@ const upvalname = function(p, uv) { }; const funcinfo = function(ar, cl) { - if (cl === null || cl.type == CT.LUA_TCCL) { + if (cl === null || cl.type === CT.LUA_TCCL) { ar.source = "=[JS]"; ar.linedefined = -1; ar.lastlinedefined = -1; @@ -77,7 +77,7 @@ const funcinfo = function(ar, cl) { }; const collectvalidlines = function(L, f) { - if (f === null || f.c.type == CT.LUA_TCCL) { + if (f === null || f.c.type === CT.LUA_TCCL) { L.stack[L.top++] = ldo.nil; assert(L.top <= L.ci.top, "stack overflow"); } else { @@ -123,7 +123,7 @@ const auxgetinfo = function(L, what, ar, f, ci) { } case 'u': { ar.nups = f === null ? 0 : f.c.nupvalues; - if (f === null || f.c.type == CT.LUA_TCCL) { + if (f === null || f.c.type === CT.LUA_TCCL) { ar.isvararg = true; ar.nparams = 0; } else { diff --git a/src/ldo.js b/src/ldo.js index 8d12c9f..4345b15 100644 --- a/src/ldo.js +++ b/src/ldo.js @@ -509,7 +509,7 @@ const f_parser = function(L, data) { let p = new BytecodeParser(data); let cl = p.luaU_undump(L); - assert(cl.nupvalues == cl.p.upvalues.length); + assert(cl.nupvalues === cl.p.upvalues.length); lfunc.luaF_initupvals(L, cl); }; diff --git a/src/lfunc.js b/src/lfunc.js index fde910b..37239b2 100644 --- a/src/lfunc.js +++ b/src/lfunc.js @@ -119,7 +119,7 @@ const luaF_getlocalname = function(f, local_number, pc) { for (let i = 0; i < f.locvars.length && f.locvars[i].startpc <= pc; i++) { if (pc < f.locvars[i].endpc) { /* is variable active? */ local_number--; - if (local_number == 0) + if (local_number === 0) return f.locvars[i].varname; } } diff --git a/src/lobject.js b/src/lobject.js index 1a0df1c..f3078d1 100644 --- a/src/lobject.js +++ b/src/lobject.js @@ -4,6 +4,9 @@ const assert = require('assert'); const ljstype = require('./ljstype.js'); +const ltm = require('./ltm.js'); +const lua = require('./lua.js'); +const lvm = require('./lvm.js'); const CT = require('./lua.js').constant_types; const UpVal = require('./lfunc.js').UpVal; @@ -125,6 +128,7 @@ class Table extends TValue { // Those lua values are used by value, others by reference if (key instanceof TValue && [CT.LUA_TNIL, + CT.LUA_TBOOLEAN, CT.LUA_TSTRING, CT.LUA_TSHRSTR, CT.LUA_TLNGSTR, @@ -349,7 +353,7 @@ const luaO_str2num = function(s) { /* ** converts an integer to a "floating point byte", represented as ** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if -** eeeee != 0 and (xxx) otherwise. +** eeeee !== 0 and (xxx) otherwise. */ const luaO_int2fb = function(x) { let e = 0; /* exponent */ @@ -365,13 +369,90 @@ const luaO_int2fb = function(x) { return ((e+1) << 3) | (x - 8); }; +const intarith = function(L, op, v1, v2) { + switch (op) { + case lua.LUA_OPADD: return (v1 + v2)|0; + case lua.LUA_OPSUB: return (v1 - v2)|0; + case lua.LUA_OPMUL: return (v1 * v2)|0; + case lua.LUA_OPMOD: return (v1 % v2)|0; + case lua.LUA_OPIDIV: return (v1 / v2)|0; + case lua.LUA_OPBAND: return (v1 & v2)|0; + case lua.LUA_OPBOR: return (v1 | v2)|0; + case lua.LUA_OPBXOR: return (v1 ^ v2)|0; + case lua.LUA_OPSHL: return (v1 << v2)|0; + case lua.LUA_OPSHR: return (v1 >> v2)|0; + case lua.LUA_OPUNM: return (-v1)|0; + case lua.LUA_OPBNOT: return (~v1)|0; + } +}; + + +const numarith = function(L, op, v1, v2) { + switch (op) { + case lua.LUA_OPADD: return v1 + v2; + case lua.LUA_OPSUB: return v1 - v2; + case lua.LUA_OPMUL: return v1 * v2; + case lua.LUA_OPDIV: return v1 / v2; + case lua.LUA_OPPOW: return Math.pow(v1, v2); + case lua.LUA_OPIDIV: return (v1 / v2)|0; + case lua.LUA_OPUNM: return -v1; + case lua.LUA_OPMOD: return v1 % v2; + } +}; + +const luaO_arith = function(L, op, p1, p2, res) { + switch (op) { + case lvm.LUA_OPBAND: case lvm.LUA_OPBOR: case lvm.LUA_OPBXOR: + case lvm.LUA_OPSHL: case lvm.LUA_OPSHR: + case lvm.LUA_OPBNOT: { /* operate only on integers */ + let i1 = lvm.tointeger(p1); + let i2 = lvm.tointeger(p2); + if (i1 !== false && i2 !== false) { + res.type = CT.LUA_TNUMINT; + res.value = intarith(L, op, i1, i2); + return; + } + else break; /* go to the end */ + } + case lvm.LUA_OPDIV: case lvm.LUA_OPPOW: { /* operate only on floats */ + let n1 = lvm.tonumber(p1); + let n2 = lvm.tonumber(p2); + if (n1 !== false && n2 !== false) { + res.type = CT.LUA_TNUMFLT; + res.value = numarith(L, op, n1, n2); + return; + } + else break; /* go to the end */ + } + default: { /* other operations */ + let n1 = lvm.tonumber(p1); + let n2 = lvm.tonumber(p2); + if (p1.ttisinteger() && p2.ttisinteger()) { + res.type = CT.LUA_TNUMINT; + res.value = intarith(L, op, p1.value, p2.value); + return; + } + else if (n1 !== false && n2 !== false) { + res.type = CT.LUA_TNUMFLT; + res.value = numarith(L, op, n1, n2); + return; + } + else break; /* go to the end */ + } + } + /* could not perform raw operation; try metamethod */ + assert(L !== null); /* should not fail when folding (compile time) */ + ltm.luaT_trybinTM(L, p1, p2, res, (op - lua.LUA_OPADD) + ltm.TMS.TM_ADD); +}; + module.exports.CClosure = CClosure; module.exports.LClosure = LClosure; +module.exports.TValue = TValue; +module.exports.Table = Table; +module.exports.UTF8BUFFSZ = UTF8BUFFSZ; +module.exports.luaO_arith = luaO_arith; module.exports.luaO_chunkid = luaO_chunkid; module.exports.luaO_hexavalue = luaO_hexavalue; module.exports.luaO_int2fb = luaO_int2fb; module.exports.luaO_str2num = luaO_str2num; -module.exports.luaO_utf8desc = luaO_utf8desc; -module.exports.Table = Table; -module.exports.TValue = TValue; -module.exports.UTF8BUFFSZ = UTF8BUFFSZ; \ No newline at end of file +module.exports.luaO_utf8desc = luaO_utf8desc; \ No newline at end of file diff --git a/src/lopcodes.js b/src/lopcodes.js index d83897c..3f7f63b 100644 --- a/src/lopcodes.js +++ b/src/lopcodes.js @@ -299,7 +299,7 @@ const CREATE_ABx = function(o, a, bc) { return fullins(o << POS_OP | a << POS_A | bc << POS_Bx); }; -const CREATE_Ax = function(o a) { +const CREATE_Ax = function(o, a) { return fullins(o << POS_OP | a << POS_Ax); }; diff --git a/src/lparser.js b/src/lparser.js index 154310b..074542a 100644 --- a/src/lparser.js +++ b/src/lparser.js @@ -22,7 +22,7 @@ const UpVal = lfunc.UpVal; const MAXVARS = 200; const hasmultret = function(k) { - return k == expkind.VCALL || k == expkind.VVARARG; + return k === expkind.VCALL || k === expkind.VVARARG; }; class BlockCnt { @@ -65,6 +65,10 @@ const vkisvar = function(k) { return expkind.VLOCAL <= k && k <= expkind.VINDEXED; }; +const vkisinreg = function(k) { + return k === expkind.VNONRELOC || k === expkind.VLOCAL; +}; + class expdesc { constructor() { this.k = NaN; @@ -586,7 +590,7 @@ const recfield = function(ls, cc) { if (ls.t.token === R.TK_NAME) { checklimit(fs, cc.nh, Number.MAX_SAFE_INTEGER, "items in a constructor"); checkname(ls, key); - } else /* ls->t.token == '[' */ + } else /* ls->t.token === '[' */ yindex(ls, key); cc.nh++; checknext(ls, '='); @@ -1334,9 +1338,9 @@ const funcname = function(ls, v) { /* funcname -> NAME {fieldsel} [':' NAME] */ let ismethod = 0; singlevar(ls, v); - while (ls.t.token == '.') + while (ls.t.token === '.') fieldsel(ls, v); - if (ls.t.token == ':') { + if (ls.t.token === ':') { ismethod = 1; fieldsel(ls, v); } @@ -1392,7 +1396,7 @@ const retstat = function(ls) { else { lcode.luaK_exp2nextreg(fs, e); /* values must go to the stack */ first = fs.nactvar; /* return all active values */ - assert(nret == fs.freereg - first); + assert(nret === fs.freereg - first); } } } @@ -1507,4 +1511,6 @@ const luaY_parser = function(L, z, buff, dyd, name, firstchar) { }; -module.exports.luaY_parser = luaY_parser; \ No newline at end of file +module.exports.expkind = expkind; +module.exports.luaY_parser = luaY_parser; +module.exports.vkisinreg = vkisinreg; \ No newline at end of file diff --git a/src/lstate.js b/src/lstate.js index 9a9c9e0..1fc7765 100644 --- a/src/lstate.js +++ b/src/lstate.js @@ -105,7 +105,7 @@ const init_registry = function(L, g) { /* ** open parts of the state that may cause memory-allocation errors. -** ('g->version' != NULL flags that the state was completely build) +** ('g->version' !== NULL flags that the state was completely build) */ const f_luaopen = function(L) { let g = L.l_G; diff --git a/src/lua.js b/src/lua.js index c7d1fd7..60cc78e 100644 --- a/src/lua.js +++ b/src/lua.js @@ -62,6 +62,25 @@ 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 */ +/* +** Comparison and arithmetic functions +*/ + +const LUA_OPADD = 0; /* ORDER TM, ORDER OP */ +const LUA_OPSUB = 1; +const LUA_OPMUL = 2; +const LUA_OPMOD = 3; +const LUA_OPPOW = 4; +const LUA_OPDIV = 5; +const LUA_OPIDIV = 6; +const LUA_OPBAND = 7; +const LUA_OPBOR = 8; +const LUA_OPBXOR = 9; +const LUA_OPSHL = 10; +const LUA_OPSHR = 11; +const LUA_OPUNM = 12; +const LUA_OPBNOT = 13; + const LUA_OPEQ = 0; const LUA_OPLT = 1; const LUA_OPLE = 2; @@ -121,9 +140,23 @@ module.exports.LUA_INIT_VAR = LUA_INIT_VAR; module.exports.LUA_MINSTACK = LUA_MINSTACK; module.exports.LUA_MULTRET = -1; module.exports.LUA_NUMTAGS = LUA_NUMTAGS; +module.exports.LUA_OPADD = LUA_OPADD; +module.exports.LUA_OPBAND = LUA_OPBAND; +module.exports.LUA_OPBNOT = LUA_OPBNOT; +module.exports.LUA_OPBOR = LUA_OPBOR; +module.exports.LUA_OPBXOR = LUA_OPBXOR; +module.exports.LUA_OPDIV = LUA_OPDIV; module.exports.LUA_OPEQ = LUA_OPEQ; +module.exports.LUA_OPIDIV = LUA_OPIDIV; module.exports.LUA_OPLE = LUA_OPLE; module.exports.LUA_OPLT = LUA_OPLT; +module.exports.LUA_OPMOD = LUA_OPMOD; +module.exports.LUA_OPMUL = LUA_OPMUL; +module.exports.LUA_OPPOW = LUA_OPPOW; +module.exports.LUA_OPSHL = LUA_OPSHL; +module.exports.LUA_OPSHR = LUA_OPSHR; +module.exports.LUA_OPSUB = LUA_OPSUB; +module.exports.LUA_OPUNM = LUA_OPUNM; module.exports.LUA_REGISTRYINDEX = LUA_REGISTRYINDEX; module.exports.LUA_RELEASE = LUA_RELEASE; module.exports.LUA_RIDX_GLOBALS = LUA_RIDX_GLOBALS; diff --git a/src/lvm.js b/src/lvm.js index d987cc1..0ff433e 100644 --- a/src/lvm.js +++ b/src/lvm.js @@ -792,6 +792,10 @@ const luaV_equalobj = function(L, t1, t2) { return L.stack[L.top].l_isfalse() ? 0 : 1; }; +const luaV_rawequalobj = function(t1, t2) { + return luaV_equalobj(null, t1, t2); +}; + const forlimit = function(obj, step) { let stopnow = false; let ilimit = luaV_tointeger(obj, step < 0 ? 2 : 1); @@ -818,9 +822,9 @@ const forlimit = function(obj, step) { /* ** try to convert a value to an integer, rounding according to 'mode': -** mode == 0: accepts only integral values -** mode == 1: takes the floor of the number -** mode == 2: takes the ceil of the number +** mode === 0: accepts only integral values +** mode === 1: takes the floor of the number +** mode === 2: takes the ceil of the number */ const luaV_tointeger = function(obj, mode) { if (obj.ttisfloat()) { @@ -831,7 +835,7 @@ const luaV_tointeger = function(obj, mode) { if (mode === 0) return false; /* fails if mode demands integral value */ else if (mode > 1) /* needs ceil? */ - f += 1; /* convert floor to ceil (remember: n != f) */ + f += 1; /* convert floor to ceil (remember: n !== f) */ } return f|0; @@ -1072,29 +1076,30 @@ const luaV_finishset = function(L, t, key, val, slot, recur) { } -module.exports.dojump = dojump; -module.exports.donextjump = donextjump; -module.exports.forlimit = forlimit; -module.exports.gettable = gettable; -module.exports.l_strcmp = l_strcmp; -module.exports.LEintfloat = LEintfloat; -module.exports.LEnum = LEnum; -module.exports.LTintfloat = LTintfloat; -module.exports.LTnum = LTnum; -module.exports.luaV_concat = luaV_concat; -module.exports.luaV_equalobj = luaV_equalobj; -module.exports.luaV_execute = luaV_execute; -module.exports.luaV_finishOp = luaV_finishOp; -module.exports.luaV_finishset = luaV_finishset; -module.exports.luaV_lessequal = luaV_lessequal; -module.exports.luaV_lessthan = luaV_lessthan; -module.exports.luaV_objlen = luaV_objlen; -module.exports.luaV_tointeger = luaV_tointeger; -module.exports.RA = RA; -module.exports.RB = RB; -module.exports.RC = RC; -module.exports.RKB = RKB; -module.exports.RKC = RKC; -module.exports.settable = settable; -module.exports.tointeger = tointeger; -module.exports.tonumber = tonumber; \ No newline at end of file +module.exports.LEintfloat = LEintfloat; +module.exports.LEnum = LEnum; +module.exports.LTintfloat = LTintfloat; +module.exports.LTnum = LTnum; +module.exports.RA = RA; +module.exports.RB = RB; +module.exports.RC = RC; +module.exports.RKB = RKB; +module.exports.RKC = RKC; +module.exports.dojump = dojump; +module.exports.donextjump = donextjump; +module.exports.forlimit = forlimit; +module.exports.gettable = gettable; +module.exports.l_strcmp = l_strcmp; +module.exports.luaV_concat = luaV_concat; +module.exports.luaV_equalobj = luaV_equalobj; +module.exports.luaV_execute = luaV_execute; +module.exports.luaV_finishOp = luaV_finishOp; +module.exports.luaV_finishset = luaV_finishset; +module.exports.luaV_lessequal = luaV_lessequal; +module.exports.luaV_lessthan = luaV_lessthan; +module.exports.luaV_objlen = luaV_objlen; +module.exports.luaV_rawequalobj = luaV_rawequalobj; +module.exports.luaV_tointeger = luaV_tointeger; +module.exports.settable = settable; +module.exports.tointeger = tointeger; +module.exports.tonumber = tonumber; \ No newline at end of file diff --git a/tests/llex.js b/tests/llex.js index 725d3f8..1457af3 100644 --- a/tests/llex.js +++ b/tests/llex.js @@ -45,7 +45,7 @@ test('basic lexing: TK_RETURN, TK_STRING', function (t) { readTokens, [R.TK_RETURN, R.TK_STRING], "Correct tokens found" - ) + ); }); @@ -93,6 +93,6 @@ test('TK_LOCAL, TK_NAME, TK_INT', function (t) { R.TK_RETURN, R.TK_NAME, '(', R.TK_STRING, ')', '+', R.TK_INT ], "Correct tokens found" - ) + ); }); \ No newline at end of file diff --git a/todo-temp.md b/todo-temp.md deleted file mode 100644 index d86e75c..0000000 --- a/todo-temp.md +++ /dev/null @@ -1,25 +0,0 @@ -- [ ] luaK_checkstack -- [ ] luaK_codek -- [ ] luaK_dischargevars -- [ ] luaK_exp2RK -- [ ] luaK_exp2anyreg -- [ ] luaK_exp2anyregup -- [ ] luaK_exp2nextreg -- [ ] luaK_exp2val -- [ ] luaK_fixline -- [ ] luaK_goiffalse -- [ ] luaK_goiftrue -- [ ] luaK_indexed -- [ ] luaK_infix -- [ ] luaK_intK -- [ ] luaK_jumpto -- [ ] luaK_posfix -- [ ] luaK_prefix -- [ ] luaK_reserveregs -- [ ] luaK_self -- [ ] luaK_setlist -- [ ] luaK_setmultret -- [ ] luaK_setoneret -- [ ] luaK_setreturns -- [ ] luaK_storevar -- [ ] luaK_stringK \ No newline at end of file -- cgit v1.2.3-54-g00ecf From ae8b95ee9c3871f506b20c74367fdc9e8cb098e2 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Wed, 1 Mar 2017 10:23:32 +0100 Subject: lua_load will load both binary and text --- src/lapi.js | 31 +++++++++++----------- src/lcode.js | 50 +++++++++++++++++++++++++++++++++-- src/ldo.js | 78 +++++++++++++++++++++++++++++++++++++++++-------------- src/llex.js | 46 ++++++++++++++++++-------------- src/lobject.js | 50 +---------------------------------- src/lparser.js | 44 ++++++++++++++++++++++++++++++- src/lua.js | 32 +++++++++++++---------- src/lundump.js | 3 ++- tests/lapi.js | 1 - tests/lbaselib.js | 1 - tests/lcorolib.js | 1 - tests/ldebug.js | 1 - tests/llex.js | 1 - tests/lmathlib.js | 1 - tests/load.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/ltablib.js | 1 - tests/ltm.js | 1 - tests/lvm.js | 1 - tests/tests.js | 1 - 19 files changed, 282 insertions(+), 130 deletions(-) create mode 100644 tests/load.js diff --git a/src/lapi.js b/src/lapi.js index 9a05638..36da7c6 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -2,16 +2,17 @@ const assert = require('assert'); +const ldebug = require('./ldebug.js'); const ldo = require('./ldo.js'); +const lfunc = require('./lfunc.js'); +const llex = require('./llex.js'); const lobject = require('./lobject.js'); +const lstate = require('./lstate.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'); -const ldebug = require('./ldebug.js'); +const lvm = require('./lvm.js'); const MAXUPVAL = lfunc.MAXUPVAL; const CT = lua.constant_types; const TS = lua.thread_status; @@ -137,7 +138,7 @@ const lua_settop = function(L, idx) { const lua_pop = function(L, n) { lua_settop(L, -n - 1); -} +}; const reverse = function(L, from, to) { for (; from < to; from++, to--) { @@ -356,7 +357,7 @@ const lua_settable = function(L, idx) { }; const lua_setfield = function(L, idx, k) { - auxsetstr(L, index2addr(L, idx), k) + auxsetstr(L, index2addr(L, idx), k); }; const lua_seti = function(L, idx, n) { @@ -514,11 +515,11 @@ const lua_rawlen = function(L, idx) { }; const lua_tointeger = function(L, idx) { - return lvm.tointeger(index2addr(L, idx)) + return lvm.tointeger(index2addr(L, idx)); }; const lua_tonumber = function(L, idx) { - return lvm.tonumber(index2addr(L, idx)) + return lvm.tonumber(index2addr(L, idx)); }; const lua_tothread = function(L, idx) { @@ -596,7 +597,7 @@ const lua_typename = function(L, t) { const lua_isnoneornil = function(L, n) { return lua_type(L, n) <= 0; -} +}; const lua_istable = function(L, idx) { return index2addr(L, idx).ttistable(); @@ -625,13 +626,14 @@ const lua_rawequal = function(L, index1, index2) { ** 'load' and 'call' functions (run Lua code) */ -const lua_load = function(L, data, chunckname) { +// TODO: reader is ignored because we don't implement ZIO +const lua_load = function(L, reader, data, chunckname, mode) { + let z = new llex.MBuffer(data); if (!chunckname) chunckname = "?"; - - let status = ldo.luaD_protectedparser(L, data, chunckname); - if (status === TS.LUA_OK) { + let status = ldo.luaD_protectedparser(L, z, chunckname, mode); + if (status === TS.LUA_OK) { /* no errors? */ let f = L.stack[L.top - 1]; /* get newly created function */ - if (f.nupvalues >= 1) { /* does it have an upvalue? */ + if (f.nupvalues >= 1) { /* does it have an upvalue? */ /* get global table from registry */ let reg = L.l_G.l_registry; let gt = reg.value.get(lua.LUA_RIDX_GLOBALS - 1); @@ -639,7 +641,6 @@ const lua_load = function(L, data, chunckname) { f.upvals[0].u.value = gt; } } - return status; }; diff --git a/src/lcode.js b/src/lcode.js index c2134ff..9b48d4d 100644 --- a/src/lcode.js +++ b/src/lcode.js @@ -5,14 +5,60 @@ const assert = require('assert'); const llex = require('./llex.js'); const llimit = require('./llimit.js'); const lobject = require('./lobject.js'); -const lopcode = require('./lopcode.js'); +const lopcode = require('./lopcodes.js'); const lparser = require('./lparser.js'); +const ltm = require('./ltm.js'); const lua = require('./lua.js'); const lvm = require('./lvm.js'); const CT = lua.constants_type; const OpCodesI = lopcode.OpCodesI; const TValue = lobject.TValue; +const luaO_arith = function(L, op, p1, p2, res) { + switch (op) { + case lvm.LUA_OPBAND: case lvm.LUA_OPBOR: case lvm.LUA_OPBXOR: + case lvm.LUA_OPSHL: case lvm.LUA_OPSHR: + case lvm.LUA_OPBNOT: { /* operate only on integers */ + let i1 = lvm.tointeger(p1); + let i2 = lvm.tointeger(p2); + if (i1 !== false && i2 !== false) { + res.type = CT.LUA_TNUMINT; + res.value = lobject.intarith(L, op, i1, i2); + return; + } + else break; /* go to the end */ + } + case lvm.LUA_OPDIV: case lvm.LUA_OPPOW: { /* operate only on floats */ + let n1 = lvm.tonumber(p1); + let n2 = lvm.tonumber(p2); + if (n1 !== false && n2 !== false) { + res.type = CT.LUA_TNUMFLT; + res.value = lobject.numarith(L, op, n1, n2); + return; + } + else break; /* go to the end */ + } + default: { /* other operations */ + let n1 = lvm.tonumber(p1); + let n2 = lvm.tonumber(p2); + if (p1.ttisinteger() && p2.ttisinteger()) { + res.type = CT.LUA_TNUMINT; + res.value = lobject.intarith(L, op, p1.value, p2.value); + return; + } + else if (n1 !== false && n2 !== false) { + res.type = CT.LUA_TNUMFLT; + res.value = lobject.numarith(L, op, n1, n2); + return; + } + else break; /* go to the end */ + } + } + /* could not perform raw operation; try metamethod */ + assert(L !== null); /* should not fail when folding (compile time) */ + ltm.luaT_trybinTM(L, p1, p2, res, (op - lua.LUA_OPADD) + ltm.TMS.TM_ADD); +}; + /* Maximum number of registers in a Lua function (must fit in 8 bits) */ const MAXREGS = 255; @@ -964,7 +1010,7 @@ const constfolding = function(fs, op, e1, e2) { let res = new TValue(); if (!tonumeral(e1, v1) || !tonumeral(e2, v2) || !validop(op, v1, v2)) return 0; /* non-numeric operands or not safe to fold */ - lobject.luaO_arith(fs.ls.L, op, v1, v2, res); /* does operation */ + luaO_arith(fs.ls.L, op, v1, v2, res); /* does operation */ if (res.ttisinteger()) { e1.k = ek.VKINT; e1.u.ival = res.value; diff --git a/src/ldo.js b/src/ldo.js index 4345b15..f78cc9c 100644 --- a/src/ldo.js +++ b/src/ldo.js @@ -3,15 +3,18 @@ const assert = require('assert'); -const lua = require('./lua.js'); +const BytecodeParser = require('./lundump.js'); +const lapi = require('./lapi.js'); +const ldebug = require('./ldebug.js'); +const lfunc = require('./lfunc.js'); +const llex = require('./llex.js'); +const llimit = require('./llimit.js'); const lobject = require('./lobject.js'); +const lparser = require('./lparser.js'); const lstate = require('./lstate.js'); -const llimit = require('./llimit.js'); const ltm = require('./ltm.js'); +const lua = require('./lua.js'); const lvm = require('./lvm.js'); -const lfunc = require('./lfunc.js'); -const BytecodeParser = require('./lundump.js'); -const ldebug = require('./ldebug.js'); const CT = lua.constant_types; const TS = lua.thread_status; const LUA_MULTRET = lua.LUA_MULTRET; @@ -475,7 +478,7 @@ const lua_yield = function(L, n) { const luaD_pcall = function(L, func, u, old_top, ef) { let old_ci = L.ci; - // TODO: lu_byte old_allowhooks = L->allowhook; + let old_allowhooks = L.allowhook; let old_nny = L.nny; let old_errfunc = L.errfunc; L.errfunc = ef; @@ -486,7 +489,7 @@ const luaD_pcall = function(L, func, u, old_top, ef) { lfunc.luaF_close(L, old_top); seterrorobj(L, status, old_top); L.ci = old_ci; - // TODO: L->allowhook = old_allowhooks; + L.allowhook = old_allowhooks; L.nny = old_nny; } @@ -504,31 +507,64 @@ const luaD_callnoyield = function(L, off, nResults) { L.nny--; }; -// TODO: since we only handle binary, no need for a reader or mode -const f_parser = function(L, data) { - let p = new BytecodeParser(data); - let cl = p.luaU_undump(L); +/* +** Execute a protected parser. +*/ +class SParser { + constructor() { /* data to 'f_parser' */ + this.z = new llex.MBuffer(); + this.buff = new llex.MBuffer(); /* dynamic structure used by the scanner */ + this.dyd = new lparser.Dyndata(); /* dynamic structures used by the parser */ + this.mode = null; + this.name = null; + } +} - assert(cl.nupvalues === cl.p.upvalues.length); +const checkmode = function(L, mode, x) { + if (mode && mode !== x) { + lapi.lua_pushstring(L, `attempt to load a ${x} chunk (mode is '${mode}')`); + luaD_throw(L, TS.LUA_ERRSYNTAX); + } +}; + +const f_parser = function(L, p) { + let cl; + let c = p.z.getc(); /* read first character */ + if (String.fromCharCode(c) === lua.LUA_SIGNATURE.charAt(0)) { + checkmode(L, p.mode, "binary"); + cl = new BytecodeParser(p.z.buffer).luaU_undump(L); + } else { + checkmode(L, p.mode, "text"); + cl = lparser.luaY_parser(L, p.z, p.buff, p.dyd, p.name, c); + } + assert(cl.nupvalues === cl.p.upvalues.length); lfunc.luaF_initupvals(L, cl); }; -const luaD_protectedparser = function(L, data, name) { - L.nny++; +const luaD_protectedparser = function(L, z, name, mode) { + let p = new SParser(); + L.nny++; /* cannot yield during parsing */ + + p.z = z; + p.name = name; + p.mode = mode; + p.dyd.actvar.arr = null; + p.dyd.actvar.size = 0; + p.dyd.gt.arr = null; + p.dyd.gt.size = 0; + p.dyd.label.arr = null; + p.dyd.label.size = 0; - let status = luaD_pcall(L, f_parser, data, L.top, L.errfunc); + let status = luaD_pcall(L, f_parser, p, L.top, L.errfunc); L.nny--; return status; }; +module.exports.SParser = SParser; module.exports.adjust_varargs = adjust_varargs; -module.exports.lua_isyieldable = lua_isyieldable; -module.exports.lua_resume = lua_resume; -module.exports.lua_yield = lua_yield; -module.exports.lua_yieldk = lua_yieldk; module.exports.luaD_call = luaD_call; module.exports.luaD_callnoyield = luaD_callnoyield; module.exports.luaD_pcall = luaD_pcall; @@ -537,6 +573,10 @@ module.exports.luaD_precall = luaD_precall; module.exports.luaD_protectedparser = luaD_protectedparser; module.exports.luaD_rawrunprotected = luaD_rawrunprotected; module.exports.luaD_throw = luaD_throw; +module.exports.lua_isyieldable = lua_isyieldable; +module.exports.lua_resume = lua_resume; +module.exports.lua_yield = lua_yield; +module.exports.lua_yieldk = lua_yieldk; module.exports.moveresults = moveresults; module.exports.nil = nil; module.exports.stackerror = stackerror; diff --git a/src/llex.js b/src/llex.js index 59bc4a4..73cda58 100644 --- a/src/llex.js +++ b/src/llex.js @@ -1,17 +1,18 @@ "use strict"; -const assert = require('assert'); - -const lapi = require('./lapi.js'); -const lauxlib = require('./lauxlib.js'); -const ldebug = require('./ldebug.js'); -const ldo = require('./ldo.js'); -const ljstype = require('./ljstype'); -const lobject = require('./lobject'); -const lua = require('./lua.js'); -const TValue = lobject.TValue; -const CT = lua.constant_types; -const TS = lua.thread_status; +const DataView = require('buffer-dataview'); +const assert = require('assert'); + +const lapi = require('./lapi.js'); +const lauxlib = require('./lauxlib.js'); +const ldebug = require('./ldebug.js'); +const ldo = require('./ldo.js'); +const ljstype = require('./ljstype'); +const lobject = require('./lobject'); +const lua = require('./lua.js'); +const TValue = lobject.TValue; +const CT = lua.constant_types; +const TS = lua.thread_status; const FIRST_RESERVED = 257; @@ -72,11 +73,18 @@ const luaX_tokens = [ const NUM_RESERVED = Object.keys(RESERVED).length; class MBuffer { - constructor(string) { - this.buffer = string ? string.split('') : []; - this.n = this.buffer.length; + constructor(data) { + this.buffer = typeof data === "string" ? data.split('') : (data ? data : []); + this.n = this.buffer instanceof DataView ? this.buffer.byteLength : this.buffer.length; this.off = 0; } + + getc() { + if (this.buffer instanceof DataView) + return this.n-- > 0 ? this.buffer.getUint8(this.off++, true) : -1; + + return this.n-- > 0 ? this.buffer[this.off++] : -1; + } } class SemInfo { @@ -90,7 +98,7 @@ class SemInfo { class Token { constructor() { this.token = NaN; - this.seminfo = null; + this.seminfo = new SemInfo(); } } @@ -101,8 +109,8 @@ class LexState { this.current = NaN; /* current character (charint) */ this.linenumber = NaN; /* input line counter */ this.lastline = NaN; /* line of last token 'consumed' */ - this.t = null; /* current token */ - this.lookahead = null; /* look ahead token */ + this.t = new Token(); /* current token */ + this.lookahead = new Token(); /* look ahead token */ this.fs = null; /* current function (parser) */ this.L = null; this.z = new MBuffer(); @@ -140,7 +148,7 @@ const currIsNewline = function(ls) { }; const next = function(ls) { - ls.current = ls.z.n-- > 0 ? ls.z.buffer[ls.z.off++] : -1; + ls.current = ls.z.getc(); }; const save_and_next = function(ls) { diff --git a/src/lobject.js b/src/lobject.js index f3078d1..f748416 100644 --- a/src/lobject.js +++ b/src/lobject.js @@ -4,10 +4,8 @@ const assert = require('assert'); const ljstype = require('./ljstype.js'); -const ltm = require('./ltm.js'); const lua = require('./lua.js'); -const lvm = require('./lvm.js'); -const CT = require('./lua.js').constant_types; +const CT = lua.constant_types; const UpVal = require('./lfunc.js').UpVal; class TValue { @@ -400,57 +398,11 @@ const numarith = function(L, op, v1, v2) { } }; -const luaO_arith = function(L, op, p1, p2, res) { - switch (op) { - case lvm.LUA_OPBAND: case lvm.LUA_OPBOR: case lvm.LUA_OPBXOR: - case lvm.LUA_OPSHL: case lvm.LUA_OPSHR: - case lvm.LUA_OPBNOT: { /* operate only on integers */ - let i1 = lvm.tointeger(p1); - let i2 = lvm.tointeger(p2); - if (i1 !== false && i2 !== false) { - res.type = CT.LUA_TNUMINT; - res.value = intarith(L, op, i1, i2); - return; - } - else break; /* go to the end */ - } - case lvm.LUA_OPDIV: case lvm.LUA_OPPOW: { /* operate only on floats */ - let n1 = lvm.tonumber(p1); - let n2 = lvm.tonumber(p2); - if (n1 !== false && n2 !== false) { - res.type = CT.LUA_TNUMFLT; - res.value = numarith(L, op, n1, n2); - return; - } - else break; /* go to the end */ - } - default: { /* other operations */ - let n1 = lvm.tonumber(p1); - let n2 = lvm.tonumber(p2); - if (p1.ttisinteger() && p2.ttisinteger()) { - res.type = CT.LUA_TNUMINT; - res.value = intarith(L, op, p1.value, p2.value); - return; - } - else if (n1 !== false && n2 !== false) { - res.type = CT.LUA_TNUMFLT; - res.value = numarith(L, op, n1, n2); - return; - } - else break; /* go to the end */ - } - } - /* could not perform raw operation; try metamethod */ - assert(L !== null); /* should not fail when folding (compile time) */ - ltm.luaT_trybinTM(L, p1, p2, res, (op - lua.LUA_OPADD) + ltm.TMS.TM_ADD); -}; - module.exports.CClosure = CClosure; module.exports.LClosure = LClosure; module.exports.TValue = TValue; module.exports.Table = Table; module.exports.UTF8BUFFSZ = UTF8BUFFSZ; -module.exports.luaO_arith = luaO_arith; module.exports.luaO_chunkid = luaO_chunkid; module.exports.luaO_hexavalue = luaO_hexavalue; module.exports.luaO_int2fb = luaO_int2fb; diff --git a/src/lparser.js b/src/lparser.js index 074542a..40d6c88 100644 --- a/src/lparser.js +++ b/src/lparser.js @@ -7,7 +7,7 @@ const lfunc = require('./lfunc.js'); const llex = require('./llex.js'); const llimit = require('./llimit.js'); const lobject = require('./lobject.js'); -const lopcode = require('./lopcode.js'); +const lopcode = require('./lopcodes.js'); const lua = require('./lua.js'); const BinOpr = lcode.BinOpr; const CT = lua.constants_type; @@ -106,6 +106,47 @@ class FuncState { } } + /* description of active local variable */ +class Vardesc { + constructor() { + this.idx = NaN; /* variable index in stack */ + } +} + + +/* description of pending goto statements and label statements */ +class Labeldesc { + constructor() { + this.name = null; /* label identifier */ + this.pc = NaN; /* position in code */ + this.line = NaN; /* line where it appeared */ + this.nactvar = NaN; /* local level where it appears in current block */ + } +} + + +/* list of labels or gotos */ +class Labellist { + constructor() { + this.arr = []; /* array */ + this.n = NaN; /* number of entries in use */ + this.size = NaN; /* array size */ + } +} + +/* dynamic structures used by the parser */ +class Dyndata { + constructor() { + this.actvar = { /* list of active local variables */ + arr: [], + n: NaN, + size: NaN + }; + this.gt = new Labellist(); + this.label = new Labellist(); + } +} + const semerror = function(ls, msg) { ls.t.token = 0; /* remove "near " from final message */ llex.luaX_syntaxerror(ls, msg); @@ -1511,6 +1552,7 @@ const luaY_parser = function(L, z, buff, dyd, name, firstchar) { }; +module.exports.Dyndata = Dyndata; module.exports.expkind = expkind; module.exports.luaY_parser = luaY_parser; module.exports.vkisinreg = vkisinreg; \ No newline at end of file diff --git a/src/lua.js b/src/lua.js index 60cc78e..9367746 100644 --- a/src/lua.js +++ b/src/lua.js @@ -5,6 +5,9 @@ const assert = require('assert'); const lualib = require('./lualib.js'); const luaconf = require('./luaconf.js'); +/* mark for precompiled code ('Lua') */ +const LUA_SIGNATURE = "\x1bLua"; + const LUA_VERSION_MAJOR = "5"; const LUA_VERSION_MINOR = "3"; const LUA_VERSION_NUM = 503; @@ -106,21 +109,21 @@ const print_version = function() { class lua_Debug { constructor() { - // int event; - // const char *name; /* (n) */ - // const char *namewhat; /* (n) 'global', 'local', 'field', 'method' */ - // const char *what; /* (S) 'Lua', 'C', 'main', 'tail' */ - // const char *source; /* (S) */ - // int currentline; /* (l) */ - // int linedefined; /* (S) */ - // int lastlinedefined; /* (S) */ - // unsigned char nups; /* (u) number of upvalues */ - // unsigned char nparams;/* (u) number of parameters */ - // char isvararg; /* (u) */ - // char istailcall; /* (t) */ - // char short_src[LUA_IDSIZE]; /* (S) */ + this.event = NaN; + this.name = null; /* (n) */ + this.namewhat = null; /* (n) 'global', 'local', 'field', 'method' */ + this.what = null; /* (S) 'Lua', 'C', 'main', 'tail' */ + this.source = null; /* (S) */ + this.currentline = NaN; /* (l) */ + this.linedefined = NaN; /* (S) */ + this.lastlinedefined = NaN; /* (S) */ + this.nups = NaN; /* (u) number of upvalues */ + this.nparams = NaN; /* (u) number of parameters */ + this.isvararg = NaN; /* (u) */ + this.istailcall = NaN; /* (t) */ + this.short_src = null; /* (S) */ /* private part */ - // struct CallInfo *i_ci; /* active function */ + this.i_ci = null; /* active function */ } } @@ -162,6 +165,7 @@ module.exports.LUA_RELEASE = LUA_RELEASE; module.exports.LUA_RIDX_GLOBALS = LUA_RIDX_GLOBALS; module.exports.LUA_RIDX_LAST = LUA_RIDX_LAST; module.exports.LUA_RIDX_MAINTHREAD = LUA_RIDX_MAINTHREAD; +module.exports.LUA_SIGNATURE = LUA_SIGNATURE; module.exports.LUA_VERSION = LUA_VERSION; module.exports.LUA_VERSION_MAJOR = LUA_VERSION_MAJOR; module.exports.LUA_VERSION_MINOR = LUA_VERSION_MINOR; diff --git a/src/lundump.js b/src/lundump.js index b79e6ea..5a6d8c4 100644 --- a/src/lundump.js +++ b/src/lundump.js @@ -5,6 +5,7 @@ const DataView = require('buffer-dataview'); const fs = require('fs'); const assert = require('assert'); +const lua = require('./lua.js'); const LClosure = require('./lobject.js').LClosure; const TValue = require('./lobject.js').TValue; const Proto = require('./lfunc.js').Proto; @@ -256,7 +257,7 @@ class BytecodeParser { } checkHeader() { - if (this.readString(4) !== "\x1bLua") + if (this.readString(4) !== lua.LUA_SIGNATURE) throw new Error("bad LUA_SIGNATURE, expected 'Lua'"); if (this.readByte() !== 0x53) diff --git a/tests/lapi.js b/tests/lapi.js index 30366d5..6761425 100644 --- a/tests/lapi.js +++ b/tests/lapi.js @@ -1,4 +1,3 @@ -/*jshint esversion: 6 */ "use strict"; const test = require('tape'); diff --git a/tests/lbaselib.js b/tests/lbaselib.js index be5cdad..72a2f15 100644 --- a/tests/lbaselib.js +++ b/tests/lbaselib.js @@ -1,4 +1,3 @@ -/*jshint esversion: 6 */ "use strict"; const test = require('tape'); diff --git a/tests/lcorolib.js b/tests/lcorolib.js index 78024dd..022cda6 100644 --- a/tests/lcorolib.js +++ b/tests/lcorolib.js @@ -1,4 +1,3 @@ -/*jshint esversion: 6 */ "use strict"; const test = require('tape'); diff --git a/tests/ldebug.js b/tests/ldebug.js index 491b8ba..45194e9 100644 --- a/tests/ldebug.js +++ b/tests/ldebug.js @@ -1,4 +1,3 @@ -/*jshint esversion: 6 */ "use strict"; const test = require('tape'); diff --git a/tests/llex.js b/tests/llex.js index 1457af3..8b69acc 100644 --- a/tests/llex.js +++ b/tests/llex.js @@ -1,4 +1,3 @@ -/*jshint esversion: 6 */ "use strict"; const test = require('tape'); diff --git a/tests/lmathlib.js b/tests/lmathlib.js index ba6853c..48ae1a2 100644 --- a/tests/lmathlib.js +++ b/tests/lmathlib.js @@ -1,4 +1,3 @@ -/*jshint esversion: 6 */ "use strict"; const test = require('tape'); diff --git a/tests/load.js b/tests/load.js new file mode 100644 index 0000000..4df3f6a --- /dev/null +++ b/tests/load.js @@ -0,0 +1,68 @@ +"use strict"; + +const test = require('tape'); +const beautify = require('js-beautify').js_beautify; + +const tests = require("./tests.js"); +const toByteCode = tests.toByteCode; + +const lapi = require("../src/lapi.js"); +const lauxlib = require("../src/lauxlib.js"); +const linit = require('../src/linit.js'); + +test('lua_load, binary mode', function (t) { + let luaCode = ` + return "hello world" + `, L; + + t.plan(2); + + t.doesNotThrow(function () { + + let bc = toByteCode(luaCode).dataView; + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lapi.lua_load(L, null, bc, "test-math", "binary"); + + lapi.lua_call(L, 0, -1); + + }, "JS Lua program ran without error"); + + t.strictEqual( + lapi.lua_tostring(L, -1), + "hello world", + "Correct element(s) on the stack" + ); + +}); + + +test('lua_load, text mode', function (t) { + let luaCode = ` + return "hello world" + `, L; + + t.plan(2); + + // t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lapi.lua_load(L, null, luaCode, "test-math", "text"); + + lapi.lua_call(L, 0, -1); + + // }, "JS 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/ltablib.js b/tests/ltablib.js index fa15d6a..2ebf98c 100644 --- a/tests/ltablib.js +++ b/tests/ltablib.js @@ -1,4 +1,3 @@ -/*jshint esversion: 6 */ "use strict"; const test = require('tape'); diff --git a/tests/ltm.js b/tests/ltm.js index a05d2b5..d749d7b 100644 --- a/tests/ltm.js +++ b/tests/ltm.js @@ -1,4 +1,3 @@ -/*jshint esversion: 6 */ "use strict"; const test = require('tape'); diff --git a/tests/lvm.js b/tests/lvm.js index 4f02ed5..5d4eae1 100644 --- a/tests/lvm.js +++ b/tests/lvm.js @@ -1,4 +1,3 @@ -/*jshint esversion: 6 */ "use strict"; const test = require('tape'); diff --git a/tests/tests.js b/tests/tests.js index 0963159..a47aee5 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -1,4 +1,3 @@ -/*jshint esversion: 6 */ "use strict"; const fs = require('fs'); -- cgit v1.2.3-54-g00ecf From 444182dbbb18f44cf7cafc378f092c28006be365 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Wed, 1 Mar 2017 11:51:00 +0100 Subject: Loading tests (binary/text) --- src/lcode.js | 20 ++++++++------------ src/lfunc.js | 4 ++++ src/llex.js | 4 ++-- src/lopcodes.js | 34 ++++++++++++++++++++++++++-------- src/lparser.js | 10 ++++------ src/lua.js | 8 +++++++- src/lualib.js | 25 ++++++++++--------------- tests/lapi.js | 6 +++--- tests/lbaselib.js | 34 +++++++++++++++++----------------- tests/lcorolib.js | 10 +++++----- tests/ldebug.js | 14 +++++++------- tests/lexparse.js | 39 +++++++++++++++++++++++++++++++++++++++ tests/lmathlib.js | 26 +++++++++++++------------- tests/load.js | 8 ++++---- tests/ltablib.js | 16 ++++++++-------- 15 files changed, 157 insertions(+), 101 deletions(-) create mode 100644 tests/lexparse.js diff --git a/src/lcode.js b/src/lcode.js index 9b48d4d..d8b0252 100644 --- a/src/lcode.js +++ b/src/lcode.js @@ -10,7 +10,7 @@ const lparser = require('./lparser.js'); const ltm = require('./ltm.js'); const lua = require('./lua.js'); const lvm = require('./lvm.js'); -const CT = lua.constants_type; +const CT = lua.CT; const OpCodesI = lopcode.OpCodesI; const TValue = lobject.TValue; @@ -483,7 +483,7 @@ const freeexps = function(fs, e1, e2) { const addk = function(fs, key, v) { let f = fs.f; let idx = fs.ls.h.__index(fs.ls.h, key); /* index scanner table */ - if (idx) { /* is there an index there? */ + if (idx && !idx.ttisnil()) { /* is there an index there? */ /* correct value? (warning: must distinguish floats from integers!) */ if (f.k[idx].ttype() === v.ttype() && f.k[idx].value === v.value) return idx; /* reuse index */ @@ -500,8 +500,7 @@ const addk = function(fs, key, v) { ** Add a string to list of constants and return its index. */ const luaK_stringK = function(fs, s) { - let o = new TValue(CT.LUA_TLNGSTR, s); - return addk(fs, o, o); /* use string itself as key */ + return addk(fs, s, s); /* use string itself as key */ }; @@ -512,8 +511,8 @@ const luaK_stringK = function(fs, s) { ** are no "precision" problems. */ const luaK_intK = function(fs, n) { - let k = new TValue(CT.LUA_TLNGSTR, `n`); - let o = new TValue(CT.LUA_TNUMINT, n); + let k = new TValue(CT.LUA_TLNGSTR, `${n.value}`); + let o = new TValue(CT.LUA_TNUMINT, n.value); return addk(fs, k, o); }; @@ -521,8 +520,7 @@ const luaK_intK = function(fs, n) { ** Add a float to list of constants and return its index. */ const luaK_numberK = function(fs, r) { - let o = new TValue(CT.LUA_TNUMFLT, r); - return addk(fs, o, o); /* use number itself as key */ + return addk(fs, r, r); /* use number itself as key */ }; @@ -530,8 +528,7 @@ const luaK_numberK = function(fs, r) { ** Add a boolean to list of constants and return its index. */ const boolK = function(fs, b) { - let o = new TValue(CT.LUA_TBOOLEAN, b); - return addk(fs, o, o); /* use boolean itself as key */ + return addk(fs, b, b); /* use boolean itself as key */ }; @@ -539,8 +536,7 @@ const boolK = function(fs, b) { ** Add nil to list of constants and return its index. */ const nilK = function(fs) { - let o = new TValue(CT.LUA_TNIL, null); - return addk(fs, o, o); + return addk(fs, new TValue(CT.LUA_TLNGSTR, `null`), new TValue(CT.LUA_TNIL, null)); }; /* diff --git a/src/lfunc.js b/src/lfunc.js index 37239b2..227606d 100644 --- a/src/lfunc.js +++ b/src/lfunc.js @@ -102,12 +102,16 @@ const luaF_close = function(L, level) { } }; +/* +** fill a closure with new closed upvalues +*/ const luaF_initupvals = function(L, cl) { for (let i = 0; i < cl.nupvalues; i++) { let uv = new UpVal(L); uv.refcount = 1; uv.u.value = null; uv.v = uv.u.value; + cl.upvals[i] = uv; } }; diff --git a/src/llex.js b/src/llex.js index 73cda58..63a1fbb 100644 --- a/src/llex.js +++ b/src/llex.js @@ -453,7 +453,7 @@ const read_string = function(ls, del, seminfo) { } } save_and_next(ls); /* skip delimiter */ - seminfo.ts = new TValue(CT.LUA_TLNGSTR, ls.buff.buffer.slice(1).join('')); + seminfo.ts = new TValue(CT.LUA_TLNGSTR, ls.buff.buffer.slice(1, ls.buff.buffer.length-1).join('')); }; const isreserved = function(w) { @@ -566,7 +566,7 @@ const llex = function(ls, seminfo) { let ts = new TValue(CT.LUA_TLNGSTR, ls.buff.buffer.join('')); seminfo.ts = ts; - let kidx = luaX_tokens.slice(0, 22).indexOf(ts.value) + let kidx = luaX_tokens.slice(0, 22).indexOf(ts.value); if (kidx >= 0) /* reserved word? */ return kidx + FIRST_RESERVED; else diff --git a/src/lopcodes.js b/src/lopcodes.js index 3f7f63b..bfcd993 100644 --- a/src/lopcodes.js +++ b/src/lopcodes.js @@ -209,6 +209,7 @@ const MAXARG_A = ((1 << SIZE_A) - 1); const MAXARG_B = ((1 << SIZE_B) - 1); const MAXARG_C = ((1 << SIZE_C) - 1); +/* this bit 1 means constant (0 means register) */ const BITRK = (1 << (SIZE_B - 1)); /* @@ -216,14 +217,22 @@ const BITRK = (1 << (SIZE_B - 1)); */ const NO_REG = MAXARG_A; +/* test whether value is a constant */ const ISK = function (x) { return x & BITRK; }; +/* gets the index of the constant */ const INDEXK = function (r) { return r & ~BITRK; }; +/* code a constant index as a RK value */ +const RKASK = function(x) { + return x | BITRK; +}; + + /* creates a mask with 'n' 1 bits at position 'p' */ const MASK1 = function(n, p) { return ((~((~0)<<(n)))<<(p)); @@ -267,7 +276,7 @@ const SETARG_sBx = function(i, b) { ** Pre-calculate all possible part of the instruction */ const fullins = function(ins) { - if (typeof ins === "integer") { + if (typeof ins === "number") { return { code: ins, opcode: (ins >> POS_OP) & MASK1(SIZE_OP, 0), @@ -280,13 +289,13 @@ const fullins = function(ins) { }; } else { let i = ins.code; - ins.opcode = (i >> POS_OP) & MASK1(SIZE_OP, 0), - ins.A = (i >> POS_A) & MASK1(SIZE_A, 0), - ins.B = (i >> POS_B) & MASK1(SIZE_B, 0), - ins.C = (i >> POS_C) & MASK1(SIZE_C, 0), - ins.Bx = (i >> POS_Bx) & MASK1(SIZE_Bx, 0), - ins.Ax = (i >> POS_Ax) & MASK1(SIZE_Ax, 0), - ins.sBx = ((i >> POS_Bx) & MASK1(SIZE_Bx, 0)) - MAXARG_sBx + ins.opcode = (i >> POS_OP) & MASK1(SIZE_OP, 0); + ins.A = (i >> POS_A) & MASK1(SIZE_A, 0); + ins.B = (i >> POS_B) & MASK1(SIZE_B, 0); + ins.C = (i >> POS_C) & MASK1(SIZE_C, 0); + ins.Bx = (i >> POS_Bx) & MASK1(SIZE_Bx, 0); + ins.Ax = (i >> POS_Ax) & MASK1(SIZE_Ax, 0); + ins.sBx = ((i >> POS_Bx) & MASK1(SIZE_Bx, 0)) - MAXARG_sBx; return ins; } }; @@ -320,6 +329,10 @@ module.exports.MAXARG_Bx = MAXARG_Bx; module.exports.MAXARG_C = MAXARG_C; module.exports.MAXARG_sBx = MAXARG_sBx; module.exports.NO_REG = NO_REG; +module.exports.OpArgK = OpArgK; +module.exports.OpArgN = OpArgN; +module.exports.OpArgR = OpArgR; +module.exports.OpArgU = OpArgU; module.exports.OpCodes = OpCodes; module.exports.OpCodesI = OpCodesI; module.exports.POS_A = POS_A; @@ -328,6 +341,7 @@ module.exports.POS_B = POS_B; module.exports.POS_Bx = POS_Bx; module.exports.POS_C = POS_C; module.exports.POS_OP = POS_OP; +module.exports.RKASK = RKASK; module.exports.SETARG_A = SETARG_A; module.exports.SETARG_Ax = SETARG_Ax; module.exports.SETARG_B = SETARG_B; @@ -344,5 +358,9 @@ module.exports.fullins = fullins; module.exports.getBMode = getBMode; module.exports.getCMode = getCMode; module.exports.getOpMode = getOpMode; +module.exports.iABC = iABC; +module.exports.iABx = iABx; +module.exports.iAsBx = iAsBx; +module.exports.iAx = iAx; module.exports.testAMode = testAMode; module.exports.testTMode = testTMode; \ No newline at end of file diff --git a/src/lparser.js b/src/lparser.js index 40d6c88..aa48f10 100644 --- a/src/lparser.js +++ b/src/lparser.js @@ -10,7 +10,6 @@ const lobject = require('./lobject.js'); const lopcode = require('./lopcodes.js'); const lua = require('./lua.js'); const BinOpr = lcode.BinOpr; -const CT = lua.constants_type; const OpCodesI = lopcode.OpCodesI; const Proto = lfunc.Proto; const R = llex.RESERVED; @@ -239,7 +238,7 @@ const new_localvar = function(ls, name) { }; const new_localvarliteral = function(ls, name) { - new_localvar(ls, new TValue(CT.LUA_TLNGSTR, name)); + new_localvar(ls, new TValue(lua.CT.LUA_TLNGSTR, name)); }; const getlocvar = function(fs, i) { @@ -469,7 +468,7 @@ const enterblock = function(fs, bl, isloop) { ** create a label named 'break' to resolve break statements */ const breaklabel = function(ls) { - let n = new TValue(CT.LUA_TLNGSTR, "break"); + let n = new TValue(lua.CT.LUA_TLNGSTR, "break"); let l = newlabelentry(ls, ls.dyd.label, n, 0, ls.fs.pc); findgotos(ls, ls.dyd.label.arr[l]); }; @@ -507,7 +506,6 @@ const codeclosure = function(ls, v) { }; const open_func = function(ls, fs, bl) { - this.f = new Proto(); fs.prev = ls.fs; /* linked list of funcstates */ fs.ls = ls; ls.fs = fs; @@ -1132,7 +1130,7 @@ const gotostat = function(ls, pc) { label = str_checkname(ls); else { llex.luaX_next(ls); /* skip break */ - label = new TValue(CT.LUA_TLNGSTR, "break"); + label = new TValue(lua.CT.LUA_TLNGSTR, "break"); } let g = newlabelentry(ls, ls.dyd.gt, label, line, pc); findlabel(ls, g); /* close it if label already defined */ @@ -1538,7 +1536,7 @@ const luaY_parser = function(L, z, buff, dyd, name, firstchar) { lexstate.h = new Table(); /* create table for scanner */ L.stack[L.top++] = lexstate.h; funcstate.f = cl.p = new Proto(L); - funcstate.f.source = new TValue(CT.LUA_TLNGSTR, name); + funcstate.f.source = new TValue(lua.CT.LUA_TLNGSTR, name); lexstate.buff = buff; lexstate.dyd = dyd; dyd.actvar.n = dyd.gt.n = dyd.label.n = 0; diff --git a/src/lua.js b/src/lua.js index 9367746..ef5385c 100644 --- a/src/lua.js +++ b/src/lua.js @@ -28,8 +28,10 @@ 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_VERSUFFIX = "_" + LUA_VERSION_MAJOR + "_" + LUA_VERSION_MINOR; + const LUA_INIT_VAR = "LUA_INIT"; -const LUA_INITVARVERSION = LUA_INIT_VAR + lualib.LUA_VERSUFFIX; +const LUA_INITVARVERSION = LUA_INIT_VAR + LUA_VERSUFFIX; const thread_status = { LUA_OK: 0, @@ -55,6 +57,8 @@ const constant_types = { LUA_NUMTAGS: 9 }; +const CT = constant_types; + constant_types.LUA_TSHRSTR = constant_types.LUA_TSTRING | (0 << 4); /* short strings */ constant_types.LUA_TLNGSTR = constant_types.LUA_TSTRING | (1 << 4); /* long strings */ @@ -128,6 +132,7 @@ class lua_Debug { } +module.exports.CT = CT; module.exports.FENGARI_AUTHORS = FENGARI_AUTHORS; module.exports.FENGARI_COPYRIGHT = FENGARI_COPYRIGHT; module.exports.FENGARI_RELEASE = FENGARI_RELEASE; @@ -171,6 +176,7 @@ 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_VERSUFFIX = LUA_VERSUFFIX; module.exports.constant_types = constant_types; module.exports.lua_Debug = lua_Debug; module.exports.lua_upvalueindex = lua_upvalueindex; diff --git a/src/lualib.js b/src/lualib.js index d87cfbc..da9b290 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -2,21 +2,17 @@ "use strict"; const assert = require('assert'); -const lua = require('./lua.js'); - - -const LUA_VERSUFFIX = "_" + lua.LUA_VERSION_MAJOR + "_" + lua.LUA_VERSION_MINOR; const LUA_COLIBNAME = "coroutine"; -const LUA_TABLIBNAME = "table" -const LUA_IOLIBNAME = "io" -const LUA_OSLIBNAME = "os" -const LUA_STRLIBNAME = "string" -const LUA_UTF8LIBNAME = "utf8" -const LUA_BITLIBNAME = "bit32" -const LUA_MATHLIBNAME = "math" -const LUA_DBLIBNAME = "debug" -const LUA_LOADLIBNAME = "package" +const LUA_TABLIBNAME = "table"; +const LUA_IOLIBNAME = "io"; +const LUA_OSLIBNAME = "os"; +const LUA_STRLIBNAME = "string"; +const LUA_UTF8LIBNAME = "utf8"; +const LUA_BITLIBNAME = "bit32"; +const LUA_MATHLIBNAME = "math"; +const LUA_DBLIBNAME = "debug"; +const LUA_LOADLIBNAME = "package"; module.exports.LUA_BITLIBNAME = LUA_BITLIBNAME; @@ -28,5 +24,4 @@ module.exports.LUA_MATHLIBNAME = LUA_MATHLIBNAME; module.exports.LUA_OSLIBNAME = LUA_OSLIBNAME; module.exports.LUA_STRLIBNAME = LUA_STRLIBNAME; module.exports.LUA_TABLIBNAME = LUA_TABLIBNAME; -module.exports.LUA_UTF8LIBNAME = LUA_UTF8LIBNAME; -module.exports.LUA_VERSUFFIX = LUA_VERSUFFIX; \ No newline at end of file +module.exports.LUA_UTF8LIBNAME = LUA_UTF8LIBNAME; \ No newline at end of file diff --git a/tests/lapi.js b/tests/lapi.js index 6761425..c422ada 100644 --- a/tests/lapi.js +++ b/tests/lapi.js @@ -329,7 +329,7 @@ test('lua_pcall that breaks', function (t) { t.doesNotThrow(function () { let fn = function(L) { - return undefined_value; + return "undefined_value"; }; L = lauxlib.luaL_newstate(); @@ -382,7 +382,7 @@ test('lua_load and lua_call it', function (t) { L = lauxlib.luaL_newstate(); - lapi.lua_load(L, bc, "test-lua_load") + lapi.lua_load(L, null, bc, "test-lua_load", "binary"); lapi.lua_call(L, 0, 1); @@ -409,7 +409,7 @@ test('lua script reads js upvalues', function (t) { L = lauxlib.luaL_newstate(); - lapi.lua_load(L, bc, "test-lua_load") + lapi.lua_load(L, null, bc, "test-lua_load", "binary"); lapi.lua_pushstring(L, "hello"); lapi.lua_setglobal(L, "js"); diff --git a/tests/lbaselib.js b/tests/lbaselib.js index 72a2f15..be741dc 100644 --- a/tests/lbaselib.js +++ b/tests/lbaselib.js @@ -31,7 +31,7 @@ test('print', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-print"); + lapi.lua_load(L, null, bc, "test-print", "binary"); lapi.lua_call(L, 0, -1); @@ -65,7 +65,7 @@ test('setmetatable, getmetatable', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-setmetatable-getmetatable"); + lapi.lua_load(L, null, bc, "test-setmetatable-getmetatable", "binary"); lapi.lua_call(L, 0, -1); @@ -110,7 +110,7 @@ test('rawequal', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-rawequal"); + lapi.lua_load(L, null, bc, "test-rawequal", "binary"); lapi.lua_call(L, 0, -1); @@ -156,7 +156,7 @@ test('rawset, rawget', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-rawequal"); + lapi.lua_load(L, null, bc, "test-rawequal", "binary"); lapi.lua_call(L, 0, -1); @@ -203,7 +203,7 @@ test('type', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-type"); + lapi.lua_load(L, null, bc, "test-type", "binary"); lapi.lua_call(L, 0, -1); @@ -256,7 +256,7 @@ test('error', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-error"); + lapi.lua_load(L, null, bc, "test-error", "binary"); lapi.lua_call(L, 0, -1); @@ -279,7 +279,7 @@ test('error, protected', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-error"); + lapi.lua_load(L, null, bc, "test-error", "binary"); lapi.lua_pcall(L, 0, -1, 0); @@ -311,7 +311,7 @@ test('pcall', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-pcall"); + lapi.lua_load(L, null, bc, "test-pcall", "binary"); lapi.lua_call(L, 0, -1); @@ -347,7 +347,7 @@ test('xpcall', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-pcall"); + lapi.lua_load(L, null, bc, "test-pcall", "binary"); lapi.lua_call(L, 0, -1); @@ -389,7 +389,7 @@ test('ipairs', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-ipairs"); + lapi.lua_load(L, null, bc, "test-ipairs", "binary"); lapi.lua_call(L, 0, -1); @@ -418,7 +418,7 @@ test('select', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-select"); + lapi.lua_load(L, null, bc, "test-select", "binary"); lapi.lua_call(L, 0, -1); @@ -459,7 +459,7 @@ test('tonumber', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-tonumber"); + lapi.lua_load(L, null, bc, "test-tonumber", "binary"); lapi.lua_call(L, 0, -1); @@ -506,7 +506,7 @@ test('assert', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-assert"); + lapi.lua_load(L, null, bc, "test-assert", "binary"); lapi.lua_pcall(L, 0, -1, 0); @@ -534,7 +534,7 @@ test('rawlen', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-rawlen"); + lapi.lua_load(L, null, bc, "test-rawlen", "binary"); lapi.lua_call(L, 0, -1); @@ -581,7 +581,7 @@ test('next', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-next"); + lapi.lua_load(L, null, bc, "test-next", "binary"); lapi.lua_call(L, 0, -1); @@ -622,7 +622,7 @@ test('pairs', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-pairs"); + lapi.lua_load(L, null, bc, "test-pairs", "binary"); lapi.lua_call(L, 0, -1); @@ -672,7 +672,7 @@ test('pairs with __pairs', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-pairs"); + lapi.lua_load(L, null, bc, "test-pairs", "binary"); lapi.lua_call(L, 0, -1); diff --git a/tests/lcorolib.js b/tests/lcorolib.js index 022cda6..9c1d9b1 100644 --- a/tests/lcorolib.js +++ b/tests/lcorolib.js @@ -40,7 +40,7 @@ test('coroutine.create, coroutine.yield, coroutine.resume', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-coroutine"); + lapi.lua_load(L, null, bc, "test-coroutine", "binary"); lapi.lua_call(L, 0, -1); @@ -83,7 +83,7 @@ test('coroutine.status', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-coroutine.status"); + lapi.lua_load(L, null, bc, "test-coroutine.status", "binary"); lapi.lua_call(L, 0, -1); @@ -124,7 +124,7 @@ test('coroutine.isyieldable', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-coroutine.isyieldable"); + lapi.lua_load(L, null, bc, "test-coroutine.isyieldable", "binary"); lapi.lua_call(L, 0, -1); @@ -165,7 +165,7 @@ test('coroutine.running', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-coroutine.running"); + lapi.lua_load(L, null, bc, "test-coroutine.running", "binary"); lapi.lua_call(L, 0, -1); @@ -206,7 +206,7 @@ test('coroutine.wrap', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-coroutine.wrap"); + lapi.lua_load(L, null, bc, "test-coroutine.wrap", "binary"); lapi.lua_call(L, 0, -1); diff --git a/tests/ldebug.js b/tests/ldebug.js index 45194e9..472c66b 100644 --- a/tests/ldebug.js +++ b/tests/ldebug.js @@ -30,7 +30,7 @@ test('luaG_typeerror', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-typeerror"); + lapi.lua_load(L, null, bc, "test-typeerror", "binary"); lapi.lua_pcall(L, 0, -1, 0); @@ -60,7 +60,7 @@ test('luaG_typeerror', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-typeerror"); + lapi.lua_load(L, null, bc, "test-typeerror", "binary"); lapi.lua_pcall(L, 0, -1, 0); @@ -89,7 +89,7 @@ test('luaG_typeerror', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-typeerror"); + lapi.lua_load(L, null, bc, "test-typeerror", "binary"); lapi.lua_pcall(L, 0, -1, 0); @@ -118,7 +118,7 @@ test('luaG_typeerror', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-typeerror"); + lapi.lua_load(L, null, bc, "test-typeerror", "binary"); lapi.lua_pcall(L, 0, -1, 0); @@ -146,7 +146,7 @@ test('luaG_concaterror', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-typeerror"); + lapi.lua_load(L, null, bc, "test-typeerror", "binary"); lapi.lua_pcall(L, 0, -1, 0); @@ -174,7 +174,7 @@ test('luaG_opinterror', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-typeerror"); + lapi.lua_load(L, null, bc, "test-typeerror", "binary"); lapi.lua_pcall(L, 0, -1, 0); @@ -202,7 +202,7 @@ test('luaG_tointerror', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-typeerror"); + lapi.lua_load(L, null, bc, "test-typeerror", "binary"); lapi.lua_pcall(L, 0, -1, 0); diff --git a/tests/lexparse.js b/tests/lexparse.js new file mode 100644 index 0000000..8320588 --- /dev/null +++ b/tests/lexparse.js @@ -0,0 +1,39 @@ +"use strict"; + +const test = require('tape'); +const beautify = require('js-beautify').js_beautify; + +const tests = require("./tests.js"); +const toByteCode = tests.toByteCode; + +const lapi = require("../src/lapi.js"); +const lauxlib = require("../src/lauxlib.js"); +const linit = require('../src/linit.js'); + + +test('Hello World', function (t) { + let luaCode = ` + return "hello world" + `, L; + + t.plan(2); + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lapi.lua_load(L, null, luaCode, "test-load", "text"); + + lapi.lua_call(L, 0, -1); + + }, "JS 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/lmathlib.js b/tests/lmathlib.js index 48ae1a2..ae0c40b 100644 --- a/tests/lmathlib.js +++ b/tests/lmathlib.js @@ -33,7 +33,7 @@ test('math.abs, math.sin, math.cos, math.tan, math.asin, math.acos, math.atan', linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); @@ -98,7 +98,7 @@ test('math.ceil, math.floor', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); @@ -134,7 +134,7 @@ test('math.deg, math.rad', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); @@ -170,7 +170,7 @@ test('math.log', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); @@ -212,7 +212,7 @@ test('math.exp', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); @@ -242,7 +242,7 @@ test('math.min, math.max', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); @@ -278,7 +278,7 @@ test('math.random', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); @@ -312,7 +312,7 @@ test('math.sqrt', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); @@ -342,7 +342,7 @@ test('math.tointeger', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); @@ -372,7 +372,7 @@ test('math.type', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); @@ -414,7 +414,7 @@ test('math.ult', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); @@ -444,7 +444,7 @@ test('math.fmod', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); @@ -474,7 +474,7 @@ test('math.modf', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-math"); + lapi.lua_load(L, null, bc, "test-math", "binary"); lapi.lua_call(L, 0, -1); diff --git a/tests/load.js b/tests/load.js index 4df3f6a..a71de6b 100644 --- a/tests/load.js +++ b/tests/load.js @@ -25,7 +25,7 @@ test('lua_load, binary mode', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, null, bc, "test-math", "binary"); + lapi.lua_load(L, null, bc, "test-load", "binary"); lapi.lua_call(L, 0, -1); @@ -47,17 +47,17 @@ test('lua_load, text mode', function (t) { t.plan(2); - // t.doesNotThrow(function () { + t.doesNotThrow(function () { L = lauxlib.luaL_newstate(); linit.luaL_openlibs(L); - lapi.lua_load(L, null, luaCode, "test-math", "text"); + lapi.lua_load(L, null, luaCode, "test-load", "text"); lapi.lua_call(L, 0, -1); - // }, "JS Lua program ran without error"); + }, "JS Lua program ran without error"); t.strictEqual( lapi.lua_tostring(L, -1), diff --git a/tests/ltablib.js b/tests/ltablib.js index 2ebf98c..c8a1e73 100644 --- a/tests/ltablib.js +++ b/tests/ltablib.js @@ -42,7 +42,7 @@ test('table.concat', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-table.concat"); + lapi.lua_load(L, null, bc, "test-table.concat", "binary"); lapi.lua_call(L, 0, -1); @@ -71,7 +71,7 @@ test('table.pack', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-table.pack"); + lapi.lua_load(L, null, bc, "test-table.pack", "binary"); lapi.lua_call(L, 0, -1); @@ -102,7 +102,7 @@ test('table.unpack', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-table.unpack"); + lapi.lua_load(L, null, bc, "test-table.unpack", "binary"); lapi.lua_call(L, 0, -1); @@ -146,7 +146,7 @@ test('table.insert', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-table.insert"); + lapi.lua_load(L, null, bc, "test-table.insert", "binary"); lapi.lua_call(L, 0, -1); @@ -180,7 +180,7 @@ test('table.remove', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-table.remove"); + lapi.lua_load(L, null, bc, "test-table.remove", "binary"); lapi.lua_call(L, 0, -1); @@ -213,7 +213,7 @@ test('table.move', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-table.move"); + lapi.lua_load(L, null, bc, "test-table.move", "binary"); lapi.lua_call(L, 0, -1); @@ -246,7 +246,7 @@ test('table.sort (<)', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-table.sort"); + lapi.lua_load(L, null, bc, "test-table.sort", "binary"); lapi.lua_call(L, 0, -1); @@ -279,7 +279,7 @@ test('table.sort with cmp function', function (t) { linit.luaL_openlibs(L); - lapi.lua_load(L, bc, "test-table.sort"); + lapi.lua_load(L, null, bc, "test-table.sort", "binary"); lapi.lua_call(L, 0, -1); -- cgit v1.2.3-54-g00ecf