Browse Source

First implementation for 'const' variables

A variable can be declared const, which means it cannot be assigned to,
with the syntax 'local <const> name = exp'.
Roberto Ierusalimschy 6 năm trước cách đây
mục cha
commit
d9f40e3f6f
7 tập tin đã thay đổi với 205 bổ sung56 xóa
  1. 21 15
      lcode.c
  2. 89 20
      lparser.c
  3. 9 4
      lparser.h
  4. 20 8
      manual/manual.of
  5. 59 2
      testes/constructs.lua
  6. 4 4
      testes/files.lua
  7. 3 3
      testes/math.lua

+ 21 - 15
lcode.c

@@ -678,11 +678,12 @@ void luaK_setoneret (FuncState *fs, expdesc *e) {
 void luaK_dischargevars (FuncState *fs, expdesc *e) {
   switch (e->k) {
     case VLOCAL: {  /* already in a register */
+      e->u.info = e->u.var.idx;
       e->k = VNONRELOC;  /* becomes a non-relocatable value */
       break;
     }
     case VUPVAL: {  /* move value to some (pending) register */
-      e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.info, 0);
+      e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.var.idx, 0);
       e->k = VRELOC;
       break;
     }
@@ -938,12 +939,12 @@ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) {
   switch (var->k) {
     case VLOCAL: {
       freeexp(fs, ex);
-      exp2reg(fs, ex, var->u.info);  /* compute 'ex' into proper place */
+      exp2reg(fs, ex, var->u.var.idx);  /* compute 'ex' into proper place */
       return;
     }
     case VUPVAL: {
       int e = luaK_exp2anyreg(fs, ex);
-      luaK_codeABC(fs, OP_SETUPVAL, e, var->u.info, 0);
+      luaK_codeABC(fs, OP_SETUPVAL, e, var->u.var.idx, 0);
       break;
     }
     case VINDEXUP: {
@@ -1165,25 +1166,30 @@ static int isSCnumber (expdesc *e, lua_Integer *i, int *isfloat) {
 ** values in registers.
 */
 void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) {
-  lua_assert(!hasjumps(t) && (vkisinreg(t->k) || t->k == VUPVAL));
+  lua_assert(!hasjumps(t) &&
+             (t->k == VLOCAL || t->k == VNONRELOC || t->k == VUPVAL));
   if (t->k == VUPVAL && !isKstr(fs, k))  /* upvalue indexed by non string? */
     luaK_exp2anyreg(fs, t);  /* put it in a register */
-  t->u.ind.t = t->u.info;  /* register or upvalue index */
   if (t->k == VUPVAL) {
+    t->u.ind.t = t->u.var.idx;  /* upvalue index */
     t->u.ind.idx = k->u.info;  /* literal string */
     t->k = VINDEXUP;
   }
-  else if (isKstr(fs, k)) {
-    t->u.ind.idx = k->u.info;  /* literal string */
-    t->k = VINDEXSTR;
-  }
-  else if (isCint(k)) {
-    t->u.ind.idx = cast_int(k->u.ival);  /* integer constant in proper range */
-    t->k = VINDEXI;
-  }
   else {
-    t->u.ind.idx = luaK_exp2anyreg(fs, k);  /* register */
-    t->k = VINDEXED;
+    /* register index of the table */
+    t->u.ind.t = (t->k == VLOCAL) ? t->u.var.idx: t->u.info;
+    if (isKstr(fs, k)) {
+      t->u.ind.idx = k->u.info;  /* literal string */
+      t->k = VINDEXSTR;
+    }
+    else if (isCint(k)) {
+      t->u.ind.idx = cast_int(k->u.ival);  /* int. constant in proper range */
+      t->k = VINDEXI;
+    }
+    else {
+      t->u.ind.idx = luaK_exp2anyreg(fs, k);  /* register */
+      t->k = VINDEXED;
+    }
   }
 }
 

+ 89 - 20
lparser.c

@@ -156,6 +156,13 @@ static void init_exp (expdesc *e, expkind k, int i) {
 }
 
 
+static void init_var (expdesc *e, expkind k, int i) {
+  e->f = e->t = NO_JUMP;
+  e->k = k;
+  e->u.var.idx = i;
+}
+
+
 static void codestring (LexState *ls, expdesc *e, TString *s) {
   init_exp(e, VK, luaK_stringK(ls->fs, s));
 }
@@ -187,31 +194,82 @@ static int registerlocalvar (LexState *ls, TString *varname) {
 /*
 ** Create a new local variable with the given 'name'.
 */
-static void new_localvar (LexState *ls, TString *name) {
+static Vardesc *new_localvar (LexState *ls, TString *name) {
   FuncState *fs = ls->fs;
   Dyndata *dyd = ls->dyd;
+  Vardesc *var;
   int reg = registerlocalvar(ls, name);
   checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal,
                   MAXVARS, "local variables");
   luaM_growvector(ls->L, dyd->actvar.arr, dyd->actvar.n + 1,
                   dyd->actvar.size, Vardesc, MAX_INT, "local variables");
-  dyd->actvar.arr[dyd->actvar.n++].idx = cast(short, reg);
+  var = &dyd->actvar.arr[dyd->actvar.n++];
+  var->idx = cast(short, reg);
+  var->name = name;
+  var->ro = 0;
+  return var;
 }
 
 #define new_localvarliteral(ls,v) \
     new_localvar(ls, luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char)) - 1));
 
 
+
+/*
+** Return the "variable description" (Vardesc) of a given
+** variable
+*/
+static Vardesc *getlocalvardesc (FuncState *fs, int i) {
+  return &fs->ls->dyd->actvar.arr[fs->firstlocal + i];
+}
+
 /*
 ** Get the debug-information entry for current variable 'i'.
 */
 static LocVar *getlocvar (FuncState *fs, int i) {
-  int idx = fs->ls->dyd->actvar.arr[fs->firstlocal + i].idx;
+  int idx = getlocalvardesc(fs, i)->idx;
   lua_assert(idx < fs->nlocvars);
   return &fs->f->locvars[idx];
 }
 
 
+/*
+** Return the "variable description" (Vardesc) of a given
+** variable or upvalue
+*/
+static Vardesc *getvardesc (FuncState *fs, expdesc *e) {
+  if (e->k == VLOCAL)
+    return getlocalvardesc(fs, e->u.var.idx);
+  else if (e->k != VUPVAL)
+    return NULL;  /* not a local variable */
+  else {  /* upvalue: must go up all levels up to the original local */
+    int idx = e->u.var.idx;
+    for (;;) {
+      Upvaldesc *up = &fs->f->upvalues[idx];
+      fs = fs->prev;  /* must look at the previous level */
+      idx = up->idx;  /* at this index */
+      if (fs == NULL) {  /* no more levels? (can happen only with _ENV) */
+        lua_assert(strcmp(getstr(up->name), LUA_ENV) == 0);
+        return NULL;
+      }
+      else if (up->instack)  /* got to the original level? */
+        return getlocalvardesc(fs, idx);
+      /* else repeat for previous level */
+    }
+  }
+}
+
+
+static void check_readonly (LexState *ls, expdesc *e) {
+  Vardesc *vardesc = getvardesc(ls->fs, e);
+  if (vardesc && vardesc->ro) {  /* is variable local and const? */
+    const char *msg = luaO_pushfstring(ls->L,
+       "assignment to const variable '%s'", getstr(vardesc->name));
+    luaK_semerror(ls, msg);  /* error */
+  }
+}
+
+
 /*
 ** Start the scope for the last 'nvars' created variables.
 ** (debug info.)
@@ -259,7 +317,7 @@ static int newupvalue (FuncState *fs, TString *name, expdesc *v) {
   while (oldsize < f->sizeupvalues)
     f->upvalues[oldsize++].name = NULL;
   f->upvalues[fs->nups].instack = (v->k == VLOCAL);
-  f->upvalues[fs->nups].idx = cast_byte(v->u.info);
+  f->upvalues[fs->nups].idx = cast_byte(v->u.var.idx);
   f->upvalues[fs->nups].name = name;
   luaC_objbarrier(fs->ls->L, f, name);
   return fs->nups++;
@@ -304,7 +362,7 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
   else {
     int v = searchvar(fs, n);  /* look up locals at current level */
     if (v >= 0) {  /* found? */
-      init_exp(var, VLOCAL, v);  /* variable is local */
+      init_var(var, VLOCAL, v);  /* variable is local */
       if (!base)
         markupval(fs, v);  /* local will be used as an upval */
     }
@@ -317,7 +375,7 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
         /* else was LOCAL or UPVAL */
         idx  = newupvalue(fs, n, var);  /* will be a new upvalue */
       }
-      init_exp(var, VUPVAL, idx);  /* new or old upvalue */
+      init_var(var, VUPVAL, idx);  /* new or old upvalue */
     }
   }
 }
@@ -1199,20 +1257,20 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) {
   for (; lh; lh = lh->prev) {  /* check all previous assignments */
     if (vkisindexed(lh->v.k)) {  /* assignment to table field? */
       if (lh->v.k == VINDEXUP) {  /* is table an upvalue? */
-        if (v->k == VUPVAL && lh->v.u.ind.t == v->u.info) {
+        if (v->k == VUPVAL && lh->v.u.ind.t == v->u.var.idx) {
           conflict = 1;  /* table is the upvalue being assigned now */
           lh->v.k = VINDEXSTR;
           lh->v.u.ind.t = extra;  /* assignment will use safe copy */
         }
       }
       else {  /* table is a register */
-        if (v->k == VLOCAL && lh->v.u.ind.t == v->u.info) {
+        if (v->k == VLOCAL && lh->v.u.ind.t == v->u.var.idx) {
           conflict = 1;  /* table is the local being assigned now */
           lh->v.u.ind.t = extra;  /* assignment will use safe copy */
         }
         /* is index the local being assigned? */
         if (lh->v.k == VINDEXED && v->k == VLOCAL &&
-            lh->v.u.ind.idx == v->u.info) {
+            lh->v.u.ind.idx == v->u.var.idx) {
           conflict = 1;
           lh->v.u.ind.idx = extra;  /* previous assignment will use safe copy */
         }
@@ -1222,7 +1280,7 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) {
   if (conflict) {
     /* copy upvalue/local value to a temporary (in position 'extra') */
     OpCode op = (v->k == VLOCAL) ? OP_MOVE : OP_GETUPVAL;
-    luaK_codeABC(fs, op, extra, v->u.info, 0);
+    luaK_codeABC(fs, op, extra, v->u.var.idx, 0);
     luaK_reserveregs(fs, 1);
   }
 }
@@ -1237,6 +1295,7 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) {
 static void restassign (LexState *ls, struct LHS_assign *lh, int nvars) {
   expdesc e;
   check_condition(ls, vkisvar(lh->v.k), "syntax error");
+  check_readonly(ls, &lh->v);
   if (testnext(ls, ',')) {  /* restassign -> ',' suffixedexp restassign */
     struct LHS_assign nv;
     nv.prev = lh;
@@ -1615,20 +1674,30 @@ static void commonlocalstat (LexState *ls) {
 }
 
 
-static void tocloselocalstat (LexState *ls) {
+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);
+  fs->bl->insidetbc = 1;  /* in the scope of a to-be-closed variable */
+  luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0);
+}
+
+
+static void attriblocalstat (LexState *ls) {
+  Vardesc *var;
   TString *attr = str_checkname(ls);
-  if (strcmp(getstr(attr), "toclose") != 0)
-    luaK_semerror(ls,
-      luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr)));
   testnext(ls, '>');
-  new_localvar(ls, str_checkname(ls));
+  var = new_localvar(ls, str_checkname(ls));
   checknext(ls, '=');
   exp1(ls);
-  markupval(fs, fs->nactvar);
-  fs->bl->insidetbc = 1;  /* in the scope of a to-be-closed variable */
   adjustlocalvars(ls, 1);
-  luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0);
+  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)));
 }
 
 
@@ -1636,7 +1705,7 @@ static void localstat (LexState *ls) {
   /* stat -> LOCAL NAME {',' NAME} ['=' explist]
            | LOCAL *toclose NAME '=' exp */
   if (testnext(ls, '<'))
-    tocloselocalstat(ls);
+    attriblocalstat(ls);
   else
     commonlocalstat(ls);
 }
@@ -1801,7 +1870,7 @@ static void mainfunc (LexState *ls, FuncState *fs) {
   expdesc v;
   open_func(ls, fs, &bl);
   setvararg(fs, 0);  /* main function is always declared vararg */
-  init_exp(&v, VLOCAL, 0);  /* create and... */
+  init_var(&v, VLOCAL, 0);  /* create and... */
   newupvalue(fs, ls->envn, &v);  /* ...set environment upvalue */
   luaX_next(ls);  /* read first token */
   statlist(ls);  /* parse main body */

+ 9 - 4
lparser.h

@@ -33,8 +33,8 @@ typedef enum {
   VKINT,  /* integer constant; nval = numerical integer value */
   VNONRELOC,  /* expression has its value in a fixed register;
                  info = result register */
-  VLOCAL,  /* local variable; info = local register */
-  VUPVAL,  /* upvalue variable; info = index of upvalue in 'upvalues' */
+  VLOCAL,  /* local variable; var.idx = local register */
+  VUPVAL,  /* upvalue variable; var.idx = index of upvalue in 'upvalues' */
   VINDEXED,  /* indexed variable;
                 ind.t = table register;
                 ind.idx = key's R index */
@@ -58,7 +58,7 @@ typedef enum {
 
 #define vkisvar(k)	(VLOCAL <= (k) && (k) <= VINDEXSTR)
 #define vkisindexed(k)	(VINDEXED <= (k) && (k) <= VINDEXSTR)
-#define vkisinreg(k)	((k) == VNONRELOC || (k) == VLOCAL)
+
 
 typedef struct expdesc {
   expkind k;
@@ -70,15 +70,20 @@ typedef struct expdesc {
       short idx;  /* index (R or "long" K) */
       lu_byte t;  /* table (register or upvalue) */
     } ind;
+    struct {  /* for local variables and upvalues */
+      lu_byte idx;  /* index of the variable */
+    } var;
   } u;
   int t;  /* patch list of 'exit when true' */
   int f;  /* patch list of 'exit when false' */
 } expdesc;
 
 
-/* description of active local variable */
+/* description of an active local variable */
 typedef struct Vardesc {
+  TString *name;
   short idx;  /* index of the variable in the Proto's 'locvars' array */
+  lu_byte ro;  /* true if variable is 'const' */
 } Vardesc;
 
 

+ 20 - 8
manual/manual.of

@@ -1488,13 +1488,24 @@ Function calls are explained in @See{functioncall}.
 
 @sect3{localvar| @title{Local Declarations}
 @x{Local variables} can be declared anywhere inside a block.
-The declaration can include an initial assignment:
+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
+}}
 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.
+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}, wich declares a to-be-closed variable @see{to-be-closed}.
+
 
 A chunk is also a block @see{chunks},
 and so local variables can be declared in a chunk outside any explicit block.
@@ -1506,12 +1517,12 @@ 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,
-with the following syntax:
+using the identifier @id{toclose} as its attribute:
 @Produc{
 @producname{stat}@producbody{
-  @Rw{local} @bnfter{<} @bnfter{toclose} @bnfter{>} Name @bnfter{=} exp
+  @Rw{local} @bnfter{<} @id{toclose} @bnfter{>} Name @bnfter{=} exp
 }}
-A to-be-closed variable behaves like a normal local variable,
+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,
 exiting its block by @Rw{break}/@Rw{goto}/@Rw{return},
@@ -7603,7 +7614,7 @@ or a float otherwise.
 
 @LibEntry{math.abs (x)|
 
-Returns the absolute value of @id{x}. (integer/float)
+Returns the maximum value between @id{x} and @id{-x}. (integer/float)
 
 }
 
@@ -8042,7 +8053,8 @@ following the lexical conventions of Lua.
 This format always reads the longest input sequence that
 is a valid prefix for a numeral;
 if that prefix does not form a valid numeral
-(e.g., an empty string, @St{0x}, or @St{3.4e-}),
+(e.g., an empty string, @St{0x}, or @St{3.4e-})
+or it is too long (more than 200 characters),
 it is discarded and the format returns @nil.
 }
 
@@ -8949,7 +8961,7 @@ and @bnfNter{LiteralString}, see @See{lexical}.)
 @OrNL	@Rw{function} funcname funcbody
 @OrNL	@Rw{local} @Rw{function} @bnfNter{Name} funcbody
 @OrNL	@Rw{local} namelist @bnfopt{@bnfter{=} explist}
-@OrNL	@Rw{local} @bnfter{<} @bnfter{toclose} @bnfter{>} Name @bnfter{=} exp
+@OrNL	@Rw{local} @bnfter{<} Name @bnfter{>} Name @bnfter{=} exp
 }
 
 @producname{retstat}@producbody{@Rw{return}

+ 59 - 2
testes/constructs.lua

@@ -59,6 +59,41 @@ assert((x>y) and x or y == 2);
 
 assert(1234567890 == tonumber('1234567890') and 1234567890+1 == 1234567891)
 
+do   -- testing operators with diffent kinds of constants
+  -- operands to consider:
+  --  * fit in register
+  --  * constant doesn't fit in register
+  --  * floats with integral values
+  local operand = {3, 100, 5.0, -10, -5.0, 10000, -10000}
+  local operator = {"+", "-", "*", "/", "//", "%", "^",
+                    "&", "|", "^", "<<", ">>",
+                    "==", "~=", "<", ">", "<=", ">=",}
+  for _, op in ipairs(operator) do
+    local f = assert(load(string.format([[return function (x,y)
+                return x %s y
+              end]], op)))();
+    for _, o1 in ipairs(operand) do
+      for _, o2 in ipairs(operand) do
+        local gab = f(o1, o2)
+
+        _ENV.XX = o1
+        code = string.format("return XX %s %s", op, o2)
+        res = assert(load(code))()
+        assert(res == gab)
+
+        _ENV.XX = o2
+        local code = string.format("return (%s) %s XX", o1, op)
+        local res = assert(load(code))()
+        assert(res == gab)
+
+        code = string.format("return (%s) %s %s", o1, op, o2)
+        res = assert(load(code))()
+        assert(res == gab)
+      end
+    end
+  end
+end
+
 
 -- silly loops
 repeat until 1; repeat until true;
@@ -175,6 +210,28 @@ assert(a==1 and b==nil)
 
 print'+';
 
+do   -- testing constants
+  local <const> prog = [[local <XXX> x = 10]]
+  checkload(prog, "unknown attribute 'XXX'")
+
+  checkload([[local <const> xxx = 20; xxx = 10]],
+             ":1: assignment to const variable 'xxx'")
+
+  checkload([[
+    local xx;
+    local <const> xxx = 20;
+    local yyy;
+    local function foo ()
+      local abc = xx + yyy + xxx;
+      return function () return function () xxx = yyy end end
+    end
+  ]], ":6: assignment to const variable 'xxx'")
+
+  checkload([[
+    local <toclose> x = nil
+    x = io.open()
+  ]], ":2: assignment to const variable 'x'")
+end
 
 f = [[
 return function ( a , b , c , d , e )
@@ -245,12 +302,12 @@ print('testing short-circuit optimizations (' .. _ENV.GLOB1 .. ')')
 
 
 -- operators with their respective values
-local binops = {
+local <const> binops = {
   {" and ", function (a,b) if not a then return a else return b end end},
   {" or ", function (a,b) if a then return a else return b end end},
 }
 
-local cases = {}
+local <const> cases = {}
 
 -- creates all combinations of '(cases[i] op cases[n-i])' plus
 -- 'not(cases[i] op cases[n-i])' (syntax + value)

+ 4 - 4
testes/files.lua

@@ -144,7 +144,7 @@ do
   f:write(string.format("0x%X\n", -maxint))
   f:write("-0xABCp-3", '\n')
   assert(f:close())
-  f = assert(io.open(file, "r"))
+  local <toclose> f = assert(io.open(file, "r"))
   assert(f:read("n") == maxint)
   assert(f:read("n") == maxint)
   assert(f:read("n") == 0xABCp-3)
@@ -170,18 +170,18 @@ three
 ]]
   local l1, l2, l3, l4, n1, n2, c, dummy
   assert(f:close())
-  f = assert(io.open(file, "r"))
+  local <toclose> f = assert(io.open(file, "r"))
   l1, l2, n1, n2, dummy = f:read("l", "L", "n", "n")
   assert(l1 == "a line" and l2 == "another line\n" and
          n1 == 1234 and n2 == 3.45 and dummy == nil)
   assert(f:close())
-  f = assert(io.open(file, "r"))
+  local <toclose> f = assert(io.open(file, "r"))
   l1, l2, n1, n2, c, l3, l4, dummy = f:read(7, "l", "n", "n", 1, "l", "l")
   assert(l1 == "a line\n" and l2 == "another line" and c == '\n' and
          n1 == 1234 and n2 == 3.45 and l3 == "one" and l4 == "two"
          and dummy == nil)
   assert(f:close())
-  f = assert(io.open(file, "r"))
+  local <toclose> f = assert(io.open(file, "r"))
   -- second item failing
   l1, n1, n2, dummy = f:read("l", "n", "n", "l")
   assert(l1 == "a line" and n1 == nil)

+ 3 - 3
testes/math.lua

@@ -3,10 +3,10 @@
 
 print("testing numbers and math lib")
 
-local minint = math.mininteger
-local maxint = math.maxinteger
+local <const> minint = math.mininteger
+local <const> maxint = math.maxinteger
 
-local intbits = math.floor(math.log(maxint, 2) + 0.5) + 1
+local <const> intbits = math.floor(math.log(maxint, 2) + 0.5) + 1
 assert((1 << intbits) == 0)
 
 assert(minint == 1 << (intbits - 1))