【发布时间】:2021-11-20 17:30:37
【问题描述】:
Lua 5.2 引入了__pairs(当然还有__ipairs)元方法。但是,lua_next() 似乎并不支持它们,我认为这是有道理的。
是否有一种“正确”的方法可以使用内置的 C 函数在任何一种情况下(使用或不使用 __[i]pairs 元方法)很好地遍历表的键?
专门要求 5.4,但返回 5.2 的解决方案当然也很好。
【问题讨论】:
Lua 5.2 引入了__pairs(当然还有__ipairs)元方法。但是,lua_next() 似乎并不支持它们,我认为这是有道理的。
是否有一种“正确”的方法可以使用内置的 C 函数在任何一种情况下(使用或不使用 __[i]pairs 元方法)很好地遍历表的键?
专门要求 5.4,但返回 5.2 的解决方案当然也很好。
【问题讨论】:
据我所知,在撰写本文时,还没有真正“优雅”的方式来解决这个问题。
我能想到的最好办法是创建两个原型相同的闭包并直接与它们交互。这个答案主要关注__pairs,但我相信它可以很容易地适应__ipairs。
首先是next() 迭代器,它适用于裸表(没有__pairs 元方法)。它采用单个上值 - 目标表 - 并将调用转发到 lua_next()。
static int next_iterator(lua_State *L) {
/* -1, +(2|0) */
/*
requires upvalues:
1: the table on which to call lua_next()
*/
return lua_next(L, lua_upvalueindex(1))
? 2
: 0;
}
然后我们有 __pairs() 迭代器,它将表作为上值 1,将调用 __pairs() 的结果作为上值 2,并使用表和当前调用的第一个参数(键)调用迭代器。
static int pair_iterator(lua_State *L) {
/* -1, +2 */
/*
requires upvalues:
1: the table on which to call the iterator
2: the iterator function (usually result
of a call to __pairs() metamethod)
*/
lua_pushvalue(L, lua_upvalueindex(1));
lua_pushvalue(L, -2);
lua_copy(L, lua_upvalueindex(2), -3);
lua_call(L, 2, 2);
return 2;
}
最后是迭代器函数。这意味着直接调用,不是作为lua_call()等人的参数。
它弹出迭代器函数(概念上,实际上不是)和键,并将迭代器和键/值压入堆栈。如果迭代已经结束,则两者都不会被压入堆栈 - 只是迭代器。
我强调说“pops . . . then pushes the iterator”作为文档的一个要点 - 像对待
lua_next()一样对待这个函数。这也意味着您需要lua_pushnil(L)进行第一次迭代,就像使用lua_next()一样。
static int iterator_next(lua_State *L) {
/* -2, +(1|3), r */
lua_pushvalue(L, -1);
lua_copy(L, -3, -2);
lua_call(L, 1, 2);
if (lua_isnil(L, -2)) {
lua_pop(L, 2);
return 0;
}
return 1;
}
最后,我们可以利用一些 C 巫术来为迭代创建一个基本干净的设置。这设置了 upvalues 和典型的 lua_next()-like 迭代,初始“key”为 nil。
/*
This assumes -1 has our table value
NOTE: This will also remove the table itself,
since we specify non-zero upvalue counts
in the call to `lua_pushcclosure()`.
This effectively replaces the table with
a valid iterator so that lua_pop() cleanly
cleans up the remaining artifacts after
iteration.
If you don't want to lose the table,
make sure to lua_pushvalue(L, -1) before
this switch statement.
*/
switch (luaL_getmetafield(L, -1, "__pairs")) {
default:
/* unsupported type; fall back to default */
lua_pop(L, 1);
/* fallthrough */
case LUA_TNIL:
/* nothing was pushed; no need to pop first. */
lua_pushcclosure(L, &next_iterator, 1);
break;
case LUA_TFUNCTION:
/* call the __pairs() metamethod and get a function back */
lua_pushvalue(L, -2);
lua_call(L, 1, 1);
/* now pass the pairs function iterator closure */
lua_pushcclosure(L, &pair_iterator, 2);
break;
}
/* Push `nil` as our first "key" */
lua_pushnil(L);
/* Iterate! */
while (iterator_next(L)) {
/*
-3 has our iterator function
-2 has the next key
-1 has the next value
*/
/* ... do something ... */
(void)0;
/*
Pop the value so that
the next call to iterator_next()
uses the current iteration's key.
*/
lua_pop(L, 1);
}
/* Finally, pop off the iterator. */
lua_pop(L, 1);
【讨论】: