fengari
Version:
A Lua VM written in JS ES6 targeting the browser
1,554 lines (1,431 loc) • 54.4 kB
JavaScript
"use strict";
const {
LUA_MULTRET,
to_luastring
} = require('./defs.js');
const {
BinOpr: {
OPR_ADD,
OPR_AND,
OPR_BAND,
OPR_BOR,
OPR_BXOR,
OPR_CONCAT,
OPR_DIV,
OPR_EQ,
OPR_GE,
OPR_GT,
OPR_IDIV,
OPR_LE,
OPR_LT,
OPR_MOD,
OPR_MUL,
OPR_NE,
OPR_NOBINOPR,
OPR_OR,
OPR_POW,
OPR_SHL,
OPR_SHR,
OPR_SUB
},
UnOpr: {
OPR_BNOT,
OPR_LEN,
OPR_MINUS,
OPR_NOT,
OPR_NOUNOPR
},
NO_JUMP,
getinstruction,
luaK_checkstack,
luaK_codeABC,
luaK_codeABx,
luaK_codeAsBx,
luaK_codek,
luaK_concat,
luaK_dischargevars,
luaK_exp2RK,
luaK_exp2anyreg,
luaK_exp2anyregup,
luaK_exp2nextreg,
luaK_exp2val,
luaK_fixline,
luaK_getlabel,
luaK_goiffalse,
luaK_goiftrue,
luaK_indexed,
luaK_infix,
luaK_intK,
luaK_jump,
luaK_jumpto,
luaK_nil,
luaK_patchclose,
luaK_patchlist,
luaK_patchtohere,
luaK_posfix,
luaK_prefix,
luaK_reserveregs,
luaK_ret,
luaK_self,
luaK_setlist,
luaK_setmultret,
luaK_setoneret,
luaK_setreturns,
luaK_storevar,
luaK_stringK
} = require('./lcode.js');
const ldo = require('./ldo.js');
const lfunc = require('./lfunc.js');
const llex = require('./llex.js');
const {
LUAI_MAXCCALLS,
MAX_INT,
lua_assert
} = require('./llimits.js');
const lobject = require('./lobject.js');
const {
OpCodesI: {
OP_CALL,
OP_CLOSURE,
OP_FORLOOP,
OP_FORPREP,
OP_GETUPVAL,
OP_MOVE,
OP_NEWTABLE,
OP_SETTABLE,
OP_TAILCALL,
OP_TFORCALL,
OP_TFORLOOP,
OP_VARARG
},
LFIELDS_PER_FLUSH,
SETARG_B,
SETARG_C,
SET_OPCODE
} = require('./lopcodes.js');
const {
luaS_eqlngstr,
luaS_new,
luaS_newliteral
} = require('./lstring.js');
const ltable = require('./ltable.js');
const Proto = lfunc.Proto;
const R = llex.RESERVED;
const MAXVARS = 200;
const hasmultret = function(k) {
return k === expkind.VCALL || k === expkind.VVARARG;
};
const eqstr = function(a, b) {
/* TODO: use plain equality as strings are cached */
return luaS_eqlngstr(a, b);
};
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' */
}
to(e) { // Copy e content to this, cf. luaK_posfix
this.k = e.k;
this.u = e.u;
this.t = e.t;
this.f = e.f;
}
}
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, lobject.luaO_pushfstring(ls.L, to_luastring("%s expected", true), llex.luaX_token2str(ls, token)));
};
const errorlimit = function(fs, limit, what) {
let L = fs.ls.L;
let line = fs.f.linedefined;
let where = (line === 0)
? to_luastring("main function", true)
: lobject.luaO_pushfstring(L, to_luastring("function at line %d", true), line);
let msg = lobject.luaO_pushfstring(L, to_luastring("too many %s (limit is %d) in %s", true),
what, limit, 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, lobject.luaO_pushfstring(ls.L,
to_luastring("%s expected (to close %s at line %d)"),
llex.luaX_token2str(ls, what), llex.luaX_token2str(ls, who), 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 = NO_JUMP;
e.k = k;
e.u.info = i;
};
const codestring = function(ls, e, s) {
init_exp(e, expkind.VK, 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, to_luastring("local variables", true));
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, llex.luaX_newstring(ls, to_luastring(name, true)));
};
const getlocvar = function(fs, i) {
let idx = fs.ls.dyd.actvar.arr[fs.firstlocal + i].idx;
lua_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 (eqstr(up[i].name, name))
return i;
}
return -1; /* not found */
};
const newupvalue = function(fs, name, v) {
let f = fs.f;
checklimit(fs, fs.nups + 1, lfunc.MAXUPVAL, to_luastring("upvalues", true));
f.upvalues[fs.nups] = {
instack: v.k === expkind.VLOCAL,
idx: v.u.info,
name: name
};
return fs.nups++;
};
const searchvar = function(fs, n) {
for (let i = fs.nactvar - 1; i >= 0; i--) {
if (eqstr(n, getlocvar(fs, i).varname))
return i;
}
return -1;
};
/*
** Mark block where variable at given level was defined
** (to emit close instructions later).
*/
const markupval = function(fs, level) {
let bl = fs.bl;
while (bl.nactvar > level)
bl = bl.previous;
bl.upval = 1;
};
/*
** Find variable with given name 'n'. If it is an upvalue, add this
** upvalue into all intermediate functions.
*/
const singlevaraux = function(fs, n, vr, base) {
if (fs === null) /* no more levels? */
init_exp(vr, expkind.VVOID, 0); /* default is global */
else {
let v = searchvar(fs, n); /* look up locals at current level */
if (v >= 0) { /* found? */
init_exp(vr, expkind.VLOCAL, v); /* variable is local */
if (!base)
markupval(fs, v); /* local will be used as an upval */
} else { /* not found as local at current level; try upvalues */
let idx = searchupvalue(fs, n); /* try existing upvalues */
if (idx < 0) { /* not found? */
singlevaraux(fs.prev, n, vr, 0); /* try upper levels */
if (vr.k === expkind.VVOID) /* not found? */
return; /* it is a global */
/* else was LOCAL or UPVAL */
idx = newupvalue(fs, n, vr); /* will be a new upvalue */
}
init_exp(vr, expkind.VUPVAL, idx); /* new or old upvalue */
}
}
};
const singlevar = function(ls, vr) {
let varname = str_checkname(ls);
let fs = ls.fs;
singlevaraux(fs, varname, vr, 1);
if (vr.k === expkind.VVOID) { /* is global name? */
let key = new expdesc();
singlevaraux(fs, ls.envn, vr, 1); /* get environment variable */
lua_assert(vr.k !== expkind.VVOID); /* this one must exist */
codestring(ls, key, varname); /* key is variable name */
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;
luaK_setreturns(fs, e, extra); /* last exp. provides the difference */
if (extra > 1) luaK_reserveregs(fs, extra - 1);
} else {
if (e.k !== expkind.VVOID) luaK_exp2nextreg(fs, e); /* close last expression */
if (extra > 0) {
let reg = fs.freereg;
luaK_reserveregs(fs, extra);
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, LUAI_MAXCCALLS, to_luastring("JS levels", true));
};
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];
lua_assert(eqstr(gt.name, label.name));
if (gt.nactvar < label.nactvar) {
let vname = getlocvar(fs, gt.nactvar).varname;
let msg = lobject.luaO_pushfstring(ls.L,
to_luastring("<goto %s> at line %d jumps into the scope of local '%s'"),
gt.name.getstr(), gt.line, vname.getstr());
semerror(ls, msg);
}
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 (eqstr(lb.name, gt.name)) { /* correct label? */
if (gt.nactvar > lb.nactvar && (bl.upval || dyd.label.n > bl.firstlabel))
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 (eqstr(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.dyd.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)
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;
lua_assert(fs.freereg === fs.nactvar);
};
/*
** create a label named 'break' to resolve break statements
*/
const breaklabel = function(ls) {
let n = luaS_newliteral(ls.L, "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) {
let msg = llex.isreserved(gt.name)
? "<%s> at line %d not inside a loop"
: "no visible label '%s' for <goto> at line %d";
msg = lobject.luaO_pushfstring(ls.L, to_luastring(msg), gt.name.getstr(), gt.line);
semerror(ls, msg);
};
/*
** adds a new prototype into list of prototypes
*/
const addprototype = function(ls) {
let L = ls.L;
let clp = new Proto(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, luaK_codeABx(fs, OP_CLOSURE, 0, fs.np -1));
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 = 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 = 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 = luaK_jump(fs);
luaK_patchclose(fs, j , bl.nactvar);
luaK_patchtohere(fs, j);
}
if (bl.isloop)
breaklabel(ls); /* close pending breaks */
fs.bl = bl.previous;
removevars(fs, bl.nactvar);
lua_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 fs = ls.fs;
luaK_ret(fs, 0, 0); /* final return */
leaveblock(fs);
lua_assert(fs.bl === null);
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();
luaK_exp2anyregup(fs, v);
llex.luaX_next(ls); /* skip the dot or colon */
checkname(ls, key);
luaK_indexed(fs, v, key);
};
const yindex = function(ls, v) {
/* index -> '[' expr ']' */
llex.luaX_next(ls); /* skip the '[' */
expr(ls, v);
luaK_exp2val(ls.fs, v);
checknext(ls, 93 /* (']').charCodeAt(0) */);
};
/*
** {======================================================================
** 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, MAX_INT, to_luastring("items in a constructor", true));
checkname(ls, key);
} else /* ls->t.token === '[' */
yindex(ls, key);
cc.nh++;
checknext(ls, 61 /* ('=').charCodeAt(0) */);
let rkkey = luaK_exp2RK(fs, key);
expr(ls, val);
luaK_codeABC(fs, OP_SETTABLE, cc.t.u.info, rkkey, 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 */
luaK_exp2nextreg(fs, cc.v);
cc.v.k = expkind.VVOID;
if (cc.tostore === LFIELDS_PER_FLUSH) {
luaK_setlist(fs, cc.t.u.info, cc.na, cc.tostore); /* flush */
cc.tostore = 0; /* no more items pending */
}
};
const lastlistfield = function(fs, cc) {
if (cc.tostore === 0) return;
if (hasmultret(cc.v.k)) {
luaK_setmultret(fs, cc.v);
luaK_setlist(fs, cc.t.u.info, cc.na, LUA_MULTRET);
cc.na--; /* do not count last expression (unknown number of elements) */
} else {
if (cc.v.k !== expkind.VVOID)
luaK_exp2nextreg(fs, cc.v);
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, MAX_INT, to_luastring("items in a constructor", true));
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) !== 61 /* ('=').charCodeAt(0) */) /* expression? */
listfield(ls, cc);
else
recfield(ls, cc);
break;
}
case 91 /* ('[').charCodeAt(0) */: {
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 = luaK_codeABC(fs, 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) */
luaK_exp2nextreg(ls.fs, t); /* fix it at stack top */
checknext(ls, 123 /* ('{').charCodeAt(0) */);
do {
lua_assert(cc.v.k === expkind.VVOID || cc.tostore > 0);
if (ls.t.token === 125 /* ('}').charCodeAt(0) */) break;
closelistfield(fs, cc);
field(ls, cc);
} while (testnext(ls, 44 /* (',').charCodeAt(0) */) || testnext(ls, 59 /* (';').charCodeAt(0) */));
check_match(ls, 125 /* ('}').charCodeAt(0) */, 123 /* ('{').charCodeAt(0) */, line);
lastlistfield(fs, cc);
SETARG_B(fs.f.code[pc], lobject.luaO_int2fb(cc.na)); /* set initial array size */
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 = false;
if (ls.t.token !== 41 /* (')').charCodeAt(0) */) { /* 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 = true; /* declared vararg */
break;
}
default: llex.luaX_syntaxerror(ls, to_luastring("<name> or '...' expected", true));
}
} while(!f.is_vararg && testnext(ls, 44 /* (',').charCodeAt(0) */));
}
adjustlocalvars(ls, nparams);
f.numparams = fs.nactvar;
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, 40 /* ('(').charCodeAt(0) */);
if (ismethod) {
new_localvarliteral(ls, "self"); /* create 'self' parameter */
adjustlocalvars(ls, 1);
}
parlist(ls);
checknext(ls, 41 /* (')').charCodeAt(0) */);
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, 44 /* (',').charCodeAt(0) */)) {
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 40 /* ('(').charCodeAt(0) */: { /* funcargs -> '(' [ explist ] ')' */
llex.luaX_next(ls);
if (ls.t.token === 41 /* (')').charCodeAt(0) */) /* arg list is empty? */
args.k = expkind.VVOID;
else {
explist(ls, args);
luaK_setmultret(fs, args);
}
check_match(ls, 41 /* (')').charCodeAt(0) */, 40 /* ('(').charCodeAt(0) */, line);
break;
}
case 123 /* ('{').charCodeAt(0) */: { /* 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, to_luastring("function arguments expected", true));
}
}
lua_assert(f.k === expkind.VNONRELOC);
let nparams;
let base = f.u.info; /* base register for call */
if (hasmultret(args.k))
nparams = LUA_MULTRET; /* open call */
else {
if (args.k !== expkind.VVOID)
luaK_exp2nextreg(fs, args); /* close last argument */
nparams = fs.freereg - (base+1);
}
init_exp(f, expkind.VCALL, luaK_codeABC(fs, OP_CALL, base, nparams+1, 2));
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 40 /* ('(').charCodeAt(0) */: {
let line = ls.linenumber;
llex.luaX_next(ls);
expr(ls, v);
check_match(ls, 41 /* (')').charCodeAt(0) */, 40 /* ('(').charCodeAt(0) */, line);
luaK_dischargevars(ls.fs, v);
return;
}
case R.TK_NAME: {
singlevar(ls, v);
return;
}
default: {
llex.luaX_syntaxerror(ls, to_luastring("unexpected symbol", true));
}
}
};
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 46 /* ('.').charCodeAt(0) */: { /* fieldsel */
fieldsel(ls, v);
break;
}
case 91 /* ('[').charCodeAt(0) */: { /* '[' exp1 ']' */
let key = new expdesc();
luaK_exp2anyregup(fs, v);
yindex(ls, key);
luaK_indexed(fs, v, key);
break;
}
case 58 /* (':').charCodeAt(0) */: { /* ':' NAME funcargs */
let key = new expdesc();
llex.luaX_next(ls);
checkname(ls, key);
luaK_self(fs, v, key);
funcargs(ls, v, line);
break;
}
case 40 /* ('(').charCodeAt(0) */: case R.TK_STRING: case 123 /* ('{').charCodeAt(0) */: { /* funcargs */
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.VKFLT, 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, to_luastring("cannot use '...' outside a vararg function", true));
init_exp(v, expkind.VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 1, 0));
break;
}
case 123 /* ('{').charCodeAt(0) */: { /* 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 OPR_NOT;
case 45 /* ('-').charCodeAt(0) */: return OPR_MINUS;
case 126 /* ('~').charCodeAt(0) */: return OPR_BNOT;
case 35 /* ('#').charCodeAt(0) */: return OPR_LEN;
default: return OPR_NOUNOPR;
}
};
const getbinopr = function(op) {
switch (op) {
case 43 /* ('+').charCodeAt(0) */: return OPR_ADD;
case 45 /* ('-').charCodeAt(0) */: return OPR_SUB;
case 42 /* ('*').charCodeAt(0) */: return OPR_MUL;
case 37 /* ('%').charCodeAt(0) */: return OPR_MOD;
case 94 /* ('^').charCodeAt(0) */: return OPR_POW;
case 47 /* ('/').charCodeAt(0) */: return OPR_DIV;
case R.TK_IDIV: return OPR_IDIV;
case 38 /* ('&').charCodeAt(0) */: return OPR_BAND;
case 124 /* ('|').charCodeAt(0) */: return OPR_BOR;
case 126 /* ('~').charCodeAt(0) */: return OPR_BXOR;
case R.TK_SHL: return OPR_SHL;
case R.TK_SHR: return OPR_SHR;
case R.TK_CONCAT: return OPR_CONCAT;
case R.TK_NE: return OPR_NE;
case R.TK_EQ: return OPR_EQ;
case 60 /* ('<').charCodeAt(0) */: return OPR_LT;
case R.TK_LE: return OPR_LE;
case 62 /* ('>').charCodeAt(0) */: return OPR_GT;
case R.TK_GE: return OPR_GE;
case R.TK_AND: return OPR_AND;
case R.TK_OR: return OPR_OR;
default: return 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 !== OPR_NOUNOPR) {
let line = ls.linenumber;
llex.luaX_next(ls);
subexpr(ls, v, UNARY_PRIORITY);
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 !== OPR_NOBINOPR && priority[op].left > limit) {
let v2 = new expdesc();
let line = ls.linenumber;
llex.luaX_next(ls);
luaK_infix(ls.fs, op, v);
/* read sub-expression with higher priority */
let nextop = subexpr(ls, v2, priority[op].right);
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 ? OP_MOVE : OP_GETUPVAL;
luaK_codeABC(fs, op, extra, v.u.info, 0);
luaK_reserveregs(fs, 1);
}
};
const assignment = function(ls, lh, nvars) {
let e = new expdesc();
check_condition(ls, vkisvar(lh.v.k), to_luastring("syntax error", true));
if (testnext(ls, 44 /* (',').charCodeAt(0) */)) { /* 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, LUAI_MAXCCALLS, to_luastring("JS levels", true));
assignment(ls, nv, nvars + 1);
} else { /* assignment -> '=' explist */
checknext(ls, 61 /* ('=').charCodeAt(0) */);
let nexps = explist(ls, e);
if (nexps !== nvars)
adjust_assign(ls, nvars, nexps, e);
else {
luaK_setoneret(ls.fs, e); /* close last expression */
luaK_storevar(ls.fs, lh.v, e);
return; /* avoid default */
}
}
init_exp(e, expkind.VNONRELOC, ls.fs.freereg-1); /* default assignment */
luaK_storevar(ls.fs, lh.v, e);
};
const cond = function(ls) {
/* cond -> exp */
let v = new expdesc();
expr(ls, v); /* read condition */
if (v.k === expkind.VNIL) v.k = expkind.VFALSE; /* 'falses' are all equal here */
luaK_goiftrue(ls.fs, v);
return v.f;
};
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 = luaS_newliteral(ls.L, "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 (eqstr(label, ll.arr[i].name)) {
let msg = lobject.luaO_pushfstring(fs.ls.L,
to_luastring("label '%s' already defined on line %d", true),
label.getstr(), ll.arr[i].line);
semerror(fs.ls, msg);
}
}
};
/* skip no-op statements */
const skipnoopstat = function(ls) {
while (ls.t.token === 59 /* (';').charCodeAt(0) */ || 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, 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 = luaK_getlabel(fs);
let condexit = cond(ls);
enterblock(fs, bl, 1);
checknext(ls, R.TK_DO);
block(ls);
luaK_jumpto(fs, whileinit);
check_match(ls, R.TK_END, R.TK_WHILE, line);
leaveblock(fs);
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 = 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? */
luaK_patchclose(fs, condexit, bl2.nactvar);
leaveblock(fs); /* finish scope */
luaK_patchlist(fs, condexit, repeat_init); /* close the loop */
leaveblock(fs); /* finish loop */
};
const exp1 = function(ls) {
let e = new expdesc();
expr(ls, e);
luaK_exp2nextreg(ls.fs, e);
lua_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 ? luaK_codeAsBx(fs, OP_FORPREP, base, NO_JUMP) : luaK_jump(fs);
enterblock(fs, bl, 0); /* scope for declared variables */
adjustlocalvars(ls, nvars);
luaK_reserveregs(fs, nvars);
block(ls);
leaveblock(fs); /* end of scope for declared variables */
luaK_patchtohere(fs, prep);
if (isnum) /* end of scope for declared variables */
endfor = luaK_codeAsBx(fs, OP_FORLOOP, base, NO_JUMP);
else { /* generic for */
luaK_codeABC(fs, OP_TFORCALL, base, 0, nvars);
luaK_fixline(fs, line);
endfor = luaK_codeAsBx(fs, OP_TFORLOOP, base + 2, NO_JUMP);
}
luaK_patchlist(fs, endfor, prep + 1);
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, 61 /* ('=').charCodeAt(0) */);
exp1(ls); /* initial value */
checknext(ls, 44 /* (',').charCodeAt(0) */);
exp1(ls); /* limit */
if (testnext(ls, 44 /* (',').charCodeAt(0) */))
exp1(ls); /* optional step */
else { /* default step = 1 */
luaK_codek(fs, fs.freereg, luaK_intK(fs, 1));
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, 44 /* (',').charCodeAt(0) */)) {
new_localvar(ls, str_checkname(ls));
nvars++;
}
checknext(ls, R.TK_IN);
let line = ls.linenumber;
adjust_assign(ls, 3, explist(ls, e), e);
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 61 /* ('=').charCodeAt(0) */: fornum(ls, varname, line); break;
case 44 /* (',').charCodeAt(0) */: case R.TK_IN: forlist(ls, varname); break;
default: llex.luaX_syntaxerror(ls, to_luastring("'=' or 'in' expected", true));
}
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) {
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 */
while (testnext(ls, 59 /* (';').charCodeAt(0) */)); /* skip colons */
if (block_follow(ls, 0)) { /* 'goto' is the entire block? */
leaveblock(fs);
return escapelist; /* and that is it */
} else /* must skip over 'then' part if condition is false */
jf = luaK_jump(fs);
} else { /* regular case (not goto/break) */
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 = luaK_concat(fs, escapelist, luaK_jump(fs)); /* must jump over it */
luaK_patchtohere(fs, jf);
return escapelist;
};
const ifstat = function(ls, line) {
/* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */
let fs = ls.fs;
let escapelist = NO_JUMP; /* exit list for finished parts */
escapelist = test_then_block(ls, escapelist); /* IF cond THEN block */
while (ls.t.token === R.TK_ELSEIF)
escapelist = 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);
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, 44 /* (',').charCodeAt(0) */));
if (testnext(ls, 61 /* ('=').charCodeAt(0) */))
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 === 46 /* ('.').charCodeAt(0) */)
fieldsel(ls, v);
if (ls.t.token === 58 /* (':').charCodeAt(0) */) {
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);
luaK_storevar(ls.fs, v, b);
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 === 61 /* ('=').charCodeAt(0) */ || ls.t.token === 44 /* (',').charCodeAt(0) */) { /* stat . assignment ? */
v.prev = null;
assignment(ls, v, 1);
}
else { /* stat -> func */
check_condition(ls, v.v.k === expkind.VCALL, to_luastring("syntax error", true));
SETARG_C(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 === 59 /* (';').charCodeAt(0) */)
first = nret = 0; /* return no values */
else {
nret = explist(ls, e); /* optional return values */
if (hasmultret(e.k)) {
luaK_setmultret(fs, e);
if (e.k === expkind.VCALL && nret === 1) { /* tail call? */
SET_OPCODE(getinstruction(fs, e), OP_TAILCALL);
lua_assert(getinstruction(fs, e).A === fs.nactvar);
}
first = fs.nactvar;
nret = LUA_MULTRET; /* retur