diff --git a/lcode.c b/lcode.c index 1e36e584..eccb8380 100644 --- a/lcode.c +++ b/lcode.c @@ -678,11 +678,12 @@ void luaK_setoneret (FuncState *fs, expdesc *e) { void luaK_dischargevars (FuncState *fs, expdesc *e) { switch (e->k) { case VLOCAL: { /* already in a register */ + e->u.info = e->u.var.idx; e->k = VNONRELOC; /* becomes a non-relocatable value */ break; } case VUPVAL: { /* move value to some (pending) register */ - e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.info, 0); + e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.var.idx, 0); e->k = VRELOC; break; } @@ -938,12 +939,12 @@ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { switch (var->k) { case VLOCAL: { freeexp(fs, ex); - exp2reg(fs, ex, var->u.info); /* compute 'ex' into proper place */ + exp2reg(fs, ex, var->u.var.idx); /* compute 'ex' into proper place */ return; } case VUPVAL: { int e = luaK_exp2anyreg(fs, ex); - luaK_codeABC(fs, OP_SETUPVAL, e, var->u.info, 0); + luaK_codeABC(fs, OP_SETUPVAL, e, var->u.var.idx, 0); break; } case VINDEXUP: { @@ -1165,25 +1166,30 @@ static int isSCnumber (expdesc *e, lua_Integer *i, int *isfloat) { ** values in registers. */ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { - lua_assert(!hasjumps(t) && (vkisinreg(t->k) || t->k == VUPVAL)); + lua_assert(!hasjumps(t) && + (t->k == VLOCAL || t->k == VNONRELOC || t->k == VUPVAL)); if (t->k == VUPVAL && !isKstr(fs, k)) /* upvalue indexed by non string? */ luaK_exp2anyreg(fs, t); /* put it in a register */ - t->u.ind.t = t->u.info; /* register or upvalue index */ if (t->k == VUPVAL) { + t->u.ind.t = t->u.var.idx; /* upvalue index */ t->u.ind.idx = k->u.info; /* literal string */ t->k = VINDEXUP; } - else if (isKstr(fs, k)) { - t->u.ind.idx = k->u.info; /* literal string */ - t->k = VINDEXSTR; - } - else if (isCint(k)) { - t->u.ind.idx = cast_int(k->u.ival); /* integer constant in proper range */ - t->k = VINDEXI; - } else { - t->u.ind.idx = luaK_exp2anyreg(fs, k); /* register */ - t->k = VINDEXED; + /* register index of the table */ + t->u.ind.t = (t->k == VLOCAL) ? t->u.var.idx: t->u.info; + if (isKstr(fs, k)) { + t->u.ind.idx = k->u.info; /* literal string */ + t->k = VINDEXSTR; + } + else if (isCint(k)) { + t->u.ind.idx = cast_int(k->u.ival); /* int. constant in proper range */ + t->k = VINDEXI; + } + else { + t->u.ind.idx = luaK_exp2anyreg(fs, k); /* register */ + t->k = VINDEXED; + } } } diff --git a/lparser.c b/lparser.c index 4e6c27fe..7c23710a 100644 --- a/lparser.c +++ b/lparser.c @@ -156,6 +156,13 @@ static void init_exp (expdesc *e, expkind k, int i) { } +static void init_var (expdesc *e, expkind k, int i) { + e->f = e->t = NO_JUMP; + e->k = k; + e->u.var.idx = i; +} + + static void codestring (LexState *ls, expdesc *e, TString *s) { init_exp(e, VK, luaK_stringK(ls->fs, s)); } @@ -187,31 +194,82 @@ static int registerlocalvar (LexState *ls, TString *varname) { /* ** Create a new local variable with the given 'name'. */ -static void new_localvar (LexState *ls, TString *name) { +static Vardesc *new_localvar (LexState *ls, TString *name) { FuncState *fs = ls->fs; Dyndata *dyd = ls->dyd; + Vardesc *var; int reg = registerlocalvar(ls, name); checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal, MAXVARS, "local variables"); luaM_growvector(ls->L, dyd->actvar.arr, dyd->actvar.n + 1, dyd->actvar.size, Vardesc, MAX_INT, "local variables"); - dyd->actvar.arr[dyd->actvar.n++].idx = cast(short, reg); + var = &dyd->actvar.arr[dyd->actvar.n++]; + var->idx = cast(short, reg); + var->name = name; + var->ro = 0; + return var; } #define new_localvarliteral(ls,v) \ new_localvar(ls, luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char)) - 1)); + +/* +** Return the "variable description" (Vardesc) of a given +** variable +*/ +static Vardesc *getlocalvardesc (FuncState *fs, int i) { + return &fs->ls->dyd->actvar.arr[fs->firstlocal + i]; +} + /* ** Get the debug-information entry for current variable 'i'. */ static LocVar *getlocvar (FuncState *fs, int i) { - int idx = fs->ls->dyd->actvar.arr[fs->firstlocal + i].idx; + int idx = getlocalvardesc(fs, i)->idx; lua_assert(idx < fs->nlocvars); return &fs->f->locvars[idx]; } +/* +** Return the "variable description" (Vardesc) of a given +** variable or upvalue +*/ +static Vardesc *getvardesc (FuncState *fs, expdesc *e) { + if (e->k == VLOCAL) + return getlocalvardesc(fs, e->u.var.idx); + else if (e->k != VUPVAL) + return NULL; /* not a local variable */ + else { /* upvalue: must go up all levels up to the original local */ + int idx = e->u.var.idx; + for (;;) { + Upvaldesc *up = &fs->f->upvalues[idx]; + fs = fs->prev; /* must look at the previous level */ + idx = up->idx; /* at this index */ + if (fs == NULL) { /* no more levels? (can happen only with _ENV) */ + lua_assert(strcmp(getstr(up->name), LUA_ENV) == 0); + return NULL; + } + else if (up->instack) /* got to the original level? */ + return getlocalvardesc(fs, idx); + /* else repeat for previous level */ + } + } +} + + +static void check_readonly (LexState *ls, expdesc *e) { + Vardesc *vardesc = getvardesc(ls->fs, e); + if (vardesc && vardesc->ro) { /* is variable local and const? */ + const char *msg = luaO_pushfstring(ls->L, + "assignment to const variable '%s'", getstr(vardesc->name)); + luaK_semerror(ls, msg); /* error */ + } +} + + /* ** Start the scope for the last 'nvars' created variables. ** (debug info.) @@ -259,7 +317,7 @@ static int newupvalue (FuncState *fs, TString *name, expdesc *v) { while (oldsize < f->sizeupvalues) f->upvalues[oldsize++].name = NULL; f->upvalues[fs->nups].instack = (v->k == VLOCAL); - f->upvalues[fs->nups].idx = cast_byte(v->u.info); + f->upvalues[fs->nups].idx = cast_byte(v->u.var.idx); f->upvalues[fs->nups].name = name; luaC_objbarrier(fs->ls->L, f, name); return fs->nups++; @@ -304,7 +362,7 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { else { int v = searchvar(fs, n); /* look up locals at current level */ if (v >= 0) { /* found? */ - init_exp(var, VLOCAL, v); /* variable is local */ + init_var(var, VLOCAL, v); /* variable is local */ if (!base) markupval(fs, v); /* local will be used as an upval */ } @@ -317,7 +375,7 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { /* else was LOCAL or UPVAL */ idx = newupvalue(fs, n, var); /* will be a new upvalue */ } - init_exp(var, VUPVAL, idx); /* new or old upvalue */ + init_var(var, VUPVAL, idx); /* new or old upvalue */ } } } @@ -1199,20 +1257,20 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { for (; lh; lh = lh->prev) { /* check all previous assignments */ if (vkisindexed(lh->v.k)) { /* assignment to table field? */ if (lh->v.k == VINDEXUP) { /* is table an upvalue? */ - if (v->k == VUPVAL && lh->v.u.ind.t == v->u.info) { + if (v->k == VUPVAL && lh->v.u.ind.t == v->u.var.idx) { conflict = 1; /* table is the upvalue being assigned now */ lh->v.k = VINDEXSTR; lh->v.u.ind.t = extra; /* assignment will use safe copy */ } } else { /* table is a register */ - if (v->k == VLOCAL && lh->v.u.ind.t == v->u.info) { + if (v->k == VLOCAL && lh->v.u.ind.t == v->u.var.idx) { conflict = 1; /* table is the local being assigned now */ lh->v.u.ind.t = extra; /* assignment will use safe copy */ } /* is index the local being assigned? */ if (lh->v.k == VINDEXED && v->k == VLOCAL && - lh->v.u.ind.idx == v->u.info) { + lh->v.u.ind.idx == v->u.var.idx) { conflict = 1; lh->v.u.ind.idx = extra; /* previous assignment will use safe copy */ } @@ -1222,7 +1280,7 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { if (conflict) { /* copy upvalue/local value to a temporary (in position 'extra') */ OpCode op = (v->k == VLOCAL) ? OP_MOVE : OP_GETUPVAL; - luaK_codeABC(fs, op, extra, v->u.info, 0); + luaK_codeABC(fs, op, extra, v->u.var.idx, 0); luaK_reserveregs(fs, 1); } } @@ -1237,6 +1295,7 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { static void restassign (LexState *ls, struct LHS_assign *lh, int nvars) { expdesc e; check_condition(ls, vkisvar(lh->v.k), "syntax error"); + check_readonly(ls, &lh->v); if (testnext(ls, ',')) { /* restassign -> ',' suffixedexp restassign */ struct LHS_assign nv; nv.prev = lh; @@ -1615,28 +1674,38 @@ static void commonlocalstat (LexState *ls) { } -static void tocloselocalstat (LexState *ls) { +static void tocloselocalstat (LexState *ls, Vardesc *var) { FuncState *fs = ls->fs; - TString *attr = str_checkname(ls); - if (strcmp(getstr(attr), "toclose") != 0) - luaK_semerror(ls, - luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr))); - testnext(ls, '>'); - new_localvar(ls, str_checkname(ls)); - checknext(ls, '='); - exp1(ls); + var->ro = 1; /* to-be-closed variables are always read-only */ markupval(fs, fs->nactvar); fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */ - adjustlocalvars(ls, 1); luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0); } +static void attriblocalstat (LexState *ls) { + Vardesc *var; + TString *attr = str_checkname(ls); + testnext(ls, '>'); + var = new_localvar(ls, str_checkname(ls)); + checknext(ls, '='); + exp1(ls); + adjustlocalvars(ls, 1); + if (strcmp(getstr(attr), "const") == 0) + var->ro = 1; /* set variable as read-only */ + else if (strcmp(getstr(attr), "toclose") == 0) + tocloselocalstat(ls, var); + else + luaK_semerror(ls, + luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr))); +} + + static void localstat (LexState *ls) { /* stat -> LOCAL NAME {',' NAME} ['=' explist] | LOCAL *toclose NAME '=' exp */ if (testnext(ls, '<')) - tocloselocalstat(ls); + attriblocalstat(ls); else commonlocalstat(ls); } @@ -1801,7 +1870,7 @@ static void mainfunc (LexState *ls, FuncState *fs) { expdesc v; open_func(ls, fs, &bl); setvararg(fs, 0); /* main function is always declared vararg */ - init_exp(&v, VLOCAL, 0); /* create and... */ + init_var(&v, VLOCAL, 0); /* create and... */ newupvalue(fs, ls->envn, &v); /* ...set environment upvalue */ luaX_next(ls); /* read first token */ statlist(ls); /* parse main body */ diff --git a/lparser.h b/lparser.h index 3d6bd978..3b5d399f 100644 --- a/lparser.h +++ b/lparser.h @@ -33,8 +33,8 @@ typedef enum { VKINT, /* integer constant; nval = numerical integer value */ VNONRELOC, /* expression has its value in a fixed register; info = result register */ - VLOCAL, /* local variable; info = local register */ - VUPVAL, /* upvalue variable; info = index of upvalue in 'upvalues' */ + VLOCAL, /* local variable; var.idx = local register */ + VUPVAL, /* upvalue variable; var.idx = index of upvalue in 'upvalues' */ VINDEXED, /* indexed variable; ind.t = table register; ind.idx = key's R index */ @@ -58,7 +58,7 @@ typedef enum { #define vkisvar(k) (VLOCAL <= (k) && (k) <= VINDEXSTR) #define vkisindexed(k) (VINDEXED <= (k) && (k) <= VINDEXSTR) -#define vkisinreg(k) ((k) == VNONRELOC || (k) == VLOCAL) + typedef struct expdesc { expkind k; @@ -70,15 +70,20 @@ typedef struct expdesc { short idx; /* index (R or "long" K) */ lu_byte t; /* table (register or upvalue) */ } ind; + struct { /* for local variables and upvalues */ + lu_byte idx; /* index of the variable */ + } var; } u; int t; /* patch list of 'exit when true' */ int f; /* patch list of 'exit when false' */ } expdesc; -/* description of active local variable */ +/* description of an active local variable */ typedef struct Vardesc { + TString *name; short idx; /* index of the variable in the Proto's 'locvars' array */ + lu_byte ro; /* true if variable is 'const' */ } Vardesc; diff --git a/manual/manual.of b/manual/manual.of index 54a07879..6cac8c6c 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1488,13 +1488,24 @@ Function calls are explained in @See{functioncall}. @sect3{localvar| @title{Local Declarations} @x{Local variables} can be declared anywhere inside a block. -The declaration can include an initial assignment: +The declaration can include an initialization: @Produc{ @producname{stat}@producbody{@Rw{local} namelist @bnfopt{@bnfter{=} explist}} -} +@producname{stat}@producbody{ + @Rw{local} @bnfter{<} Name @bnfter{>} Name @bnfter{=} exp +}} If present, an initial assignment has the same semantics of a multiple assignment @see{assignment}. Otherwise, all variables are initialized with @nil. +The second syntax declares a local with a given attribute, +which is the name between the angle brackets. +In this case, there must be an initialization. +There are two possible attributes: +@id{const}, which declares a @x{constant variable}, +that is, a variable that cannot be assigned to +after its initialization; +and @id{toclose}, wich declares a to-be-closed variable @see{to-be-closed}. + A chunk is also a block @see{chunks}, and so local variables can be declared in a chunk outside any explicit block. @@ -1506,12 +1517,12 @@ The visibility rules for local variables are explained in @See{visibility}. @sect3{to-be-closed| @title{To-be-closed Variables} A local variable can be declared as a @def{to-be-closed} variable, -with the following syntax: +using the identifier @id{toclose} as its attribute: @Produc{ @producname{stat}@producbody{ - @Rw{local} @bnfter{<} @bnfter{toclose} @bnfter{>} Name @bnfter{=} exp + @Rw{local} @bnfter{<} @id{toclose} @bnfter{>} Name @bnfter{=} exp }} -A to-be-closed variable behaves like a normal local variable, +A to-be-closed variable behaves like a constant local variable, except that its value is @emph{closed} whenever the variable goes out of scope, including normal block termination, exiting its block by @Rw{break}/@Rw{goto}/@Rw{return}, @@ -7603,7 +7614,7 @@ or a float otherwise. @LibEntry{math.abs (x)| -Returns the absolute value of @id{x}. (integer/float) +Returns the maximum value between @id{x} and @id{-x}. (integer/float) } @@ -8042,7 +8053,8 @@ following the lexical conventions of Lua. This format always reads the longest input sequence that is a valid prefix for a numeral; if that prefix does not form a valid numeral -(e.g., an empty string, @St{0x}, or @St{3.4e-}), +(e.g., an empty string, @St{0x}, or @St{3.4e-}) +or it is too long (more than 200 characters), it is discarded and the format returns @nil. } @@ -8949,7 +8961,7 @@ and @bnfNter{LiteralString}, see @See{lexical}.) @OrNL @Rw{function} funcname funcbody @OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody @OrNL @Rw{local} namelist @bnfopt{@bnfter{=} explist} -@OrNL @Rw{local} @bnfter{<} @bnfter{toclose} @bnfter{>} Name @bnfter{=} exp +@OrNL @Rw{local} @bnfter{<} Name @bnfter{>} Name @bnfter{=} exp } @producname{retstat}@producbody{@Rw{return} diff --git a/testes/constructs.lua b/testes/constructs.lua index a83df79e..b91e0979 100644 --- a/testes/constructs.lua +++ b/testes/constructs.lua @@ -59,6 +59,41 @@ assert((x>y) and x or y == 2); assert(1234567890 == tonumber('1234567890') and 1234567890+1 == 1234567891) +do -- testing operators with diffent kinds of constants + -- operands to consider: + -- * fit in register + -- * constant doesn't fit in register + -- * floats with integral values + local operand = {3, 100, 5.0, -10, -5.0, 10000, -10000} + local operator = {"+", "-", "*", "/", "//", "%", "^", + "&", "|", "^", "<<", ">>", + "==", "~=", "<", ">", "<=", ">=",} + for _, op in ipairs(operator) do + local f = assert(load(string.format([[return function (x,y) + return x %s y + end]], op)))(); + for _, o1 in ipairs(operand) do + for _, o2 in ipairs(operand) do + local gab = f(o1, o2) + + _ENV.XX = o1 + code = string.format("return XX %s %s", op, o2) + res = assert(load(code))() + assert(res == gab) + + _ENV.XX = o2 + local code = string.format("return (%s) %s XX", o1, op) + local res = assert(load(code))() + assert(res == gab) + + code = string.format("return (%s) %s %s", o1, op, o2) + res = assert(load(code))() + assert(res == gab) + end + end + end +end + -- silly loops repeat until 1; repeat until true; @@ -175,6 +210,28 @@ assert(a==1 and b==nil) print'+'; +do -- testing constants + local prog = [[local x = 10]] + checkload(prog, "unknown attribute 'XXX'") + + checkload([[local xxx = 20; xxx = 10]], + ":1: assignment to const variable 'xxx'") + + checkload([[ + local xx; + local xxx = 20; + local yyy; + local function foo () + local abc = xx + yyy + xxx; + return function () return function () xxx = yyy end end + end + ]], ":6: assignment to const variable 'xxx'") + + checkload([[ + local x = nil + x = io.open() + ]], ":2: assignment to const variable 'x'") +end f = [[ return function ( a , b , c , d , e ) @@ -245,12 +302,12 @@ print('testing short-circuit optimizations (' .. _ENV.GLOB1 .. ')') -- operators with their respective values -local binops = { +local binops = { {" and ", function (a,b) if not a then return a else return b end end}, {" or ", function (a,b) if a then return a else return b end end}, } -local cases = {} +local cases = {} -- creates all combinations of '(cases[i] op cases[n-i])' plus -- 'not(cases[i] op cases[n-i])' (syntax + value) diff --git a/testes/files.lua b/testes/files.lua index eb100fe1..54931c14 100644 --- a/testes/files.lua +++ b/testes/files.lua @@ -144,7 +144,7 @@ do f:write(string.format("0x%X\n", -maxint)) f:write("-0xABCp-3", '\n') assert(f:close()) - f = assert(io.open(file, "r")) + local f = assert(io.open(file, "r")) assert(f:read("n") == maxint) assert(f:read("n") == maxint) assert(f:read("n") == 0xABCp-3) @@ -170,18 +170,18 @@ three ]] local l1, l2, l3, l4, n1, n2, c, dummy assert(f:close()) - f = assert(io.open(file, "r")) + local f = assert(io.open(file, "r")) l1, l2, n1, n2, dummy = f:read("l", "L", "n", "n") assert(l1 == "a line" and l2 == "another line\n" and n1 == 1234 and n2 == 3.45 and dummy == nil) assert(f:close()) - f = assert(io.open(file, "r")) + local f = assert(io.open(file, "r")) l1, l2, n1, n2, c, l3, l4, dummy = f:read(7, "l", "n", "n", 1, "l", "l") assert(l1 == "a line\n" and l2 == "another line" and c == '\n' and n1 == 1234 and n2 == 3.45 and l3 == "one" and l4 == "two" and dummy == nil) assert(f:close()) - f = assert(io.open(file, "r")) + local f = assert(io.open(file, "r")) -- second item failing l1, n1, n2, dummy = f:read("l", "n", "n", "l") assert(l1 == "a line" and n1 == nil) diff --git a/testes/math.lua b/testes/math.lua index b010ff6c..c45a91ad 100644 --- a/testes/math.lua +++ b/testes/math.lua @@ -3,10 +3,10 @@ print("testing numbers and math lib") -local minint = math.mininteger -local maxint = math.maxinteger +local minint = math.mininteger +local maxint = math.maxinteger -local intbits = math.floor(math.log(maxint, 2) + 0.5) + 1 +local intbits = math.floor(math.log(maxint, 2) + 0.5) + 1 assert((1 << intbits) == 0) assert(minint == 1 << (intbits - 1))