Răsfoiți Sursa

Collective declaration for globals ('global *')

Roberto Ierusalimschy 2 luni în urmă
părinte
comite
3b9dd52be0
14 a modificat fișierele cu 155 adăugiri și 63 ștergeri
  1. 36 19
      lparser.c
  2. 58 18
      manual/manual.of
  3. 6 2
      testes/all.lua
  4. 9 6
      testes/calls.lua
  5. 2 0
      testes/closure.lua
  6. 4 2
      testes/code.lua
  7. 4 2
      testes/files.lua
  8. 18 6
      testes/goto.lua
  9. 2 0
      testes/literals.lua
  10. 8 4
      testes/locals.lua
  11. 3 4
      testes/nextvar.lua
  12. 2 0
      testes/pm.lua
  13. 1 0
      testes/strings.lua
  14. 2 0
      testes/utf8.lua

+ 36 - 19
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 <name>' or 'global function' */
+        if (lk == TK_NAME || lk == '*' || lk == TK_FUNCTION) {
+          /* 'global <name>' or 'global *' or 'global function' */
           globalstatfunc(ls, line);
           break;
         }

+ 58 - 18
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 * <const>} implicitly declares
+as read-only globals all names not explicitly declared previously;
+see the following example:
+@verbatim{
+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 @emph{read-only} atribute is only a syntactical restriction:
+the effect of any declaration is only syntactical:
 @verbatim{
-global X <const>
-X = 1         -- ERROR
-_ENV.X = 1    -- Ok
-foo()         -- 'foo' can freely change the global X
+global X <const>, _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{;}}}

+ 6 - 2
testes/all.lua

@@ -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)

+ 9 - 6
testes/calls.lua

@@ -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

+ 2 - 0
testes/closure.lua

@@ -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

+ 4 - 2
testes/code.lua

@@ -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)

+ 4 - 2
testes/files.lua

@@ -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

+ 18 - 6
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 <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
@@ -349,6 +355,12 @@ do
       return
     end
   ]], "%:2%:")   -- correct line in error message
+
+  checkerr([[
+    global * <const>;
+    print(X)    -- Ok to use
+    Y = 1   -- ERROR
+  ]], "assign to const variable 'Y'")
   
 end
 

+ 2 - 0
testes/literals.lua

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

+ 8 - 4
testes/locals.lua

@@ -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

+ 3 - 4
testes/nextvar.lua

@@ -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)

+ 2 - 0
testes/pm.lua

@@ -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))

+ 1 - 0
testes/strings.lua

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

+ 2 - 0
testes/utf8.lua

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