From 3b9dd52be02fd43c598f4adb6fa7844e6a573923 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 13 May 2025 11:43:10 -0300 Subject: Collective declaration for globals ('global *') --- lparser.c | 55 ++++++++++++++++++++++++-------------- manual/manual.of | 76 ++++++++++++++++++++++++++++++++++++++++------------- testes/all.lua | 8 ++++-- testes/calls.lua | 15 ++++++----- testes/closure.lua | 2 ++ testes/code.lua | 6 +++-- testes/files.lua | 6 +++-- testes/goto.lua | 24 ++++++++++++----- testes/literals.lua | 2 ++ testes/locals.lua | 12 ++++++--- testes/nextvar.lua | 7 +++-- testes/pm.lua | 2 ++ testes/strings.lua | 1 + testes/utf8.lua | 2 ++ 14 files changed, 155 insertions(+), 63 deletions(-) diff --git a/lparser.c b/lparser.c index 93991cb0..242bb001 100644 --- a/lparser.c +++ b/lparser.c @@ -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 lu_byte getglobalattribute (LexState *ls) { + lu_byte kind = getvarattribute(ls); + if (kind == RDKTOCLOSE) + luaK_semerror(ls, "global variables cannot be to-be-closed"); + /* adjust kind for global variable */ + return (kind == VDKREG) ? GDKREG : GDKCONST; +} + + static void globalstat (LexState *ls) { - /* globalstat -> (GLOBAL) NAME attrib {',' NAME attrib} */ + /* globalstat -> (GLOBAL) '*' attrib + globalstat -> (GLOBAL) NAME attrib {',' NAME attrib} */ FuncState *fs = ls->fs; - do { - TString *vname = str_checkname(ls); - lu_byte kind = getvarattribute(ls); - if (kind == RDKTOCLOSE) - luaK_semerror(ls, "global variable ('%s') cannot be to-be-closed", - getstr(vname)); - /* adjust kind for global variable */ - kind = (kind == VDKREG) ? GDKREG : GDKCONST; - new_varkind(ls, vname, kind); + if (testnext(ls, '*')) { + lu_byte kind = getglobalattribute(ls); + /* use NULL as name to represent '*' entries */ + new_varkind(ls, NULL, kind); fs->nactvar++; /* activate declaration */ - } while (testnext(ls, ',')); + } + 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 ' or 'global function' */ + if (lk == TK_NAME || lk == '*' || lk == TK_FUNCTION) { + /* 'global ' or 'global *' or 'global function' */ globalstatfunc(ls, line); break; } diff --git a/manual/manual.of b/manual/manual.of index cc71aaad..effb95da 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -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. +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 * } implicitly declares +as read-only globals all names not explicitly declared previously; +see the following example: +@verbatim{ +global X +global * +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 * } +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 @emph{read-only} atribute is only a syntactical restriction: +the effect of any declaration is only syntactical: @verbatim{ -global X -X = 1 -- ERROR -_ENV.X = 1 -- Ok -foo() -- 'foo' can freely change the global X +global X , _G +X = 1 -- ERROR +_ENV.X = 1 -- Ok +_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{;}}} diff --git a/testes/all.lua b/testes/all.lua index 5c7ebfa5..499c100d 100755 --- a/testes/all.lua +++ b/testes/all.lua @@ -2,6 +2,10 @@ -- $Id: testes/all.lua $ -- See Copyright Notice in file lua.h +global * + +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) diff --git a/testes/calls.lua b/testes/calls.lua index 942fad72..0ea1c4ab 100644 --- a/testes/calls.lua +++ b/testes/calls.lua @@ -1,6 +1,8 @@ -- $Id: testes/calls.lua $ -- See Copyright Notice in file lua.h +global * + 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 diff --git a/testes/closure.lua b/testes/closure.lua index d3b9f621..c55d1583 100644 --- a/testes/closure.lua +++ b/testes/closure.lua @@ -1,6 +1,8 @@ -- $Id: testes/closure.lua $ -- See Copyright Notice in file lua.h +global * + print "testing closures" do -- bug in 5.4.7 diff --git a/testes/code.lua b/testes/code.lua index 111717ce..b6ceb34c 100644 --- a/testes/code.lua +++ b/testes/code.lua @@ -1,6 +1,8 @@ -- $Id: testes/code.lua $ -- See Copyright Notice in file lua.h +global * + 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) diff --git a/testes/files.lua b/testes/files.lua index a0ae661c..c2b355fb 100644 --- a/testes/files.lua +++ b/testes/files.lua @@ -1,6 +1,8 @@ -- $Id: testes/files.lua $ -- See Copyright Notice in file lua.h +global * + 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 diff --git a/testes/goto.lua b/testes/goto.lua index 59713dd7..3f1f6e69 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -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 = 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; global assert - global string + global T 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", "cannot be") + checkerr("global * ", "cannot be") do local X = 10 @@ -349,6 +355,12 @@ do return end ]], "%:2%:") -- correct line in error message + + checkerr([[ + global * ; + print(X) -- Ok to use + Y = 1 -- ERROR + ]], "assign to const variable 'Y'") end diff --git a/testes/literals.lua b/testes/literals.lua index 28995718..fecdd6d3 100644 --- a/testes/literals.lua +++ b/testes/literals.lua @@ -3,6 +3,8 @@ print('testing scanner') +global * + local debug = require "debug" diff --git a/testes/locals.lua b/testes/locals.lua index 421595bb..99ff9edc 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -1,6 +1,8 @@ -- $Id: testes/locals.lua $ -- See Copyright Notice in file lua.h +global * + 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 diff --git a/testes/nextvar.lua b/testes/nextvar.lua index 679cb1e4..e5a97178 100644 --- a/testes/nextvar.lua +++ b/testes/nextvar.lua @@ -1,6 +1,8 @@ -- $Id: testes/nextvar.lua $ -- See Copyright Notice in file lua.h +global * + 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) diff --git a/testes/pm.lua b/testes/pm.lua index ab19eb5d..1700ca2c 100644 --- a/testes/pm.lua +++ b/testes/pm.lua @@ -6,6 +6,8 @@ print('testing pattern matching') +global * + local function checkerror (msg, f, ...) local s, err = pcall(f, ...) assert(not s and string.find(err, msg)) diff --git a/testes/strings.lua b/testes/strings.lua index ce28e4c5..455398c3 100644 --- a/testes/strings.lua +++ b/testes/strings.lua @@ -3,6 +3,7 @@ -- ISO Latin encoding +global * print('testing strings and string library') diff --git a/testes/utf8.lua b/testes/utf8.lua index d0c0184d..ec9b706f 100644 --- a/testes/utf8.lua +++ b/testes/utf8.lua @@ -3,6 +3,8 @@ -- UTF-8 file +global * + print "testing UTF-8 library" local utf8 = require'utf8' -- cgit v1.2.3-55-g6feb