Search code examples
lua-5.1lua-c++-connection

How to create table in table in Lua 5.1 using C-API?


I need to create construction like this in Lua 5.1 C-API, not in Lua 5.2 and above

a = {["b"] = {["c"] = {["d"] = {["e"] = "GOOD"}}}}

print(a.b.c.d.e);

Expected Result: GOOD

Thanks for answers!


Solution

  • The Lua C API is stack based. That means that the behavior of most of the C API functions depends not only on the arguments given, but also on the contents of the Lua stack (which is part of the lua_State* variable commonly called L). What a particular API function expects in terms of stack contents and how it affects the elements of the stack, you'll have to look up in the Lua manual.

    Let's start with creating a table and assigning it to a global variable a:

    lua_newtable( L );
    lua_setglobal( L, "a" );
    

    This is equivalent to the Lua code snippet: a = {}.

    The documentation for lua_newtable() tells us that the function pushes the new table to the top of the Lua stack. That fits nicely with lua_setglobal(), which pops a value from the stack top and assigns it to the global variable of the given name. So both functions combined (as in the snippet above) are balanced in terms of stack effects. The nice thing about stack-balanced pieces of code is that you can insert them anywhere, and the combined code is still valid. (The general rule is: You can replace a single statement with a series of statements, and vice versa, as long as the (combined) stack effects are the same.) E.g.:

    lua_newtable( L );  /* ==> stack: ..., {} */
    lua_pushnil( L ); /* ==> stack: ..., {}, nil */
    lua_pop( L, 1 ); /* ==> stack: ..., {} */
    lua_setglobal( L, "a" ); /* ==> stack: ... */
    

    will still assign a table to the global variable a because lua_pushnil( L ); and lua_pop( L, 1 ); combined don't change the Lua stack contents. Instead of this useless pushing/popping we will add code that modifies the table after it is pushed onto the stack, and before it is removed from the stack and assigned to the global variable. As I said, you can insert stack-balanced code pieces anywhere between two Lua C API functions, so you just have to identify the right spot where the stack contains all the elements you'll need.

    We want to add a field to the table with the key "b" and another table as the value. The Lua C API function to do that is lua_settable() (there are other convenience functions that work for certain key types only, e.g. lua_setfield(), but we'll go with lua_settable() here). lua_settable() needs the table to store the key/value-pair in somewhere on the Lua stack (which usually means that you'll have to pass a stack index as an argument), and the key and the value as the two top-most elements on the stack. Both key and value (but not the table) will be popped by lua_settable():

    lua_newtable( L );  /* ==> stack: ..., {} */
    lua_pushliteral( L, "b" ); /* ==> stack: ..., {}, "b" */
    lua_newtable( L ); /* ==> stack: ..., {}, "b", {} */
    lua_settable( L, -3 ); /* ==> stack: ..., {} */
    lua_setglobal( L, "a" ); /* ==> stack: ... */
    

    The equivalent Lua code would be a = { b = {} }

    Often you don't really care about what is below a certain point of the Lua stack, and that's where indices relative to the stack top (the -3 in the code snippet above) come into play. The -3 refers to the table that is just below the key "b" (which is at -2) and below the other table (which is at the stack top at -1).

    You'll probably already see where this is going: Now we want to modify the new table, so we add stack-balanced code at the right place (after the new table is pushed onto the stack). I'll skip a few steps and indicate where I've inserted code by indentation:

    lua_newtable( L );  /* ==> stack: ..., {} */
    {
      lua_pushliteral( L, "b" ); /* ==> stack: ..., {}, "b" */
      lua_newtable( L ); /* ==> stack: ..., {}, "b", {} */
      {
        lua_pushliteral( L, "c" ); /* == stack: ..., {}, "b", {}, "c" */
        lua_newtable( L ); /* ==> stack: ..., {}, "b", {}, "c", {} */
        {
          lua_pushliteral( L, "d" );
          lua_newtable( L );
          {
            lua_pushliteral( L, "e" );
            lua_pushliteral( L, "GOOD" );
            lua_settable( L, -3 );
          }
          lua_settable( L, -3 );
        }
        lua_settable( L, -3 ); */ ==> stack: ..., {}, "b", {} */
      }
      lua_settable( L, -3 ); /* ==> stack: ..., {} */
    }
    lua_setglobal( L, "a" ); /* ==> stack: ... */
    

    When you are developing code with complex stack manipulations, it often helps to print out the current stack contents at key points, or at least check that the number of elements on the stack (see lua_gettop()) is what you expect. Here is what I use for that.