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) { static int getlocalattribute (LexState *ls) {
/* stat -> LOCAL NAME {',' NAME} ['=' explist] */ /* 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 nvars = 0;
int nexps; int nexps;
expdesc e; expdesc e;
do { 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++; nvars++;
} while (testnext(ls, ',')); } while (testnext(ls, ','));
if (testnext(ls, '=')) if (testnext(ls, '='))
@ -1672,56 +1709,11 @@ static void commonlocalstat (LexState *ls) {
nexps = 0; nexps = 0;
} }
adjust_assign(ls, nvars, nexps, &e); adjust_assign(ls, nvars, nexps, &e);
checktoclose(ls, toclose);
adjustlocalvars(ls, nvars); 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) { static int funcname (LexState *ls, expdesc *v) {
/* funcname -> NAME {fieldsel} [':' NAME] */ /* funcname -> NAME {fieldsel} [':' NAME] */
int ismethod = 0; int ismethod = 0;

View File

@ -1399,23 +1399,30 @@ they must all result in numbers.
Their values are called respectively Their values are called respectively
the @emph{initial value}, the @emph{limit}, and the @emph{step}. the @emph{initial value}, the @emph{limit}, and the @emph{step}.
If the step is absent, it defaults @N{to 1}. 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, going through an arithmetic progression,
starting at the initial value, starting at the initial value,
with a common difference given by the step, with a common difference given by the step.
until that value passes the limit.
A negative step makes a decreasing sequence; A negative step makes a decreasing sequence;
a step equal to zero raises an error. 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 If the initial value is already greater than the limit
(or less than, if the step is negative), (or less than, if the step is negative),
the body is not executed. the body is not executed.
If both the initial value and the step are integers, For integer loops,
the loop is done with integers; the control variable never wraps around;
in this case, the range of the control variable is clipped instead, the loop ends in case of an overflow.
by the range of integers.
Otherwise, the loop is done with floats.
(Beware of floating-point accuracy in this case.)
You should not change the value of the control variable You should not change the value of the control variable
during the loop. during the loop.
@ -1490,22 +1497,25 @@ Function calls are explained in @See{functioncall}.
@x{Local variables} can be declared anywhere inside a block. @x{Local variables} can be declared anywhere inside a block.
The declaration can include an initialization: The declaration can include an initialization:
@Produc{ @Produc{
@producname{stat}@producbody{@Rw{local} namelist @bnfopt{@bnfter{=} explist}} @producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}}
@producname{stat}@producbody{ @producname{attnamelist}@producbody{
@Rw{local} @bnfter{<} Name @bnfter{>} Name @bnfter{=} exp attrib @bnfNter{Name} @bnfrep{@bnfter{,} attrib @bnfNter{Name}}}
}} }
If present, an initial assignment has the same semantics If present, an initial assignment has the same semantics
of a multiple assignment @see{assignment}. of a multiple assignment @see{assignment}.
Otherwise, all variables are initialized with @nil. 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. Each variable name may be preceded by an attribute
In this case, there must be an initialization. (a name between angle brackets):
@Produc{
@producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}}
}
There are two possible attributes: There are two possible attributes:
@id{const}, which declares a @x{constant variable}, @id{const}, which declares a @x{constant variable},
that is, a variable that cannot be assigned to that is, a variable that cannot be assigned to
after its initialization; after its initialization;
and @id{toclose}, which declares a to-be-closed variable @see{to-be-closed}. 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}, A chunk is also a block @see{chunks},
and so local variables can be declared in a chunk outside any explicit block. 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} @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, A to-be-closed variable behaves like a constant local variable,
except that its value is @emph{closed} whenever the variable except that its value is @emph{closed} whenever the variable
goes out of scope, including normal block termination, 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}. formatted according to the same rules as the @ANSI{strftime}.
If @id{format} is absent, it defaults to @St{%c}, 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. using the current locale.
On non-POSIX systems, 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{for} namelist @Rw{in} explist @Rw{do} block @Rw{end}
@OrNL @Rw{function} funcname funcbody @OrNL @Rw{function} funcname funcbody
@OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody @OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody
@OrNL @Rw{local} namelist @bnfopt{@bnfter{=} explist} @OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist}
@OrNL @Rw{local} @bnfter{<} Name @bnfter{>} Name @bnfter{=} exp
} }
@producname{attnamelist}@producbody{
attrib @bnfNter{Name} @bnfrep{@bnfter{,} attrib @bnfNter{Name}}}
@producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}}
@producname{retstat}@producbody{@Rw{return} @producname{retstat}@producbody{@Rw{return}
@bnfopt{explist} @bnfopt{@bnfter{;}}} @bnfopt{explist} @bnfopt{@bnfter{;}}}

View File

@ -173,12 +173,32 @@ end
assert(x==20) 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" print"testing to-be-closed variables"
local function stack(n) n = ((n == 0) or stack(n - 1)) end local function stack(n) n = ((n == 0) or stack(n - 1)) end
local function func2close (f) local function func2close (f, x, y)
return setmetatable({}, {__close = f}) local obj = setmetatable({}, {__close = f})
if x then
return x, obj, y
else
return obj
end
end end
@ -187,10 +207,11 @@ do
do do
local <toclose> x = setmetatable({"x"}, {__close = function (self) local <toclose> x = setmetatable({"x"}, {__close = function (self)
a[#a + 1] = self[1] end}) a[#a + 1] = self[1] end})
local <toclose> y = func2close(function (self, err) local w, <toclose> y, z = func2close(function (self, err)
assert(err == nil); a[#a + 1] = "y" assert(err == nil); a[#a + 1] = "y"
end) end, 10, 20)
a[#a + 1] = "in" a[#a + 1] = "in"
assert(w == 10 and z == 20)
end end
a[#a + 1] = "out" a[#a + 1] = "out"
assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out") assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out")
@ -199,7 +220,8 @@ end
do do
local X = false 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 -- closing functions do not corrupt returning values
local function foo (x) local function foo (x)