cluanested-tablepolarssl

Wrapping a C library for Lua: how do I create nested tables of functions?


The code related to this question is here: https://github.com/jchester/lua-polarssl/tree/master/src

Currently I'm trying to wrap one part of the PolarSSL library (http://polarssl.org) to give me access to SHA-512 HMACs (luacrypto does not provide this).

The API I'm aiming for is of the form:

a_sha512_hash = polarssl.hash.sha512('text')

or more fully

local polarssl = require 'polarssl'
local hash = polarssl.hash

a_sha512_hash = hash.sha512('test')

If you refer to polarssl.c in the link above, you'll see I've written functions that wrap PolarSSL code. Then I'm trying to build the function tables thus:

LUA_API int luaopen_polarssl( lua_State *L ) {
  static const struct luaL_Reg core[] = {
    { NULL, NULL }
  };

  static const struct luaL_Reg hash_functions[] = {
    { "sha512", hash_sha512 },
    { "sha384", hash_sha384 },
    { NULL, NULL }
  };

  static const struct luaL_Reg hmac_functions[] = {
    { "sha512", hmac_sha512 },
    { "sha384", hmac_sha384 },
    { NULL, NULL }
  };

  luaL_register( L, CORE_MOD_NAME, core );
  luaL_register( L, HASH_MOD_NAME, hash_functions );
  luaL_register( L, HMAC_MOD_NAME, hmac_functions );

  return 1;
}

Where CORE_MOD_NAME = 'polarssl', HASH_MOD_NAME = 'polarssl.hash', HMAC_MOD_NAME = 'polarssl.hmac'.

When I run a test script similar to the Lua code at the top of this question, I get this:

lua: test.lua:23: attempt to index global 'polarssl' (a nil value)
stack traceback:
    test.lua:23: in main chunk
    [C]: ?

I've tried looking for examples of how to achieve this module.submodule approach (eg naim vs luasockets), but everyone seems to have a different way of achieving it. I'm completely lost.


Solution

  • everyone seems to have a different way of achieving it.

    That's Lua; everyone does it their own way. It's Lua's greatest strength and greatest weakness: the language provides mechanisms, not policies.

    The first thing you need to do is stop using luaL_register. Yes, I know it's convenient. But you want something special, and luaL_register isn't going to help you get it.

    What you want is to create a table that contains a table that contains one or more functions. So... do that.

    Create a table.

    lua_newtable(L);
    

    That was easy. The function pushes a table on to the stack, so our stack now has a table on top of it. This is the table we will return.

    Now, we need to create a new table to go inside the old one.

    lua_newtable(L);
    

    Again, easy. Next, we want to put the function we want to go into that table on the stack.

    lua_pushcfunction(L, hash_sha512);
    

    So the stack has three things: the destination table, the "hash" table (we'll get to "naming" it in a second), and the function we want to put into the "hash" table.

    So put the function into the hash table.

    lua_setfield(L, -2, "sha512");
    

    This takes whatever is on top of the stack and sets it into the field named "sha512" on the table at the -2 index on the stack. That's where our "hash" table is. After this function completes, it removes the top item from the stack. This leaves the "hash" table at the top.

    We can repeat the process for the second function:

    lua_pushcfunction(L, hash_sha384);
    lua_setfield(L, -2, "sha384");
    

    Now, we want to put the "hash" table into the table we want to return. That's done easily enough with another lua_setfield call:

    lua_setfield(L, -2, "hash");
    

    Remember: this function takes whatever is on top of the stack. At this point, the table we want to return (which will be our module's table) is on the stack.

    We can repeat this process for the "hmac" table:

    lua_newtable(L); //Create "hmac" table
    lua_pushcfunction(L, hmac_sha512);
    lua_setfield(L, -2, "sha512");
    lua_pushcfunction(L, hmac_sha384);  
    lua_setfield(L, -2, "sha384");
    lua_setfield(L, -2, "hmac"); //Put the "hmac" table into our module table
    

    The module's table now has two entries in it: "hash" and "hmac". Both are tables with two functions in them.

    We can stick it into the global table with this:

    lua_pushvalue(L, -1);
    lua_setfield(L, LUA_GLOBALSINDEX, "polarssl");
    

    Not every module maker wants to do that. Some prefer to force people to use the local polarssl = require "polarssl" syntax, to avoid polluting the global namespace. It's up to you.

    But what you must do either way is to return this table. Return 1 from your luaopen function, to let Lua know that there is one return value. The lua_pushvalue call above exists for the sole purpose of copying the table (remember: tables are referenced, so it's like copying a pointer). That way, when you use lua_setfield, the copy gets removed from the stack, while the original remains to be used as a return value.