Collective declaration for globals ('global *')

This commit is contained in:
Roberto Ierusalimschy
2025-05-13 11:43:10 -03:00
parent 7dc6aae290
commit 3b9dd52be0
14 changed files with 156 additions and 64 deletions

View File

@@ -405,7 +405,12 @@ static int searchvar (FuncState *fs, TString *n, expdesc *var) {
int i;
for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) {
Vardesc *vd = getlocalvardesc(fs, i);
if (eqstr(n, vd->vd.name)) { /* found? */
if (vd->vd.name == NULL) { /* 'global *'? */
if (var->u.info == -1) { /* no previous collective declaration? */
var->u.info = fs->firstlocal + i; /* will use this one as default */
}
}
else if (eqstr(n, vd->vd.name)) { /* found? */
if (vd->vd.kind == RDKCTC) /* compile-time constant? */
init_exp(var, VCONST, fs->firstlocal + i);
else if (vd->vd.kind == GDKREG || vd->vd.kind == GDKCONST)
@@ -449,18 +454,16 @@ static void marktobeclosed (FuncState *fs) {
** 'var' as 'void' as a flag.
*/
static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
int v = searchvar(fs, n, var); /* look up locals at current level */
int v = searchvar(fs, n, var); /* look up variables at current level */
if (v >= 0) { /* found? */
if (v == VLOCAL && !base)
markupval(fs, var->u.var.vidx); /* local will be used as an upval */
}
else { /* not found as local at current level; try upvalues */
else { /* not found at current level; try upvalues */
int idx = searchupvalue(fs, n); /* try existing upvalues */
if (idx < 0) { /* not found? */
if (fs->prev != NULL) /* more levels? */
singlevaraux(fs->prev, n, var, 0); /* try upper levels */
else /* no more levels */
init_exp(var, VGLOBAL, -1); /* global by default */
if (var->k == VLOCAL || var->k == VUPVAL) /* local or upvalue? */
idx = newupvalue(fs, n, var); /* will be a new upvalue */
else /* it is a global or a constant */
@@ -477,6 +480,7 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
*/
static void buildvar (LexState *ls, TString *varname, expdesc *var) {
FuncState *fs = ls->fs;
init_exp(var, VGLOBAL, -1); /* global by default */
singlevaraux(fs, varname, var, 1);
if (var->k == VGLOBAL) { /* global name? */
expdesc key;
@@ -1796,20 +1800,33 @@ static void localstat (LexState *ls) {
}
static void globalstat (LexState *ls) {
/* globalstat -> (GLOBAL) NAME attrib {',' NAME attrib} */
FuncState *fs = ls->fs;
do {
TString *vname = str_checkname(ls);
static lu_byte getglobalattribute (LexState *ls) {
lu_byte kind = getvarattribute(ls);
if (kind == RDKTOCLOSE)
luaK_semerror(ls, "global variable ('%s') cannot be to-be-closed",
getstr(vname));
luaK_semerror(ls, "global variables cannot be to-be-closed");
/* adjust kind for global variable */
kind = (kind == VDKREG) ? GDKREG : GDKCONST;
return (kind == VDKREG) ? GDKREG : GDKCONST;
}
static void globalstat (LexState *ls) {
/* globalstat -> (GLOBAL) '*' attrib
globalstat -> (GLOBAL) NAME attrib {',' NAME attrib} */
FuncState *fs = ls->fs;
if (testnext(ls, '*')) {
lu_byte kind = getglobalattribute(ls);
/* use NULL as name to represent '*' entries */
new_varkind(ls, NULL, kind);
fs->nactvar++; /* activate declaration */
}
else {
do {
TString *vname = str_checkname(ls);
lu_byte kind = getglobalattribute(ls);
new_varkind(ls, vname, kind);
fs->nactvar++; /* activate declaration */
} while (testnext(ls, ','));
}
}
@@ -1983,10 +2000,10 @@ static void statement (LexState *ls) {
case TK_NAME: {
/* compatibility code to parse global keyword when "global"
is not reserved */
if (eqstr(ls->t.seminfo.ts, luaS_newliteral(ls->L, "global"))) {
if (strcmp(getstr(ls->t.seminfo.ts), "global") == 0) {
int lk = luaX_lookahead(ls);
if (lk == TK_NAME || lk == TK_FUNCTION) {
/* 'global <name>' or 'global function' */
if (lk == TK_NAME || lk == '*' || lk == TK_FUNCTION) {
/* 'global <name>' or 'global *' or 'global function' */
globalstatfunc(ls, line);
break;
}

View File

@@ -223,14 +223,15 @@ a function's formal parameter is equivalent to a local variable.)
All chunks start with an implicit declaration @T{global *},
which declares all free names as global variables;
this implicit declaration becomes void inside the scope of any other
@Rw{global} declaration, regardless of the names being declared.
this preambular declaration becomes void inside the scope of any other
@Rw{global} declaration,
as the following example illustrates:
@verbatim{
X = 1 -- Ok, global by default
do
global Y -- voids implicit initial declaration
X = 1 -- ERROR, X not declared
Y = 1 -- Ok, Y declared as global
X = 1 -- ERROR, X not declared
end
X = 2 -- Ok, global by default again
}
@@ -1110,9 +1111,9 @@ and cannot be used as names:
@index{reserved words}
@verbatim{
and break do else elseif end
false for function goto if in
local nil not or repeat return
then true until while
false for function global goto if
in local nil not or repeat
return then true until while
}
Lua is a case-sensitive language:
@@ -1653,7 +1654,8 @@ The declaration for locals can include an initialization:
@producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}}
@producname{stat}@producbody{@Rw{global} attnamelist}
@producname{attnamelist}@producbody{
@bnfNter{Name} attrib @bnfrep{@bnfter{,} @bnfNter{Name} attrib}}
@bnfNter{Name} @bnfopt{attrib}
@bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}}
}
If present, an initial assignment has the same semantics
of a multiple assignment @see{assignment}.
@@ -1662,24 +1664,55 @@ Otherwise, all local variables are initialized with @nil.
Each variable name may be postfixed by an attribute
(a name between angle brackets):
@Produc{
@producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}}
@producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}}
}
There are two possible attributes:
@id{const}, which declares a @emph{constant} or @emph{read-only} variable,
@index{constant variable}
that is, a variable that cannot be assigned to
after its initialization;
that is, a variable that cannot be used as the left-hand side of an
assignment,
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.
Note that, for global variables,
the @emph{read-only} atribute is only a syntactical restriction:
Lua offers also a collective declaration for global variables:
@Produc{
@producname{stat}@producbody{@Rw{global} @bnfter{*} @bnfopt{attrib}}
}
This special form implicitly declares
as globals all names not explicitly declared previously.
In particular,
@T{global * <const>} implicitly declares
as read-only globals all names not explicitly declared previously;
see the following example:
@verbatim{
global X <const>
global X
global * <const>
print(math.pi) -- Ok, 'print' and 'math' are read-only
X = 1 -- Ok, declared as read-write
Y = 1 -- Error, Y is read-only
}
As noted in @See{globalenv},
all chunks start with an implicit declaration @T{global *},
but this preambular declaration becomes void inside
the scope of any other @Rw{global} declaration.
Therefore, a program that does not use global declarations
or start with @T{global *}
has free read-write access to any global;
a program that starts with @T{global * <const>}
has free read-only access to any global;
and a program that starts with any other global declaration
(e.g., @T{global none}) can only refer to declared variables.
Note that, for global variables,
the effect of any declaration is only syntactical:
@verbatim{
global X <const>, _G
X = 1 -- ERROR
_ENV.X = 1 -- Ok
foo() -- 'foo' can freely change the global X
_G.print(X) -- Ok
foo() -- 'foo' can freely change any global
}
A chunk is also a block @see{chunks},
@@ -9453,7 +9486,12 @@ change between versions.
@itemize{
@item{
The control variable in @Rw{for} loops are read only.
The word @Rw{global} is a reserved word.
Do not use it as a regular name.
}
@item{
The control variable in @Rw{for} loops is read only.
If you need to change it,
declare a local variable with the same name in the loop body.
}
@@ -9582,12 +9620,14 @@ and @bnfNter{LiteralString}, see @See{lexical}.)
@OrNL @Rw{global} @Rw{function} @bnfNter{Name} funcbody
@OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist}
@OrNL @Rw{global} attnamelist
@OrNL @Rw{global} @bnfter{*} @bnfopt{attrib}
}
@producname{attnamelist}@producbody{
@bnfNter{Name} attrib @bnfrep{@bnfter{,} @bnfNter{Name} attrib}}
@bnfNter{Name} @bnfopt{attrib}
@bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}}
@producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}}
@producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}}
@producname{retstat}@producbody{@Rw{return}
@bnfopt{explist} @bnfopt{@bnfter{;}}}

View File

@@ -2,6 +2,10 @@
-- $Id: testes/all.lua $
-- See Copyright Notice in file lua.h
global * <const>
global _soft, _port, _nomsg
global T
local version = "Lua 5.5"
if _VERSION ~= version then
@@ -34,7 +38,7 @@ if usertests then
end
-- tests should require debug when needed
debug = nil
global debug; debug = nil
if usertests then
@@ -71,7 +75,7 @@ do -- (
-- track messages for tests not performed
local msgs = {}
function Message (m)
global function Message (m)
if not _nomsg then
print(m)
msgs[#msgs+1] = string.sub(m, 3, -3)

View File

@@ -1,6 +1,8 @@
-- $Id: testes/calls.lua $
-- See Copyright Notice in file lua.h
global * <const>
print("testing functions and calls")
local debug = require "debug"
@@ -22,7 +24,7 @@ assert(not pcall(type))
-- testing local-function recursion
fact = false
global fact; fact = false
do
local res = 1
local function fact (n)
@@ -63,7 +65,7 @@ a.b.c:f2('k', 12); assert(a.b.c.k == 12)
print('+')
t = nil -- 'declare' t
global t; t = nil -- 'declare' t
function f(a,b,c) local d = 'a'; t={a,b,c,d} end
f( -- this line change must be valid
@@ -75,7 +77,7 @@ assert(t[1] == 1 and t[2] == 2 and t[3] == 3 and t[4] == 'a')
t = nil -- delete 't'
function fat(x)
global function fat(x)
if x <= 1 then return 1
else return x*load("return fat(" .. x-1 .. ")", "")()
end
@@ -107,7 +109,7 @@ end
_G.deep = nil -- "declaration" (used by 'all.lua')
function deep (n)
global function deep (n)
if n>0 then deep(n-1) end
end
deep(10)
@@ -352,7 +354,7 @@ assert(not load(function () return true end))
-- small bug
local t = {nil, "return ", "3"}
f, msg = load(function () return table.remove(t, 1) end)
local f, msg = load(function () return table.remove(t, 1) end)
assert(f() == nil) -- should read the empty chunk
-- another small bug (in 5.2.1)
@@ -388,7 +390,8 @@ assert(load("return _ENV", nil, nil, 123)() == 123)
-- load when _ENV is not first upvalue
local x; XX = 123
global XX; local x
XX = 123
local function h ()
local y=x -- use 'x', so that it becomes 1st upvalue
return XX -- global name

View File

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

View File

@@ -1,6 +1,8 @@
-- $Id: testes/code.lua $
-- See Copyright Notice in file lua.h
global * <const>
if T==nil then
(Message or print)('\n >>> testC not active: skipping opcode tests <<<\n')
return
@@ -405,8 +407,8 @@ do -- tests for table access in upvalues
end
-- de morgan
checkequal(function () local a; if not (a or b) then b=a end end,
function () local a; if (not a and not b) then b=a end end)
checkequal(function () local a, b; if not (a or b) then b=a end end,
function () local a, b; if (not a and not b) then b=a end end)
checkequal(function (l) local a; return 0 <= a and a <= l end,
function (l) local a; return not (not(a >= 0) or not(a <= l)) end)

View File

@@ -1,6 +1,8 @@
-- $Id: testes/files.lua $
-- See Copyright Notice in file lua.h
global * <const>
local debug = require "debug"
local maxint = math.maxinteger
@@ -838,13 +840,13 @@ assert(os.date("!\0\0") == "\0\0")
local x = string.rep("a", 10000)
assert(os.date(x) == x)
local t = os.time()
D = os.date("*t", t)
global D; D = os.date("*t", t)
assert(os.date(string.rep("%d", 1000), t) ==
string.rep(os.date("%d", t), 1000))
assert(os.date(string.rep("%", 200)) == string.rep("%", 100))
local function checkDateTable (t)
_G.D = os.date("*t", t)
D = os.date("*t", t)
assert(os.time(D) == t)
load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and
D.hour==%H and D.min==%M and D.sec==%S and

View File

@@ -1,6 +1,10 @@
-- $Id: testes/goto.lua $
-- See Copyright Notice in file lua.h
global require
global print, load, assert, string, setmetatable
global collectgarbage, error
print("testing goto and global declarations")
collectgarbage()
@@ -254,6 +258,8 @@ assert(testG(5) == 10)
do -- test goto's around to-be-closed variable
global *
-- set 'var' and return an object that will reset 'var' when
-- it goes out of scope
local function newobj (var)
@@ -265,16 +271,16 @@ do -- test goto's around to-be-closed variable
goto L1
::L4:: assert(not X); goto L5 -- varX dead here
::L4:: assert(not varX); goto L5 -- varX dead here
::L1::
local varX <close> = newobj("X")
assert(X); goto L2 -- varX alive here
assert(varX); goto L2 -- varX alive here
::L3::
assert(X); goto L4 -- varX alive here
assert(varX); goto L4 -- varX alive here
::L2:: assert(X); goto L3 -- varX alive here
::L2:: assert(varX); goto L3 -- varX alive here
::L5:: -- return
end
@@ -285,8 +291,7 @@ foo()
--------------------------------------------------------------------------
do
global print, load, T<const>; global assert<const>
global string
global T<const>
local function checkerr (code, err)
local st, msg = load(code)
@@ -299,6 +304,7 @@ do
-- global variables cannot be to-be-closed
checkerr("global X<close>", "cannot be")
checkerr("global * <close>", "cannot be")
do
local X = 10
@@ -350,6 +356,12 @@ do
end
]], "%:2%:") -- correct line in error message
checkerr([[
global * <const>;
print(X) -- Ok to use
Y = 1 -- ERROR
]], "assign to const variable 'Y'")
end
print'OK'

View File

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

View File

@@ -1,6 +1,8 @@
-- $Id: testes/locals.lua $
-- See Copyright Notice in file lua.h
global * <const>
print('testing local variables and environments')
local debug = require"debug"
@@ -39,9 +41,11 @@ f = nil
local f
local x = 1
a = nil
load('local a = {}')()
assert(a == nil)
do
global a; a = nil
load('local a = {}')()
assert(a == nil)
end
function f (a)
local _1, _2, _3, _4, _5
@@ -154,7 +158,7 @@ local _ENV = (function (...) return ... end)(_G, dummy) -- {
do local _ENV = {assert=assert}; assert(true) end
local mt = {_G = _G}
local foo,x
A = false -- "declare" A
global A; A = false -- "declare" A
do local _ENV = mt
function foo (x)
A = x

View File

@@ -1,6 +1,8 @@
-- $Id: testes/nextvar.lua $
-- See Copyright Notice in file lua.h
global * <const>
print('testing tables, next, and for')
local function checkerror (msg, f, ...)
@@ -345,9 +347,6 @@ end
local nofind = {}
a,b,c = 1,2,3
a,b,c = nil
-- next uses always the same iteration function
assert(next{} == next{})
@@ -396,7 +395,7 @@ for i=0,10000 do
end
end
n = {n=0}
local n = {n=0}
for i,v in pairs(a) do
n.n = n.n+1
assert(i and v and a[i] == v)

View File

@@ -6,6 +6,8 @@
print('testing pattern matching')
global * <const>
local function checkerror (msg, f, ...)
local s, err = pcall(f, ...)
assert(not s and string.find(err, msg))

View File

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

View File

@@ -3,6 +3,8 @@
-- UTF-8 file
global * <const>
print "testing UTF-8 library"
local utf8 = require'utf8'