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.
This commit is contained in:
Roberto Ierusalimschy 2019-07-03 14:18:07 -03:00
parent 8eca21c2e8
commit 4d46289331
3 changed files with 103 additions and 81 deletions

View File

@ -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;

View File

@ -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{;}}}

View File

@ -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)