The result of an expression in Lua has to be assigned to a variable. The select
function can pick one value from an argument list, but what about selecting all but one value?
This patch adds the following features:
nil
as an lvalue in regular assignment statements. This does what you’d expect, it skips the corresponding rvalue without creating an unnecessary local variable as assigning to _
would.nil
as an lvalue in local
statements. A local
statement without nil
acts exactly the same as before. When there is a nil
, it acts like a local declaration followed by an assignment statement. i.e. local a,nil,b = 1,2,3
is the same as
local a,b a,nil,b = 1,2,3
nil
as the lvalue in a for
statement. A dummy variable(s) with the name (nil)
is created so there’s no upside to doing it, except for consistency with the other uses.Formally, the syntax is
stat ::= varlist '=' explist | local varnamelist ['=' explist] | for varnamelist in explist do block end varlist ::= var {',' var} var ::= Name | prefixexp '[' exp ']' | prefixexp '.' Name | nil varnamelist ::= varname {',' varname} varname ::= Name | nil
I originally proposed this on the Lua mailing list.
diff --git a/src/lcode.c b/src/lcode.c index 5e34624..e02befb 100644 --- a/src/lcode.c +++ b/src/lcode.c @@ -606,6 +606,10 @@ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { luaK_codeABC(fs, op, var->u.ind.t, var->u.ind.idx, e); break; } + case VNIL: { + freeexp(fs, ex); + return; + } default: { lua_assert(0); /* invalid var kind to store */ break; diff --git a/src/lparser.c b/src/lparser.c index 9a54dfc..ed9c6ab 100644 --- a/src/lparser.c +++ b/src/lparser.c @@ -862,7 +862,7 @@ static void funcargs (LexState *ls, expdesc *f, int line) { */ -static void primaryexp (LexState *ls, expdesc *v) { +static int primaryexp (LexState *ls, expdesc *v) { /* primaryexp -> NAME | '(' expr ')' */ switch (ls->t.token) { case '(': { @@ -871,14 +871,20 @@ static void primaryexp (LexState *ls, expdesc *v) { expr(ls, v); check_match(ls, ')', '(', line); luaK_dischargevars(ls->fs, v); - return; + return 1; + } + case TK_NIL: { + init_exp(v, VNIL, 0); + luaX_next(ls); + return 0; } case TK_NAME: { singlevar(ls, v); - return; + return 1; } default: { luaX_syntaxerror(ls, "unexpected symbol"); + return 0; } } } @@ -886,10 +892,11 @@ static void primaryexp (LexState *ls, expdesc *v) { static void suffixedexp (LexState *ls, expdesc *v) { /* suffixedexp -> - primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */ + primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } | + nil */ FuncState *fs = ls->fs; int line = ls->linenumber; - primaryexp(ls, v); + if (!primaryexp(ls, v)) return; for (;;) { switch (ls->t.token) { case '.': { /* fieldsel */ @@ -1140,7 +1147,7 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { static void assignment (LexState *ls, struct LHS_assign *lh, int nvars) { expdesc e; - check_condition(ls, vkisvar(lh->v.k), "syntax error"); + check_condition(ls, (vkisvar(lh->v.k) | (lh->v.k==VNIL)), "syntax error"); if (testnext(ls, ',')) { /* assignment -> ',' suffixedexp assignment */ struct LHS_assign nv; nv.prev = lh; @@ -1312,13 +1319,16 @@ static void forbody (LexState *ls, int base, int line, int nvars, int isnum) { static void fornum (LexState *ls, TString *varname, int line) { - /* fornum -> NAME = exp1,exp1[,exp1] forbody */ + /* fornum -> (NAME | nil) = exp1,exp1[,exp1] forbody */ FuncState *fs = ls->fs; int base = fs->freereg; new_localvarliteral(ls, "(for index)"); new_localvarliteral(ls, "(for limit)"); new_localvarliteral(ls, "(for step)"); - new_localvar(ls, varname); + if (varname) + new_localvar(ls, varname); + else + new_localvarliteral(ls, "(for variable)"); checknext(ls, '='); exp1(ls); /* initial value */ checknext(ls, ','); @@ -1334,7 +1344,7 @@ static void fornum (LexState *ls, TString *varname, int line) { static void forlist (LexState *ls, TString *indexname) { - /* forlist -> NAME {,NAME} IN explist forbody */ + /* forlist -> (NAME | nil) {, (NAME | nil)} IN explist forbody */ FuncState *fs = ls->fs; expdesc e; int nvars = 4; /* gen, state, control, plus at least one declared var */ @@ -1345,10 +1355,19 @@ static void forlist (LexState *ls, TString *indexname) { new_localvarliteral(ls, "(for state)"); new_localvarliteral(ls, "(for control)"); /* create declared variables */ - new_localvar(ls, indexname); + if (indexname) + new_localvar(ls, indexname); + else + new_localvarliteral(ls, "(for variable)"); while (testnext(ls, ',')) { - new_localvar(ls, str_checkname(ls)); - nvars++; + if (testnext(ls, TK_NIL)) { + new_localvarliteral(ls, "(nil)"); + nvars++; + } + else { + new_localvar(ls, str_checkname(ls)); + nvars++; + } } checknext(ls, TK_IN); line = ls->linenumber; @@ -1365,7 +1384,8 @@ static void forstat (LexState *ls, int line) { BlockCnt bl; enterblock(fs, &bl, 1); /* scope for loop and control variables */ luaX_next(ls); /* skip 'for' */ - varname = str_checkname(ls); /* first variable name */ + varname = testnext(ls, TK_NIL) ? NULL + : str_checkname(ls); /* first variable name */ switch (ls->t.token) { case '=': fornum(ls, varname, line); break; case ',': case TK_IN: forlist(ls, varname); break; @@ -1436,23 +1456,65 @@ static void localfunc (LexState *ls) { } -static void localstat (LexState *ls) { - /* stat -> LOCAL NAME {',' NAME} ['=' explist] */ +static int localstat (LexState *ls, int prev, int level) { + /* stat -> LOCAL (NAME | nil) {',' (NAME | nil)} ['=' explist] */ + int first = ls->fs->nactvar + prev; int nvars = 0; + int total; int nexps; expdesc e; do { - new_localvar(ls, str_checkname(ls)); - nvars++; + if (testnext(ls, TK_NIL)) { + /* slot is empty, push and handle the next group of assignments */ + if (testnext(ls, ',')) { + nexps = localstat(ls, prev+nvars, level+1); + if (nexps == 0) return 0; + if (nexps > prev+nvars) { /* drop the unused value */ + ls->fs->freereg--; + nexps--; + } + goto lexp; + } else + break; + } + else { + new_localvar(ls, str_checkname(ls)); + nvars++; + } } while (testnext(ls, ',')); + if (level > 0) { + /* assignment list has holes, initialize the vars */ + e.k = VVOID; + adjust_assign(ls, prev+nvars, 0, &e); + adjustlocalvars(ls, prev+nvars); + } if (testnext(ls, '=')) nexps = explist(ls, &e); else { + if (level > 0) return 0; e.k = VVOID; nexps = 0; } - adjust_assign(ls, nvars, nexps, &e); - adjustlocalvars(ls, nvars); + /* each level is one nil */ + total = level + prev + nvars; + adjust_assign(ls, total, nexps, &e); + if (level == 0) { + /* no holes in the assignment list, set vars in-place */ + adjustlocalvars(ls, total); + return 0; + } + if (nexps > total) + ls->fs->freereg -= nexps - total; /* remove extra values */ + nexps = total; + lexp: + while (nvars-- > 0) { + expdesc var; + init_exp(&var, VLOCAL, first+nvars); + init_exp(&e, VNONRELOC, ls->fs->freereg-1); + luaK_storevar(ls->fs, &var, &e); + nexps--; + } + return nexps; } @@ -1570,7 +1632,7 @@ static void statement (LexState *ls) { if (testnext(ls, TK_FUNCTION)) /* local function? */ localfunc(ls); else - localstat(ls); + localstat(ls, 0, 0); break; } case TK_DBCOLON: { /* stat -> label */