Creating a userdata class#

LCL enables the creation of Lua classes for userdata objects. Consider the following structure representing a file handle:

typedef struct {
    FILE *file;
    char *name;
} file_t;

In order to create a Lua class for file_t objects, we must provide an allocator function and a garbage collector function. The allocator function should push onto the stack a userdata object with at least one user value. The garbage collector function should perform any necessary cleanup of resources used by the userdata object.

Warning

The garbage collector function should not free the userdata itself. This is done by the Lua garbage collector.

// allocator
static void file_alloc(lua_State *L) {
    // must have at least one user value
    lua_newuserdatauv(L, sizeof(file_t), 1);
}

// garbage collector. free any resources used by the object here.
// note that we do not free the pointer itself; it is a userdata and
// will be freed by the lua garbage collector.
static void file_gc(lua_State *L, void *p) {
    file_t *o = (file_t *)p;
    if (o->file) {
        fclose(o->file);
        o->file = NULL;
    }
    if (o->name) {
        free(o->name);
        o->name = NULL;
    }
}

The methods for the class should be defined just like the previous example. The function luaC_checkuclass() can be used to check if an object on the Lua stack is an instance of a class, and provide a pointer to the userdata if it is.

// init function. equivalent to the "new" function in
// moonscript classes. performs the actual setup of the object.
static int file_init(lua_State *L) {
    file_t     *o = (file_t *)luaC_checkuclass(L, 1, "lcltests.File");
    size_t      len;
    const char *str = lua_tolstring(L, 2, &len);
    o->name         = (char *)malloc(len + 1);
    o->file         = fopen(str, "r");
    strcpy(o->name, str);
    return 0;
}

// retrieves the filename. we could also have stored this as a
// standard Lua value in the userdata's user value.
static int file_filename(lua_State *L) {
    file_t *o = (file_t *)luaC_checkuclass(L, 1, "lcltests.File");
    lua_pushstring(L, o->name);
    return 1;
}

// reads a line from the file.
static int file_readline(lua_State *L) {
    file_t     *o = (file_t *)luaC_checkuclass(L, 1, "lcltests.File");
    luaL_Buffer b;
    luaL_buffinit(L, &b);
    int c;
    while ((c = fgetc(o->file)) != EOF && c != '\n')
        luaL_addchar(&b, c);
    luaL_pushresult(&b);
    return 1;
}

Put the methods in a luaL_Reg and then throw everything into a luaC_Class.

static luaL_Reg file_methods[] = {
    {"new",      file_init    },
    {"filename", file_filename},
    {"readline", file_readline},
    {NULL,       NULL         }
};

luaC_Class file_class = {
    .name      = "File",
    .parent    = NULL,
    .user_ctor = 1,
    .alloc     = file_alloc,
    .gc        = file_gc,
    .methods   = file_methods};

To create the class object, push the luaC_Class as a light userdata and call luaC_classfromptr(). The object can then either be manipulated directly, or added to the package.loaded table where it can be found by LCL.

// create a class
lua_pushlightuserdata(L, &file_class);
luaC_classfromptr(L);

// put it in `package.loaded`
luaC_setpackageloaded(L, "lcltests.File");

The class can now be constructed with a call to luaC_construct():

lua_pushstring(L, "myfile.txt");
luaC_construct(L, 1, "lcltests.File");

Since we set luaC_Class::user_ctor to 1, our class can be constructed from Lua code by calling the class object.