Преглед на файлове

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 години
родител
ревизия
4d46289331
променени са 3 файла, в които са добавени 103 реда и са изтрити 81 реда
  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 nexps;
   expdesc e;
   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++;
   } while (testnext(ls, ','));
   if (testnext(ls, '='))
@@ -1672,56 +1709,11 @@ static void commonlocalstat (LexState *ls) {
     nexps = 0;
   }
   adjust_assign(ls, nvars, nexps, &e);
+  checktoclose(ls, toclose);
   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) {
   /* funcname -> NAME {fieldsel} [':' NAME] */
   int ismethod = 0;

+ 34 - 26
manual/manual.of

@@ -1399,23 +1399,30 @@ they must all result in numbers.
 Their values are called respectively
 the @emph{initial value}, the @emph{limit}, and the @emph{step}.
 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,
 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 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
 (or less than, if the step is negative),
 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
 during the loop.
@@ -1490,22 +1497,25 @@ Function calls are explained in @See{functioncall}.
 @x{Local variables} can be declared anywhere inside a block.
 The declaration can include an initialization:
 @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
 of a multiple assignment @see{assignment}.
 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:
 @id{const}, which declares a @x{constant variable},
 that is, a variable that cannot be assigned to
 after its initialization;
 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},
 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}
 
-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,
 except that its value is @emph{closed} whenever the variable
 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}.
 
 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.
 
 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{function} funcname 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}
                                    @bnfopt{explist} @bnfopt{@bnfter{;}}}
 

+ 28 - 6
testes/locals.lua

@@ -173,12 +173,32 @@ end
 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"
 
 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
 
 
@@ -187,10 +207,11 @@ do
   do
     local <toclose> x = setmetatable({"x"}, {__close = function (self)
                                                    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"
+    assert(w == 10 and z == 20)
   end
   a[#a + 1] = "out"
   assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out")
@@ -199,7 +220,8 @@ end
 do
   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
   local function foo (x)