Thursday, 13 January 2011

Lua and C++: storing chuinks, functions, packages, modules

So: if we want to use a Lua chunk more than once, we need to keep track of where we can find it. Let's look at how functions work in Lua for a moment.

Normally, you define a lua function like this:

function foo()
    print "woof"
end

foo()


All fairly unremarkable. You can also create anonymous functions and assign them to variables:

foo = function()
        print "woof"
end

foo()


In actual fact, the two cases are identical. Just like in JavaScript/Actionscript (and probably a ton of other languages as well) if you write "foo()", the interpreter looks for a variable called "foo", checks to see if "foo"  contains a reference to a functions, and if it does, that function is called.

So, thinking about our C++ program, all we need to do is take that chunk and store it in  a global symbol. We can check that. First I want to split the loading and execution parts into their own funcs:

/*
 * load the string: the compiled code will be left in a "chunk"
 * (an anyonymous function) on the top of the stack
 */
int load(lua_State *L, string proggy)
{
        int rc = luaL_loadstring(L, proggy.c_str());
/*
 *      zero means success here
 */
        if(rc == 0) {
                return 0;
        }
/*
 *      report the error
 */
        cout    << "there was an error loading the script: <<"
                << lua_tostring(L, -1)
                << ">>"
                << endl
        ;
        return 1;
}

That's exactly the same code as before, just in its own func and with some extra comments and whitespace.

Similarly for the call part:

/*
 * call the function on top of the stack - bare minimum
 */
int call_top(lua_State *L)
{
        int rc;
/*
 *      run pcall: no args, no return, default handler
 */
        rc = lua_pcall(
                L,              // lua state
                0,              // number of arguments
                0,              // number of expected return values
                0               // error handler - use lua default
        );
/*
 *      a return of zero means everything worked OK
 */
        if(rc == 0) {
                return 0;
        }
/*
 *      the only other possible return (according to the docs) is one
 *      which means an error
 */
        cout    << "there was an error loading the script: <<"
                << lua_tostring(L, -1)
                << ">>"
                << endl
        ;
        return 1;
}

And then the main func gets a bit more manageable (and I can stop copying the include statements each time...)

int main()
{
        int rc;
        string prog = "print \"hello\"";
/*
 *      create a state for the lua interpreter
 */
        lua_State *L = lua_open();
/*
 *      set up the standard lua libraries
 */
        luaL_openlibs(L);
/*
 *      try and load the program - exit if it fails
 */
        if(load(L, prog) == 1) {
                return 0;
        }
/*
 *      call the chunk: it's on top of the stack
 */
        call_top(L);
/*
 *      shutdown the interpreter
 */
        lua_close(L);
        return 0;
}


Storing the chunk is easy:

/*
 * stores the function on top of the stack as global name
 * lua_setglobal pops a value from the stack, so the func will
 * no longer be there when this returns
 */
void save_chunk(lua_State *L, string name)
{
        lua_setglobal(L, name.c_str());
}


Looking it up again:

/*
 * stores the function on top of the stack as global name
 * lua_setglobal pops a value from the stack, so the func will
 * no longer be there when this returns
 */
void find_chunk(lua_State *L, string name)
{
        lua_getglobal(L, name.c_str());
}


It's fairly easy to see that these functions are just wrappers around the lua calls - and in fact they can be used to assign anything to a global name - whatever is on top of the stack. So normally I wouldn't bother with the functions, and I do so here purely for the sake of clearer explanations. (Hope it works).

Anyway:

int main()
{
        int rc;
        string prog = "print \"hello\"";
/*
 *      create a state for the lua interpreter
 */
        lua_State *L = lua_open();
/*
 *      set up the standard lua libraries
 */
        luaL_openlibs(L);
/*
 *      try and load the program - exit if it fails
 */
        if(load(L, prog) == 1) {
                return 0;
        }
/*
 *      chunk is on top of the stack - store it!
 */
        save_chunk(L, "hello");
/*
 *      lookup the chunk under global "hello" and then call it
 */
        for(int i = 0; i < 3; i++) {
/*
 *              need to find it before each call
 */
                find_chunk(L, "hello");
                call_top(L);
        }
/*
 *      shutdown the interpreter
 */
        lua_close(L);
        return 0;
}


And that now works as expected, printing "hello" three times. So we can parse a script and store itas a named function. But is that the best way to do things?


And this is where I started to get seriously confused...

No comments: