diff --git a/lparser.c b/lparser.c index 875f7d04..52486e08 100644 --- a/lparser.c +++ b/lparser.c @@ -1656,13 +1656,50 @@ static void localfunc (LexState *ls) { } -static void commonlocalstat (LexState *ls) { - /* stat -> LOCAL NAME {',' NAME} ['=' explist] */ +static int getlocalattribute (LexState *ls) { + /* ATTRIB -> ['<' Name '>'] */ + if (testnext(ls, '<')) { + const char *attr = getstr(str_checkname(ls)); + checknext(ls, '>'); + if (strcmp(attr, "const") == 0) + return 1; /* read-only variable */ + else if (strcmp(attr, "toclose") == 0) + return 2; /* to-be-closed variable */ + else + luaK_semerror(ls, + luaO_pushfstring(ls->L, "unknown attribute '%s'", attr)); + } + return 0; +} + + +static void checktoclose (LexState *ls, int toclose) { + if (toclose != -1) { /* is there a to-be-closed variable? */ + FuncState *fs = ls->fs; + markupval(fs, fs->nactvar + toclose + 1); + fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */ + luaK_codeABC(fs, OP_TBC, fs->nactvar + toclose, 0, 0); + } +} + + +static void localstat (LexState *ls) { + /* stat -> LOCAL ATTRIB NAME {',' ATTRIB NAME} ['=' explist] */ + int toclose = -1; /* index of to-be-closed variable (if any) */ int nvars = 0; int nexps; expdesc e; do { - new_localvar(ls, str_checkname(ls)); + int kind = getlocalattribute(ls); + Vardesc *var = new_localvar(ls, str_checkname(ls)); + if (kind != 0) { /* is there an attribute? */ + var->ro = 1; /* all attributes make variable read-only */ + if (kind == 2) { /* to-be-closed? */ + if (toclose != -1) /* one already present? */ + luaK_semerror(ls, "multiple to-be-closed variables in local list"); + toclose = nvars; + } + } nvars++; } while (testnext(ls, ',')); if (testnext(ls, '=')) @@ -1672,56 +1709,11 @@ static void commonlocalstat (LexState *ls) { nexps = 0; } adjust_assign(ls, nvars, nexps, &e); + checktoclose(ls, toclose); adjustlocalvars(ls, nvars); } -static void tocloselocalstat (LexState *ls, Vardesc *var) { - FuncState *fs = ls->fs; - var->ro = 1; /* to-be-closed variables are always read-only */ - markupval(fs, fs->nactvar + 1); - fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */ - luaK_codeABC(fs, OP_TBC, fs->nactvar, 0, 0); -} - - -static void checkattrib (LexState *ls, TString *attr, Vardesc *var) { - 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 attriblocalstat (LexState *ls) { - FuncState *fs = ls->fs; - Vardesc *var; - expdesc e; - TString *attr = str_checkname(ls); - testnext(ls, '>'); - var = new_localvar(ls, str_checkname(ls)); - checknext(ls, '='); - expr(ls, &e); - checkattrib(ls, attr, var); - luaK_tonumeral(fs, &e, &var->val); - luaK_exp2nextreg(fs, &e); - adjustlocalvars(ls, 1); -} - - -static void localstat (LexState *ls) { - /* stat -> LOCAL NAME {',' NAME} ['=' explist] - | LOCAL *toclose NAME '=' exp */ - if (testnext(ls, '<')) - attriblocalstat(ls); - else - commonlocalstat(ls); -} - - static int funcname (LexState *ls, expdesc *v) { /* funcname -> NAME {fieldsel} [':' NAME] */ int ismethod = 0; diff --git a/manual/manual.of b/manual/manual.of index e9416956..136e9022 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1399,23 +1399,30 @@ they must all result in numbers. Their values are called respectively the @emph{initial value}, the @emph{limit}, and the @emph{step}. If the step is absent, it defaults @N{to 1}. -Then the loop body is repeated with the value of the control variable + +If both the initial value and the step are integers, +the loop is done with integers; +note that the limit may not be an integer. +Otherwise, the loop is done with floats. +(Beware of floating-point accuracy in this case.) + +After that initialization, +the loop body is repeated with the value of the control variable going through an arithmetic progression, starting at the initial value, -with a common difference given by the step, -until that value passes the limit. +with a common difference given by the step. A negative step makes a decreasing sequence; a step equal to zero raises an error. +The loop continues while the value is less than +or equal to the limit +(greater than or equal to for a negative step). If the initial value is already greater than the limit (or less than, if the step is negative), the body is not executed. -If both the initial value and the step are integers, -the loop is done with integers; -in this case, the range of the control variable is clipped -by the range of integers. -Otherwise, the loop is done with floats. -(Beware of floating-point accuracy in this case.) +For integer loops, +the control variable never wraps around; +instead, the loop ends in case of an overflow. You should not change the value of the control variable during the loop. @@ -1490,22 +1497,25 @@ Function calls are explained in @See{functioncall}. @x{Local variables} can be declared anywhere inside a block. 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 -}} +@producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}} +@producname{attnamelist}@producbody{ + attrib @bnfNter{Name} @bnfrep{@bnfter{,} attrib @bnfNter{Name}}} +} 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. + +Each variable name may be preceded by an attribute +(a name between angle brackets): +@Produc{ +@producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}} +} 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}, which declares a to-be-closed variable @see{to-be-closed}. - +A list of variables can contain at most one to-be-closed variable. A chunk is also a block @see{chunks}, and so local variables can be declared in a chunk outside any explicit block. @@ -1516,12 +1526,6 @@ 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, -using the identifier @id{toclose} as its attribute: -@Produc{ -@producname{stat}@producbody{ - @Rw{local} @bnfter{<} @id{toclose} @bnfter{>} Name @bnfter{=} exp -}} 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, @@ -8215,7 +8219,7 @@ then @id{date} returns the date as a string, formatted according to the same rules as the @ANSI{strftime}. If @id{format} is absent, it defaults to @St{%c}, -which gives a reasonable date and time representation +which gives a human-readable date and time representation using the current locale. On non-POSIX systems, @@ -9022,10 +9026,14 @@ and @bnfNter{LiteralString}, see @See{lexical}.) @OrNL @Rw{for} namelist @Rw{in} explist @Rw{do} block @Rw{end} @OrNL @Rw{function} funcname funcbody @OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody -@OrNL @Rw{local} namelist @bnfopt{@bnfter{=} explist} -@OrNL @Rw{local} @bnfter{<} Name @bnfter{>} Name @bnfter{=} exp +@OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist} } +@producname{attnamelist}@producbody{ + attrib @bnfNter{Name} @bnfrep{@bnfter{,} attrib @bnfNter{Name}}} + +@producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}} + @producname{retstat}@producbody{@Rw{return} @bnfopt{explist} @bnfopt{@bnfter{;}}} diff --git a/testes/locals.lua b/testes/locals.lua index a41b6f0e..50230a27 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -173,12 +173,32 @@ end assert(x==20) +do -- constants + local a, b, c = 10, 20, 30 + b = a + c + b -- 'b' is not constant + assert(a == 10 and b == 60 and c == 30) + local function checkro (code, name) + local st, msg = load(code) + local gab = string.format("attempt to assign to const variable '%s'", name) + assert(not st and string.find(msg, gab)) + end + checkro("local x, y, z = 10, 20, 30; x = 11; y = 12", "y") + checkro("local x, y, z = 10, 20, 30; x = 11", "x") + checkro("local x, y, z = 10, 20, 30; y = 10; z = 11", "z") +end + + print"testing to-be-closed variables" local function stack(n) n = ((n == 0) or stack(n - 1)) end -local function func2close (f) - return setmetatable({}, {__close = f}) +local function func2close (f, x, y) + local obj = setmetatable({}, {__close = f}) + if x then + return x, obj, y + else + return obj + end end @@ -187,10 +207,11 @@ do do local x = setmetatable({"x"}, {__close = function (self) a[#a + 1] = self[1] end}) - local y = func2close(function (self, err) - assert(err == nil); a[#a + 1] = "y" - end) + local w, y, z = func2close(function (self, err) + assert(err == nil); a[#a + 1] = "y" + end, 10, 20) a[#a + 1] = "in" + assert(w == 10 and z == 20) end a[#a + 1] = "out" assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out") @@ -199,7 +220,8 @@ end do local X = false - local closescope = func2close(function () stack(10); X = true end) + local x, closescope = func2close(function () stack(10); X = true end, 100) + assert(x == 100); x = 101; -- 'x' is not read-only -- closing functions do not corrupt returning values local function foo (x)