Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterate through Lua Table

Tags:

c

lua

lua-table

I am trying to iterate through a lua table but I keep getting this error:

invalid key to 'next'

I know that index starts off as -8 and I know that there is a table there because it gets the first (and only) value in it. However, it tries to loop round again even though I know there is only one string in the table.

if (lua_istable(L, index))
{
    lua_pushnil(L);

    // This is needed for it to even get the first value
    index--;

    while (lua_next(L, index) != 0)
    {
        const char *item = luaL_checkstring(L, -1);
        lua_pop(L, 1);

        printf("%s\n", item);
    }
}
else
{
    luaL_typerror(L, index, "string table");
}

Any help would be appreciated.

This works fine when I use a positive index (as long as I don't remove 1 from it)

Edit: I've noticed that I don't get this error if I leave the value of item alone. Only when I start reading the value of item do I get this error. When I've got the value from the table, I call another Lua function, could this be disrupting lua_next?

like image 664
Tom Leese Avatar asked May 26 '11 11:05

Tom Leese


People also ask

How do I iterate a table in Lua?

For tables using numeric keys, Lua provides an ipairs function. The ipairs function will always iterate from table[1] , table[2] , etc. until the first nil value is found. A final way to iterate over tables is to use the next selector in a generic for loop.

What is an iterator in Lua?

An iterator is any construction that allows you to iterate over the elements of a collection. In Lua, we typically represent iterators by functions: Each time we call that function, it returns a "next" element from the collection.

How do you find the length of a table in Lua?

1) Remember in Lua we do not have any function or we can say direct function to get the size or length of the table directly. 2) we need to write the code manually to get the length of the table in Lua. 3) For this we can use pair() which will iterator the table object and give us the desired result.

What is unpack Lua?

In Lua, if you want to call a variable function f with variable arguments in an array a , you simply write f(unpack(a)) The call to unpack returns all values in a , which become the arguments to f . For instance, if we execute f = string.find a = {"hello", "ll"}


4 Answers

There are 2 things you need to watch:

  • ensure the original key is left on the stack before the next call to lua_next. luaL_checkstring will convert non-string keys to strings (as the resulting string is not in the table, it becomes an invalid key.) This is most easily done by passing luaL_checkstring a copy of the key instead of the original.
  • ensure you preserve the stack structure (i.e. pop as many values as you push) on each pass through the loop

Your function will only work for negative values of index. You are correct that index--; will ensure that index still points to the table after pushing the key, but only if index was negative (i.e. relative to the top of the stack.) If index is an absolute or pseudo index then it this will cause it to point to the wrong item. The easiest workaround is to push another reference to the table onto the top of the stack.

Here's a minimal C program to demonstrate:

#include <lauxlib.h>
#include <lua.h>

static void iterate_and_print(lua_State *L, int index);

int main(int ac, char **av)
{
   lua_State *L = luaL_newstate();
   luaL_openlibs(L);

   // Create a table and put it on the top of the stack
   luaL_loadstring(L, "return {one=1,[2]='two',three=3}");
   lua_call(L, 0, 1);

   iterate_and_print(L, -1);
   return 0;
}

static void iterate_and_print(lua_State *L, int index)
{
    // Push another reference to the table on top of the stack (so we know
    // where it is, and this function can work for negative, positive and
    // pseudo indices
    lua_pushvalue(L, index);
    // stack now contains: -1 => table
    lua_pushnil(L);
    // stack now contains: -1 => nil; -2 => table
    while (lua_next(L, -2))
    {
        // stack now contains: -1 => value; -2 => key; -3 => table
        // copy the key so that lua_tostring does not modify the original
        lua_pushvalue(L, -2);
        // stack now contains: -1 => key; -2 => value; -3 => key; -4 => table
        const char *key = lua_tostring(L, -1);
        const char *value = lua_tostring(L, -2);
        printf("%s => %s\n", key, value);
        // pop value + copy of key, leaving original key
        lua_pop(L, 2);
        // stack now contains: -1 => key; -2 => table
    }
    // stack now contains: -1 => table (when lua_next returns 0 it pops the key
    // but does not push anything.)
    // Pop table
    lua_pop(L, 1);
    // Stack is now the same as it was on entry to this function
}
like image 157
finnw Avatar answered Sep 24 '22 13:09

finnw


Do not use luaL_checkstring with negative arguments. Use lua_tostring instead.

Also, make sure the stack remains the same after you call a function in the loop: lua_next expects the previous table key at the top of the stack so that it can resume the traversal.

like image 32
lhf Avatar answered Sep 21 '22 13:09

lhf


From the manual:

const char *lua_tolstring (lua_State *L, int index, size_t *len);

Converts the Lua value at the given acceptable index to a C string. If len is not NULL, it also sets *len with the string length. The Lua value must be a string or a number; otherwise, the function returns NULL. If the value is a number, then lua_tolstring also changes the actual value in the stack to a string. (This change confuses lua_next when lua_tolstring is applied to keys during a table traversal.)

luaL_checkstring calls lua_tolstring.

like image 40
BMitch Avatar answered Sep 25 '22 13:09

BMitch


See also the example from the docs for lua_next, excerpted here:

int lua_next (lua_State *L, int index);

Pops a key from the stack, and pushes a key–value pair from the table at the given index (the "next" pair after the given key). If there are no more elements in the table, then lua_next returns 0 (and pushes nothing).

A typical traversal looks like this:

/* table is in the stack at index 't' */
 lua_pushnil(L);  /* first key */
 while (lua_next(L, t) != 0) {
   /* uses 'key' (at index -2) and 'value' (at index -1) */
   printf("%s - %s\n",
          lua_typename(L, lua_type(L, -2)),
          lua_typename(L, lua_type(L, -1)));
   /* removes 'value'; keeps 'key' for next iteration */
   lua_pop(L, 1);
 }

While traversing a table, do not call lua_tolstring directly on a key, unless you know that the key is actually a string. Recall that lua_tolstring may change the value at the given index; this confuses the next call to lua_next.

See function next for the caveats of modifying the table during its traversal.

like image 30
Wolfram Arnold Avatar answered Sep 23 '22 13:09

Wolfram Arnold