Browse Source

Local attributes can be used in list of local variables

The syntax for local attributes ('const'/'toclose') was unified with
the regular syntax for local variables, so that we can have variables
with attributes in local definitions with multiple names; for instance:

  local <toclose> f, <const> err = io.open(fname)

This new syntax does not implement constant propagation, yet.

This commit also has some small improvements to the manual.
Roberto Ierusalimschy 6 years ago
parent
commit
4d46289331
3 changed files with 103 additions and 81 deletions
  1. 41 49
      lparser.c
  2. 34 26
      manual/manual.of
  3. 28 6
      testes/locals.lua

+ 41 - 49
lparser.c

@@ -1656,13 +1656,50 @@ static void localfunc (LexState *ls) {
 }
 }
 
 
 
 
-static void commonlocalstat (LexState *ls) {
-  /* stat -> LOCAL NAME {',' NAME} ['=' explist] */
+static int getlocalattribute (LexState *ls) {
+  /* ATTRIB -> ['<' Name '>'] */
+  if (testnext(ls, '<')) {
+    const char *attr = getstr(str_checkname(ls));
+    checknext(ls, '>');
+    if (strcmp(attr, "const") == 0)
+      return 1;  /* read-only variable */
+    else if (strcmp(attr, "toclose") == 0)
+      return 2;  /* to-be-closed variable */
+    else
+      luaK_semerror(ls,
+        luaO_pushfstring(ls->L, "unknown attribute '%s'", attr));
+  }
+  return 0;
+}
+
+
+static void checktoclose (LexState *ls, int toclose) {
+  if (toclose != -1) {  /* is there a to-be-closed variable? */
+    FuncState *fs = ls->fs;
+    markupval(fs, fs->nactvar + toclose + 1);
+    fs->bl->insidetbc = 1;  /* in the scope of a to-be-closed variable */
+    luaK_codeABC(fs, OP_TBC, fs->nactvar + toclose, 0, 0);
+  }
+}
+
+
+static void localstat (LexState *ls) {
+  /* stat -> LOCAL ATTRIB NAME {',' ATTRIB NAME} ['=' explist] */
+  int toclose = -1;  /* index of to-be-closed variable (if any) */
   int nvars = 0;
   int nvars = 0;
   int nexps;
   int nexps;
   expdesc e;
   expdesc e;
   do {
   do {
-    new_localvar(ls, str_checkname(ls));
+    int kind = getlocalattribute(ls);
+    Vardesc *var = new_localvar(ls, str_checkname(ls));
+    if (kind != 0) {  /* is there an attribute? */
+      var->ro = 1;  /* all attributes make variable read-only */
+      if (kind == 2) {  /* to-be-closed? */
+        if (toclose != -1)  /* one already present? */
+          luaK_semerror(ls, "multiple to-be-closed variables in local list");
+        toclose = nvars;
+      }
+    }
     nvars++;
     nvars++;
   } while (testnext(ls, ','));
   } while (testnext(ls, ','));
   if (testnext(ls, '='))
   if (testnext(ls, '='))
@@ -1672,56 +1709,11 @@ static void commonlocalstat (LexState *ls) {
     nexps = 0;
     nexps = 0;
   }
   }
   adjust_assign(ls, nvars, nexps, &e);
   adjust_assign(ls, nvars, nexps, &e);
+  checktoclose(ls, toclose);
   adjustlocalvars(ls, nvars);
   adjustlocalvars(ls, nvars);
 }
 }
 
 
 
 
-static void tocloselocalstat (LexState *ls, Vardesc *var) {
-  FuncState *fs = ls->fs;
-  var->ro = 1;  /* to-be-closed variables are always read-only */
-  markupval(fs, fs->nactvar + 1);
-  fs->bl->insidetbc = 1;  /* in the scope of a to-be-closed variable */
-  luaK_codeABC(fs, OP_TBC, fs->nactvar, 0, 0);
-}
-
-
-static void checkattrib (LexState *ls, TString *attr, Vardesc *var) {
-  if (strcmp(getstr(attr), "const") == 0)
-    var->ro = 1;  /* set variable as read-only */
-  else if (strcmp(getstr(attr), "toclose") == 0)
-    tocloselocalstat(ls, var);
-  else
-    luaK_semerror(ls,
-      luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr)));
-}
-
-
-static void attriblocalstat (LexState *ls) {
-  FuncState *fs = ls->fs;
-  Vardesc *var;
-  expdesc e;
-  TString *attr = str_checkname(ls);
-  testnext(ls, '>');
-  var = new_localvar(ls, str_checkname(ls));
-  checknext(ls, '=');
-  expr(ls, &e);
-  checkattrib(ls, attr, var);
-  luaK_tonumeral(fs, &e, &var->val);
-  luaK_exp2nextreg(fs, &e);
-  adjustlocalvars(ls, 1);
-}
-
-
-static void localstat (LexState *ls) {
-  /* stat -> LOCAL NAME {',' NAME} ['=' explist]
-           | LOCAL *toclose NAME '=' exp */
-  if (testnext(ls, '<'))
-    attriblocalstat(ls);
-  else
-    commonlocalstat(ls);
-}
-
-
 static int funcname (LexState *ls, expdesc *v) {
 static int funcname (LexState *ls, expdesc *v) {
   /* funcname -> NAME {fieldsel} [':' NAME] */
   /* funcname -> NAME {fieldsel} [':' NAME] */
   int ismethod = 0;
   int ismethod = 0;

+ 34 - 26
manual/manual.of

@@ -1399,23 +1399,30 @@ they must all result in numbers.
 Their values are called respectively
 Their values are called respectively
 the @emph{initial value}, the @emph{limit}, and the @emph{step}.
 the @emph{initial value}, the @emph{limit}, and the @emph{step}.
 If the step is absent, it defaults @N{to 1}.
 If the step is absent, it defaults @N{to 1}.
-Then the loop body is repeated with the value of the control variable
+
+If both the initial value and the step are integers,
+the loop is done with integers;
+note that the limit may not be an integer.
+Otherwise, the loop is done with floats.
+(Beware of floating-point accuracy in this case.)
+
+After that initialization,
+the loop body is repeated with the value of the control variable
 going through an arithmetic progression,
 going through an arithmetic progression,
 starting at the initial value,
 starting at the initial value,
-with a common difference given by the step,
-until that value passes the limit.
+with a common difference given by the step.
 A negative step makes a decreasing sequence;
 A negative step makes a decreasing sequence;
 a step equal to zero raises an error.
 a step equal to zero raises an error.
+The loop continues while the value is less than
+or equal to the limit
+(greater than or equal to for a negative step).
 If the initial value is already greater than the limit
 If the initial value is already greater than the limit
 (or less than, if the step is negative),
 (or less than, if the step is negative),
 the body is not executed.
 the body is not executed.
 
 
-If both the initial value and the step are integers,
-the loop is done with integers;
-in this case, the range of the control variable is clipped
-by the range of integers.
-Otherwise, the loop is done with floats.
-(Beware of floating-point accuracy in this case.)
+For integer loops,
+the control variable never wraps around;
+instead, the loop ends in case of an overflow.
 
 
 You should not change the value of the control variable
 You should not change the value of the control variable
 during the loop.
 during the loop.
@@ -1490,22 +1497,25 @@ Function calls are explained in @See{functioncall}.
 @x{Local variables} can be declared anywhere inside a block.
 @x{Local variables} can be declared anywhere inside a block.
 The declaration can include an initialization:
 The declaration can include an initialization:
 @Produc{
 @Produc{
-@producname{stat}@producbody{@Rw{local} namelist @bnfopt{@bnfter{=} explist}}
-@producname{stat}@producbody{
-    @Rw{local} @bnfter{<} Name @bnfter{>} Name @bnfter{=} exp
-}}
+@producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}}
+@producname{attnamelist}@producbody{
+  attrib @bnfNter{Name} @bnfrep{@bnfter{,} attrib @bnfNter{Name}}}
+}
 If present, an initial assignment has the same semantics
 If present, an initial assignment has the same semantics
 of a multiple assignment @see{assignment}.
 of a multiple assignment @see{assignment}.
 Otherwise, all variables are initialized with @nil.
 Otherwise, all variables are initialized with @nil.
-The second syntax declares a local with a given attribute,
-which is the name between the angle brackets.
-In this case, there must be an initialization.
+
+Each variable name may be preceded by an attribute
+(a name between angle brackets):
+@Produc{
+@producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}}
+}
 There are two possible attributes:
 There are two possible attributes:
 @id{const}, which declares a @x{constant variable},
 @id{const}, which declares a @x{constant variable},
 that is, a variable that cannot be assigned to
 that is, a variable that cannot be assigned to
 after its initialization;
 after its initialization;
 and @id{toclose}, which declares a to-be-closed variable @see{to-be-closed}.
 and @id{toclose}, which declares a to-be-closed variable @see{to-be-closed}.
-
+A list of variables can contain at most one to-be-closed variable.
 
 
 A chunk is also a block @see{chunks},
 A chunk is also a block @see{chunks},
 and so local variables can be declared in a chunk outside any explicit block.
 and so local variables can be declared in a chunk outside any explicit block.
@@ -1516,12 +1526,6 @@ The visibility rules for local variables are explained in @See{visibility}.
 
 
 @sect3{to-be-closed| @title{To-be-closed Variables}
 @sect3{to-be-closed| @title{To-be-closed Variables}
 
 
-A local variable can be declared as a @def{to-be-closed} variable,
-using the identifier @id{toclose} as its attribute:
-@Produc{
-@producname{stat}@producbody{
-  @Rw{local} @bnfter{<} @id{toclose} @bnfter{>} Name @bnfter{=} exp
-}}
 A to-be-closed variable behaves like a constant local variable,
 A to-be-closed variable behaves like a constant local variable,
 except that its value is @emph{closed} whenever the variable
 except that its value is @emph{closed} whenever the variable
 goes out of scope, including normal block termination,
 goes out of scope, including normal block termination,
@@ -8215,7 +8219,7 @@ then @id{date} returns the date as a string,
 formatted according to the same rules as the @ANSI{strftime}.
 formatted according to the same rules as the @ANSI{strftime}.
 
 
 If @id{format} is absent, it defaults to @St{%c},
 If @id{format} is absent, it defaults to @St{%c},
-which gives a reasonable date and time representation
+which gives a human-readable date and time representation
 using the current locale.
 using the current locale.
 
 
 On non-POSIX systems,
 On non-POSIX systems,
@@ -9022,10 +9026,14 @@ and @bnfNter{LiteralString}, see @See{lexical}.)
 @OrNL   @Rw{for} namelist @Rw{in} explist @Rw{do} block @Rw{end}
 @OrNL   @Rw{for} namelist @Rw{in} explist @Rw{do} block @Rw{end}
 @OrNL	@Rw{function} funcname funcbody
 @OrNL	@Rw{function} funcname funcbody
 @OrNL	@Rw{local} @Rw{function} @bnfNter{Name} funcbody
 @OrNL	@Rw{local} @Rw{function} @bnfNter{Name} funcbody
-@OrNL	@Rw{local} namelist @bnfopt{@bnfter{=} explist}
-@OrNL	@Rw{local} @bnfter{<} Name @bnfter{>} Name @bnfter{=} exp
+@OrNL	@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}
 }
 }
 
 
+@producname{attnamelist}@producbody{
+  attrib @bnfNter{Name} @bnfrep{@bnfter{,} attrib @bnfNter{Name}}}
+
+@producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}}
+
 @producname{retstat}@producbody{@Rw{return}
 @producname{retstat}@producbody{@Rw{return}
                                    @bnfopt{explist} @bnfopt{@bnfter{;}}}
                                    @bnfopt{explist} @bnfopt{@bnfter{;}}}
 
 

+ 28 - 6
testes/locals.lua

@@ -173,12 +173,32 @@ end
 assert(x==20)
 assert(x==20)
 
 
 
 
+do   -- constants
+  local <const> a, b, <const> c = 10, 20, 30
+  b = a + c + b    -- 'b' is not constant
+  assert(a == 10 and b == 60 and c == 30)
+  local function checkro (code, name)
+    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("local x, <const> y, z = 10, 20, 30; x = 11; y = 12", "y")
+  checkro("local <const> x, y, <const> z = 10, 20, 30; x = 11", "x")
+  checkro("local <const> x, y, <const> z = 10, 20, 30; y = 10; z = 11", "z")
+end
+
+
 print"testing to-be-closed variables"
 print"testing to-be-closed variables"
 
 
 local function stack(n) n = ((n == 0) or stack(n - 1)) end
 local function stack(n) n = ((n == 0) or stack(n - 1)) end
 
 
-local function func2close (f)
-  return setmetatable({}, {__close = f})
+local function func2close (f, x, y)
+  local obj = setmetatable({}, {__close = f})
+  if x then
+    return x, obj, y
+  else
+    return obj
+  end
 end
 end
 
 
 
 
@@ -187,10 +207,11 @@ do
   do
   do
     local <toclose> x = setmetatable({"x"}, {__close = function (self)
     local <toclose> x = setmetatable({"x"}, {__close = function (self)
                                                    a[#a + 1] = self[1] end})
                                                    a[#a + 1] = self[1] end})
-    local <toclose> y = func2close(function (self, err)
-                         assert(err == nil); a[#a + 1] = "y"
-                       end)
+    local w, <toclose> y, z = func2close(function (self, err)
+                                assert(err == nil); a[#a + 1] = "y"
+                              end, 10, 20)
     a[#a + 1] = "in"
     a[#a + 1] = "in"
+    assert(w == 10 and z == 20)
   end
   end
   a[#a + 1] = "out"
   a[#a + 1] = "out"
   assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out")
   assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out")
@@ -199,7 +220,8 @@ end
 do
 do
   local X = false
   local X = false
 
 
-  local closescope = func2close(function () stack(10); X = true end)
+  local x, closescope = func2close(function () stack(10); X = true end, 100)
+  assert(x == 100);  x = 101;   -- 'x' is not read-only
 
 
   -- closing functions do not corrupt returning values
   -- closing functions do not corrupt returning values
   local function foo (x)
   local function foo (x)