From abbae57c7844b1121e7251d56f681394f20c1821 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Sun, 18 May 2025 11:43:43 -0300 Subject: Variable attributes can prefix name list In this format, the attribute applies to all names in the list; e.g. "global print, require, math". --- lparser.c | 53 +++++++++++++++++++++++++++++++---------------------- manual/manual.of | 30 +++++++++++++++++------------- testes/all.lua | 2 +- testes/calls.lua | 2 +- testes/closure.lua | 2 +- testes/code.lua | 2 +- testes/files.lua | 2 +- testes/goto.lua | 12 ++++++------ testes/literals.lua | 2 +- testes/locals.lua | 22 +++++++++++++++++----- testes/math.lua | 7 +++---- testes/nextvar.lua | 2 +- testes/pm.lua | 2 +- testes/strings.lua | 2 +- testes/utf8.lua | 2 +- 15 files changed, 84 insertions(+), 60 deletions(-) diff --git a/lparser.c b/lparser.c index 384ef690..bad3592a 100644 --- a/lparser.c +++ b/lparser.c @@ -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 '>'] */ if (testnext(ls, '<')) { TString *ts = str_checkname(ls); @@ -1746,7 +1746,7 @@ static lu_byte getvarattribute (LexState *ls) { else 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 nexps; expdesc e; - do { - TString *vname = str_checkname(ls); - lu_byte kind = getvarattribute(ls); - vidx = new_varkind(ls, vname, kind); + /* get prefixed attribute (if any); default is regular local variable */ + lu_byte defkind = getvarattribute(ls, VDKREG); + do { /* for each variable */ + 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 (toclose != -1) /* one already present? */ luaK_semerror(ls, "multiple to-be-closed variables in local list"); @@ -1778,13 +1780,13 @@ static void localstat (LexState *ls) { } nvars++; } while (testnext(ls, ',')); - if (testnext(ls, '=')) + if (testnext(ls, '=')) /* initialization? */ nexps = explist(ls, &e); else { e.k = VVOID; nexps = 0; } - var = getlocalvardesc(fs, vidx); /* get last variable */ + var = getlocalvardesc(fs, vidx); /* retrieve last variable */ if (nvars == nexps && /* no adjustments? */ var->vd.kind == RDKCONST && /* last variable is const? */ luaK_exp2const(fs, &e, &var->k)) { /* compile-time constant? */ @@ -1800,29 +1802,35 @@ 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 lu_byte getglobalattribute (LexState *ls, lu_byte df) { + lu_byte kind = getvarattribute(ls, df); + switch (kind) { + case RDKTOCLOSE: + luaK_semerror(ls, "global variables cannot be to-be-closed"); + break; /* to avoid warnings */ + case RDKCONST: + return GDKCONST; /* adjust kind for global variable */ + default: + return kind; + } } static void globalstat (LexState *ls) { - /* globalstat -> (GLOBAL) '*' attrib - globalstat -> (GLOBAL) NAME attrib {',' NAME attrib} */ + /* globalstat -> (GLOBAL) attrib '*' + globalstat -> (GLOBAL) attrib NAME attrib {',' NAME attrib} */ FuncState *fs = ls->fs; + /* get prefixed attribute (if any); default is regular global variable */ + lu_byte defkind = getglobalattribute(ls, GDKREG); if (testnext(ls, '*')) { - lu_byte kind = getglobalattribute(ls); /* use NULL as name to represent '*' entries */ - new_varkind(ls, NULL, kind); + new_varkind(ls, NULL, defkind); fs->nactvar++; /* activate declaration */ } else { - do { + do { /* list of names */ TString *vname = str_checkname(ls); - lu_byte kind = getglobalattribute(ls); + lu_byte kind = getglobalattribute(ls, defkind); new_varkind(ls, vname, kind); fs->nactvar++; /* activate declaration */ } while (testnext(ls, ',')); @@ -2003,8 +2011,9 @@ static void statement (LexState *ls) { is not reserved */ if (ls->t.seminfo.ts == ls->glbn) { /* current = "global"? */ int lk = luaX_lookahead(ls); - if (lk == TK_NAME || lk == '*' || lk == TK_FUNCTION) { - /* 'global name' or 'global *' or 'global function' */ + if (lk == '<' || lk == TK_NAME || lk == '*' || lk == TK_FUNCTION) { + /* 'global ' or 'global name' or 'global *' or + 'global function' */ globalstatfunc(ls, line); break; } diff --git a/manual/manual.of b/manual/manual.of index effb95da..eb97e853 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1651,43 +1651,47 @@ Function calls are explained in @See{functioncall}. Local and global variables can be declared anywhere inside a block. The declaration for locals can include an initialization: @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{attnamelist}@producbody{ - @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}. Otherwise, all local variables are initialized with @nil. -Each variable name may be postfixed by an attribute -(a name between angle brackets): +The list of names may be prefixed by an attribute +(a name between angle brackets) +and each variable name may be postfixed by an attribute: @Produc{ +@producname{attnamelist}@producbody{ + @bnfopt{attrib} @bnfNter{Name} @bnfopt{attrib} + @bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}} @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: @id{const}, which declares a @emph{constant} or @emph{read-only} variable, @index{constant variable} 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. +A list of variables can contain at most one to-be-closed variable. Lua offers also a collective declaration for global variables: @Produc{ -@producname{stat}@producbody{@Rw{global} @bnfter{*} @bnfopt{attrib}} +@producname{stat}@producbody{@Rw{global} @bnfopt{attrib} @bnfter{*}} } This special form implicitly declares as globals all names not explicitly declared previously. In particular, -@T{global * } implicitly declares +@T{global *} implicitly declares as read-only globals all names not explicitly declared previously; see the following example: @verbatim{ global X -global * +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 @@ -1700,7 +1704,7 @@ 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 * } +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. @@ -9620,11 +9624,11 @@ 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} +@OrNL @Rw{global} @bnfopt{attrib} @bnfter{*} } @producname{attnamelist}@producbody{ - @bnfNter{Name} @bnfopt{attrib} + @bnfopt{attrib} @bnfNter{Name} @bnfopt{attrib} @bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}} @producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}} diff --git a/testes/all.lua b/testes/all.lua index 499c100d..d3e2f123 100755 --- a/testes/all.lua +++ b/testes/all.lua @@ -2,7 +2,7 @@ -- $Id: testes/all.lua $ -- See Copyright Notice in file lua.h -global * +global * global _soft, _port, _nomsg global T diff --git a/testes/calls.lua b/testes/calls.lua index 0ea1c4ab..21441701 100644 --- a/testes/calls.lua +++ b/testes/calls.lua @@ -1,7 +1,7 @@ -- $Id: testes/calls.lua $ -- See Copyright Notice in file lua.h -global * +global * print("testing functions and calls") diff --git a/testes/closure.lua b/testes/closure.lua index c55d1583..0c2e96c0 100644 --- a/testes/closure.lua +++ b/testes/closure.lua @@ -1,7 +1,7 @@ -- $Id: testes/closure.lua $ -- See Copyright Notice in file lua.h -global * +global * print "testing closures" diff --git a/testes/code.lua b/testes/code.lua index b6ceb34c..633f4896 100644 --- a/testes/code.lua +++ b/testes/code.lua @@ -1,7 +1,7 @@ -- $Id: testes/code.lua $ -- See Copyright Notice in file lua.h -global * +global * if T==nil then (Message or print)('\n >>> testC not active: skipping opcode tests <<<\n') diff --git a/testes/files.lua b/testes/files.lua index c2b355fb..d4e327b7 100644 --- a/testes/files.lua +++ b/testes/files.lua @@ -1,7 +1,7 @@ -- $Id: testes/files.lua $ -- See Copyright Notice in file lua.h -global * +global * local debug = require "debug" diff --git a/testes/goto.lua b/testes/goto.lua index 3f1f6e69..44486e20 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -1,9 +1,9 @@ -- $Id: testes/goto.lua $ -- See Copyright Notice in file lua.h -global require -global print, load, assert, string, setmetatable -global collectgarbage, error +global require +global print, load, assert, string, setmetatable +global collectgarbage, error print("testing goto and global declarations") @@ -304,7 +304,7 @@ do -- global variables cannot be to-be-closed checkerr("global X", "cannot be") - checkerr("global * ", "cannot be") + checkerr("global *", "cannot be") do local X = 10 @@ -345,7 +345,7 @@ do end checkerr([[ - global foo ; + global foo; function foo (x) return end -- ERROR: foo is read-only ]], "assign to const variable 'foo'") @@ -357,7 +357,7 @@ do ]], "%:2%:") -- correct line in error message checkerr([[ - global * ; + global *; print(X) -- Ok to use Y = 1 -- ERROR ]], "assign to const variable 'Y'") diff --git a/testes/literals.lua b/testes/literals.lua index fecdd6d3..336ef585 100644 --- a/testes/literals.lua +++ b/testes/literals.lua @@ -3,7 +3,7 @@ print('testing scanner') -global * +global * local debug = require "debug" diff --git a/testes/locals.lua b/testes/locals.lua index 99ff9edc..02f41980 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -1,7 +1,7 @@ -- $Id: testes/locals.lua $ -- See Copyright Notice in file lua.h -global * +global * print('testing local variables and environments') @@ -181,23 +181,25 @@ assert(x==20) A = nil -do -- constants +do print("testing local constants") global assert, load, string, X X = 1 -- not a constant local a, b, c = 10, 20, 30 b = a + c + b -- 'b' is not constant assert(a == 10 and b == 60 and c == 30) + local function checkro (name, code) 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("y", "local x, y , z = 10, 20, 30; x = 11; y = 12") checkro("x", "local x , y, z = 10, 20, 30; x = 11") checkro("z", "local x , y, z = 10, 20, 30; y = 10; z = 11") - checkro("foo", "local foo = 10; function foo() end") - checkro("foo", "local foo = {}; function foo() end") - checkro("foo", "global foo ; function foo() end") + checkro("foo", "local foo = 10; function foo() end") + checkro("foo", "local foo = {}; function foo() end") + checkro("foo", "global foo ; function foo() end") checkro("XX", "global XX ; XX = 10") checkro("XX", "local _ENV; global XX ; XX = 10") @@ -218,8 +220,18 @@ do -- constants end + print"testing to-be-closed variables" + +do + local st, msg = load("local a, b") + assert(not st and string.find(msg, "multiple")) + + local st, msg = load("local a, b") + assert(not st and string.find(msg, "multiple")) +end + local function stack(n) n = ((n == 0) or stack(n - 1)) end local function func2close (f, x, y) diff --git a/testes/math.lua b/testes/math.lua index 242579b1..0d228d09 100644 --- a/testes/math.lua +++ b/testes/math.lua @@ -8,11 +8,10 @@ local string = require "string" global none -global print, assert, pcall, type, pairs, load -global tonumber, tostring, select +global print, assert, pcall, type, pairs, load +global tonumber, tostring, select -local minint = math.mininteger -local maxint = math.maxinteger +local minint, maxint = math.mininteger, math.maxinteger local intbits = math.floor(math.log(maxint, 2) + 0.5) + 1 assert((1 << intbits) == 0) diff --git a/testes/nextvar.lua b/testes/nextvar.lua index e5a97178..03810a8e 100644 --- a/testes/nextvar.lua +++ b/testes/nextvar.lua @@ -1,7 +1,7 @@ -- $Id: testes/nextvar.lua $ -- See Copyright Notice in file lua.h -global * +global * print('testing tables, next, and for') diff --git a/testes/pm.lua b/testes/pm.lua index 1700ca2c..720d2a35 100644 --- a/testes/pm.lua +++ b/testes/pm.lua @@ -6,7 +6,7 @@ print('testing pattern matching') -global * +global * local function checkerror (msg, f, ...) local s, err = pcall(f, ...) diff --git a/testes/strings.lua b/testes/strings.lua index 455398c3..46912d43 100644 --- a/testes/strings.lua +++ b/testes/strings.lua @@ -3,7 +3,7 @@ -- ISO Latin encoding -global * +global * print('testing strings and string library') diff --git a/testes/utf8.lua b/testes/utf8.lua index ec9b706f..143c6d34 100644 --- a/testes/utf8.lua +++ b/testes/utf8.lua @@ -3,7 +3,7 @@ -- UTF-8 file -global * +global * print "testing UTF-8 library" -- cgit v1.2.3-55-g6feb