diff options
-rw-r--r-- | lparser.c | 90 | ||||
-rw-r--r-- | manual/manual.of | 60 | ||||
-rw-r--r-- | testes/locals.lua | 34 |
3 files changed, 103 insertions, 81 deletions
@@ -1656,13 +1656,50 @@ static void localfunc (LexState *ls) { | |||
1656 | } | 1656 | } |
1657 | 1657 | ||
1658 | 1658 | ||
1659 | static void commonlocalstat (LexState *ls) { | 1659 | static int getlocalattribute (LexState *ls) { |
1660 | /* stat -> LOCAL NAME {',' NAME} ['=' explist] */ | 1660 | /* ATTRIB -> ['<' Name '>'] */ |
1661 | if (testnext(ls, '<')) { | ||
1662 | const char *attr = getstr(str_checkname(ls)); | ||
1663 | checknext(ls, '>'); | ||
1664 | if (strcmp(attr, "const") == 0) | ||
1665 | return 1; /* read-only variable */ | ||
1666 | else if (strcmp(attr, "toclose") == 0) | ||
1667 | return 2; /* to-be-closed variable */ | ||
1668 | else | ||
1669 | luaK_semerror(ls, | ||
1670 | luaO_pushfstring(ls->L, "unknown attribute '%s'", attr)); | ||
1671 | } | ||
1672 | return 0; | ||
1673 | } | ||
1674 | |||
1675 | |||
1676 | static void checktoclose (LexState *ls, int toclose) { | ||
1677 | if (toclose != -1) { /* is there a to-be-closed variable? */ | ||
1678 | FuncState *fs = ls->fs; | ||
1679 | markupval(fs, fs->nactvar + toclose + 1); | ||
1680 | fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */ | ||
1681 | luaK_codeABC(fs, OP_TBC, fs->nactvar + toclose, 0, 0); | ||
1682 | } | ||
1683 | } | ||
1684 | |||
1685 | |||
1686 | static void localstat (LexState *ls) { | ||
1687 | /* stat -> LOCAL ATTRIB NAME {',' ATTRIB NAME} ['=' explist] */ | ||
1688 | int toclose = -1; /* index of to-be-closed variable (if any) */ | ||
1661 | int nvars = 0; | 1689 | int nvars = 0; |
1662 | int nexps; | 1690 | int nexps; |
1663 | expdesc e; | 1691 | expdesc e; |
1664 | do { | 1692 | do { |
1665 | new_localvar(ls, str_checkname(ls)); | 1693 | int kind = getlocalattribute(ls); |
1694 | Vardesc *var = new_localvar(ls, str_checkname(ls)); | ||
1695 | if (kind != 0) { /* is there an attribute? */ | ||
1696 | var->ro = 1; /* all attributes make variable read-only */ | ||
1697 | if (kind == 2) { /* to-be-closed? */ | ||
1698 | if (toclose != -1) /* one already present? */ | ||
1699 | luaK_semerror(ls, "multiple to-be-closed variables in local list"); | ||
1700 | toclose = nvars; | ||
1701 | } | ||
1702 | } | ||
1666 | nvars++; | 1703 | nvars++; |
1667 | } while (testnext(ls, ',')); | 1704 | } while (testnext(ls, ',')); |
1668 | if (testnext(ls, '=')) | 1705 | if (testnext(ls, '=')) |
@@ -1672,56 +1709,11 @@ static void commonlocalstat (LexState *ls) { | |||
1672 | nexps = 0; | 1709 | nexps = 0; |
1673 | } | 1710 | } |
1674 | adjust_assign(ls, nvars, nexps, &e); | 1711 | adjust_assign(ls, nvars, nexps, &e); |
1712 | checktoclose(ls, toclose); | ||
1675 | adjustlocalvars(ls, nvars); | 1713 | adjustlocalvars(ls, nvars); |
1676 | } | 1714 | } |
1677 | 1715 | ||
1678 | 1716 | ||
1679 | static void tocloselocalstat (LexState *ls, Vardesc *var) { | ||
1680 | FuncState *fs = ls->fs; | ||
1681 | var->ro = 1; /* to-be-closed variables are always read-only */ | ||
1682 | markupval(fs, fs->nactvar + 1); | ||
1683 | fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */ | ||
1684 | luaK_codeABC(fs, OP_TBC, fs->nactvar, 0, 0); | ||
1685 | } | ||
1686 | |||
1687 | |||
1688 | static void checkattrib (LexState *ls, TString *attr, Vardesc *var) { | ||
1689 | if (strcmp(getstr(attr), "const") == 0) | ||
1690 | var->ro = 1; /* set variable as read-only */ | ||
1691 | else if (strcmp(getstr(attr), "toclose") == 0) | ||
1692 | tocloselocalstat(ls, var); | ||
1693 | else | ||
1694 | luaK_semerror(ls, | ||
1695 | luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr))); | ||
1696 | } | ||
1697 | |||
1698 | |||
1699 | static void attriblocalstat (LexState *ls) { | ||
1700 | FuncState *fs = ls->fs; | ||
1701 | Vardesc *var; | ||
1702 | expdesc e; | ||
1703 | TString *attr = str_checkname(ls); | ||
1704 | testnext(ls, '>'); | ||
1705 | var = new_localvar(ls, str_checkname(ls)); | ||
1706 | checknext(ls, '='); | ||
1707 | expr(ls, &e); | ||
1708 | checkattrib(ls, attr, var); | ||
1709 | luaK_tonumeral(fs, &e, &var->val); | ||
1710 | luaK_exp2nextreg(fs, &e); | ||
1711 | adjustlocalvars(ls, 1); | ||
1712 | } | ||
1713 | |||
1714 | |||
1715 | static void localstat (LexState *ls) { | ||
1716 | /* stat -> LOCAL NAME {',' NAME} ['=' explist] | ||
1717 | | LOCAL *toclose NAME '=' exp */ | ||
1718 | if (testnext(ls, '<')) | ||
1719 | attriblocalstat(ls); | ||
1720 | else | ||
1721 | commonlocalstat(ls); | ||
1722 | } | ||
1723 | |||
1724 | |||
1725 | static int funcname (LexState *ls, expdesc *v) { | 1717 | static int funcname (LexState *ls, expdesc *v) { |
1726 | /* funcname -> NAME {fieldsel} [':' NAME] */ | 1718 | /* funcname -> NAME {fieldsel} [':' NAME] */ |
1727 | int ismethod = 0; | 1719 | int ismethod = 0; |
diff --git a/manual/manual.of b/manual/manual.of index e9416956..136e9022 100644 --- a/manual/manual.of +++ b/manual/manual.of | |||
@@ -1399,23 +1399,30 @@ they must all result in numbers. | |||
1399 | Their values are called respectively | 1399 | Their values are called respectively |
1400 | the @emph{initial value}, the @emph{limit}, and the @emph{step}. | 1400 | the @emph{initial value}, the @emph{limit}, and the @emph{step}. |
1401 | If the step is absent, it defaults @N{to 1}. | 1401 | If the step is absent, it defaults @N{to 1}. |
1402 | Then the loop body is repeated with the value of the control variable | 1402 | |
1403 | If both the initial value and the step are integers, | ||
1404 | the loop is done with integers; | ||
1405 | note that the limit may not be an integer. | ||
1406 | Otherwise, the loop is done with floats. | ||
1407 | (Beware of floating-point accuracy in this case.) | ||
1408 | |||
1409 | After that initialization, | ||
1410 | the loop body is repeated with the value of the control variable | ||
1403 | going through an arithmetic progression, | 1411 | going through an arithmetic progression, |
1404 | starting at the initial value, | 1412 | starting at the initial value, |
1405 | with a common difference given by the step, | 1413 | with a common difference given by the step. |
1406 | until that value passes the limit. | ||
1407 | A negative step makes a decreasing sequence; | 1414 | A negative step makes a decreasing sequence; |
1408 | a step equal to zero raises an error. | 1415 | a step equal to zero raises an error. |
1416 | The loop continues while the value is less than | ||
1417 | or equal to the limit | ||
1418 | (greater than or equal to for a negative step). | ||
1409 | If the initial value is already greater than the limit | 1419 | If the initial value is already greater than the limit |
1410 | (or less than, if the step is negative), | 1420 | (or less than, if the step is negative), |
1411 | the body is not executed. | 1421 | the body is not executed. |
1412 | 1422 | ||
1413 | If both the initial value and the step are integers, | 1423 | For integer loops, |
1414 | the loop is done with integers; | 1424 | the control variable never wraps around; |
1415 | in this case, the range of the control variable is clipped | 1425 | instead, the loop ends in case of an overflow. |
1416 | by the range of integers. | ||
1417 | Otherwise, the loop is done with floats. | ||
1418 | (Beware of floating-point accuracy in this case.) | ||
1419 | 1426 | ||
1420 | You should not change the value of the control variable | 1427 | You should not change the value of the control variable |
1421 | during the loop. | 1428 | during the loop. |
@@ -1490,22 +1497,25 @@ Function calls are explained in @See{functioncall}. | |||
1490 | @x{Local variables} can be declared anywhere inside a block. | 1497 | @x{Local variables} can be declared anywhere inside a block. |
1491 | The declaration can include an initialization: | 1498 | The declaration can include an initialization: |
1492 | @Produc{ | 1499 | @Produc{ |
1493 | @producname{stat}@producbody{@Rw{local} namelist @bnfopt{@bnfter{=} explist}} | 1500 | @producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}} |
1494 | @producname{stat}@producbody{ | 1501 | @producname{attnamelist}@producbody{ |
1495 | @Rw{local} @bnfter{<} Name @bnfter{>} Name @bnfter{=} exp | 1502 | attrib @bnfNter{Name} @bnfrep{@bnfter{,} attrib @bnfNter{Name}}} |
1496 | }} | 1503 | } |
1497 | If present, an initial assignment has the same semantics | 1504 | If present, an initial assignment has the same semantics |
1498 | of a multiple assignment @see{assignment}. | 1505 | of a multiple assignment @see{assignment}. |
1499 | Otherwise, all variables are initialized with @nil. | 1506 | Otherwise, all variables are initialized with @nil. |
1500 | The second syntax declares a local with a given attribute, | 1507 | |
1501 | which is the name between the angle brackets. | 1508 | Each variable name may be preceded by an attribute |
1502 | In this case, there must be an initialization. | 1509 | (a name between angle brackets): |
1510 | @Produc{ | ||
1511 | @producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}} | ||
1512 | } | ||
1503 | There are two possible attributes: | 1513 | There are two possible attributes: |
1504 | @id{const}, which declares a @x{constant variable}, | 1514 | @id{const}, which declares a @x{constant variable}, |
1505 | that is, a variable that cannot be assigned to | 1515 | that is, a variable that cannot be assigned to |
1506 | after its initialization; | 1516 | after its initialization; |
1507 | and @id{toclose}, which declares a to-be-closed variable @see{to-be-closed}. | 1517 | and @id{toclose}, which declares a to-be-closed variable @see{to-be-closed}. |
1508 | 1518 | A list of variables can contain at most one to-be-closed variable. | |
1509 | 1519 | ||
1510 | A chunk is also a block @see{chunks}, | 1520 | A chunk is also a block @see{chunks}, |
1511 | and so local variables can be declared in a chunk outside any explicit block. | 1521 | 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}. | |||
1516 | 1526 | ||
1517 | @sect3{to-be-closed| @title{To-be-closed Variables} | 1527 | @sect3{to-be-closed| @title{To-be-closed Variables} |
1518 | 1528 | ||
1519 | A local variable can be declared as a @def{to-be-closed} variable, | ||
1520 | using the identifier @id{toclose} as its attribute: | ||
1521 | @Produc{ | ||
1522 | @producname{stat}@producbody{ | ||
1523 | @Rw{local} @bnfter{<} @id{toclose} @bnfter{>} Name @bnfter{=} exp | ||
1524 | }} | ||
1525 | A to-be-closed variable behaves like a constant local variable, | 1529 | A to-be-closed variable behaves like a constant local variable, |
1526 | except that its value is @emph{closed} whenever the variable | 1530 | except that its value is @emph{closed} whenever the variable |
1527 | goes out of scope, including normal block termination, | 1531 | goes out of scope, including normal block termination, |
@@ -8215,7 +8219,7 @@ then @id{date} returns the date as a string, | |||
8215 | formatted according to the same rules as the @ANSI{strftime}. | 8219 | formatted according to the same rules as the @ANSI{strftime}. |
8216 | 8220 | ||
8217 | If @id{format} is absent, it defaults to @St{%c}, | 8221 | If @id{format} is absent, it defaults to @St{%c}, |
8218 | which gives a reasonable date and time representation | 8222 | which gives a human-readable date and time representation |
8219 | using the current locale. | 8223 | using the current locale. |
8220 | 8224 | ||
8221 | On non-POSIX systems, | 8225 | On non-POSIX systems, |
@@ -9022,10 +9026,14 @@ and @bnfNter{LiteralString}, see @See{lexical}.) | |||
9022 | @OrNL @Rw{for} namelist @Rw{in} explist @Rw{do} block @Rw{end} | 9026 | @OrNL @Rw{for} namelist @Rw{in} explist @Rw{do} block @Rw{end} |
9023 | @OrNL @Rw{function} funcname funcbody | 9027 | @OrNL @Rw{function} funcname funcbody |
9024 | @OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody | 9028 | @OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody |
9025 | @OrNL @Rw{local} namelist @bnfopt{@bnfter{=} explist} | 9029 | @OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist} |
9026 | @OrNL @Rw{local} @bnfter{<} Name @bnfter{>} Name @bnfter{=} exp | ||
9027 | } | 9030 | } |
9028 | 9031 | ||
9032 | @producname{attnamelist}@producbody{ | ||
9033 | attrib @bnfNter{Name} @bnfrep{@bnfter{,} attrib @bnfNter{Name}}} | ||
9034 | |||
9035 | @producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}} | ||
9036 | |||
9029 | @producname{retstat}@producbody{@Rw{return} | 9037 | @producname{retstat}@producbody{@Rw{return} |
9030 | @bnfopt{explist} @bnfopt{@bnfter{;}}} | 9038 | @bnfopt{explist} @bnfopt{@bnfter{;}}} |
9031 | 9039 | ||
diff --git a/testes/locals.lua b/testes/locals.lua index a41b6f0e..50230a27 100644 --- a/testes/locals.lua +++ b/testes/locals.lua | |||
@@ -173,12 +173,32 @@ end | |||
173 | assert(x==20) | 173 | assert(x==20) |
174 | 174 | ||
175 | 175 | ||
176 | do -- constants | ||
177 | local <const> a, b, <const> c = 10, 20, 30 | ||
178 | b = a + c + b -- 'b' is not constant | ||
179 | assert(a == 10 and b == 60 and c == 30) | ||
180 | local function checkro (code, name) | ||
181 | local st, msg = load(code) | ||
182 | local gab = string.format("attempt to assign to const variable '%s'", name) | ||
183 | assert(not st and string.find(msg, gab)) | ||
184 | end | ||
185 | checkro("local x, <const> y, z = 10, 20, 30; x = 11; y = 12", "y") | ||
186 | checkro("local <const> x, y, <const> z = 10, 20, 30; x = 11", "x") | ||
187 | checkro("local <const> x, y, <const> z = 10, 20, 30; y = 10; z = 11", "z") | ||
188 | end | ||
189 | |||
190 | |||
176 | print"testing to-be-closed variables" | 191 | print"testing to-be-closed variables" |
177 | 192 | ||
178 | local function stack(n) n = ((n == 0) or stack(n - 1)) end | 193 | local function stack(n) n = ((n == 0) or stack(n - 1)) end |
179 | 194 | ||
180 | local function func2close (f) | 195 | local function func2close (f, x, y) |
181 | return setmetatable({}, {__close = f}) | 196 | local obj = setmetatable({}, {__close = f}) |
197 | if x then | ||
198 | return x, obj, y | ||
199 | else | ||
200 | return obj | ||
201 | end | ||
182 | end | 202 | end |
183 | 203 | ||
184 | 204 | ||
@@ -187,10 +207,11 @@ do | |||
187 | do | 207 | do |
188 | local <toclose> x = setmetatable({"x"}, {__close = function (self) | 208 | local <toclose> x = setmetatable({"x"}, {__close = function (self) |
189 | a[#a + 1] = self[1] end}) | 209 | a[#a + 1] = self[1] end}) |
190 | local <toclose> y = func2close(function (self, err) | 210 | local w, <toclose> y, z = func2close(function (self, err) |
191 | assert(err == nil); a[#a + 1] = "y" | 211 | assert(err == nil); a[#a + 1] = "y" |
192 | end) | 212 | end, 10, 20) |
193 | a[#a + 1] = "in" | 213 | a[#a + 1] = "in" |
214 | assert(w == 10 and z == 20) | ||
194 | end | 215 | end |
195 | a[#a + 1] = "out" | 216 | a[#a + 1] = "out" |
196 | assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out") | 217 | assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out") |
@@ -199,7 +220,8 @@ end | |||
199 | do | 220 | do |
200 | local X = false | 221 | local X = false |
201 | 222 | ||
202 | local closescope = func2close(function () stack(10); X = true end) | 223 | local x, closescope = func2close(function () stack(10); X = true end, 100) |
224 | assert(x == 100); x = 101; -- 'x' is not read-only | ||
203 | 225 | ||
204 | -- closing functions do not corrupt returning values | 226 | -- closing functions do not corrupt returning values |
205 | local function foo (x) | 227 | local function foo (x) |