diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-10-31 14:54:45 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-10-31 14:54:45 -0300 |
commit | 947a372f5860a76fcafb4a2845abc322e440d6fc (patch) | |
tree | e35840847207c850af5262f93f863f583d2af76d | |
parent | e073cbc2e538369e0611abfc9948f301aea6aef3 (diff) | |
download | lua-947a372f5860a76fcafb4a2845abc322e440d6fc.tar.gz lua-947a372f5860a76fcafb4a2845abc322e440d6fc.tar.bz2 lua-947a372f5860a76fcafb4a2845abc322e440d6fc.zip |
State in generic 'for' acts as a to-be-closed variable
The implicit variable 'state' in a generic 'for' is marked as a
to-be-closed variable, so that the state will be closed as soon
as the loop ends, no matter how.
Taking advantage of this new facility, the call 'io.lines(filename)'
now returns the open file as a second result. Therefore,
an iteraction like 'for l in io.lines(name)...' will close the
file even when the loop ends with a break or an error.
-rw-r--r-- | liolib.c | 35 | ||||
-rw-r--r-- | lparser.c | 1 | ||||
-rw-r--r-- | lvm.c | 41 | ||||
-rw-r--r-- | testes/files.lua | 8 | ||||
-rw-r--r-- | testes/locals.lua | 50 |
5 files changed, 111 insertions, 24 deletions
@@ -354,12 +354,22 @@ static int io_readline (lua_State *L); | |||
354 | */ | 354 | */ |
355 | #define MAXARGLINE 250 | 355 | #define MAXARGLINE 250 |
356 | 356 | ||
357 | /* | ||
358 | ** Auxiliar function to create the iteration function for 'lines'. | ||
359 | ** The iteration function is a closure over 'io_readline', with | ||
360 | ** the following upvalues: | ||
361 | ** 1) The file being read (first value in the stack) | ||
362 | ** 2) the number of arguments to read | ||
363 | ** 3) a boolean, true iff file has to be closed when finished ('toclose') | ||
364 | ** *) a variable number of format arguments (rest of the stack) | ||
365 | */ | ||
357 | static void aux_lines (lua_State *L, int toclose) { | 366 | static void aux_lines (lua_State *L, int toclose) { |
358 | int n = lua_gettop(L) - 1; /* number of arguments to read */ | 367 | int n = lua_gettop(L) - 1; /* number of arguments to read */ |
359 | luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments"); | 368 | luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments"); |
369 | lua_pushvalue(L, 1); /* file */ | ||
360 | lua_pushinteger(L, n); /* number of arguments to read */ | 370 | lua_pushinteger(L, n); /* number of arguments to read */ |
361 | lua_pushboolean(L, toclose); /* close/not close file when finished */ | 371 | lua_pushboolean(L, toclose); /* close/not close file when finished */ |
362 | lua_rotate(L, 2, 2); /* move 'n' and 'toclose' to their positions */ | 372 | lua_rotate(L, 2, 3); /* move the three values to their positions */ |
363 | lua_pushcclosure(L, io_readline, 3 + n); | 373 | lua_pushcclosure(L, io_readline, 3 + n); |
364 | } | 374 | } |
365 | 375 | ||
@@ -371,6 +381,11 @@ static int f_lines (lua_State *L) { | |||
371 | } | 381 | } |
372 | 382 | ||
373 | 383 | ||
384 | /* | ||
385 | ** Return an iteration function for 'io.lines'. If file has to be | ||
386 | ** closed, also returns the file itself as a second result (to be | ||
387 | ** closed as the state at the exit of a generic for). | ||
388 | */ | ||
374 | static int io_lines (lua_State *L) { | 389 | static int io_lines (lua_State *L) { |
375 | int toclose; | 390 | int toclose; |
376 | if (lua_isnone(L, 1)) lua_pushnil(L); /* at least one argument */ | 391 | if (lua_isnone(L, 1)) lua_pushnil(L); /* at least one argument */ |
@@ -386,8 +401,13 @@ static int io_lines (lua_State *L) { | |||
386 | lua_replace(L, 1); /* put file at index 1 */ | 401 | lua_replace(L, 1); /* put file at index 1 */ |
387 | toclose = 1; /* close it after iteration */ | 402 | toclose = 1; /* close it after iteration */ |
388 | } | 403 | } |
389 | aux_lines(L, toclose); | 404 | aux_lines(L, toclose); /* push iteration function */ |
390 | return 1; | 405 | if (toclose) { |
406 | lua_pushvalue(L, 1); /* file will be second result */ | ||
407 | return 2; | ||
408 | } | ||
409 | else | ||
410 | return 1; | ||
391 | } | 411 | } |
392 | 412 | ||
393 | 413 | ||
@@ -453,7 +473,7 @@ static int readdigits (RN *rn, int hex) { | |||
453 | /* | 473 | /* |
454 | ** Read a number: first reads a valid prefix of a numeral into a buffer. | 474 | ** Read a number: first reads a valid prefix of a numeral into a buffer. |
455 | ** Then it calls 'lua_stringtonumber' to check whether the format is | 475 | ** Then it calls 'lua_stringtonumber' to check whether the format is |
456 | ** correct and to convert it to a Lua number | 476 | ** correct and to convert it to a Lua number. |
457 | */ | 477 | */ |
458 | static int read_number (lua_State *L, FILE *f) { | 478 | static int read_number (lua_State *L, FILE *f) { |
459 | RN rn; | 479 | RN rn; |
@@ -604,6 +624,9 @@ static int f_read (lua_State *L) { | |||
604 | } | 624 | } |
605 | 625 | ||
606 | 626 | ||
627 | /* | ||
628 | ** Iteration function for 'lines'. | ||
629 | */ | ||
607 | static int io_readline (lua_State *L) { | 630 | static int io_readline (lua_State *L) { |
608 | LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1)); | 631 | LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1)); |
609 | int i; | 632 | int i; |
@@ -624,8 +647,8 @@ static int io_readline (lua_State *L) { | |||
624 | return luaL_error(L, "%s", lua_tostring(L, -n + 1)); | 647 | return luaL_error(L, "%s", lua_tostring(L, -n + 1)); |
625 | } | 648 | } |
626 | if (lua_toboolean(L, lua_upvalueindex(3))) { /* generator created file? */ | 649 | if (lua_toboolean(L, lua_upvalueindex(3))) { /* generator created file? */ |
627 | lua_settop(L, 0); | 650 | lua_settop(L, 0); /* clear stack */ |
628 | lua_pushvalue(L, lua_upvalueindex(1)); | 651 | lua_pushvalue(L, lua_upvalueindex(1)); /* push file at index 1 */ |
629 | aux_close(L); /* close it */ | 652 | aux_close(L); /* close it */ |
630 | } | 653 | } |
631 | return 0; | 654 | return 0; |
@@ -1413,6 +1413,7 @@ static void forlist (LexState *ls, TString *indexname) { | |||
1413 | /* create control variables */ | 1413 | /* create control variables */ |
1414 | new_localvarliteral(ls, "(for generator)"); | 1414 | new_localvarliteral(ls, "(for generator)"); |
1415 | new_localvarliteral(ls, "(for state)"); | 1415 | new_localvarliteral(ls, "(for state)"); |
1416 | markupval(fs, fs->nactvar); /* state may create an upvalue */ | ||
1416 | new_localvarliteral(ls, "(for control)"); | 1417 | new_localvarliteral(ls, "(for control)"); |
1417 | /* create declared variables */ | 1418 | /* create declared variables */ |
1418 | new_localvar(ls, indexname); | 1419 | new_localvar(ls, indexname); |
@@ -866,7 +866,8 @@ void luaV_finishOp (lua_State *L) { | |||
866 | #define ProtectNT(exp) (savepc(L), (exp), updatetrap(ci)) | 866 | #define ProtectNT(exp) (savepc(L), (exp), updatetrap(ci)) |
867 | 867 | ||
868 | /* | 868 | /* |
869 | ** Protect code that will finish the loop (returns). | 869 | ** Protect code that will finish the loop (returns) or can only raise |
870 | ** errors. | ||
870 | */ | 871 | */ |
871 | #define halfProtect(exp) (savepc(L), (exp)) | 872 | #define halfProtect(exp) (savepc(L), (exp)) |
872 | 873 | ||
@@ -1457,7 +1458,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) { | |||
1457 | vmbreak; | 1458 | vmbreak; |
1458 | } | 1459 | } |
1459 | vmcase(OP_TBC) { | 1460 | vmcase(OP_TBC) { |
1460 | luaF_newtbcupval(L, ra); /* create new to-be-closed upvalue */ | 1461 | /* create new to-be-closed upvalue */ |
1462 | halfProtect(luaF_newtbcupval(L, ra)); | ||
1461 | vmbreak; | 1463 | vmbreak; |
1462 | } | 1464 | } |
1463 | vmcase(OP_JMP) { | 1465 | vmcase(OP_JMP) { |
@@ -1745,21 +1747,34 @@ void luaV_execute (lua_State *L, CallInfo *ci) { | |||
1745 | vmbreak; | 1747 | vmbreak; |
1746 | } | 1748 | } |
1747 | vmcase(OP_TFORPREP) { | 1749 | vmcase(OP_TFORPREP) { |
1750 | /* is 'state' a function or has a '__close' metamethod? */ | ||
1751 | if (ttisfunction(s2v(ra + 1)) || | ||
1752 | !ttisnil(luaT_gettmbyobj(L, s2v(ra + 1), TM_CLOSE))) { | ||
1753 | /* create to-be-closed upvalue for it */ | ||
1754 | halfProtect(luaF_newtbcupval(L, ra + 1)); | ||
1755 | } | ||
1748 | pc += GETARG_Bx(i); | 1756 | pc += GETARG_Bx(i); |
1749 | vmbreak; | 1757 | i = *(pc++); /* go to next instruction */ |
1758 | lua_assert(GET_OPCODE(i) == OP_TFORCALL && ra == RA(i)); | ||
1759 | goto l_tforcall; | ||
1750 | } | 1760 | } |
1751 | vmcase(OP_TFORCALL) { | 1761 | vmcase(OP_TFORCALL) { |
1752 | StkId cb = ra + 3; /* call base */ | 1762 | l_tforcall: |
1753 | setobjs2s(L, cb+2, ra+2); | 1763 | /* 'ra' has the iterator function, 'ra + 1' has the state, |
1754 | setobjs2s(L, cb+1, ra+1); | 1764 | and 'ra + 2' has the control variable. The call will use |
1755 | setobjs2s(L, cb, ra); | 1765 | the stack after these values (starting at 'ra + 3') |
1756 | L->top = cb + 3; /* func. + 2 args (state and index) */ | 1766 | */ |
1757 | Protect(luaD_call(L, cb, GETARG_C(i))); | 1767 | /* push function, state, and control variable */ |
1758 | if (trap) /* keep 'base' correct for next instruction */ | 1768 | memcpy(ra + 3, ra, 3 * sizeof(*ra)); |
1759 | updatebase(ci); | 1769 | L->top = ra + 6; |
1770 | Protect(luaD_call(L, ra + 3, GETARG_C(i))); /* do the call */ | ||
1771 | if (trap) { /* stack may have changed? */ | ||
1772 | updatebase(ci); /* keep 'base' correct */ | ||
1773 | ra = RA(i); /* keep 'ra' correct for next instruction */ | ||
1774 | } | ||
1760 | i = *(pc++); /* go to next instruction */ | 1775 | i = *(pc++); /* go to next instruction */ |
1761 | ra = RA(i); /* get its 'ra' */ | 1776 | ra += 2; /* adjust for next instruction */ |
1762 | lua_assert(GET_OPCODE(i) == OP_TFORLOOP); | 1777 | lua_assert(GET_OPCODE(i) == OP_TFORLOOP && ra == RA(i)); |
1763 | goto l_tforloop; | 1778 | goto l_tforloop; |
1764 | } | 1779 | } |
1765 | vmcase(OP_TFORLOOP) { | 1780 | vmcase(OP_TFORLOOP) { |
diff --git a/testes/files.lua b/testes/files.lua index 9aae5913..a11c5e61 100644 --- a/testes/files.lua +++ b/testes/files.lua | |||
@@ -462,13 +462,13 @@ X | |||
462 | - y; | 462 | - y; |
463 | ]]:close() | 463 | ]]:close() |
464 | _G.X = 1 | 464 | _G.X = 1 |
465 | assert(not load(io.lines(file))) | 465 | assert(not load((io.lines(file)))) |
466 | collectgarbage() -- to close file in previous iteration | 466 | collectgarbage() -- to close file in previous iteration |
467 | load(io.lines(file, "L"))() | 467 | load((io.lines(file, "L")))() |
468 | assert(_G.X == 2) | 468 | assert(_G.X == 2) |
469 | load(io.lines(file, 1))() | 469 | load((io.lines(file, 1)))() |
470 | assert(_G.X == 4) | 470 | assert(_G.X == 4) |
471 | load(io.lines(file, 3))() | 471 | load((io.lines(file, 3)))() |
472 | assert(_G.X == 8) | 472 | assert(_G.X == 8) |
473 | 473 | ||
474 | print('+') | 474 | print('+') |
diff --git a/testes/locals.lua b/testes/locals.lua index 65b145db..1e0f525b 100644 --- a/testes/locals.lua +++ b/testes/locals.lua | |||
@@ -108,7 +108,7 @@ print'+' | |||
108 | if rawget(_G, "T") then | 108 | if rawget(_G, "T") then |
109 | -- testing clearing of dead elements from tables | 109 | -- testing clearing of dead elements from tables |
110 | collectgarbage("stop") -- stop GC | 110 | collectgarbage("stop") -- stop GC |
111 | local a = {[{}] = 4, [3] = 0, alo = 1, | 111 | local a = {[{}] = 4, [3] = 0, alo = 1, |
112 | a1234567890123456789012345678901234567890 = 10} | 112 | a1234567890123456789012345678901234567890 = 10} |
113 | 113 | ||
114 | local t = T.querytab(a) | 114 | local t = T.querytab(a) |
@@ -360,6 +360,54 @@ end) | |||
360 | co() -- start coroutine | 360 | co() -- start coroutine |
361 | assert(co == nil) -- eventually it will be collected | 361 | assert(co == nil) -- eventually it will be collected |
362 | 362 | ||
363 | |||
364 | -- to-be-closed variables in generic for loops | ||
365 | do | ||
366 | local numopen = 0 | ||
367 | local function open (x) | ||
368 | numopen = numopen + 1 | ||
369 | return | ||
370 | function () -- iteraction function | ||
371 | x = x - 1 | ||
372 | if x > 0 then return x end | ||
373 | end, | ||
374 | function () -- closing function | ||
375 | numopen = numopen - 1 | ||
376 | end | ||
377 | end | ||
378 | |||
379 | local s = 0 | ||
380 | for i in open(10) do | ||
381 | s = s + i | ||
382 | end | ||
383 | assert(s == 45 and numopen == 0) | ||
384 | |||
385 | local s = 0 | ||
386 | for i in open(10) do | ||
387 | if i < 5 then break end | ||
388 | s = s + i | ||
389 | end | ||
390 | assert(s == 35 and numopen == 0) | ||
391 | |||
392 | -- repeat test with '__open' metamethod instead of a function | ||
393 | local function open (x) | ||
394 | numopen = numopen + 1 | ||
395 | return | ||
396 | function (t) -- iteraction function | ||
397 | t[1] = t[1] - 1 | ||
398 | if t[1] > 0 then return t[1] end | ||
399 | end, | ||
400 | setmetatable({x}, {__close = function () numopen = numopen - 1 end}) | ||
401 | end | ||
402 | |||
403 | local s = 0 | ||
404 | for i in open(10) do | ||
405 | if (i < 5) then break end | ||
406 | s = s + i | ||
407 | end | ||
408 | assert(s == 35 and numopen == 0) | ||
409 | end | ||
410 | |||
363 | print('OK') | 411 | print('OK') |
364 | 412 | ||
365 | return 5,f | 413 | return 5,f |