diff options
Diffstat (limited to 'src/lparser.js')
-rw-r--r-- | src/lparser.js | 1560 |
1 files changed, 1560 insertions, 0 deletions
diff --git a/src/lparser.js b/src/lparser.js new file mode 100644 index 0000000..1e454e0 --- /dev/null +++ b/src/lparser.js @@ -0,0 +1,1560 @@ +"use strict"; + +const assert = require('assert'); + +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('./lopcodes.js'); +const lua = require('./lua.js'); +const BinOpr = lcode.BinOpr; +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; + +const MAXVARS = 200; + +const hasmultret = function(k) { + return k === expkind.VCALL || k === expkind.VVARARG; +}; + +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 */ +}; + +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; + 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 */ + } +} + + /* 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 <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 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 registerlocalvar = function(ls, varname) { + let fs = ls.fs; + let f = fs.f; + f.locvars[fs.nlocvars] = new lobject.LocVar(); + 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] = new Vardesc(); + dyd.actvar.arr[dyd.actvar.n].idx = reg; + dyd.actvar.n++; +}; + +const new_localvarliteral = function(ls, name) { + new_localvar(ls, new TValue(lua.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.value === name.value) + return i; + } + return -1; /* not found */ +}; + +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 searchvar = function(fs, n) { + for (let i = fs.nactvar - 1; i >= 0; i--) { + if (n.value === getlocvar(fs, i).varname.value) + 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; + 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; + let gt = gl.arr[g]; + assert(gt.name.value === label.name.value); + if (gt.nactvar < label.nactvar) { + let vname = getlocvar(fs, gt.nactvar).varname; + semerror(ls, `<goto ${gt.name.value}> at line ${gt.line} jumps into the scope of local '${vname.value}'`); + } + 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.value === gt.name.value) { /* 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] = new Labeldesc(); + 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.value === lb.name.value) + 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(lua.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.value) + ? `<${gt.name.value}> at line ${gt.line} not inside a loop` + : `no visible label '${gt.name.value}' for <goto> at line ${gt.line}`; + 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) { + 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; +}; + +/*============================================================*/ +/* 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)) { + if (ls.t.token === R.TK_RETURN) { + statement(ls); + return; /* 'return' must be last statement */ + } + statement(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 '[' */ + 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) { + lcode.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 */ +}; + +/* }====================================================================== */ + +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, "<name> 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 */ + 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)); + break; + } + 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 */ + {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; + +/* +** 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); +}; + +/* }==================================================================== */ + + + +/* +** {====================================================================== +** 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(lua.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.value === ll.arr[i].name.value) { + 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(); + 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 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); + 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 +*/ +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) { + 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(lua.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 */ +}; + + +module.exports.Dyndata = Dyndata; +module.exports.expkind = expkind; +module.exports.luaY_parser = luaY_parser; +module.exports.vkisinreg = vkisinreg;
\ No newline at end of file |