Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to call a function in Lua with nested tables

I am trying to create a C function called Dfetch() that is registered in Lua as fetch(). I am looking for it to be tiered such that I can call dog.beagle.fetch() as the function from Lua. It just helps with the organization of the code better. Below is what I have, but it is not calling my C function. If I just do the global and not the subtable, the C function gets called. I am new to Lua, so I think I am just setting up the tables wrong.

void myregister(lua_State *L, const char *libname, const char *sublibname, const luaL_Reg *l) 
{ 
    luaL_newmetatable(L, libname); 
    lua_newtable(L); luaL_setfuncs(L,l,0); 
    lua_pushvalue(L,-1); 
    if(sublibname != NULL) 
    { 
        lua_newtable(L); 
        lua_setfield(L, -2, sublibname); 
    } 
    lua_setglobal(L, libname);
}

luaL_Reg fidofuncModule[] = { {"fetch", Dfetch}, {NULL, NULL}};

In my main(), I call the following:

lua_State * execContext = luaL_newstate();
//adding lua basic library
luaL_openlibs(execContext);

myregister(execContext, "dog", "beagle", fidofuncModule);


strcpy(buff, "dog.beagle.fetch();");
errorcode = luaL_loadbuffer(execContext, buff, strlen(buff), "line");
errorcode = lua_pcall(execContext, 0, 0, 0);

if (errorcode)
{
  printf("%s", lua_tostring(execContext, -1));
  lua_pop(execContext, 1);  /* pop error message from the stack */
}
//cleaning house
lua_close(execContext);

Thanks, Tim

like image 492
Tim Eastham Avatar asked Nov 13 '12 18:11

Tim Eastham


1 Answers

The Problem(s)

OK, let's walk through this function and the stack.

luaL_newmetatable(L, libname); 

OK, the stack now contains a table from the metatable registry:

-1: table<libname>{possibly empty}

Next:

lua_newtable(L);

The stack now contains:

-1: table<new>{empty}
-2: table<libname>{possibly empty}

Next:

luaL_setfuncs(L,l,0); 

Doesn't change the stack. But it does set a bunch of functions into the table.

-1: table<new>{bunch of functions}
-2: table<libname>{possibly empty}

Next:

lua_pushvalue(L,-1); 

This copies the value on top of the stack. That's our table with a bunch of functions:

-1: table<new>{bunch of functions}
-2: table<new>{bunch of functions}
-3: table<libname>{possibly empty}

Next:

if(sublibname != NULL) 
{ 
    lua_newtable(L); 

This creates a new table on the stack that's empty:

-1: table<new2>
-2: table<new>{bunch of functions}
-3: table<new>{bunch of functions}
-4: table<libname>{possibly empty}

Next:

    lua_setfield(L, -2, sublibname); 

This function, as stated in the documentation, sets a value into a table at the given table key name. The value is the value at the top of the stack, but the table it puts it in is the index.

So you just did this:

-1: table<new>{bunch of functions, sublibname=table<new2>}
-2: table<new>{bunch of functions, sublibname=table<new2>}
-3: table<libname>{possibly empty}

I'm pretty sure that's not what you wanted. I'll get to how to fix that when we continue.

Next:

}

lua_setglobal(L, libname);

This takes the top of the stack and sticks it in the global table, poping it off the top of the stack.

So the stack now looks like this:

-1: table<new>{bunch of functions, sublibname=table<new2>{empty}}
-2: table<libname>{possibly empty}

And the global table now has:

_G[libname] = table<new>{bunch of functions, sublibname=table<new2>{empty}}

So not only have you unbalanced the stack (more pushes than pops), you didn't get what you actually wanted. Plus, your metatable from the registry contains... nothing at all.

The Solution

So let's fix this. And let's do it properly.

Pretty much everything about what you tried to do is wrong. First, the only reason to do the subtable thing is so code like this will work:

myregister(execContext, "dog", "beagle", fidofuncModule);
myregister(execContext, "dog", "dane", danefuncModule);

This way, you can call dog.beagle and dog.dane. Well, to do that, myregister has to check the global table to see if there's already a dog table. If there is, it needs to store its stuff in there, and if not, it needs to create it. So your whole algorithm is kinda broken.

Also, presumably you want dog.beagle and dog.dane to both have their own fetch function. Well, the registry only has one place for a dog table, so if you use just libname for your luaL_newmetatable call, they'll be stomping on each others tables.

Here's how I would solve it. I have no idea if this works for what you're doing, but this is what I'd do.

First, forget the whole newmetatable nonsense; we work based on new tables, always. So we'll create the inner table and set functions onto it:

lua_newtable(L);
luaL_setfuncs(L,l,0);

So the stack looks like:

-1: table<new>{bunch of functions}

Next step, if we don't have a sub-library name, then we should set this directly into the global variable under libname and return:

if(!sublibname)
{
    lua_setglobal(L, libname);
    return;
}

That will pop the one value from the stack and set it in that location.

Since we do have a sub-library name, we need to store this table in the main table. If there's already a table in _G[libname], then we get that table. Otherwise, we create a new table and stick it into _G[libname].

lua_getglobal(L, libname);
if(lua_isnil(L, -1))
{
  /*No table. Must create it and put it into the global*/
  lua_newtable(L);
  lua_pushvalue(L, -1); /*duplicate it on the stack*/
  lua_setglobal(L, libname); /*pushes duplicate*/
}

At this point, our stack contains:

-1: table<libname>{possibly empty}
-2: table<new>{bunch of functions}

We then stick our created table into that one, using sublibname as the field:

lua_pushvalue(L, -2); /*duplicates our created table*/
lua_setfield(L, -2, sublibname);

Now the stack contains:

-1: table<libname>{stuff, sublibname=table<new>{bunch of functions}}
-2: table<new>{bunch of functions}

Since table is already in the global table (we either got it from there, or stored it there when we created it), we're done. So clean up the stack:

lua_pop(L, 2); /*balance the stack. Remove our junk from it*/
like image 120
Nicol Bolas Avatar answered Sep 29 '22 06:09

Nicol Bolas