diff options
-rw-r--r-- | src/lcode.js | 223 | ||||
-rw-r--r-- | src/llex.js | 35 | ||||
-rw-r--r-- | src/lopcodes.js | 214 | ||||
-rw-r--r-- | src/lparser.js | 346 |
4 files changed, 779 insertions, 39 deletions
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 <token>" 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, `<goto ${gt.name}> 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 <goto> 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 */ }; |