Variable attributes can prefix name list

In this format, the attribute applies to all names in the list;
e.g. "global<const> print, require, math".
This commit is contained in:
Roberto Ierusalimschy
2025-05-18 11:43:43 -03:00
parent f2c1531e6c
commit abbae57c78
15 changed files with 84 additions and 60 deletions

View File

@@ -1733,7 +1733,7 @@ static void localfunc (LexState *ls) {
} }
static lu_byte getvarattribute (LexState *ls) { static lu_byte getvarattribute (LexState *ls, lu_byte df) {
/* attrib -> ['<' NAME '>'] */ /* attrib -> ['<' NAME '>'] */
if (testnext(ls, '<')) { if (testnext(ls, '<')) {
TString *ts = str_checkname(ls); TString *ts = str_checkname(ls);
@@ -1746,7 +1746,7 @@ static lu_byte getvarattribute (LexState *ls) {
else else
luaK_semerror(ls, "unknown attribute '%s'", attr); luaK_semerror(ls, "unknown attribute '%s'", attr);
} }
return VDKREG; /* regular variable */ return df; /* return default value */
} }
@@ -1767,10 +1767,12 @@ static void localstat (LexState *ls) {
int nvars = 0; int nvars = 0;
int nexps; int nexps;
expdesc e; expdesc e;
do { /* get prefixed attribute (if any); default is regular local variable */
TString *vname = str_checkname(ls); lu_byte defkind = getvarattribute(ls, VDKREG);
lu_byte kind = getvarattribute(ls); do { /* for each variable */
vidx = new_varkind(ls, vname, kind); TString *vname = str_checkname(ls); /* get its name */
lu_byte kind = getvarattribute(ls, defkind); /* postfixed attribute */
vidx = new_varkind(ls, vname, kind); /* predeclare it */
if (kind == RDKTOCLOSE) { /* to-be-closed? */ if (kind == RDKTOCLOSE) { /* to-be-closed? */
if (toclose != -1) /* one already present? */ if (toclose != -1) /* one already present? */
luaK_semerror(ls, "multiple to-be-closed variables in local list"); luaK_semerror(ls, "multiple to-be-closed variables in local list");
@@ -1778,13 +1780,13 @@ static void localstat (LexState *ls) {
} }
nvars++; nvars++;
} while (testnext(ls, ',')); } while (testnext(ls, ','));
if (testnext(ls, '=')) if (testnext(ls, '=')) /* initialization? */
nexps = explist(ls, &e); nexps = explist(ls, &e);
else { else {
e.k = VVOID; e.k = VVOID;
nexps = 0; nexps = 0;
} }
var = getlocalvardesc(fs, vidx); /* get last variable */ var = getlocalvardesc(fs, vidx); /* retrieve last variable */
if (nvars == nexps && /* no adjustments? */ if (nvars == nexps && /* no adjustments? */
var->vd.kind == RDKCONST && /* last variable is const? */ var->vd.kind == RDKCONST && /* last variable is const? */
luaK_exp2const(fs, &e, &var->k)) { /* compile-time constant? */ luaK_exp2const(fs, &e, &var->k)) { /* compile-time constant? */
@@ -1800,29 +1802,35 @@ static void localstat (LexState *ls) {
} }
static lu_byte getglobalattribute (LexState *ls) { static lu_byte getglobalattribute (LexState *ls, lu_byte df) {
lu_byte kind = getvarattribute(ls); lu_byte kind = getvarattribute(ls, df);
if (kind == RDKTOCLOSE) switch (kind) {
case RDKTOCLOSE:
luaK_semerror(ls, "global variables cannot be to-be-closed"); luaK_semerror(ls, "global variables cannot be to-be-closed");
/* adjust kind for global variable */ break; /* to avoid warnings */
return (kind == VDKREG) ? GDKREG : GDKCONST; case RDKCONST:
return GDKCONST; /* adjust kind for global variable */
default:
return kind;
}
} }
static void globalstat (LexState *ls) { static void globalstat (LexState *ls) {
/* globalstat -> (GLOBAL) '*' attrib /* globalstat -> (GLOBAL) attrib '*'
globalstat -> (GLOBAL) NAME attrib {',' NAME attrib} */ globalstat -> (GLOBAL) attrib NAME attrib {',' NAME attrib} */
FuncState *fs = ls->fs; FuncState *fs = ls->fs;
/* get prefixed attribute (if any); default is regular global variable */
lu_byte defkind = getglobalattribute(ls, GDKREG);
if (testnext(ls, '*')) { if (testnext(ls, '*')) {
lu_byte kind = getglobalattribute(ls);
/* use NULL as name to represent '*' entries */ /* use NULL as name to represent '*' entries */
new_varkind(ls, NULL, kind); new_varkind(ls, NULL, defkind);
fs->nactvar++; /* activate declaration */ fs->nactvar++; /* activate declaration */
} }
else { else {
do { do { /* list of names */
TString *vname = str_checkname(ls); TString *vname = str_checkname(ls);
lu_byte kind = getglobalattribute(ls); lu_byte kind = getglobalattribute(ls, defkind);
new_varkind(ls, vname, kind); new_varkind(ls, vname, kind);
fs->nactvar++; /* activate declaration */ fs->nactvar++; /* activate declaration */
} while (testnext(ls, ',')); } while (testnext(ls, ','));
@@ -2003,8 +2011,9 @@ static void statement (LexState *ls) {
is not reserved */ is not reserved */
if (ls->t.seminfo.ts == ls->glbn) { /* current = "global"? */ if (ls->t.seminfo.ts == ls->glbn) { /* current = "global"? */
int lk = luaX_lookahead(ls); int lk = luaX_lookahead(ls);
if (lk == TK_NAME || lk == '*' || lk == TK_FUNCTION) { if (lk == '<' || lk == TK_NAME || lk == '*' || lk == TK_FUNCTION) {
/* 'global name' or 'global *' or 'global function' */ /* 'global <attrib>' or 'global name' or 'global *' or
'global function' */
globalstatfunc(ls, line); globalstatfunc(ls, line);
break; break;
} }

View File

@@ -1651,43 +1651,47 @@ Function calls are explained in @See{functioncall}.
Local and global variables can be declared anywhere inside a block. Local and global variables can be declared anywhere inside a block.
The declaration for locals can include an initialization: The declaration for locals can include an initialization:
@Produc{ @Produc{
@producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}} @producname{stat}@producbody{@Rw{local}
attnamelist @bnfopt{@bnfter{=} explist}}
@producname{stat}@producbody{@Rw{global} attnamelist} @producname{stat}@producbody{@Rw{global} attnamelist}
@producname{attnamelist}@producbody{
@bnfNter{Name} @bnfopt{attrib}
@bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}}
} }
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 local variables are initialized with @nil. Otherwise, all local variables are initialized with @nil.
Each variable name may be postfixed by an attribute The list of names may be prefixed by an attribute
(a name between angle brackets): (a name between angle brackets)
and each variable name may be postfixed by an attribute:
@Produc{ @Produc{
@producname{attnamelist}@producbody{
@bnfopt{attrib} @bnfNter{Name} @bnfopt{attrib}
@bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}}
@producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}} @producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}}
} }
A prefixed attribute applies to all names in the list;
a postfixed attribute applies to its particular name.
There are two possible attributes: There are two possible attributes:
@id{const}, which declares a @emph{constant} or @emph{read-only} variable, @id{const}, which declares a @emph{constant} or @emph{read-only} variable,
@index{constant variable} @index{constant variable}
that is, a variable that cannot be used as the left-hand side of an that is, a variable that cannot be used as the left-hand side of an
assignment, assignment,
and @id{close}, which declares a to-be-closed variable @see{to-be-closed}. and @id{close}, which declares a to-be-closed variable @see{to-be-closed}.
A list of variables can contain at most one to-be-closed variable.
Only local variables can have the @id{close} attribute. Only local variables can have the @id{close} attribute.
A list of variables can contain at most one to-be-closed variable.
Lua offers also a collective declaration for global variables: Lua offers also a collective declaration for global variables:
@Produc{ @Produc{
@producname{stat}@producbody{@Rw{global} @bnfter{*} @bnfopt{attrib}} @producname{stat}@producbody{@Rw{global} @bnfopt{attrib} @bnfter{*}}
} }
This special form implicitly declares This special form implicitly declares
as globals all names not explicitly declared previously. as globals all names not explicitly declared previously.
In particular, In particular,
@T{global * <const>} implicitly declares @T{global<const> *} implicitly declares
as read-only globals all names not explicitly declared previously; as read-only globals all names not explicitly declared previously;
see the following example: see the following example:
@verbatim{ @verbatim{
global X global X
global * <const> global<const> *
print(math.pi) -- Ok, 'print' and 'math' are read-only print(math.pi) -- Ok, 'print' and 'math' are read-only
X = 1 -- Ok, declared as read-write X = 1 -- Ok, declared as read-write
Y = 1 -- Error, Y is read-only Y = 1 -- Error, Y is read-only
@@ -1700,7 +1704,7 @@ the scope of any other @Rw{global} declaration.
Therefore, a program that does not use global declarations Therefore, a program that does not use global declarations
or start with @T{global *} or start with @T{global *}
has free read-write access to any global; has free read-write access to any global;
a program that starts with @T{global * <const>} a program that starts with @T{global<const> *}
has free read-only access to any global; has free read-only access to any global;
and a program that starts with any other global declaration and a program that starts with any other global declaration
(e.g., @T{global none}) can only refer to declared variables. (e.g., @T{global none}) can only refer to declared variables.
@@ -9620,11 +9624,11 @@ and @bnfNter{LiteralString}, see @See{lexical}.)
@OrNL @Rw{global} @Rw{function} @bnfNter{Name} funcbody @OrNL @Rw{global} @Rw{function} @bnfNter{Name} funcbody
@OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist} @OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist}
@OrNL @Rw{global} attnamelist @OrNL @Rw{global} attnamelist
@OrNL @Rw{global} @bnfter{*} @bnfopt{attrib} @OrNL @Rw{global} @bnfopt{attrib} @bnfter{*}
} }
@producname{attnamelist}@producbody{ @producname{attnamelist}@producbody{
@bnfNter{Name} @bnfopt{attrib} @bnfopt{attrib} @bnfNter{Name} @bnfopt{attrib}
@bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}} @bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}}
@producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}} @producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}}

View File

@@ -2,7 +2,7 @@
-- $Id: testes/all.lua $ -- $Id: testes/all.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const> global <const> *
global _soft, _port, _nomsg global _soft, _port, _nomsg
global T global T

View File

@@ -1,7 +1,7 @@
-- $Id: testes/calls.lua $ -- $Id: testes/calls.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const> global <const> *
print("testing functions and calls") print("testing functions and calls")

View File

@@ -1,7 +1,7 @@
-- $Id: testes/closure.lua $ -- $Id: testes/closure.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const> global <const> *
print "testing closures" print "testing closures"

View File

@@ -1,7 +1,7 @@
-- $Id: testes/code.lua $ -- $Id: testes/code.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const> global <const> *
if T==nil then if T==nil then
(Message or print)('\n >>> testC not active: skipping opcode tests <<<\n') (Message or print)('\n >>> testC not active: skipping opcode tests <<<\n')

View File

@@ -1,7 +1,7 @@
-- $Id: testes/files.lua $ -- $Id: testes/files.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const> global <const> *
local debug = require "debug" local debug = require "debug"

View File

@@ -1,9 +1,9 @@
-- $Id: testes/goto.lua $ -- $Id: testes/goto.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global require global<const> require
global print, load, assert, string, setmetatable global<const> print, load, assert, string, setmetatable
global collectgarbage, error global<const> collectgarbage, error
print("testing goto and global declarations") print("testing goto and global declarations")
@@ -304,7 +304,7 @@ do
-- global variables cannot be to-be-closed -- global variables cannot be to-be-closed
checkerr("global X<close>", "cannot be") checkerr("global X<close>", "cannot be")
checkerr("global * <close>", "cannot be") checkerr("global <close> *", "cannot be")
do do
local X = 10 local X = 10
@@ -345,7 +345,7 @@ do
end end
checkerr([[ checkerr([[
global foo <const>; global<const> foo;
function foo (x) return end -- ERROR: foo is read-only function foo (x) return end -- ERROR: foo is read-only
]], "assign to const variable 'foo'") ]], "assign to const variable 'foo'")
@@ -357,7 +357,7 @@ do
]], "%:2%:") -- correct line in error message ]], "%:2%:") -- correct line in error message
checkerr([[ checkerr([[
global * <const>; global<const> *;
print(X) -- Ok to use print(X) -- Ok to use
Y = 1 -- ERROR Y = 1 -- ERROR
]], "assign to const variable 'Y'") ]], "assign to const variable 'Y'")

View File

@@ -3,7 +3,7 @@
print('testing scanner') print('testing scanner')
global * <const> global <const> *
local debug = require "debug" local debug = require "debug"

View File

@@ -1,7 +1,7 @@
-- $Id: testes/locals.lua $ -- $Id: testes/locals.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const> global <const> *
print('testing local variables and environments') print('testing local variables and environments')
@@ -181,23 +181,25 @@ assert(x==20)
A = nil A = nil
do -- constants do print("testing local constants")
global assert<const>, load, string, X global assert<const>, load, string, X
X = 1 -- not a constant X = 1 -- not a constant
local a<const>, b, c<const> = 10, 20, 30 local a<const>, b, c<const> = 10, 20, 30
b = a + c + b -- 'b' is not constant b = a + c + b -- 'b' is not constant
assert(a == 10 and b == 60 and c == 30) assert(a == 10 and b == 60 and c == 30)
local function checkro (name, code) local function checkro (name, code)
local st, msg = load(code) local st, msg = load(code)
local gab = string.format("attempt to assign to const variable '%s'", name) local gab = string.format("attempt to assign to const variable '%s'", name)
assert(not st and string.find(msg, gab)) assert(not st and string.find(msg, gab))
end end
checkro("y", "local x, y <const>, z = 10, 20, 30; x = 11; y = 12") checkro("y", "local x, y <const>, z = 10, 20, 30; x = 11; y = 12")
checkro("x", "local x <const>, y, z <const> = 10, 20, 30; x = 11") checkro("x", "local x <const>, y, z <const> = 10, 20, 30; x = 11")
checkro("z", "local x <const>, y, z <const> = 10, 20, 30; y = 10; z = 11") checkro("z", "local x <const>, y, z <const> = 10, 20, 30; y = 10; z = 11")
checkro("foo", "local foo <const> = 10; function foo() end") checkro("foo", "local<const> foo = 10; function foo() end")
checkro("foo", "local foo <const> = {}; function foo() end") checkro("foo", "local<const> foo <const> = {}; function foo() end")
checkro("foo", "global foo <const>; function foo() end") checkro("foo", "global<const> foo <const>; function foo() end")
checkro("XX", "global XX <const>; XX = 10") checkro("XX", "global XX <const>; XX = 10")
checkro("XX", "local _ENV; global XX <const>; XX = 10") checkro("XX", "local _ENV; global XX <const>; XX = 10")
@@ -218,8 +220,18 @@ do -- constants
end end
print"testing to-be-closed variables" print"testing to-be-closed variables"
do
local st, msg = load("local <close> a, b")
assert(not st and string.find(msg, "multiple"))
local st, msg = load("local a<close>, b<close>")
assert(not st and string.find(msg, "multiple"))
end
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, x, y) local function func2close (f, x, y)

View File

@@ -8,11 +8,10 @@ local string = require "string"
global none global none
global print, assert, pcall, type, pairs, load global<const> print, assert, pcall, type, pairs, load
global tonumber, tostring, select global<const> tonumber, tostring, select
local minint <const> = math.mininteger local<const> minint, maxint = math.mininteger, math.maxinteger
local maxint <const> = math.maxinteger
local intbits <const> = math.floor(math.log(maxint, 2) + 0.5) + 1 local intbits <const> = math.floor(math.log(maxint, 2) + 0.5) + 1
assert((1 << intbits) == 0) assert((1 << intbits) == 0)

View File

@@ -1,7 +1,7 @@
-- $Id: testes/nextvar.lua $ -- $Id: testes/nextvar.lua $
-- See Copyright Notice in file lua.h -- See Copyright Notice in file lua.h
global * <const> global <const> *
print('testing tables, next, and for') print('testing tables, next, and for')

View File

@@ -6,7 +6,7 @@
print('testing pattern matching') print('testing pattern matching')
global * <const> global <const> *
local function checkerror (msg, f, ...) local function checkerror (msg, f, ...)
local s, err = pcall(f, ...) local s, err = pcall(f, ...)

View File

@@ -3,7 +3,7 @@
-- ISO Latin encoding -- ISO Latin encoding
global * <const> global <const> *
print('testing strings and string library') print('testing strings and string library')

View File

@@ -3,7 +3,7 @@
-- UTF-8 file -- UTF-8 file
global * <const> global <const> *
print "testing UTF-8 library" print "testing UTF-8 library"