SoftSpoken (z1xel/SoftSpoken, a 1.12 narration port) failed to load: nine files across six addons would not compile, and the three that did then raised at runtime indexing namespaces the failed chunks never built. Every one of the nine is valid Lua 5.1 and invalid Lua 5.0 — `#`, `%`, `0x3040`, `local NAME = ...`. `third_party/lua-src` is a fork that deliberately 5.0-ifies the grammar: three arms deleted from `lparser.c` (`simpleexp`'s `TK_DOTS`, `getunopr`'s `'#'`, `getbinopr`'s `'%'`) and `read_numeral` replaced with 5.0's, which has no hex. Every deletion is byte-read out of `WoW.exe`'s own parser and every one is right about stock 1.12. No OctoWoW client runs stock 1.12. ClassicAPI.dll is in the realm's bundle and rewrites every chunk at the `luaL_loadbuffer` chokepoint — file scripts, `loadstring` and XML `OnLoad` alike — adding exactly five constructs: `#`, `%`, `...` as an expression, `0x` hex and `[=[ ]=]`. Confirmed three ways: the repo's own highlights line, PR #28, and the DLL's string pool, which twice says "Our 5.1 syntax backport makes the probe pass". `docs/ADDON_COMPAT.md`'s row for this said "free — benilla's VM is mlua/Lua 5.1 already". That was the error. So the target grammar is the union, and the fork now carries it: * the three parser arms restored, as stock 5.1 writes them; * `read_numeral` grown an explicit `0x` arm. It could not take 5.1's version back wholesale: 5.1 reached hex through a trailing `isalnum` slurp, and that slurp has to stay gone or `1then` breaks again (`AdvancedTradeSkillWindow2.lua:3878`, which the client compiles). The prefix arm gives both; * addon file chunks now called with `(addonName, addonPrivateTable)` (`UiScript::run_addon_chunk`, four call sites). Taking `...` without passing the arguments only moves the failure one line later, which is what ClassicAPI's own notes record; * `string.reverse` added to the stdlib backport — SoftSpoken calls it. 1.12's own dialect is untouched. `LUA_COMPAT_VARARG` withholds the `arg` table only from a function that *uses* `...` as a value, so the shipped FrameXML's `function f(...) … arg.n … end` still gets `arg`: the two dialects coexist one function at a time, which is what the client-plus-ClassicAPI does. The cost is the era probe. 170 corpus `loadstring` sites ask `loadstring("return function(...) return ... end")` and 92 library files in 24 folders branch on the answer; they now take the 5.1 branch, as they do on the real client. Two branch wrongly there and ClassicAPI ships a compensation for each — Compost-2.0 rev 17406+'s no-argument `Erase` stub (bundled by BigWigs and QuickHeal; it silently stops clearing in place) and Cartographer_Notes' `WorldMapDeathRelease`. Both are now carried in `lua51_probe_fallout.rs`, probed by behaviour rather than by revision and re-checked on every `ADDON_LOADED`, because AceLibrary can upgrade a library mid-session. Measured on grom's own AddOns folder: SoftSpoken, SoftSpoken_Stories, SoftSpoken_Places, SoftSpoken_Writings, SpokenZonesAudio, SpokenBooksAudio, SpokenQuestsAudio{Horde,Alliance,Gossip,Shared}, SSS_VOFallback and AI_VoiceOverData_Turtle all load with zero load errors and zero session errors, 98 `Spoken*` globals, `/softspoken` registered, and PLAYER_ENTERING_WORLD, QUEST_DETAIL, GOSSIP_SHOW and ITEM_TEXT_BEGIN reaching 108/108, 1/1, 6/6 and 2/2 handlers clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
1381 lines
39 KiB
C
1381 lines
39 KiB
C
/*
|
|
** $Id: lparser.c,v 2.42.1.4 2011/10/21 19:31:42 roberto Exp $
|
|
** Lua Parser
|
|
** See Copyright Notice in lua.h
|
|
*/
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#define lparser_c
|
|
#define LUA_CORE
|
|
|
|
#include "lua.h"
|
|
|
|
#include "lcode.h"
|
|
#include "ldebug.h"
|
|
#include "ldo.h"
|
|
#include "lfunc.h"
|
|
#include "llex.h"
|
|
#include "lmem.h"
|
|
#include "lobject.h"
|
|
#include "lopcodes.h"
|
|
#include "lparser.h"
|
|
#include "lstate.h"
|
|
#include "lstring.h"
|
|
#include "ltable.h"
|
|
|
|
|
|
|
|
#define hasmultret(k) ((k) == VCALL || (k) == VVARARG)
|
|
|
|
#define getlocvar(fs, i) ((fs)->f->locvars[(fs)->actvar[i]])
|
|
|
|
#define luaY_checklimit(fs,v,l,m) if ((v)>(l)) errorlimit(fs,l,m)
|
|
|
|
|
|
/*
|
|
** nodes for block list (list of active blocks)
|
|
*/
|
|
typedef struct BlockCnt {
|
|
struct BlockCnt *previous; /* chain */
|
|
int breaklist; /* list of jumps out of this loop */
|
|
lu_byte nactvar; /* # active locals outside the breakable structure */
|
|
lu_byte upval; /* true if some variable in the block is an upvalue */
|
|
lu_byte isbreakable; /* true if `block' is a loop */
|
|
} BlockCnt;
|
|
|
|
|
|
|
|
/*
|
|
** prototypes for recursive non-terminal functions
|
|
*/
|
|
static void chunk (LexState *ls);
|
|
static void expr (LexState *ls, expdesc *v);
|
|
|
|
|
|
static void anchor_token (LexState *ls) {
|
|
if (ls->t.token == TK_NAME || ls->t.token == TK_STRING) {
|
|
TString *ts = ls->t.seminfo.ts;
|
|
luaX_newstring(ls, getstr(ts), ts->tsv.len);
|
|
}
|
|
}
|
|
|
|
|
|
static void error_expected (LexState *ls, int token) {
|
|
luaX_syntaxerror(ls,
|
|
luaO_pushfstring(ls->L, LUA_QS " expected", luaX_token2str(ls, token)));
|
|
}
|
|
|
|
|
|
static void errorlimit (FuncState *fs, int limit, const char *what) {
|
|
const char *msg = (fs->f->linedefined == 0) ?
|
|
luaO_pushfstring(fs->L, "main function has more than %d %s", limit, what) :
|
|
luaO_pushfstring(fs->L, "function at line %d has more than %d %s",
|
|
fs->f->linedefined, limit, what);
|
|
luaX_lexerror(fs->ls, msg, 0);
|
|
}
|
|
|
|
|
|
static int testnext (LexState *ls, int c) {
|
|
if (ls->t.token == c) {
|
|
luaX_next(ls);
|
|
return 1;
|
|
}
|
|
else return 0;
|
|
}
|
|
|
|
|
|
static void check (LexState *ls, int c) {
|
|
if (ls->t.token != c)
|
|
error_expected(ls, c);
|
|
}
|
|
|
|
static void checknext (LexState *ls, int c) {
|
|
check(ls, c);
|
|
luaX_next(ls);
|
|
}
|
|
|
|
|
|
#define check_condition(ls,c,msg) { if (!(c)) luaX_syntaxerror(ls, msg); }
|
|
|
|
|
|
|
|
static void check_match (LexState *ls, int what, int who, int where) {
|
|
if (!testnext(ls, what)) {
|
|
if (where == ls->linenumber)
|
|
error_expected(ls, what);
|
|
else {
|
|
luaX_syntaxerror(ls, luaO_pushfstring(ls->L,
|
|
LUA_QS " expected (to close " LUA_QS " at line %d)",
|
|
luaX_token2str(ls, what), luaX_token2str(ls, who), where));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static TString *str_checkname (LexState *ls) {
|
|
TString *ts;
|
|
check(ls, TK_NAME);
|
|
ts = ls->t.seminfo.ts;
|
|
luaX_next(ls);
|
|
return ts;
|
|
}
|
|
|
|
|
|
static void init_exp (expdesc *e, expkind k, int i) {
|
|
e->f = e->t = NO_JUMP;
|
|
e->k = k;
|
|
e->u.s.info = i;
|
|
}
|
|
|
|
|
|
static void codestring (LexState *ls, expdesc *e, TString *s) {
|
|
init_exp(e, VK, luaK_stringK(ls->fs, s));
|
|
}
|
|
|
|
|
|
static void checkname(LexState *ls, expdesc *e) {
|
|
codestring(ls, e, str_checkname(ls));
|
|
}
|
|
|
|
|
|
static int registerlocalvar (LexState *ls, TString *varname) {
|
|
FuncState *fs = ls->fs;
|
|
Proto *f = fs->f;
|
|
int oldsize = f->sizelocvars;
|
|
luaM_growvector(ls->L, f->locvars, fs->nlocvars, f->sizelocvars,
|
|
LocVar, SHRT_MAX, "too many local variables");
|
|
while (oldsize < f->sizelocvars) f->locvars[oldsize++].varname = NULL;
|
|
f->locvars[fs->nlocvars].varname = varname;
|
|
luaC_objbarrier(ls->L, f, varname);
|
|
return fs->nlocvars++;
|
|
}
|
|
|
|
|
|
#define new_localvarliteral(ls,v,n) \
|
|
new_localvar(ls, luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char))-1), n)
|
|
|
|
|
|
static void new_localvar (LexState *ls, TString *name, int n) {
|
|
FuncState *fs = ls->fs;
|
|
luaY_checklimit(fs, fs->nactvar+n+1, LUAI_MAXVARS, "local variables");
|
|
fs->actvar[fs->nactvar+n] = cast(unsigned short, registerlocalvar(ls, name));
|
|
}
|
|
|
|
|
|
static void adjustlocalvars (LexState *ls, int nvars) {
|
|
FuncState *fs = ls->fs;
|
|
fs->nactvar = cast_byte(fs->nactvar + nvars);
|
|
for (; nvars; nvars--) {
|
|
getlocvar(fs, fs->nactvar - nvars).startpc = fs->pc;
|
|
}
|
|
}
|
|
|
|
|
|
static void removevars (LexState *ls, int tolevel) {
|
|
FuncState *fs = ls->fs;
|
|
while (fs->nactvar > tolevel)
|
|
getlocvar(fs, --fs->nactvar).endpc = fs->pc;
|
|
}
|
|
|
|
|
|
static int indexupvalue (FuncState *fs, TString *name, expdesc *v) {
|
|
int i;
|
|
Proto *f = fs->f;
|
|
int oldsize = f->sizeupvalues;
|
|
for (i=0; i<f->nups; i++) {
|
|
if (fs->upvalues[i].k == v->k && fs->upvalues[i].info == v->u.s.info) {
|
|
lua_assert(f->upvalues[i] == name);
|
|
return i;
|
|
}
|
|
}
|
|
/* new one */
|
|
luaY_checklimit(fs, f->nups + 1, LUAI_MAXUPVALUES, "upvalues");
|
|
luaM_growvector(fs->L, f->upvalues, f->nups, f->sizeupvalues,
|
|
TString *, MAX_INT, "");
|
|
while (oldsize < f->sizeupvalues) f->upvalues[oldsize++] = NULL;
|
|
f->upvalues[f->nups] = name;
|
|
luaC_objbarrier(fs->L, f, name);
|
|
lua_assert(v->k == VLOCAL || v->k == VUPVAL);
|
|
fs->upvalues[f->nups].k = cast_byte(v->k);
|
|
fs->upvalues[f->nups].info = cast_byte(v->u.s.info);
|
|
return f->nups++;
|
|
}
|
|
|
|
|
|
static int searchvar (FuncState *fs, TString *n) {
|
|
int i;
|
|
for (i=fs->nactvar-1; i >= 0; i--) {
|
|
if (n == getlocvar(fs, i).varname)
|
|
return i;
|
|
}
|
|
return -1; /* not found */
|
|
}
|
|
|
|
|
|
static void markupval (FuncState *fs, int level) {
|
|
BlockCnt *bl = fs->bl;
|
|
while (bl && bl->nactvar > level) bl = bl->previous;
|
|
if (bl) bl->upval = 1;
|
|
}
|
|
|
|
|
|
static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
|
|
if (fs == NULL) { /* no more levels? */
|
|
init_exp(var, VGLOBAL, NO_REG); /* default is global variable */
|
|
return VGLOBAL;
|
|
}
|
|
else {
|
|
int v = searchvar(fs, n); /* look up at current level */
|
|
if (v >= 0) {
|
|
init_exp(var, VLOCAL, v);
|
|
if (!base)
|
|
markupval(fs, v); /* local will be used as an upval */
|
|
return VLOCAL;
|
|
}
|
|
else { /* not found at current level; try upper one */
|
|
if (singlevaraux(fs->prev, n, var, 0) == VGLOBAL)
|
|
return VGLOBAL;
|
|
var->u.s.info = indexupvalue(fs, n, var); /* else was LOCAL or UPVAL */
|
|
var->k = VUPVAL; /* upvalue in this level */
|
|
return VUPVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void singlevar (LexState *ls, expdesc *var) {
|
|
TString *varname = str_checkname(ls);
|
|
FuncState *fs = ls->fs;
|
|
if (singlevaraux(fs, varname, var, 1) == VGLOBAL)
|
|
var->u.s.info = luaK_stringK(fs, varname); /* info points to global name */
|
|
}
|
|
|
|
|
|
static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) {
|
|
FuncState *fs = ls->fs;
|
|
int 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 != VVOID) luaK_exp2nextreg(fs, e); /* close last expression */
|
|
if (extra > 0) {
|
|
int reg = fs->freereg;
|
|
luaK_reserveregs(fs, extra);
|
|
luaK_nil(fs, reg, extra);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void enterlevel (LexState *ls) {
|
|
if (++ls->L->nCcalls > LUAI_MAXCCALLS)
|
|
luaX_lexerror(ls, "chunk has too many syntax levels", 0);
|
|
}
|
|
|
|
|
|
#define leavelevel(ls) ((ls)->L->nCcalls--)
|
|
|
|
|
|
static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isbreakable) {
|
|
bl->breaklist = NO_JUMP;
|
|
bl->isbreakable = isbreakable;
|
|
bl->nactvar = fs->nactvar;
|
|
bl->upval = 0;
|
|
bl->previous = fs->bl;
|
|
fs->bl = bl;
|
|
lua_assert(fs->freereg == fs->nactvar);
|
|
}
|
|
|
|
|
|
static void leaveblock (FuncState *fs) {
|
|
BlockCnt *bl = fs->bl;
|
|
fs->bl = bl->previous;
|
|
removevars(fs->ls, bl->nactvar);
|
|
if (bl->upval)
|
|
luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0);
|
|
/* a block either controls scope or breaks (never both) */
|
|
lua_assert(!bl->isbreakable || !bl->upval);
|
|
lua_assert(bl->nactvar == fs->nactvar);
|
|
fs->freereg = fs->nactvar; /* free registers */
|
|
luaK_patchtohere(fs, bl->breaklist);
|
|
}
|
|
|
|
|
|
static void pushclosure (LexState *ls, FuncState *func, expdesc *v) {
|
|
FuncState *fs = ls->fs;
|
|
Proto *f = fs->f;
|
|
int oldsize = f->sizep;
|
|
int i;
|
|
luaM_growvector(ls->L, f->p, fs->np, f->sizep, Proto *,
|
|
MAXARG_Bx, "constant table overflow");
|
|
while (oldsize < f->sizep) f->p[oldsize++] = NULL;
|
|
f->p[fs->np++] = func->f;
|
|
luaC_objbarrier(ls->L, f, func->f);
|
|
init_exp(v, VRELOCABLE, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np-1));
|
|
for (i=0; i<func->f->nups; i++) {
|
|
OpCode o = (func->upvalues[i].k == VLOCAL) ? OP_MOVE : OP_GETUPVAL;
|
|
luaK_codeABC(fs, o, 0, func->upvalues[i].info, 0);
|
|
}
|
|
}
|
|
|
|
|
|
static void open_func (LexState *ls, FuncState *fs) {
|
|
lua_State *L = ls->L;
|
|
Proto *f = luaF_newproto(L);
|
|
fs->f = f;
|
|
fs->prev = ls->fs; /* linked list of funcstates */
|
|
fs->ls = ls;
|
|
fs->L = L;
|
|
ls->fs = fs;
|
|
fs->pc = 0;
|
|
fs->lasttarget = -1;
|
|
fs->jpc = NO_JUMP;
|
|
fs->freereg = 0;
|
|
fs->nk = 0;
|
|
fs->np = 0;
|
|
fs->nlocvars = 0;
|
|
fs->nactvar = 0;
|
|
fs->bl = NULL;
|
|
f->source = ls->source;
|
|
f->maxstacksize = 2; /* registers 0/1 are always valid */
|
|
fs->h = luaH_new(L, 0, 0);
|
|
/* anchor table of constants and prototype (to avoid being collected) */
|
|
sethvalue2s(L, L->top, fs->h);
|
|
incr_top(L);
|
|
setptvalue2s(L, L->top, f);
|
|
incr_top(L);
|
|
}
|
|
|
|
|
|
static void close_func (LexState *ls) {
|
|
lua_State *L = ls->L;
|
|
FuncState *fs = ls->fs;
|
|
Proto *f = fs->f;
|
|
removevars(ls, 0);
|
|
luaK_ret(fs, 0, 0); /* final return */
|
|
luaM_reallocvector(L, f->code, f->sizecode, fs->pc, Instruction);
|
|
f->sizecode = fs->pc;
|
|
luaM_reallocvector(L, f->lineinfo, f->sizelineinfo, fs->pc, int);
|
|
f->sizelineinfo = fs->pc;
|
|
luaM_reallocvector(L, f->k, f->sizek, fs->nk, TValue);
|
|
f->sizek = fs->nk;
|
|
luaM_reallocvector(L, f->p, f->sizep, fs->np, Proto *);
|
|
f->sizep = fs->np;
|
|
luaM_reallocvector(L, f->locvars, f->sizelocvars, fs->nlocvars, LocVar);
|
|
f->sizelocvars = fs->nlocvars;
|
|
luaM_reallocvector(L, f->upvalues, f->sizeupvalues, f->nups, TString *);
|
|
f->sizeupvalues = f->nups;
|
|
lua_assert(luaG_checkcode(f));
|
|
lua_assert(fs->bl == NULL);
|
|
ls->fs = fs->prev;
|
|
/* last token read was anchored in defunct function; must reanchor it */
|
|
if (fs) anchor_token(ls);
|
|
L->top -= 2; /* remove table and prototype from the stack */
|
|
}
|
|
|
|
|
|
Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name) {
|
|
struct LexState lexstate;
|
|
struct FuncState funcstate;
|
|
lexstate.buff = buff;
|
|
luaX_setinput(L, &lexstate, z, luaS_new(L, name));
|
|
open_func(&lexstate, &funcstate);
|
|
funcstate.f->is_vararg = VARARG_ISVARARG; /* main func. is always vararg */
|
|
luaX_next(&lexstate); /* read first token */
|
|
chunk(&lexstate);
|
|
check(&lexstate, TK_EOS);
|
|
close_func(&lexstate);
|
|
lua_assert(funcstate.prev == NULL);
|
|
lua_assert(funcstate.f->nups == 0);
|
|
lua_assert(lexstate.fs == NULL);
|
|
return funcstate.f;
|
|
}
|
|
|
|
|
|
|
|
/*============================================================*/
|
|
/* GRAMMAR RULES */
|
|
/*============================================================*/
|
|
|
|
|
|
static void field (LexState *ls, expdesc *v) {
|
|
/* field -> ['.' | ':'] NAME */
|
|
FuncState *fs = ls->fs;
|
|
expdesc key;
|
|
luaK_exp2anyreg(fs, v);
|
|
luaX_next(ls); /* skip the dot or colon */
|
|
checkname(ls, &key);
|
|
luaK_indexed(fs, v, &key);
|
|
}
|
|
|
|
|
|
static void yindex (LexState *ls, expdesc *v) {
|
|
/* index -> '[' expr ']' */
|
|
luaX_next(ls); /* skip the '[' */
|
|
expr(ls, v);
|
|
luaK_exp2val(ls->fs, v);
|
|
checknext(ls, ']');
|
|
}
|
|
|
|
|
|
/*
|
|
** {======================================================================
|
|
** Rules for Constructors
|
|
** =======================================================================
|
|
*/
|
|
|
|
|
|
struct ConsControl {
|
|
expdesc v; /* last list item read */
|
|
expdesc *t; /* table descriptor */
|
|
int nh; /* total number of `record' elements */
|
|
int na; /* total number of array elements */
|
|
int tostore; /* number of array elements pending to be stored */
|
|
};
|
|
|
|
|
|
static void recfield (LexState *ls, struct ConsControl *cc) {
|
|
/* recfield -> (NAME | `['exp1`]') = exp1 */
|
|
FuncState *fs = ls->fs;
|
|
int reg = ls->fs->freereg;
|
|
expdesc key, val;
|
|
int rkkey;
|
|
if (ls->t.token == TK_NAME) {
|
|
luaY_checklimit(fs, cc->nh, MAX_INT, "items in a constructor");
|
|
cc->nh++; /* benilla: 5.0's placement -- `[expr] = v` credits NEITHER size hint */
|
|
checkname(ls, &key);
|
|
}
|
|
else /* ls->t.token == '[' */
|
|
yindex(ls, &key);
|
|
checknext(ls, '=');
|
|
rkkey = luaK_exp2RK(fs, &key);
|
|
expr(ls, &val);
|
|
luaK_codeABC(fs, OP_SETTABLE, cc->t->u.s.info, rkkey, luaK_exp2RK(fs, &val));
|
|
fs->freereg = reg; /* free registers */
|
|
}
|
|
|
|
|
|
static void closelistfield (FuncState *fs, struct ConsControl *cc) {
|
|
if (cc->v.k == VVOID) return; /* there is no list item */
|
|
luaK_exp2nextreg(fs, &cc->v);
|
|
cc->v.k = VVOID;
|
|
if (cc->tostore == LFIELDS_PER_FLUSH) {
|
|
luaK_setlist(fs, cc->t->u.s.info, cc->na, cc->tostore); /* flush */
|
|
cc->tostore = 0; /* no more items pending */
|
|
}
|
|
}
|
|
|
|
|
|
static void lastlistfield (FuncState *fs, struct ConsControl *cc) {
|
|
if (cc->tostore == 0) return;
|
|
if (hasmultret(cc->v.k)) {
|
|
luaK_setmultret(fs, &cc->v);
|
|
luaK_setlist(fs, cc->t->u.s.info, cc->na, LUA_MULTRET);
|
|
cc->na--; /* do not count last expression (unknown number of elements) */
|
|
}
|
|
else {
|
|
if (cc->v.k != VVOID)
|
|
luaK_exp2nextreg(fs, &cc->v);
|
|
luaK_setlist(fs, cc->t->u.s.info, cc->na, cc->tostore);
|
|
}
|
|
}
|
|
|
|
|
|
static void listfield (LexState *ls, struct ConsControl *cc) {
|
|
expr(ls, &cc->v);
|
|
luaY_checklimit(ls->fs, cc->na, MAX_INT, "items in a constructor");
|
|
cc->na++;
|
|
cc->tostore++;
|
|
}
|
|
|
|
|
|
static void constructor (LexState *ls, expdesc *t) {
|
|
/* constructor -> ?? */
|
|
FuncState *fs = ls->fs;
|
|
int line = ls->linenumber;
|
|
int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0);
|
|
struct ConsControl cc;
|
|
cc.na = cc.nh = cc.tostore = 0;
|
|
cc.t = t;
|
|
init_exp(t, VRELOCABLE, pc);
|
|
init_exp(&cc.v, VVOID, 0); /* no value (yet) */
|
|
luaK_exp2nextreg(ls->fs, t); /* fix it at stack top (for gc) */
|
|
checknext(ls, '{');
|
|
do {
|
|
lua_assert(cc.v.k == VVOID || cc.tostore > 0);
|
|
/* BENILLA: Lua 5.0's constructor compat-semicolon, restored (see
|
|
** third_party/lua-src/BENILLA.md). 5.0 opened this loop with exactly the
|
|
** line below, a Lua 4.0 leftover; 5.1 deleted it, so one extra `;' after a
|
|
** field separator (`Back_Title = AL["Factions"];;', which AtlasLoot writes)
|
|
** raised "unexpected symbol near ';'" while the 1.12.1 client accepts it.
|
|
** Statement-level `;;' stays rejected by both. Byte-read in the client:
|
|
** constructor is 0x6fd430, and its loop head 0x6fd4a0 calls testnext(ls,';')
|
|
** without testing EAX (a skip, not a separator check), while the loop's
|
|
** continuation at 0x6fd4f5/0x6fd505 does test it; of testnext 0x6fccf0's 16
|
|
** call sites, the only other `;' consumer is chunk 0x6fcccc, at statement
|
|
** level, with no such head skip. */
|
|
testnext(ls, ';'); /* compatibility only */
|
|
if (ls->t.token == '}') break;
|
|
closelistfield(fs, &cc);
|
|
switch(ls->t.token) {
|
|
case TK_NAME: { /* may be listfields or recfields */
|
|
luaX_lookahead(ls);
|
|
if (ls->lookahead.token != '=') /* expression? */
|
|
listfield(ls, &cc);
|
|
else
|
|
recfield(ls, &cc);
|
|
break;
|
|
}
|
|
case '[': { /* constructor_item -> recfield */
|
|
recfield(ls, &cc);
|
|
break;
|
|
}
|
|
default: { /* constructor_part -> listfield */
|
|
listfield(ls, &cc);
|
|
break;
|
|
}
|
|
}
|
|
} while (testnext(ls, ',') || testnext(ls, ';'));
|
|
check_match(ls, '}', '{', line);
|
|
lastlistfield(fs, &cc);
|
|
SETARG_B(fs->f->code[pc], luaO_int2fb(cc.na)); /* set initial array size */
|
|
SETARG_C(fs->f->code[pc], luaO_int2fb(cc.nh)); /* set initial table size */
|
|
}
|
|
|
|
/* }====================================================================== */
|
|
|
|
|
|
|
|
static void parlist (LexState *ls) {
|
|
/* parlist -> [ param { `,' param } ] */
|
|
FuncState *fs = ls->fs;
|
|
Proto *f = fs->f;
|
|
int nparams = 0;
|
|
f->is_vararg = 0;
|
|
if (ls->t.token != ')') { /* is `parlist' not empty? */
|
|
do {
|
|
switch (ls->t.token) {
|
|
case TK_NAME: { /* param -> NAME */
|
|
new_localvar(ls, str_checkname(ls), nparams++);
|
|
break;
|
|
}
|
|
case TK_DOTS: { /* param -> `...' */
|
|
luaX_next(ls);
|
|
#if defined(LUA_COMPAT_VARARG)
|
|
/* use `arg' as default name */
|
|
new_localvarliteral(ls, "arg", nparams++);
|
|
f->is_vararg = VARARG_HASARG | VARARG_NEEDSARG;
|
|
#endif
|
|
f->is_vararg |= VARARG_ISVARARG;
|
|
break;
|
|
}
|
|
default: luaX_syntaxerror(ls, "<name> or " LUA_QL("...") " expected");
|
|
}
|
|
} while (!f->is_vararg && testnext(ls, ','));
|
|
}
|
|
adjustlocalvars(ls, nparams);
|
|
f->numparams = cast_byte(fs->nactvar - (f->is_vararg & VARARG_HASARG));
|
|
luaK_reserveregs(fs, fs->nactvar); /* reserve register for parameters */
|
|
}
|
|
|
|
|
|
static void body (LexState *ls, expdesc *e, int needself, int line) {
|
|
/* body -> `(' parlist `)' chunk END */
|
|
FuncState new_fs;
|
|
open_func(ls, &new_fs);
|
|
new_fs.f->linedefined = line;
|
|
checknext(ls, '(');
|
|
if (needself) {
|
|
new_localvarliteral(ls, "self", 0);
|
|
adjustlocalvars(ls, 1);
|
|
}
|
|
parlist(ls);
|
|
checknext(ls, ')');
|
|
chunk(ls);
|
|
new_fs.f->lastlinedefined = ls->linenumber;
|
|
check_match(ls, TK_END, TK_FUNCTION, line);
|
|
close_func(ls);
|
|
pushclosure(ls, &new_fs, e);
|
|
}
|
|
|
|
|
|
static int explist1 (LexState *ls, expdesc *v) {
|
|
/* explist1 -> expr { `,' expr } */
|
|
int n = 1; /* at least one expression */
|
|
expr(ls, v);
|
|
while (testnext(ls, ',')) {
|
|
luaK_exp2nextreg(ls->fs, v);
|
|
expr(ls, v);
|
|
n++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
|
|
static void funcargs (LexState *ls, expdesc *f) {
|
|
FuncState *fs = ls->fs;
|
|
expdesc args;
|
|
int base, nparams;
|
|
int line = ls->linenumber;
|
|
switch (ls->t.token) {
|
|
case '(': { /* funcargs -> `(' [ explist1 ] `)' */
|
|
if (line != ls->lastline)
|
|
luaX_syntaxerror(ls,"ambiguous syntax (function call x new statement)");
|
|
luaX_next(ls);
|
|
if (ls->t.token == ')') /* arg list is empty? */
|
|
args.k = VVOID;
|
|
else {
|
|
explist1(ls, &args);
|
|
luaK_setmultret(fs, &args);
|
|
}
|
|
check_match(ls, ')', '(', line);
|
|
break;
|
|
}
|
|
case '{': { /* funcargs -> constructor */
|
|
constructor(ls, &args);
|
|
break;
|
|
}
|
|
case TK_STRING: { /* funcargs -> STRING */
|
|
codestring(ls, &args, ls->t.seminfo.ts);
|
|
luaX_next(ls); /* must use `seminfo' before `next' */
|
|
break;
|
|
}
|
|
default: {
|
|
luaX_syntaxerror(ls, "function arguments expected");
|
|
return;
|
|
}
|
|
}
|
|
lua_assert(f->k == VNONRELOC);
|
|
base = f->u.s.info; /* base register for call */
|
|
if (hasmultret(args.k))
|
|
nparams = LUA_MULTRET; /* open call */
|
|
else {
|
|
if (args.k != VVOID)
|
|
luaK_exp2nextreg(fs, &args); /* close last argument */
|
|
nparams = fs->freereg - (base+1);
|
|
}
|
|
init_exp(f, 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
|
|
** =======================================================================
|
|
*/
|
|
|
|
|
|
static void prefixexp (LexState *ls, expdesc *v) {
|
|
/* prefixexp -> NAME | '(' expr ')' */
|
|
switch (ls->t.token) {
|
|
case '(': {
|
|
int line = ls->linenumber;
|
|
luaX_next(ls);
|
|
expr(ls, v);
|
|
check_match(ls, ')', '(', line);
|
|
luaK_dischargevars(ls->fs, v);
|
|
return;
|
|
}
|
|
case TK_NAME: {
|
|
singlevar(ls, v);
|
|
return;
|
|
}
|
|
default: {
|
|
luaX_syntaxerror(ls, "unexpected symbol");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void primaryexp (LexState *ls, expdesc *v) {
|
|
/* primaryexp ->
|
|
prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs } */
|
|
FuncState *fs = ls->fs;
|
|
prefixexp(ls, v);
|
|
for (;;) {
|
|
switch (ls->t.token) {
|
|
case '.': { /* field */
|
|
field(ls, v);
|
|
break;
|
|
}
|
|
case '[': { /* `[' exp1 `]' */
|
|
expdesc key;
|
|
luaK_exp2anyreg(fs, v);
|
|
yindex(ls, &key);
|
|
luaK_indexed(fs, v, &key);
|
|
break;
|
|
}
|
|
case ':': { /* `:' NAME funcargs */
|
|
expdesc key;
|
|
luaX_next(ls);
|
|
checkname(ls, &key);
|
|
luaK_self(fs, v, &key);
|
|
funcargs(ls, v);
|
|
break;
|
|
}
|
|
case '(': case TK_STRING: case '{': { /* funcargs */
|
|
luaK_exp2nextreg(fs, v);
|
|
funcargs(ls, v);
|
|
break;
|
|
}
|
|
default: return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void simpleexp (LexState *ls, expdesc *v) {
|
|
/* simpleexp -> NUMBER | STRING | NIL | true | false | constructor |
|
|
FUNCTION body | primaryexp */
|
|
switch (ls->t.token) {
|
|
case TK_NUMBER: {
|
|
init_exp(v, VKNUM, 0);
|
|
v->u.nval = ls->t.seminfo.r;
|
|
break;
|
|
}
|
|
case TK_STRING: {
|
|
codestring(ls, v, ls->t.seminfo.ts);
|
|
break;
|
|
}
|
|
case TK_NIL: {
|
|
init_exp(v, VNIL, 0);
|
|
break;
|
|
}
|
|
case TK_TRUE: {
|
|
init_exp(v, VTRUE, 0);
|
|
break;
|
|
}
|
|
case TK_FALSE: {
|
|
init_exp(v, VFALSE, 0);
|
|
break;
|
|
}
|
|
/* BENILLA: 5.1's vararg EXPRESSION arm, restored -- ClassicAPI's 5.1 syntax backport.
|
|
** Stock 1.12's parser has no `...' as a value (simpleexp 0x6fd240 has seven arms and no
|
|
** TK_DOTS), but no OctoWoW client runs stock: ClassicAPI.dll rewrites every chunk at the
|
|
** `luaL_loadbuffer' chokepoint, and `...' as an expression is one of the five constructs it
|
|
** adds. The realm's addon corpus is written against that grammar, so it is the grammar
|
|
** benilla has to accept (`third_party/lua-src/BENILLA.md').
|
|
**
|
|
** `LUA_COMPAT_VARARG' is what keeps 1.12's own code working beside it: the line below clears
|
|
** VARARG_NEEDSARG only for a function that actually *uses* `...' as a value, so the shipped
|
|
** FrameXML's `function f(...) ... arg.n ... end' still gets its `arg' table and only 5.1-era
|
|
** code opts out of it. The two dialects coexist per function, which is exactly what the
|
|
** backported client does.
|
|
**
|
|
** The cost is real and is ClassicAPI's too: 170 corpus `loadstring' sites ask which
|
|
** interpreter they are on with
|
|
**
|
|
** local lua51 = loadstring("return function(...) return ... end") and true or false
|
|
**
|
|
** and 92 library files in 24 folders branch on the answer. They now take the 5.1 branch, as
|
|
** they do on the real client. ClassicAPI compensates for the two that branch *wrongly*, and
|
|
** so do we -- `compat/classic_api/lua51_probe_fallout.rs'. */
|
|
case TK_DOTS: { /* vararg */
|
|
FuncState *fs = ls->fs;
|
|
check_condition(ls, fs->f->is_vararg,
|
|
"cannot use " LUA_QL("...") " outside a vararg function");
|
|
fs->f->is_vararg &= ~VARARG_NEEDSARG; /* don't need 'arg' */
|
|
init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 1, 0));
|
|
break;
|
|
}
|
|
case '{': { /* constructor */
|
|
constructor(ls, v);
|
|
return;
|
|
}
|
|
case TK_FUNCTION: {
|
|
luaX_next(ls);
|
|
body(ls, v, 0, ls->linenumber);
|
|
return;
|
|
}
|
|
default: {
|
|
primaryexp(ls, v);
|
|
return;
|
|
}
|
|
}
|
|
luaX_next(ls);
|
|
}
|
|
|
|
|
|
static UnOpr getunopr (int op) {
|
|
switch (op) {
|
|
case TK_NOT: return OPR_NOT;
|
|
case '-': return OPR_MINUS;
|
|
/* BENILLA: 5.1's `#' length operator, restored -- ClassicAPI's 5.1 syntax backport.
|
|
** Stock 1.12's getunopr (0x6fe0a0) tests exactly two tokens and its OPR_NOUNOPR is 2, so a
|
|
** `#' there raises "unexpected symbol". ClassicAPI adds it, the corpus uses it, and 5.1's own
|
|
** OPR_LEN / OP_LEN / TM_LEN below this layer were never removed. */
|
|
case '#': return OPR_LEN;
|
|
default: return OPR_NOUNOPR;
|
|
}
|
|
}
|
|
|
|
|
|
static BinOpr getbinopr (int op) {
|
|
switch (op) {
|
|
case '+': return OPR_ADD;
|
|
case '-': return OPR_SUB;
|
|
case '*': return OPR_MUL;
|
|
case '/': return OPR_DIV;
|
|
/* BENILLA: 5.1's `%' modulo operator, restored -- ClassicAPI's 5.1 syntax backport.
|
|
** Stock 1.12's getbinopr (0x6fe0c0) bases its switch at `*' (0x2A), so `%' (0x25) falls
|
|
** below the range and reaches a 15-member BinOpr with no OPR_MOD; 5.0 spells it math.mod,
|
|
** which this VM still publishes. ClassicAPI adds the operator, so both spellings work. */
|
|
case '%': return OPR_MOD;
|
|
case '^': return OPR_POW;
|
|
case TK_CONCAT: return OPR_CONCAT;
|
|
case TK_NE: return OPR_NE;
|
|
case TK_EQ: return OPR_EQ;
|
|
case '<': return OPR_LT;
|
|
case TK_LE: return OPR_LE;
|
|
case '>': return OPR_GT;
|
|
case TK_GE: return OPR_GE;
|
|
case TK_AND: return OPR_AND;
|
|
case TK_OR: return OPR_OR;
|
|
default: return OPR_NOBINOPR;
|
|
}
|
|
}
|
|
|
|
|
|
static const struct {
|
|
lu_byte left; /* left priority for each binary operator */
|
|
lu_byte right; /* right priority */
|
|
} priority[] = { /* ORDER OPR */
|
|
{6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, /* `+' `-' `/' `%' */
|
|
{10, 9}, {5, 4}, /* power and concat (right associative) */
|
|
{3, 3}, {3, 3}, /* equality and inequality */
|
|
{3, 3}, {3, 3}, {3, 3}, {3, 3}, /* order */
|
|
{2, 2}, {1, 1} /* logical (and/or) */
|
|
};
|
|
|
|
#define UNARY_PRIORITY 8 /* priority for unary operators */
|
|
|
|
|
|
/*
|
|
** subexpr -> (simpleexp | unop subexpr) { binop subexpr }
|
|
** where `binop' is any binary operator with a priority higher than `limit'
|
|
*/
|
|
static BinOpr subexpr (LexState *ls, expdesc *v, unsigned int limit) {
|
|
BinOpr op;
|
|
UnOpr uop;
|
|
enterlevel(ls);
|
|
uop = getunopr(ls->t.token);
|
|
if (uop != OPR_NOUNOPR) {
|
|
luaX_next(ls);
|
|
subexpr(ls, v, UNARY_PRIORITY);
|
|
luaK_prefix(ls->fs, uop, v);
|
|
}
|
|
else simpleexp(ls, v);
|
|
/* expand while operators have priorities higher than `limit' */
|
|
op = getbinopr(ls->t.token);
|
|
while (op != OPR_NOBINOPR && priority[op].left > limit) {
|
|
expdesc v2;
|
|
BinOpr nextop;
|
|
luaX_next(ls);
|
|
luaK_infix(ls->fs, op, v);
|
|
/* read sub-expression with higher priority */
|
|
nextop = subexpr(ls, &v2, priority[op].right);
|
|
luaK_posfix(ls->fs, op, v, &v2);
|
|
op = nextop;
|
|
}
|
|
leavelevel(ls);
|
|
return op; /* return first untreated operator */
|
|
}
|
|
|
|
|
|
static void expr (LexState *ls, expdesc *v) {
|
|
subexpr(ls, v, 0);
|
|
}
|
|
|
|
/* }==================================================================== */
|
|
|
|
|
|
|
|
/*
|
|
** {======================================================================
|
|
** Rules for Statements
|
|
** =======================================================================
|
|
*/
|
|
|
|
|
|
static int block_follow (int token) {
|
|
switch (token) {
|
|
case TK_ELSE: case TK_ELSEIF: case TK_END:
|
|
case TK_UNTIL: case TK_EOS:
|
|
return 1;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static void block (LexState *ls) {
|
|
/* block -> chunk */
|
|
FuncState *fs = ls->fs;
|
|
BlockCnt bl;
|
|
enterblock(fs, &bl, 0);
|
|
chunk(ls);
|
|
lua_assert(bl.breaklist == NO_JUMP);
|
|
leaveblock(fs);
|
|
}
|
|
|
|
|
|
/*
|
|
** structure to chain all variables in the left-hand side of an
|
|
** assignment
|
|
*/
|
|
struct LHS_assign {
|
|
struct LHS_assign *prev;
|
|
expdesc v; /* variable (global, local, upvalue, or indexed) */
|
|
};
|
|
|
|
|
|
/*
|
|
** check whether, in an assignment to a local variable, the local variable
|
|
** is needed in a previous assignment (to a table). If so, save original
|
|
** local value in a safe place and use this safe copy in the previous
|
|
** assignment.
|
|
*/
|
|
static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) {
|
|
FuncState *fs = ls->fs;
|
|
int extra = fs->freereg; /* eventual position to save local variable */
|
|
int conflict = 0;
|
|
for (; lh; lh = lh->prev) {
|
|
if (lh->v.k == VINDEXED) {
|
|
if (lh->v.u.s.info == v->u.s.info) { /* conflict? */
|
|
conflict = 1;
|
|
lh->v.u.s.info = extra; /* previous assignment will use safe copy */
|
|
}
|
|
if (lh->v.u.s.aux == v->u.s.info) { /* conflict? */
|
|
conflict = 1;
|
|
lh->v.u.s.aux = extra; /* previous assignment will use safe copy */
|
|
}
|
|
}
|
|
}
|
|
if (conflict) {
|
|
luaK_codeABC(fs, OP_MOVE, fs->freereg, v->u.s.info, 0); /* make copy */
|
|
luaK_reserveregs(fs, 1);
|
|
}
|
|
}
|
|
|
|
|
|
static void assignment (LexState *ls, struct LHS_assign *lh, int nvars) {
|
|
expdesc e;
|
|
check_condition(ls, VLOCAL <= lh->v.k && lh->v.k <= VINDEXED,
|
|
"syntax error");
|
|
if (testnext(ls, ',')) { /* assignment -> `,' primaryexp assignment */
|
|
struct LHS_assign nv;
|
|
nv.prev = lh;
|
|
primaryexp(ls, &nv.v);
|
|
if (nv.v.k == VLOCAL)
|
|
check_conflict(ls, lh, &nv.v);
|
|
luaY_checklimit(ls->fs, nvars, LUAI_MAXCCALLS - ls->L->nCcalls,
|
|
"variables in assignment");
|
|
assignment(ls, &nv, nvars+1);
|
|
}
|
|
else { /* assignment -> `=' explist1 */
|
|
int nexps;
|
|
checknext(ls, '=');
|
|
nexps = explist1(ls, &e);
|
|
if (nexps != nvars) {
|
|
adjust_assign(ls, nvars, nexps, &e);
|
|
if (nexps > nvars)
|
|
ls->fs->freereg -= nexps - nvars; /* remove extra values */
|
|
}
|
|
else {
|
|
luaK_setoneret(ls->fs, &e); /* close last expression */
|
|
luaK_storevar(ls->fs, &lh->v, &e);
|
|
return; /* avoid default */
|
|
}
|
|
}
|
|
init_exp(&e, VNONRELOC, ls->fs->freereg-1); /* default assignment */
|
|
luaK_storevar(ls->fs, &lh->v, &e);
|
|
}
|
|
|
|
|
|
static int cond (LexState *ls) {
|
|
/* cond -> exp */
|
|
expdesc v;
|
|
expr(ls, &v); /* read condition */
|
|
if (v.k == VNIL) v.k = VFALSE; /* `falses' are all equal here */
|
|
luaK_goiftrue(ls->fs, &v);
|
|
return v.f;
|
|
}
|
|
|
|
|
|
static void breakstat (LexState *ls) {
|
|
FuncState *fs = ls->fs;
|
|
BlockCnt *bl = fs->bl;
|
|
int upval = 0;
|
|
while (bl && !bl->isbreakable) {
|
|
upval |= bl->upval;
|
|
bl = bl->previous;
|
|
}
|
|
if (!bl)
|
|
luaX_syntaxerror(ls, "no loop to break");
|
|
if (upval)
|
|
luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0);
|
|
luaK_concat(fs, &bl->breaklist, luaK_jump(fs));
|
|
}
|
|
|
|
|
|
static void whilestat (LexState *ls, int line) {
|
|
/* whilestat -> WHILE cond DO block END */
|
|
FuncState *fs = ls->fs;
|
|
int whileinit;
|
|
int condexit;
|
|
BlockCnt bl;
|
|
luaX_next(ls); /* skip WHILE */
|
|
whileinit = luaK_getlabel(fs);
|
|
condexit = cond(ls);
|
|
enterblock(fs, &bl, 1);
|
|
checknext(ls, TK_DO);
|
|
block(ls);
|
|
luaK_patchlist(fs, luaK_jump(fs), whileinit);
|
|
check_match(ls, TK_END, TK_WHILE, line);
|
|
leaveblock(fs);
|
|
luaK_patchtohere(fs, condexit); /* false conditions finish the loop */
|
|
}
|
|
|
|
|
|
static void repeatstat (LexState *ls, int line) {
|
|
/* repeatstat -> REPEAT block UNTIL cond */
|
|
int condexit;
|
|
FuncState *fs = ls->fs;
|
|
int repeat_init = luaK_getlabel(fs);
|
|
BlockCnt bl1, bl2;
|
|
enterblock(fs, &bl1, 1); /* loop block */
|
|
enterblock(fs, &bl2, 0); /* scope block */
|
|
luaX_next(ls); /* skip REPEAT */
|
|
chunk(ls);
|
|
check_match(ls, TK_UNTIL, TK_REPEAT, line);
|
|
condexit = cond(ls); /* read condition (inside scope block) */
|
|
if (!bl2.upval) { /* no upvalues? */
|
|
leaveblock(fs); /* finish scope */
|
|
luaK_patchlist(ls->fs, condexit, repeat_init); /* close the loop */
|
|
}
|
|
else { /* complete semantics when there are upvalues */
|
|
breakstat(ls); /* if condition then break */
|
|
luaK_patchtohere(ls->fs, condexit); /* else... */
|
|
leaveblock(fs); /* finish scope... */
|
|
luaK_patchlist(ls->fs, luaK_jump(fs), repeat_init); /* and repeat */
|
|
}
|
|
leaveblock(fs); /* finish loop */
|
|
}
|
|
|
|
|
|
static int exp1 (LexState *ls) {
|
|
expdesc e;
|
|
int k;
|
|
expr(ls, &e);
|
|
k = e.k;
|
|
luaK_exp2nextreg(ls->fs, &e);
|
|
return k;
|
|
}
|
|
|
|
|
|
static void forbody (LexState *ls, int base, int line, int nvars, int isnum) {
|
|
/* forbody -> DO block */
|
|
BlockCnt bl;
|
|
FuncState *fs = ls->fs;
|
|
int prep, endfor;
|
|
adjustlocalvars(ls, 3); /* control variables */
|
|
checknext(ls, TK_DO);
|
|
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);
|
|
endfor = (isnum) ? luaK_codeAsBx(fs, OP_FORLOOP, base, NO_JUMP) :
|
|
luaK_codeABC(fs, OP_TFORLOOP, base, 0, nvars);
|
|
luaK_fixline(fs, line); /* pretend that `OP_FOR' starts the loop */
|
|
luaK_patchlist(fs, (isnum ? endfor : luaK_jump(fs)), prep + 1);
|
|
}
|
|
|
|
|
|
static void fornum (LexState *ls, TString *varname, int line) {
|
|
/* fornum -> NAME = exp1,exp1[,exp1] forbody */
|
|
FuncState *fs = ls->fs;
|
|
int base = fs->freereg;
|
|
new_localvarliteral(ls, "(for index)", 0);
|
|
new_localvarliteral(ls, "(for limit)", 1);
|
|
new_localvarliteral(ls, "(for step)", 2);
|
|
new_localvar(ls, varname, 3);
|
|
checknext(ls, '=');
|
|
exp1(ls); /* initial value */
|
|
checknext(ls, ',');
|
|
exp1(ls); /* limit */
|
|
if (testnext(ls, ','))
|
|
exp1(ls); /* optional step */
|
|
else { /* default step = 1 */
|
|
luaK_codeABx(fs, OP_LOADK, fs->freereg, luaK_numberK(fs, 1));
|
|
luaK_reserveregs(fs, 1);
|
|
}
|
|
forbody(ls, base, line, 1, 1);
|
|
}
|
|
|
|
|
|
static void forlist (LexState *ls, TString *indexname) {
|
|
/* forlist -> NAME {,NAME} IN explist1 forbody */
|
|
FuncState *fs = ls->fs;
|
|
expdesc e;
|
|
int nvars = 0;
|
|
int line;
|
|
int base = fs->freereg;
|
|
/* create control variables */
|
|
new_localvarliteral(ls, "(for generator)", nvars++);
|
|
new_localvarliteral(ls, "(for state)", nvars++);
|
|
new_localvarliteral(ls, "(for control)", nvars++);
|
|
/* create declared variables */
|
|
new_localvar(ls, indexname, nvars++);
|
|
while (testnext(ls, ','))
|
|
new_localvar(ls, str_checkname(ls), nvars++);
|
|
checknext(ls, TK_IN);
|
|
line = ls->linenumber;
|
|
adjust_assign(ls, 3, explist1(ls, &e), &e);
|
|
luaK_checkstack(fs, 3); /* extra space to call generator */
|
|
forbody(ls, base, line, nvars - 3, 0);
|
|
}
|
|
|
|
|
|
static void forstat (LexState *ls, int line) {
|
|
/* forstat -> FOR (fornum | forlist) END */
|
|
FuncState *fs = ls->fs;
|
|
TString *varname;
|
|
BlockCnt bl;
|
|
enterblock(fs, &bl, 1); /* scope for loop and control variables */
|
|
luaX_next(ls); /* skip `for' */
|
|
varname = str_checkname(ls); /* first variable name */
|
|
switch (ls->t.token) {
|
|
case '=': fornum(ls, varname, line); break;
|
|
case ',': case TK_IN: forlist(ls, varname); break;
|
|
default: luaX_syntaxerror(ls, LUA_QL("=") " or " LUA_QL("in") " expected");
|
|
}
|
|
check_match(ls, TK_END, TK_FOR, line);
|
|
leaveblock(fs); /* loop scope (`break' jumps to this point) */
|
|
}
|
|
|
|
|
|
static int test_then_block (LexState *ls) {
|
|
/* test_then_block -> [IF | ELSEIF] cond THEN block */
|
|
int condexit;
|
|
luaX_next(ls); /* skip IF or ELSEIF */
|
|
condexit = cond(ls);
|
|
checknext(ls, TK_THEN);
|
|
block(ls); /* `then' part */
|
|
return condexit;
|
|
}
|
|
|
|
|
|
static void ifstat (LexState *ls, int line) {
|
|
/* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */
|
|
FuncState *fs = ls->fs;
|
|
int flist;
|
|
int escapelist = NO_JUMP;
|
|
flist = test_then_block(ls); /* IF cond THEN block */
|
|
while (ls->t.token == TK_ELSEIF) {
|
|
luaK_concat(fs, &escapelist, luaK_jump(fs));
|
|
luaK_patchtohere(fs, flist);
|
|
flist = test_then_block(ls); /* ELSEIF cond THEN block */
|
|
}
|
|
if (ls->t.token == TK_ELSE) {
|
|
luaK_concat(fs, &escapelist, luaK_jump(fs));
|
|
luaK_patchtohere(fs, flist);
|
|
luaX_next(ls); /* skip ELSE (after patch, for correct line info) */
|
|
block(ls); /* `else' part */
|
|
}
|
|
else
|
|
luaK_concat(fs, &escapelist, flist);
|
|
luaK_patchtohere(fs, escapelist);
|
|
check_match(ls, TK_END, TK_IF, line);
|
|
}
|
|
|
|
|
|
static void localfunc (LexState *ls) {
|
|
expdesc v, b;
|
|
FuncState *fs = ls->fs;
|
|
new_localvar(ls, str_checkname(ls), 0);
|
|
init_exp(&v, VLOCAL, fs->freereg);
|
|
luaK_reserveregs(fs, 1);
|
|
adjustlocalvars(ls, 1);
|
|
body(ls, &b, 0, ls->linenumber);
|
|
luaK_storevar(fs, &v, &b);
|
|
/* debug information will only see the variable after this point! */
|
|
getlocvar(fs, fs->nactvar - 1).startpc = fs->pc;
|
|
}
|
|
|
|
|
|
static void localstat (LexState *ls) {
|
|
/* stat -> LOCAL NAME {`,' NAME} [`=' explist1] */
|
|
int nvars = 0;
|
|
int nexps;
|
|
expdesc e;
|
|
do {
|
|
new_localvar(ls, str_checkname(ls), nvars++);
|
|
} while (testnext(ls, ','));
|
|
if (testnext(ls, '='))
|
|
nexps = explist1(ls, &e);
|
|
else {
|
|
e.k = VVOID;
|
|
nexps = 0;
|
|
}
|
|
adjust_assign(ls, nvars, nexps, &e);
|
|
adjustlocalvars(ls, nvars);
|
|
}
|
|
|
|
|
|
static int funcname (LexState *ls, expdesc *v) {
|
|
/* funcname -> NAME {field} [`:' NAME] */
|
|
int needself = 0;
|
|
singlevar(ls, v);
|
|
while (ls->t.token == '.')
|
|
field(ls, v);
|
|
if (ls->t.token == ':') {
|
|
needself = 1;
|
|
field(ls, v);
|
|
}
|
|
return needself;
|
|
}
|
|
|
|
|
|
static void funcstat (LexState *ls, int line) {
|
|
/* funcstat -> FUNCTION funcname body */
|
|
int needself;
|
|
expdesc v, b;
|
|
luaX_next(ls); /* skip FUNCTION */
|
|
needself = funcname(ls, &v);
|
|
body(ls, &b, needself, line);
|
|
luaK_storevar(ls->fs, &v, &b);
|
|
luaK_fixline(ls->fs, line); /* definition `happens' in the first line */
|
|
}
|
|
|
|
|
|
static void exprstat (LexState *ls) {
|
|
/* stat -> func | assignment */
|
|
FuncState *fs = ls->fs;
|
|
struct LHS_assign v;
|
|
primaryexp(ls, &v.v);
|
|
if (v.v.k == VCALL) /* stat -> func */
|
|
SETARG_C(getcode(fs, &v.v), 1); /* call statement uses no results */
|
|
else { /* stat -> assignment */
|
|
v.prev = NULL;
|
|
assignment(ls, &v, 1);
|
|
}
|
|
}
|
|
|
|
|
|
static void retstat (LexState *ls) {
|
|
/* stat -> RETURN explist */
|
|
FuncState *fs = ls->fs;
|
|
expdesc e;
|
|
int first, nret; /* registers with returned values */
|
|
luaX_next(ls); /* skip RETURN */
|
|
if (block_follow(ls->t.token) || ls->t.token == ';')
|
|
first = nret = 0; /* return no values */
|
|
else {
|
|
nret = explist1(ls, &e); /* optional return values */
|
|
if (hasmultret(e.k)) {
|
|
luaK_setmultret(fs, &e);
|
|
if (e.k == VCALL && nret == 1) { /* tail call? */
|
|
SET_OPCODE(getcode(fs,&e), OP_TAILCALL);
|
|
lua_assert(GETARG_A(getcode(fs,&e)) == fs->nactvar);
|
|
}
|
|
first = fs->nactvar;
|
|
nret = LUA_MULTRET; /* return all values */
|
|
}
|
|
else {
|
|
if (nret == 1) /* only one single value? */
|
|
first = luaK_exp2anyreg(fs, &e);
|
|
else {
|
|
luaK_exp2nextreg(fs, &e); /* values must go to the `stack' */
|
|
first = fs->nactvar; /* return all `active' values */
|
|
lua_assert(nret == fs->freereg - first);
|
|
}
|
|
}
|
|
}
|
|
luaK_ret(fs, first, nret);
|
|
}
|
|
|
|
|
|
static int statement (LexState *ls) {
|
|
int line = ls->linenumber; /* may be needed for error messages */
|
|
switch (ls->t.token) {
|
|
case TK_IF: { /* stat -> ifstat */
|
|
ifstat(ls, line);
|
|
return 0;
|
|
}
|
|
case TK_WHILE: { /* stat -> whilestat */
|
|
whilestat(ls, line);
|
|
return 0;
|
|
}
|
|
case TK_DO: { /* stat -> DO block END */
|
|
luaX_next(ls); /* skip DO */
|
|
block(ls);
|
|
check_match(ls, TK_END, TK_DO, line);
|
|
return 0;
|
|
}
|
|
case TK_FOR: { /* stat -> forstat */
|
|
forstat(ls, line);
|
|
return 0;
|
|
}
|
|
case TK_REPEAT: { /* stat -> repeatstat */
|
|
repeatstat(ls, line);
|
|
return 0;
|
|
}
|
|
case TK_FUNCTION: {
|
|
funcstat(ls, line); /* stat -> funcstat */
|
|
return 0;
|
|
}
|
|
case TK_LOCAL: { /* stat -> localstat */
|
|
luaX_next(ls); /* skip LOCAL */
|
|
if (testnext(ls, TK_FUNCTION)) /* local function? */
|
|
localfunc(ls);
|
|
else
|
|
localstat(ls);
|
|
return 0;
|
|
}
|
|
case TK_RETURN: { /* stat -> retstat */
|
|
retstat(ls);
|
|
return 1; /* must be last statement */
|
|
}
|
|
case TK_BREAK: { /* stat -> breakstat */
|
|
luaX_next(ls); /* skip BREAK */
|
|
breakstat(ls);
|
|
return 1; /* must be last statement */
|
|
}
|
|
default: {
|
|
exprstat(ls);
|
|
return 0; /* to avoid warnings */
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void chunk (LexState *ls) {
|
|
/* chunk -> { stat [`;'] } */
|
|
int islast = 0;
|
|
enterlevel(ls);
|
|
while (!islast && !block_follow(ls->t.token)) {
|
|
islast = statement(ls);
|
|
testnext(ls, ';');
|
|
lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg &&
|
|
ls->fs->freereg >= ls->fs->nactvar);
|
|
ls->fs->freereg = ls->fs->nactvar; /* free registers */
|
|
}
|
|
leavelevel(ls);
|
|
}
|
|
|
|
/* }====================================================================== */
|