aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Giannangeli <giann008@gmail.com>2017-02-27 21:49:27 +0100
committerBenoit Giannangeli <benoit.giannangeli@boursorama.fr>2017-02-28 08:43:53 +0100
commit8285c78283f922e5b5569ce5c28fb25074db0af3 (patch)
tree22f37796163605fba0c5e0e432d24110b44b473d
parentc1cf887d702a36729bab0257247066745223cf6e (diff)
downloadfengari-8285c78283f922e5b5569ce5c28fb25074db0af3.tar.gz
fengari-8285c78283f922e5b5569ce5c28fb25074db0af3.tar.bz2
fengari-8285c78283f922e5b5569ce5c28fb25074db0af3.zip
parsing
-rw-r--r--src/lcode.js223
-rw-r--r--src/llex.js35
-rw-r--r--src/lopcodes.js214
-rw-r--r--src/lparser.js346
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 */
};