commit 4d46289331395a845c5de1f6c0e0fe873c50db4f
parent 8eca21c2e85625390a2a3b08c231e75e315980b0
Author: Roberto Ierusalimschy <roberto@inf.puc-rio.br>
Date: Wed, 3 Jul 2019 14:17:40 -0300
Local attributes can be used in list of local variables
The syntax for local attributes ('const'/'toclose') was unified with
the regular syntax for local variables, so that we can have variables
with attributes in local definitions with multiple names; for instance:
local <toclose> f, <const> err = io.open(fname)
This new syntax does not implement constant propagation, yet.
This commit also has some small improvements to the manual.
Diffstat:
M | lparser.c | | | 90 | ++++++++++++++++++++++++++++++++++++------------------------------------------- |
M | manual/manual.of | | | 60 | ++++++++++++++++++++++++++++++++++-------------------------- |
M | testes/locals.lua | | | 34 | ++++++++++++++++++++++++++++------ |
3 files changed, 103 insertions(+), 81 deletions(-)
diff --git 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
@@ -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
@@ -173,12 +173,32 @@ end
assert(x==20)
+do -- constants
+ local <const> a, b, <const> 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, <const> y, z = 10, 20, 30; x = 11; y = 12", "y")
+ checkro("local <const> x, y, <const> z = 10, 20, 30; x = 11", "x")
+ checkro("local <const> x, y, <const> 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 <toclose> x = setmetatable({"x"}, {__close = function (self)
a[#a + 1] = self[1] end})
- local <toclose> y = func2close(function (self, err)
- assert(err == nil); a[#a + 1] = "y"
- end)
+ local w, <toclose> 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)