【问题标题】:What is the canonical way to iterate a table with a __pairs metamethod from C?用 C 中的 __pairs 元方法迭代表的规范方法是什么?
【发布时间】:2021-11-20 17:30:37
【问题描述】:

Lua 5.2 引入了__pairs(当然还有__ipairs)元方法。但是,lua_next() 似乎并不支持它们,我认为这是有道理的。

是否有一种“正确”的方法可以使用内置的 C 函数在任何一种情况下(使用或不使用 __[i]pairs 元方法)很好地遍历表的键?

专门要求 5.4,但返回 5.2 的解决方案当然也很好。

【问题讨论】:

    标签: c lua


    【解决方案1】:

    据我所知,在撰写本文时,还没有真正“优雅”的方式来解决这个问题。

    我能想到的最好办法是创建两个原型相同的闭包并直接与它们交互。这个答案主要关注__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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-24
      • 1970-01-01
      • 1970-01-01
      • 2015-09-11
      相关资源
      最近更新 更多