From 942c10a5e33811a08a290ec15031c950a6d17c99 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 8 Jul 2025 13:33:57 -0300 Subject: Optional initialization for global declarations --- lparser.c | 81 ++++++++++++++++++++++++++++++++++++--------------- manual/manual.of | 18 +++++++----- testes/bwcoercion.lua | 2 +- testes/calls.lua | 4 +-- testes/files.lua | 4 +-- testes/goto.lua | 23 ++++++++++++++- testes/tracegc.lua | 2 +- 7 files changed, 96 insertions(+), 38 deletions(-) diff --git a/lparser.c b/lparser.c index 201dbe8b..dde0b6d5 100644 --- a/lparser.c +++ b/lparser.c @@ -30,7 +30,7 @@ -/* maximum number of variable declarationss per function (must be +/* maximum number of variable declarations per function (must be smaller than 250, due to the bytecode format) */ #define MAXVARS 200 @@ -197,7 +197,7 @@ static int new_varkind (LexState *ls, TString *name, lu_byte kind) { Dyndata *dyd = ls->dyd; Vardesc *var; luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1, - dyd->actvar.size, Vardesc, SHRT_MAX, "variable declarationss"); + dyd->actvar.size, Vardesc, SHRT_MAX, "variable declarations"); var = &dyd->actvar.arr[dyd->actvar.n++]; var->vd.kind = kind; /* default */ var->vd.name = name; @@ -485,6 +485,20 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { } +static void buildglobal (LexState *ls, TString *varname, expdesc *var) { + FuncState *fs = ls->fs; + expdesc key; + init_exp(var, VGLOBAL, -1); /* global by default */ + singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ + if (var->k == VGLOBAL) + luaK_semerror(ls, "_ENV is global when accessing variable '%s'", + getstr(varname)); + luaK_exp2anyregup(fs, var); /* _ENV could be a constant */ + codestring(&key, varname); /* key is variable name */ + luaK_indexed(fs, var, &key); /* 'var' represents _ENV[varname] */ +} + + /* ** Find a variable with the given name 'n', handling global variables ** too. @@ -494,18 +508,11 @@ static void buildvar (LexState *ls, TString *varname, expdesc *var) { init_exp(var, VGLOBAL, -1); /* global by default */ singlevaraux(fs, varname, var, 1); if (var->k == VGLOBAL) { /* global name? */ - expdesc key; int info = var->u.info; /* global by default in the scope of a global declaration? */ if (info == -2) luaK_semerror(ls, "variable '%s' not declared", getstr(varname)); - singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ - if (var->k == VGLOBAL) - luaK_semerror(ls, "_ENV is global when accessing variable '%s'", - getstr(varname)); - luaK_exp2anyregup(fs, var); /* but could be a constant */ - codestring(&key, varname); /* key is variable name */ - luaK_indexed(fs, var, &key); /* env[varname] */ + buildglobal(ls, varname, var); if (info != -1 && ls->dyd->actvar.arr[info].vd.kind == GDKCONST) var->u.ind.ro = 1; /* mark variable as read-only */ else /* anyway must be a global */ @@ -665,7 +672,7 @@ static void createlabel (LexState *ls, TString *name, int line, int last) { /* -** Traverse the pending goto's of the finishing block checking whether +** Traverse the pending gotos of the finishing block checking whether ** each match some label of that block. Those that do not match are ** "exported" to the outer block, to be solved there. In particular, ** its 'nactvar' is updated with the level of the inner block, @@ -1435,6 +1442,15 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { } } + +/* Create code to store the "top" register in 'var' */ +static void storevartop (FuncState *fs, expdesc *var) { + expdesc e; + init_exp(&e, VNONRELOC, fs->freereg - 1); + luaK_storevar(fs, var, &e); /* will also free the top register */ +} + + /* ** Parse and compile a multiple assignment. The first "variable" ** (a 'suffixedexp') was already read by the caller. @@ -1468,8 +1484,7 @@ static void restassign (LexState *ls, struct LHS_assign *lh, int nvars) { return; /* avoid default */ } } - init_exp(&e, VNONRELOC, ls->fs->freereg-1); /* default assignment */ - luaK_storevar(ls->fs, &lh->v, &e); + storevartop(ls->fs, &lh->v); /* default assignment */ } @@ -1821,25 +1836,45 @@ static lu_byte getglobalattribute (LexState *ls, lu_byte df) { } +static void globalnames (LexState *ls, lu_byte defkind) { + FuncState *fs = ls->fs; + int nvars = 0; + int lastidx; /* index of last registered variable */ + do { /* for each name */ + TString *vname = str_checkname(ls); + lu_byte kind = getglobalattribute(ls, defkind); + lastidx = new_varkind(ls, vname, kind); + nvars++; + } while (testnext(ls, ',')); + if (testnext(ls, '=')) { /* initialization? */ + expdesc e; + int i; + int nexps = explist(ls, &e); /* read list of expressions */ + adjust_assign(ls, nvars, nexps, &e); + for (i = 0; i < nvars; i++) { /* for each variable */ + expdesc var; + TString *varname = getlocalvardesc(fs, lastidx - i)->vd.name; + buildglobal(ls, varname, &var); /* create global variable in 'var' */ + storevartop(fs, &var); + } + } + fs->nactvar = cast_short(fs->nactvar + nvars); /* activate declaration */ +} + + static void globalstat (LexState *ls) { /* 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, '*')) { + if (!testnext(ls, '*')) + globalnames(ls, defkind); + else { /* use NULL as name to represent '*' entries */ new_varkind(ls, NULL, defkind); fs->nactvar++; /* activate declaration */ } - else { - do { /* list of names */ - TString *vname = str_checkname(ls); - lu_byte kind = getglobalattribute(ls, defkind); - new_varkind(ls, vname, kind); - fs->nactvar++; /* activate declaration */ - } while (testnext(ls, ',')); - } } @@ -1850,7 +1885,7 @@ static void globalfunc (LexState *ls, int line) { TString *fname = str_checkname(ls); new_varkind(ls, fname, GDKREG); /* declare global variable */ fs->nactvar++; /* enter its scope */ - buildvar(ls, fname, &var); + buildglobal(ls, fname, &var); body(ls, &b, 0, ls->linenumber); /* compile and return closure in 'b' */ luaK_storevar(fs, &var, &b); luaK_fixline(fs, line); /* definition "happens" in the first line */ diff --git a/manual/manual.of b/manual/manual.of index bcc8173b..8f90f942 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -229,7 +229,7 @@ as the following example illustrates: @verbatim{ X = 1 -- Ok, global by default do - global Y -- voids implicit initial declaration + global Y -- voids the implicit initial declaration Y = 1 -- Ok, Y declared as global X = 1 -- ERROR, X not declared end @@ -269,7 +269,7 @@ print(x) --> 10 (the global one) Notice that, in a declaration like @T{local x = x}, the new @id{x} being declared is not in scope yet, -and so the @id{x} in the left-hand side refers to the outside variable. +and so the @id{x} in the right-hand side refers to the outside variable. Because of the @x{lexical scoping} rules, local variables can be freely accessed by functions @@ -1651,11 +1651,12 @@ Function calls are explained in @See{functioncall}. @sect3{localvar| @title{Variable Declarations} Local and global variables can be declared anywhere inside a block. -The declaration for locals can include an initialization: +The declaration can include an initialization: @Produc{ @producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}} -@producname{stat}@producbody{@Rw{global} attnamelist} +@producname{stat}@producbody{@Rw{global} + attnamelist @bnfopt{@bnfter{=} explist}} } If present, an initial assignment has the same semantics of a multiple assignment @see{assignment}. @@ -1712,7 +1713,8 @@ 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: +the effect of any declaration is only syntactical +(except for the optional assignment): @verbatim{ global X , _G X = 1 -- ERROR @@ -3924,8 +3926,8 @@ This macro may evaluate its arguments more than once. } -@APIEntry{unsigned (lua_numbertocstring) (lua_State *L, int idx, - char *buff);| +@APIEntry{unsigned lua_numbertocstring (lua_State *L, int idx, + char *buff);| @apii{0,0,-} Converts the number at acceptable index @id{idx} to a string @@ -4050,7 +4052,7 @@ This function is equivalent to @Lid{lua_pushcclosure} with no upvalues. } -@APIEntry{const char *(lua_pushexternalstring) (lua_State *L, +@APIEntry{const char *lua_pushexternalstring (lua_State *L, const char *s, size_t len, lua_Alloc falloc, void *ud);| @apii{0,1,m} diff --git a/testes/bwcoercion.lua b/testes/bwcoercion.lua index cd735ab0..0544944d 100644 --- a/testes/bwcoercion.lua +++ b/testes/bwcoercion.lua @@ -4,7 +4,7 @@ local strsub = string.sub local print = print -_ENV = nil +global none -- Try to convert a value to an integer, without assuming any coercion. local function toint (x) diff --git a/testes/calls.lua b/testes/calls.lua index 21441701..0dacb85a 100644 --- a/testes/calls.lua +++ b/testes/calls.lua @@ -24,7 +24,7 @@ assert(not pcall(type)) -- testing local-function recursion -global fact; fact = false +global fact = false do local res = 1 local function fact (n) @@ -65,7 +65,7 @@ a.b.c:f2('k', 12); assert(a.b.c.k == 12) print('+') -global t; t = nil -- 'declare' t +global 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 diff --git a/testes/files.lua b/testes/files.lua index d4e327b7..7146ac7c 100644 --- a/testes/files.lua +++ b/testes/files.lua @@ -715,7 +715,7 @@ do end -if T and T.nonblock then +if T and T.nonblock and not _port then print("testing failed write") -- unable to write anything to /dev/full @@ -840,7 +840,7 @@ assert(os.date("!\0\0") == "\0\0") local x = string.rep("a", 10000) assert(os.date(x) == x) local t = os.time() -global D; D = os.date("*t", t) +global 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)) diff --git a/testes/goto.lua b/testes/goto.lua index 3519e75d..3310314d 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -380,7 +380,7 @@ do global * Y = x + Y assert(_ENV.Y == 20) - + Y = nil end @@ -411,5 +411,26 @@ do -- mixing lots of global/local declarations _ENV.x200 = nil end +do print "testing initialization in global declarations" + global a, b, c = 10, 20, 30 + assert(_ENV.a == 10 and b == 20 and c == 30) + + global a, b, c = 10 + assert(_ENV.a == 10 and b == nil and c == nil) + + global table + global a, b, c, d = table.unpack{1, 2, 3, 6, 5} + assert(_ENV.a == 1 and b == 2 and c == 3 and d == 6) + + local a, b = 100, 200 + do + global a, b = a, b + end + assert(_ENV.a == 100 and _ENV.b == 200) + + + _ENV.a, _ENV.b, _ENV.c, _ENV.d = nil -- erase these globals +end + print'OK' diff --git a/testes/tracegc.lua b/testes/tracegc.lua index 9c5c1b3f..a8c929df 100644 --- a/testes/tracegc.lua +++ b/testes/tracegc.lua @@ -6,7 +6,7 @@ local M = {} local setmetatable, stderr, collectgarbage = setmetatable, io.stderr, collectgarbage -_ENV = nil +global none local active = false -- cgit v1.2.3-55-g6feb