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.
This commit is contained in:
Roberto Ierusalimschy
2018-10-31 14:54:45 -03:00
parent e073cbc2e5
commit 947a372f58
5 changed files with 111 additions and 24 deletions

View File

@@ -354,12 +354,22 @@ static int io_readline (lua_State *L);
*/ */
#define MAXARGLINE 250 #define MAXARGLINE 250
/*
** Auxiliar function to create the iteration function for 'lines'.
** The iteration function is a closure over 'io_readline', with
** the following upvalues:
** 1) The file being read (first value in the stack)
** 2) the number of arguments to read
** 3) a boolean, true iff file has to be closed when finished ('toclose')
** *) a variable number of format arguments (rest of the stack)
*/
static void aux_lines (lua_State *L, int toclose) { static void aux_lines (lua_State *L, int toclose) {
int n = lua_gettop(L) - 1; /* number of arguments to read */ int n = lua_gettop(L) - 1; /* number of arguments to read */
luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments"); luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments");
lua_pushvalue(L, 1); /* file */
lua_pushinteger(L, n); /* number of arguments to read */ lua_pushinteger(L, n); /* number of arguments to read */
lua_pushboolean(L, toclose); /* close/not close file when finished */ lua_pushboolean(L, toclose); /* close/not close file when finished */
lua_rotate(L, 2, 2); /* move 'n' and 'toclose' to their positions */ lua_rotate(L, 2, 3); /* move the three values to their positions */
lua_pushcclosure(L, io_readline, 3 + n); lua_pushcclosure(L, io_readline, 3 + n);
} }
@@ -371,6 +381,11 @@ static int f_lines (lua_State *L) {
} }
/*
** Return an iteration function for 'io.lines'. If file has to be
** closed, also returns the file itself as a second result (to be
** closed as the state at the exit of a generic for).
*/
static int io_lines (lua_State *L) { static int io_lines (lua_State *L) {
int toclose; int toclose;
if (lua_isnone(L, 1)) lua_pushnil(L); /* at least one argument */ if (lua_isnone(L, 1)) lua_pushnil(L); /* at least one argument */
@@ -386,8 +401,13 @@ static int io_lines (lua_State *L) {
lua_replace(L, 1); /* put file at index 1 */ lua_replace(L, 1); /* put file at index 1 */
toclose = 1; /* close it after iteration */ toclose = 1; /* close it after iteration */
} }
aux_lines(L, toclose); aux_lines(L, toclose); /* push iteration function */
return 1; if (toclose) {
lua_pushvalue(L, 1); /* file will be second result */
return 2;
}
else
return 1;
} }
@@ -453,7 +473,7 @@ static int readdigits (RN *rn, int hex) {
/* /*
** Read a number: first reads a valid prefix of a numeral into a buffer. ** Read a number: first reads a valid prefix of a numeral into a buffer.
** Then it calls 'lua_stringtonumber' to check whether the format is ** Then it calls 'lua_stringtonumber' to check whether the format is
** correct and to convert it to a Lua number ** correct and to convert it to a Lua number.
*/ */
static int read_number (lua_State *L, FILE *f) { static int read_number (lua_State *L, FILE *f) {
RN rn; RN rn;
@@ -604,6 +624,9 @@ static int f_read (lua_State *L) {
} }
/*
** Iteration function for 'lines'.
*/
static int io_readline (lua_State *L) { static int io_readline (lua_State *L) {
LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1)); LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1));
int i; int i;
@@ -624,8 +647,8 @@ static int io_readline (lua_State *L) {
return luaL_error(L, "%s", lua_tostring(L, -n + 1)); return luaL_error(L, "%s", lua_tostring(L, -n + 1));
} }
if (lua_toboolean(L, lua_upvalueindex(3))) { /* generator created file? */ if (lua_toboolean(L, lua_upvalueindex(3))) { /* generator created file? */
lua_settop(L, 0); lua_settop(L, 0); /* clear stack */
lua_pushvalue(L, lua_upvalueindex(1)); lua_pushvalue(L, lua_upvalueindex(1)); /* push file at index 1 */
aux_close(L); /* close it */ aux_close(L); /* close it */
} }
return 0; return 0;

View File

@@ -1413,6 +1413,7 @@ static void forlist (LexState *ls, TString *indexname) {
/* create control variables */ /* create control variables */
new_localvarliteral(ls, "(for generator)"); new_localvarliteral(ls, "(for generator)");
new_localvarliteral(ls, "(for state)"); new_localvarliteral(ls, "(for state)");
markupval(fs, fs->nactvar); /* state may create an upvalue */
new_localvarliteral(ls, "(for control)"); new_localvarliteral(ls, "(for control)");
/* create declared variables */ /* create declared variables */
new_localvar(ls, indexname); new_localvar(ls, indexname);

41
lvm.c
View File

@@ -866,7 +866,8 @@ void luaV_finishOp (lua_State *L) {
#define ProtectNT(exp) (savepc(L), (exp), updatetrap(ci)) #define ProtectNT(exp) (savepc(L), (exp), updatetrap(ci))
/* /*
** Protect code that will finish the loop (returns). ** Protect code that will finish the loop (returns) or can only raise
** errors.
*/ */
#define halfProtect(exp) (savepc(L), (exp)) #define halfProtect(exp) (savepc(L), (exp))
@@ -1457,7 +1458,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak; vmbreak;
} }
vmcase(OP_TBC) { vmcase(OP_TBC) {
luaF_newtbcupval(L, ra); /* create new to-be-closed upvalue */ /* create new to-be-closed upvalue */
halfProtect(luaF_newtbcupval(L, ra));
vmbreak; vmbreak;
} }
vmcase(OP_JMP) { vmcase(OP_JMP) {
@@ -1745,21 +1747,34 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmbreak; vmbreak;
} }
vmcase(OP_TFORPREP) { vmcase(OP_TFORPREP) {
/* is 'state' a function or has a '__close' metamethod? */
if (ttisfunction(s2v(ra + 1)) ||
!ttisnil(luaT_gettmbyobj(L, s2v(ra + 1), TM_CLOSE))) {
/* create to-be-closed upvalue for it */
halfProtect(luaF_newtbcupval(L, ra + 1));
}
pc += GETARG_Bx(i); pc += GETARG_Bx(i);
vmbreak; i = *(pc++); /* go to next instruction */
lua_assert(GET_OPCODE(i) == OP_TFORCALL && ra == RA(i));
goto l_tforcall;
} }
vmcase(OP_TFORCALL) { vmcase(OP_TFORCALL) {
StkId cb = ra + 3; /* call base */ l_tforcall:
setobjs2s(L, cb+2, ra+2); /* 'ra' has the iterator function, 'ra + 1' has the state,
setobjs2s(L, cb+1, ra+1); and 'ra + 2' has the control variable. The call will use
setobjs2s(L, cb, ra); the stack after these values (starting at 'ra + 3')
L->top = cb + 3; /* func. + 2 args (state and index) */ */
Protect(luaD_call(L, cb, GETARG_C(i))); /* push function, state, and control variable */
if (trap) /* keep 'base' correct for next instruction */ memcpy(ra + 3, ra, 3 * sizeof(*ra));
updatebase(ci); L->top = ra + 6;
Protect(luaD_call(L, ra + 3, GETARG_C(i))); /* do the call */
if (trap) { /* stack may have changed? */
updatebase(ci); /* keep 'base' correct */
ra = RA(i); /* keep 'ra' correct for next instruction */
}
i = *(pc++); /* go to next instruction */ i = *(pc++); /* go to next instruction */
ra = RA(i); /* get its 'ra' */ ra += 2; /* adjust for next instruction */
lua_assert(GET_OPCODE(i) == OP_TFORLOOP); lua_assert(GET_OPCODE(i) == OP_TFORLOOP && ra == RA(i));
goto l_tforloop; goto l_tforloop;
} }
vmcase(OP_TFORLOOP) { vmcase(OP_TFORLOOP) {

View File

@@ -462,13 +462,13 @@ X
- y; - y;
]]:close() ]]:close()
_G.X = 1 _G.X = 1
assert(not load(io.lines(file))) assert(not load((io.lines(file))))
collectgarbage() -- to close file in previous iteration collectgarbage() -- to close file in previous iteration
load(io.lines(file, "L"))() load((io.lines(file, "L")))()
assert(_G.X == 2) assert(_G.X == 2)
load(io.lines(file, 1))() load((io.lines(file, 1)))()
assert(_G.X == 4) assert(_G.X == 4)
load(io.lines(file, 3))() load((io.lines(file, 3)))()
assert(_G.X == 8) assert(_G.X == 8)
print('+') print('+')

View File

@@ -108,7 +108,7 @@ print'+'
if rawget(_G, "T") then if rawget(_G, "T") then
-- testing clearing of dead elements from tables -- testing clearing of dead elements from tables
collectgarbage("stop") -- stop GC collectgarbage("stop") -- stop GC
local a = {[{}] = 4, [3] = 0, alo = 1, local a = {[{}] = 4, [3] = 0, alo = 1,
a1234567890123456789012345678901234567890 = 10} a1234567890123456789012345678901234567890 = 10}
local t = T.querytab(a) local t = T.querytab(a)
@@ -360,6 +360,54 @@ end)
co() -- start coroutine co() -- start coroutine
assert(co == nil) -- eventually it will be collected assert(co == nil) -- eventually it will be collected
-- to-be-closed variables in generic for loops
do
local numopen = 0
local function open (x)
numopen = numopen + 1
return
function () -- iteraction function
x = x - 1
if x > 0 then return x end
end,
function () -- closing function
numopen = numopen - 1
end
end
local s = 0
for i in open(10) do
s = s + i
end
assert(s == 45 and numopen == 0)
local s = 0
for i in open(10) do
if i < 5 then break end
s = s + i
end
assert(s == 35 and numopen == 0)
-- repeat test with '__open' metamethod instead of a function
local function open (x)
numopen = numopen + 1
return
function (t) -- iteraction function
t[1] = t[1] - 1
if t[1] > 0 then return t[1] end
end,
setmetatable({x}, {__close = function () numopen = numopen - 1 end})
end
local s = 0
for i in open(10) do
if (i < 5) then break end
s = s + i
end
assert(s == 35 and numopen == 0)
end
print('OK') print('OK')
return 5,f return 5,f