22 ноября 2010

Iterating over lua-table items in C

Warning! The code above does not check for recursive nesting.

We have table "tb" in our lua-script.
Let's create a function to print this table in C. "printTable" for example.
tb = {'a', 'b', {{'one', 'two'}, 'aa', 'bb', 'cc'}, 'd'}
printTable(tb)

Here we go.
#include <lua.h>
#include <stdio.h>

#define TRACE(format, ...) \
fprintf(stderr, "%s:%d " format "\n", __FILE__, __LINE__, ## __VA_ARGS__)

static int print_table__(lua_State* ls, const int tableIndex)
{
char buf[100] = {};
int i = 0;
for (; i < tableIndex; ++i) buf[i] = ' ';

lua_pushnil(ls);
while (lua_next(ls, tableIndex) != 0) {
if (lua_isnumber(ls, tableIndex + 2)) {
TRACE("%s%s - %d", buf,
lua_typename(ls, lua_type(ls, tableIndex + 2)),
(int)lua_tonumber(ls, tableIndex + 2));
} else if (lua_isstring(ls, tableIndex + 2)) {
TRACE("%s%s - %s", buf,
lua_typename(ls, lua_type(ls, tableIndex + 2)),
lua_tostring(ls, tableIndex + 2));
} else if (lua_istable(ls, tableIndex + 2)) {
TRACE("%stable", buf);
print_table__(ls, tableIndex + 2);
}
lua_pop(ls, 1);
}
return 0;
}

static int print_table(lua_State* ls)
{
return print_table__(ls, 1);
}

int main(int ac, char* av[])
{
lua_State* ls = luaL_newstate(); // init Lua-interpreter

lua_register(ls, "printTable", print_table); // register "printTable" function
luaL_loadfile(ls, "script.lua"); // load script.lua
lua_pcall(ls, 0, LUA_MULTRET, 0); // execute script

lua_close(ls);
return 0;
}

What we have here?
The Lua interpreter creates special separate stack for any function. When we enter the "print_table" function this stack contains only one element - table "tb".
We call inner "print_table__" function with one argument - table position on the stack. Stack index starts with 1 and then is incremented by 1 for every element pushed onto stack.
Inside "print_stack__" function we compute buf for nice alignment and then execute "lua_pushnil". It pushes "nil" value onto stack so we have two elements there:
2 - nil
1 - tb

Then we call "lua_next". This function considers max stack value as key ("nil" in our case). It pops key from the stack and pushes key-value pair. The stack looks like:
3 - "1"
2 - key
1 - tb

Then we check and print value. If the value is table we execute recursive call of "print_table__" with its index. At the end of iteration we call "lua_pop" and pop 1 last element from the stack. The stack again:
2 - key
1 - table

Calling "lua_next" we iterate over all keys in table.