Git Product home page Git Product logo

llua's Introduction

Rationale

llua is a higher-level C API for Lua, organized around reference objects. It was inspired by luar and LuaJava which provide similar operations when using Lua with Go and Java. The idea is to encapsulate a Lua reference as an object which supports general operations such as indexing (setting and getting), size, pushing on the Lua stack and calling.

For instance, a common use of Lua is to load configuration files:

-- config.lua
alpha = 1
beta = 2
gammon = 'pork'

Here is how this file can be loaded into a custom environment:

    llua_t *env = llua_newtable(L);
    err_t err = llua_evalfile(L,"config.lua","",env);
    if (err) {
        fprintf(stderr,"could not load config.lua: %s\n",err);
    } else {
        av = llua_gets(env,"gammon");
        printf("gammon was %s\n",av);
    }
    //--> gammon was pork

This example also works with both Lua 5.1 and 5.2, by hiding the difference in how 'environments' work with the API.

References, Objects and Strings

llua references are llib objects; to free the reference use unref. If given an arbitrary object, llua_is_lua_object(obj) will be true if a Lua reference.

llua_t has the Lua state L, the reference as ref (which is index into registry) and type.

llua_new(L,idx) will wrap a value on the stack as a reference; llua_newtable(L) will make a reference to a new table, and llua_global(L) is the global state. These operations don't effect the stack; llua_push(o) will make the reference available on the stack.

In general, all objects returned by llua will be allocated by llib, with the exception of strings returned by llua_tostring where you get the underlying char pointer managed by Lua. This should be safe, since we're guaranteed to have a reference to the string. Lua strings can contain nuls, so to be safe use array_len(s) rather than strlen(s) to determine length.

For accessing a large Lua string without wanting a copy, the special type specifier 'L' will force all Lua values (including strings) to be reference objects. Once you have the string reference, llua_tostring gives you the managed pointer and llua_len gives you its actual length.

Calling Lua Functions

llua conceals tedious and error-prone Lua stack operations when calling Lua functions from C:

    llua_t *G = llua_global(L);
    llua_t *strfind = llua_gets(G,"string.find");
    int i1,i2;

    llua_callf(strfind,"ssi","hello dolly","doll",1,"ii",&i1,&i2);

    printf("i1 %d i2 %d\n",i1,i2);
    //--> i1 7 i2 10

llua_callf takes a callable reference (a function or something which has a __call metamethod), passes arguments specified by a type string, and can return a number of values. The 'type string' is akin to printf style formats: 'i' -> int, f -> double, s -> string, b -> boolean (integer value either 0 or 1), 'o' -> object, 'v' -> "value on stack", and 'x' -> C function.

In the above example, there are three arguments, two strings and a integer, and the result is two integers, which are returned by reference.

llua_callf can return a single value, by using the special type "r". Because llib objects have run-time info, the return value can always be distinguished from an error, which is llib's solution to C's single-value-return problem.

    const char *res = llua_callf(my_tostring,"o",obj,"r");
    if (value_is_error(res)) {
        fprintf(stderr,"my_tostring failed: %s\n",res);
    } else {
        // ok!
    }

Since this little "r" can be hard to see in code, it's given a name L_VAL. As a special case, llua_callf can call methods. The pseudo-type 'm' means "call the named method of the given object":

    llua_t *out = llua_gets(G,"io.stdout");
    llua_callf(out,"ms","write","hello dolly!\n",L_NONE);

Accessing Lua Tables

We've already seen llua_gets for indexing tables and userdata; it will return an object as above; numbers will be returned as llib boxed values, which is not so very convenient. llua_gets_v allows multiple key lookups with type specified as with llua_callf.

    char *av, *bv;
    int cv;
    double dv = 23.5;
    err_t *err;
    // load a table...
    llua_t *res = llua_eval(L,"return {a = 'one', b = 'two', c=66}",L_VAL);
    err = llua_gets_v(res,
        "a","s",&av,
        "b","s",&bv,
        "c","i",&cv,
        "d","?f",&dv, // no error if not defined, use default!
    NULL);
    printf("got a='%s' b='%s' c=%d d=%f\n",av,bv,cv,dv);
    //--> got a='one' b='two' c=66 d=23.500000

Note the special '?' which allows the existing value to pass through unchanged if the key does not exist. If there was no default, then the returned error will be non-NULL.

There is also llua_geti and llua_rawgeti, which also return objects.

    llua_t *res = llua_eval(L,"return {10,20,30}",L_VAL);
    void *obj = llua_geti(res,1);  // always returns an object
    if (value_is_float(obj))   // paranoid!
        printf("value was %f\n",value_as_float(obj)); // unbox

     // or we can convert the table to an array of ints
    llua_push(res);
    int *arr = llua_tointarray(L,-1);
    int i;
    for (i = 0; i < array_len(arr); i++)
        printf("%d\n",arr[i]);

There are three table-to-array functions, llua_tointarray, llua_tonumarray and llua_tostrarray which return llib arrays. Again, using llib means that these arrays know how long they are!

A particularly intense one-liner implicitly uses this table-to-int-array conversion: you may force the return type with a type specifier after 'r'.

 int* arr = llua_eval(L,"return {10,20,30}","rI");

Trying to index a non-indexable object will cause a Lua panic, so llua_gettable and llua_settable are defined so you can program defensively.

Iterating over a table is fairly straightforward using the usual API but I usually have to look it up each time. So llua provides a FOR_TABLE macro:

    FOR_TABLE(G) { // all table keys matching "^s"
        void *obj = llua_callf(strfind,"vs",L_TKEY,"^s",L_VAL);
        if (obj) {
            printf("match %s\n",lua_tostring(L,L_TKEY));
            unref(obj);  
        }
    }

Like the explicit version, this requires stack discipline! L_TKEY is (-2) and L_TVAL is (-1); the 'v' type specifier expects a stack index.

If you do need to break out of this loop, use the llua_table_break macro which does the necessary key-popping.

Error Handling

Generally, all llua functions which can return an object, can also return an error; llib's value_is_error can distinguish between error strings and any other object, including plain strings. We can do this because all results are llib objects and have dynamic type information, which provides an elegant solution to the old "return value or error" problem with C. (These runtime checks are pretty fast since the type infomation is part of the hidden object header.)

Scalar values like ints and floats will also be returned this way, as llib 'boxed' values (check with value_is_int, unbox with value_as_int, etc.) This is not so convenient, hence llua's use of scanf-like type specifiers with variables.

Lua has a common idiom, where normally a function will return one value, or nil plus an error string. The llua_callf return type L_ERR makes this into a single value, which can be distinguished from an error as above.

However, passing a Lua API or library function a wrong parameter will result in an error being raised. For instance, trying to index a number, or call a nil value. So llua_gets* will not return an error in this case; you should query llua_gettable(o) first. This is true if the object was a table or something with an __index metamethod. (It would be too expensive to do this check each time.) So then you need the C equivalent of Lua's protected function calls:

int l_myfun(lua_State *L) {
    const char *sval;
    llua_t *r;
    lua_pushnil(L);
    r = llua_new(L,-1);
    sval = llua_gets(r,"woop");
    printf("got '%s'\n",sval);
    return 0;
}

.....
    llua_t  *myfun = llua_cfunction(L,l_myfun);
    err_t res = llua_callf(myfun,L_NONE,L_VAL);
    if (res) {
        fprintf(stderr,"failed: %s\n",res);
    }
    --> failed: attempt to index a nil value

Note that l_myfun doesn't return any value, so llua_callf only returns non-nil when there's an error.

However, if we tried to call the nil reference r above with llua_callf, no error will be raised, since it does a protected call. You can use the following form to raise the error:

    // make 'calling a nil value' a fatal error
    void *val = llua_call_or_die(r,L_NONE,L_VAL);
    ...
    // ---> failed errors.c:23: attempt to call a nil value

This is a macro built on llua_assert(L,val) which you can generally use for converting error values into raised errors. (Like the C macro assert, it adds file:line information)

There is yet another mechanism that forces returned errors into raised errors. If a llua reference has its error field set, then any operation involving it will raise an error. So an alternative way of doing the last operation would be:

    llua_set_error(r,true);
    void *val = llua_callf(r,L_NONE,L_VAL);

The error message won't be so pretty, however. (I apologize for providing several mechanisms for achieving the same result; in the early days of experimenting with a library interface it's useful to present alternatives.)

This is useful if you're found of exception handling as a strategy to separate 'normal' program flow from error handling. Consider the case of reading a configuration file. All the straightforward business of loading and querying happens in a protected call:

typedef struct {
    int alpha;
    double beta;
    char *address;
    int *ports;    
} Config;

int parse_config (lua_State *L) {
    llua_t *env;
    Config *c = malloc(sizeof(Config));
    c->address = "127.0.0.1";
    
    env = llua_newtable(L);
    llua_set_error(env,true);   // <--- any op on 'env' will raise an error, not just return it
    llua_evalfile(L,"config.lua","",env);

     llua_gets_v(env,
        "alpha","i",&c->alpha,
        "beta","f",&c->beta,
        "address","?s",&c->address, // ? means use existing value as default!
        "ports","I",&c->ports, // big 'I' means 'array of int'
    NULL);
    
    unref(env); // don't need it any more...

    lua_pushlightuserdata(L,c);
    return 1;
}

And the error handling then becomes centralized:

    Config* c = llua_callf(llua_cfunction(L,parse_config),L_NONE,L_VAL);
    if (value_is_error(c)) { // compile, run or bad field error?
        fprintf(stderr,"could not load config: %s\n",value_as_string(c));
        return 1;
    }    
    
    printf("got alpha=%d beta=%f address='%s'\n",
        c->alpha, c->beta,c->address
    );
    
    // note how you get the size of the returned array
    int *ports = c->ports;
    printf("ports ");
    for (int i = 0; i < array_len(ports); i++)
        printf("%d ",ports[i]);
    printf("\n");    

The point is that both llua_evalfile and llua_gets_v may throw errors; the first if there's an error parsing and executing the configuration, the second if a required field is missing or has the wrong type. For complicated sequences of operations, this will give you cleaner code and leave your 'happy path' uncluttered.

Please note how easy it is for the protected function to return a pointer as 'light userdata'; alternatively we could have created the config object and passed it as light userdata using the 'p' type specifier, and picked it up as lua_topointer(L,1) in the protected code.

Managing References

In the above example, there are two 'reference leaks'; the first comes from throwing away the reference to parse_config and the second happens when an error is raised and env is not dereferenced with unref. This is not C++, and we don't have any guaranteed cleanup on scope exit!

In this case (once-off reading of configuration file) it's probably no big deal, but you do have to manage the lifetime of references in general.

One approach to scope cleanup is to use 'object pools', which is similar to how Objective-C manages reference-counting.

void my_task() {
    void *P = obj_pool();
    llua_t *result;
    
    //.... many references created
    result = ref(obj);
    
    unref(P); // all references freed, except 'result'
    return result;
}

Object pools can be nested (they are implemented as a stack of resizeable reference-owning arrays) and they will do the Right Thing. To make sure they don't clean up everything, use ref to increase the reference count of objects you wish to keep - in this case, the result of the function.

If you're using GCC (and compilers which remain GCC-compatible, like Clang and later versions of ICC) there is a 'cleanup' attribute which allows us to leave out that explicit 'unref(P)`:

  {
   scoped void *P = obj_pool();
   
   // no unref needed!
   }

The cool thing is that we then have that favourite feature of C++ programmers: the ability to do auto cleanup on a scope level. This is particularly convenient if you like the freedom to do multiple returns from a function and dislike the bookkeeping needed to do manual cleanup. It isn't a C standard, but its wide implementation makes it an option for those who can choose their compilers.

(scoped_pool)

A potential problem with relying too heavily on object pools is that you may create and destroy many temporary references, which could slow you down in critical places.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.