The Lua Programming Language
C is hard to master. Pointers and memory management are just two of the many pitfalls. Lua, on the other hand, is very easy to learn, reasonably fast and comes with automatic memory management. Lua is the ideal language for the occasional programmer or a person who merely wants to change the behaviour of a program or extend an existing program.
This makes Lua an ideal scripting language.
Not everything can be written in Lua. A lot of software, especially libraries, is available only in C, or C is used to implement some low-level stuff like a binary protocol or direct hardware access.
Lua as an Orchestration Tool
Lua is ideal to orchestrate software parts written in C. Complex or computing intensive parts can be written in C, which Lua is used to drive the whole.
Lua as an Extension Language
Existing software can be changed in behaviour or be extended using Lua scripts. For this to happen, the software must be prepared for such extension.
Multilanguage Development
Programmers have a tendency of favoring a particular programming language and sometimes defend their choice with almost religious zeal. And then they try to solve every problem in that very language.
This is not always a good idea. Especially not when different people with different skill levels work together. Image a senior C programmer with years long of experience and the student who just started learning to program. Giving the experienced programmer C and the student Lua will make them both productive and let them collaborate. Multilanguage development bridges the skill and experience gap.
The art of Lua integration is to make available complex software, written in C, to Lua in a way a Lua programmer would expect it. Clean and clear syntax, using tables as the only data structure, automatic memory management, automatic cleaning up of ressources, and, using Lua paradigms.
Making Software Scriptable
The crucial step to make software that is written in C scriptable with Lua is to integrate the Lua language into the software written in C.
Lua is a small library that the C program must be linked with. The C program, which we call the host program from now on, must then create one or more Lua states. A Lua state is an isolated Lua execution environment which means that if the host program creates more than one Lua state, these Lua states can not see each other nor can they exchange any data (unless the host program provides means for such data exchange.)
Linking With Lua
Link Lua to the host program in the usual way of your development environment.
It is assumed that the Lua library liblua.a resides in the path /usr/local/lua and the Lua header files in /usr/local/lua/include:
$ cc -o host host.c -I/usr/local/lua/include -L/usr/local/lua -llua
Creating a Lua State
In the host program, we must include the Lua header files.
#include <lualib.h> (1)
#include <lauxlib.h> (2)
1 | This is the main Lua header file. |
2 | This is the auxiliary Lua header file, it provides macros that usually come in handy then working with the Lua C library. |
Now anywhere in the host program we have to create a Lua state and keep a reference to it. The function that creates the Lua state returns a pointer to a lua_State variable, which we can store let’s say in a global variable:
lua_State *L;
void
main(int argc, char *argv[])
{
/* Create a Lua state */
L = luaL_newstate(); (1)
/* Check the return value */
if (L == NULL) {
fprintf(stderr, "Lua: cannot initialize\n");
return -1;
}
}
1 | luaL_newstate() is actually a macro from lauxlib.h, making it very easy
to create a new Lua state. The lua_newstate() function would require us
to pass a memory allocation function. |
Opening the Lua Libraries and Execute Lua Code
The newly created Lua state is completely empty, not even the Lua standard
libraries are available. To make them available, we must call
the luaL_openlibs()
convenience function, again from lauxlib.h:
luaL_openlibs(L);
We are now ready to call some Lua code, e.g. from a file named script.lua:
luaL_dofile(L, "script.lua");
When we are done with our Lua state, we call lua_close()
to finish the
Lua state. Remind yourself that Lua is garbage collected language and
calling lua_close()
will allow it to run the garbage collector one last
time, freeing all resources.
lua_close(L);
We see a pattern here: All functions of the C API, besides the functions that
create a new Lua state — luaL_newstate()
and lua_newstate()
— expect a
lua_State * to be passed as the first parameter.
The complete short example now looks like this:
#include <stdio.h>
#include <lualib.h>
#include <lauxlib.h>
lua_State *L;
void
main(int argc, char *argv[])
{
/* Create a Lua state */
L = luaL_newstate();
/* Check the return value */
if (L == NULL) {
fprintf(stderr, "Lua: cannot initialize\n");
return -1;
}
/* Provide the Lua standard libraries to the Lua state */
luaL_openlibs(L);
/* Execute a Lua program in script.lua */
luaL_dofile(L, "script.lua");
/* Close the Lua state */
lua_close(L);
return 0;
}
We have seen how to link Lua with a host program, create an empty Lua state, open the standard libraries, and, executing Lua code.
But this is a rather boring example, since all it does is to run a Lua script in complete isolation from the host program. There is absolute no interaction between the host program and the Lua script. This is where the Lua C API enters the stage.
The Lua C API
The Lua C API provides a rich and very carefully designed set of functions for a host program to interact with a Lua script.
Using the Lua C API it is not only possible to create or close a Lua state, open the standard Lua libraries and execute Lua code from a file, but a lot, lot more.
With the Lua C API you can:
-
Manage Lua states, creating and closing them.
-
Load libraries into a Lua state.
-
Create variables in a Lua state.
-
Access variables in a Lua state.
-
Run Lua code from C.
-
Run C from Lua by providing C functions to the Lua state.
-
Create uservalues, i.e. values that hold C data structures.
-
And much more.
The Virtual Lua Stack
Lua uses a virtual stack to pass values between C and Lua. That means that the C function has to pop its arguments from the stack and push and return values to the stack, returning an int indicating the number of return values. Each element on the stack has a numerical index, starting at 1.
Passing Values Between Lua and C
Arguments and return values are passed between Lua and C using the virtual stack. That means that a C function has to pop its arguments from the stack and push and return values to the stack, returning an int indicating the number of return values. Each element on the stack has a numerical index, starting at 1. Consider the following Lua call:
local n = myFunction('miller', 42, 'south', 'north')
The stack will then look as follows:
Index | Content |
---|---|
1 |
"miller" |
2 |
42 |
3 |
"south" |
4 |
"north" |
To return values to Lua, the C function pushes the values to the stack in the order they should be returned. Consider the following stack and assume the function returns the integer 3, indicating three result values:
Index | Content |
---|---|
1 |
"west" |
2 |
"east" |
3 |
42 |
The calling Lua function will receive this values as three return values:
local dir1, dir2, media = myFunction() -- 'west', 'east', 42
So dir1 contains 'west, dir2 'east, and, media the integer 42.
The Lua C API provides a rich set functions for querying and manipulating the stack. The Lua auxiliary library provides additional functions which not retrieve values from the stack, but check for the expected type on go.
The functions for retrieving values from the stack are named
lua_totype()
while the functions in the auxiliary library are named
luaL_checktype()
.
To push values to the stack, functions named lua_pushtype()
are provided
by the Lua C API.
Calling Lua Code From C
Lua provides basically two functions to call Lua code from C, lua_call()
and lua_pcall()
. They differ in how error situations are handled.
lua_call()
is used to call a Lua function in unprotected mode, meaning
the host program will simply be terminated by a call to exit()
in
the case of an error, e.g. the Lua code calling the error()
function.
lua_pcall()
on the other hand calls a Lua function in protected mode,
which means it will not terminate the calling program in case of an error.
It will rather signal through its return value that there has been a problem
and the Lua error message will be provided to the host program conveniently
in the first element of the virtual stack. Thus we can call
lua_tostring(L, 1)
to retrieve the Lua error message.
Using lua_pcall()
is usually the preferred way of calling Lua code.
To call a Lua function, the Lua virtual stack is again used to pass arguments
and retrieve return values. To call a Lua function we first push onto the
stack the function to be called, then the arguments to be passed to the
function. Once the stack is setup, we call either lua_pcall()
or
lua_call()
, passing the number of arguments be pushed onto the stack
and the number of return values we expect:
lua_call(lua_State *L, int nargs, int nresults);
As an example, we want to call the print()
function with three arguments,
"miller", "north", and, the number 42. The following C code would prepare our
stack:
lua_getglobal(L, "print"); (1)
lua_pushstring(L, "miller");
lua_pushstring(L, "north");
lua_pushinteger(L, 42);
1 | lua_getglobal() pushes onto the stack the value of the global
passed as argument, in this case the print() function (remember that in
Lua functions are just ordinary values, so they can be put onto the stack). |
Our stack now looks as follows:
Index | Content |
---|---|
1 |
The Lua |
2 |
The string "miller" |
3 |
The string "north" |
4 |
The integer 42 |
We can now call the function, expecting no result values:
lua_call(L, 3, 0);
Calling C Code From Lua
For Lua to be able to call C code, the C code must be made available to Lua. In the simplest form, the host program will create a global variable holding the C function to be called.
Any C function that will be called from Lua must follow this protocol:
-
It receives a lua_State * as its sole argument
-
It pulls the arguments, if any, from the Lua stack.
-
It does whatever it’s purpose is.
-
It pushes any return values to the Lua stack.
-
It returns the number of return values as an int.
The signature of a C function that will be called from Lua looks as follows:
int
myFunction(lua_State *L)
{
}
Once we have written such a function it must be made known to the Lua state. A simple way is to define a global name for the function.
...
lua_pushcfunction(L, myFunction);
lua_setglobal(L, "myFunction");
...
In Lua, the function can now be called as myFunction()
.
A simple, Yet Complete, Example
In this simple example we will provide a greetings()
function to Lua
which expects a string argument, the name of the person to greet, and
which will return the number of invocations back to Lua. The function will
output a greeting message to the console.
/* The function that will be called from Lua */
static int
greetings(lua_State *L)
{
const char *name;
static int invocations = 1;
name = luaL_checkstring(L, 1); (1)
printf("greetings, %s\n", name);
lua_pushinteger(L, invocations++);
return 1;
}
...
/* Register the function */
lua_pushcfunction(L, greetings);
lua_setglobal(L, "greetings");
1 | We use the auxiliary function luaL_checkstring() here which will
ensure that the stack element 1 is actually a string. It will throw an
error otherwise. |
Once this has been setup, Lua can call the function in the usual way:
local invocations = greetings 'miller' (1)
print('the greetings functions has been called ' .. invocations .. ' times.')
1 | If the sole argument to a Lua function is a string, then no parentheses are needed. |
Userdata: Defining Your Own Data Types
Lua comes with basic data types like numbers, integers, strings, boolean etc. For more complex data structures it knows exactly one data type, the table.
C code, on the other hand, usually comes with complex data types and structures. As an example let’s look at libuuid, a C library to generate and deal with universal unique identifiers, UUIDs (this library will also serve as an example in the chapter Binding Existing Libraries.)
libuuid defines an opaque data type uuid_t
, which is used to hold a
generated uuid. We have no idea how uuid_t
is composed internally,
neither do we need to. What we need is a way to tie a uuid_t
C
variable to a Lua value, so that Lua can deal with uuids and pass uuids
to other functions.
The Lua C API provides us with userdata for exactly this purpose. A Lua userdata value is a Lua value that can be passed around just like any Lua value. Internally it is a piece of memory with a user defined size that we declare when we create the userdata value. Like all Lua values, userdata values get garbage collected eventually when they are no longer in use, the allocated memory is then automatically freed.
We create a userdata value by calling the lua_newuserdatauv()
function,
which returns a pointer to the allocated memory. As the Lua manual states,
our code can freely use this memory:
uuid_t uuid, *u;
uuid_generate(uuid); (1)
u = lua_newuserdatauv(L, sizeof(uuid_t), 0); (2)
uuid_copy(*u, uuid); (3)
1 | Call uuid_generate() from libuuid to generate a uuid. |
2 | Allocate enough memory to hold a variable of the uuid_t type.
Ignore the third argument for now. |
3 | Copy the previously generated uuid into the userdata memory. |
In short, userdata is application memory that is managed by Lua, with automatic memory management for free.
Userdata for Application Memory
It is actually a good practice to use Lua userdata for application memory, even if that memory is never used as a Lua value. Consider the following fictional code fragment:
char *text;
text = malloc(1024); (1)
memcpy(text, "Hello, world!");
lua_getlobal(L, "print");
lua_pushstring(L, text);
lua_call(L, 1, 0); (2)
free(text); (3)
1 | Allocate memory using the standard malloc() function. |
2 | We call the Lua print() function with one argument and expect no
results. |
3 | Free the allocated memory. |
Why is this bad?
Remember that Lua uses a longjmp()
call when it encounters an error
internally? And in fact, all three functions involved in the example above,
lua_getglobal()
, lua_pushstring()
, and, lua_call()
may raise
an error, causing our program to longjmp()
to a location that Lua
defined using a setjmp()
call earlier.
The longjmp()
call will unwind the stack, but it will not free the
allocated memory. We have just created a potential memory leak.
We can, however, leave memory management to the Lua C API and be completely
safe, even if any of the lua_
functions raises an error:
char *text;
text = lua_newuserdatauv(L, 1024, 0); (1)
memcpy(text, "Hello, world!");
lua_getlobal(L, "print");
lua_pushstring(L, text);
lua_call(L, 1, 0); (2)
/* free(text); */ (3)
1 | Allocate memory using the lua_newuserdata() function. |
2 | We again call the Lua print() function with one argument and expect no
results. |
3 | Don’t free the allocated memory. Lua will take care of it. |
Metatables
Values in Lua can be assigned a metatable which defines the behaviour of the value. Metatables are ordinary Lua tables that contain functions, so-called metamethods, which have predefined names starting with two underscores.
You can not just invent any name for a metamethod, there is a list of metamethods than can be defined and in which situation they are called by the Lua core.
Metamethod | Used for | Operator |
---|---|---|
__add |
The addition operation |
+ |
__sub |
The subtraction operation |
- |
__mul |
The multiplication operation |
* |
__div |
The division operation |
/ |
__mod |
The modulo operation |
% |
__pow |
The exponentiation operation |
^ |
__unm |
The negation operation (unary minus) |
unary - |
__idiv |
The floor division |
// |
__band |
The bitwise and operation |
& |
__bor |
The bitwise or operation |
| |
__bxor |
The bitwise xor operation |
binary ~ |
__bnot |
The bitwise not operation |
unary ~ |
__shl |
The bitwise shift left operation |
<< |
__shr |
The bitwise shift right operation |
>> |
__concat |
The concatenation operation |
.. |
__len |
The length operation |
# |
__eq |
Test for equality |
== |
__lt |
Test for less than |
< |
__le |
Test for less or equal |
⇐ |
__index |
The value is being indexed |
[] |
__newindex |
A value is being set at a new index |
[] = |
__call |
Call operation (execute as function) |
() |
__tostring |
Used by tostring() to convert the value to a string |
n/a |
__close |
The to-be-closed value is going out of scope |
n/a |
__gc |
The value is about to be garbage collected |
n/a |
Once a metatable has been assigned to a value, Lua will call the metamethods whenever needed under the hood and completely invisible to the Lua program.
Metatables are one of the most powerfull concepts in Lua, especially when combined with userdata, one can do magic. The concept of metatables underline one strongest paradigm in Lua, namely to provide mechanisms, not policies.
For example, while Lua itself is not an object-oriented language, it can quite easily be made one by proper use of metatables.
To get an idea of how powerful metatables are, consider the following scenario: We have a userdata value that is not the actual data, but merely a pointer to the real data.
In the example that follows, a PostgreSQL result set is returned by a query function. The actual size of the data is not known, so we have to store a pointer to it. Our userdata value effectively becomes a pointer to a pointer:
PGresult **res;
res = lua_newuserdata(L, sizeof(PGresult *));
*res = PQexec(db, "select name from people");
What happens if this userdata value is garbage collected? The pointer to the
pointer will be freed by Lua, but not the pointer itself, leading to a memory
leak (in PostgreSQL, you have to call PQfreemem(*res)
to free the result
set.)
If, however, we define a table with a __gc()
function and set this table as
the metatable of above userdata value, then Lua would call the __gc()
function just before the value is garbage collected. In the __gc()
function, which receives the to-be-freed value on the virtual stack, we can then
pop our value from the stack and call PQfreemeem()
, releasing the memory
held by the result set.
To-be-closed Variables
As Lua is a garbage collected language, the Lua programmer does not have to care about memory management at all. Every value, once it is no longer used, will eventually be garbage collected and the memory it occupied will be be freed. This is also true for values that have been created by C code as userdata values.
While garbage collection is a great concept from the Lua programmers point of view, it is not optimal from the view of a C programmer who writes a Lua module in C. There is no control over when a value actually gets garbage collected. This can lead to situations where a lot of no longer used — and no longer accessible — memory is still being held by the process running the Lua interpreter.
As an example, imagine a PostgreSQL database interface being used in a Lua
program that runs thousands of SQL queries per second. Each query returns a
PostgreSQL result set that is essentially allocated memory that has to be
explicitely freed by a call to the PQfree()
C function. Whe could free
the memory with an explicit call from the Lua program, but a Lua programmer
might forget to call the free function, assuming everything is garbage
collected.
On the other hand, if we free the memory at garbage collection time only, we have exactly the situation where potentially a lot of no longer used memory remains allocated.
Database result sets are only one example, we can think of a lot of other resources that are release to late or later than needed.
To address this problem, Lua 5.4 introduced to-be-closed variables, a mechanism to free memory or otherwise release resources at the very moment the variable goes out of scope.
To do so, two requirements must be met:
-
The underlying userdata value must have a
__close
metamethod. -
The Lua program must annotate a variable as to-be-closed.
Consider the following function:
local function getName(db)
local res = db:exec('select name from account')
return res[1].name
end
The memory used by the variable res
will only be freed at garbage
collection time (whenever that will be.)
If, however, res
is annotated as to-be-closed using the new keyword
<close>
, the underlying memory can be freed in the __close
metamethod, which gets called as soon a the variable goes out of scope, i.e.
when the getName()
function returns:
local function getName(db)
local res <close> = db:exec('select name from account') (1)
return res[1].name
end
1 | res is annoted as to-be-closed with the <close> keyword. |
If you try to annotate a variable as to-be-closed when the underlying userdata
has no __close
metamethod, Lua will throw an error.
For the curious, this is the C function that is called in the Lua PostgreSQL
interface to free memory either when __close
is called or when the
garbage collector runs:
static int
res_clear(lua_State *L)
{
PGresult **r;
r = luaL_checkudata(L, 1, RES_METATABLE);
if (*r) { (1)
PQclear(*r);
*r = NULL;
}
return 0;
}
1 | Prevent double free of the memory as this function can be called more than one time. |
Binding Existing Libraries
When binding an existing library, you make its functionality available to Lua. And there is an awful lot of libraries out there that provide functionality of all sorts and that usually have seen the proof of time and are well maintained.
Thanks to the well designed Lua C API this process is not very hard. A bit of planning ahead is nevertheless needed. C libraries are designed with the C programmer in mind, using C programming paradigms. In Lua things are quite different, e.g.:
-
In C we can define complex data structures and our own complex types. Lua has only tables as data structures.
-
In C we must carefully manage memory. Lua has automatic garbage collection and to-be-closed variables.
Designing a Lua Binding
The first step to designing a good Lua binding is to truly understand the C library, how it works on the C side, and what its data structures are. You must be able to use the library in your C code if you want to create a binding for it.
Next you must think about how you would like to use the libraries functionality in Lua. I wrote to use the libraries functionality on purpose, and not provide the libraries functions to Lua because that’s what designing good Lua bindings is all about: Build a bridge between the Lua and the C world.
Of course you could just write a binding that is a one-to-one mapping of the C functions to corresponding Lua functions, but with this approach you’ll end up with a Lua binding that is non-intuitive to use in many cases.
The libyaml library serves us as an example for a design decision. libyaml is used to parse YAML data in a C program. It works in a way where the C program repeatetly calls a function which returns an event. Such an event can be that a data element has been found in the YAML stream, or that the start of an array has been detected etc. The events itself are not important here, but the fact that libyaml works by returning a stream of events is.
We must think about what should be achieved with a libyaml Lua binding. YAML describes data in an easy to read, easy to write text format. In the end, we probably want all that data to be available in a Lua table. Converting the YAML data into a (probably nested) table seems like a reasonable design:
local yaml = require 'yaml'
local data = yaml.parsefile('mydata.yaml')
Clearly, the approach of simply exposing the event-returning function to Lua would not work well, it would mean that we have to do a lot of overhead work on the Lua side. A better approach is to do all the processing in C, populating a table while the events are processed. This means more C code (in fact a lot more) than if we just exposed the event-returning function, but it leads to a very straightforward usage in Lua.
If you are interested in the full code of luayaml, you may fetch it from Github at https://github.com/arcapos/luayaml.
luauuid, Working with UUIDs in Lua
luauuid is a Lua binding for the libuuid UUID library by Theodore Y. Ts’o.
A UUID (Universally Unique ID) is an ID that consists of 128-bits and
comes in different flavors: It can contain purely random bits or it can
contain parts that are based e.g. on the current time. A UUID can be
represented as a 36 character long string in the form
4309877e-29c7-4d7a-b4a3-4382ea7deead
.
The purpose of the Lua UUID binding is to create and parse UUIDs, compare them against each other or the UUID null value. Let’s first have a look at the uuid test program testuuid.lua to show its usage:
local uuid = require 'uuid'
local function test_1 ()
local myuuid <close> = uuid.generate_random()
print(myuuid:unparse())
end
local function test_2 ()
local myuuid <close> = uuid.generate_random()
print(myuuid:unparse())
end
print 'test_1'
test_1()
print 'test_2'
test_2()
In this binding we use Lua userdata to store a pointer to a uuid_t
, the
internal C representation of a UUID. We define a metatable name in the
headerfile luauuid.h:
#ifndef __LUAUUID_H__
#define __LUAUUID_H__
#define UUID_STR_SIZE 36
#define UUID_METATABLE "uuid"
#endif /* __LUAUUID_H__ */
When a uuid is created using one of the libuuid generator functions,
teh code will actually create a new userdata value, store the uuid in it and
set the metatable of the new value to the UUID_METATABLE. As libuuid
offers different uuid-generating functions, helper functions are used that
are called by Lua and which hand the actual generator function to the
lua_uuid_generator()
function:
static int
lua_uuid_generator(lua_State *L, void (*generator)(uuid_t)) (1)
{
uuid_t uuid, *u;
const char *format;
char str[UUID_STR_SIZE + 1];
generator(uuid);
format = lua_tostring(L, 1);
if (format && *format == 't') { (2)
uuid_unparse(uuid, str);
lua_pushlstring(L, str, UUID_STR_SIZE);
} else {
u = (uuid_t *)lua_newuserdata(L, sizeof(uuid_t)); (3)
uuid_copy(*u, uuid);
luaL_setmetatable(L, UUID_METATABLE);
}
return 1;
}
static int
lua_uuid_generate(lua_State *L)
{
return lua_uuid_generator(L, uuid_generate);
}
static int
lua_uuid_generate_random(lua_State *L)
{
return lua_uuid_generator(L, uuid_generate_random);
}
static int
lua_uuid_generate_time(lua_State *L)
{
return lua_uuid_generator(L, uuid_generate_time);
}
1 | The lua_uuid_generator() is used only internally and not exposed to
Lua. |
2 | A UUID in text form has been requested, there is no need to store it, instead the string representation is returned. |
3 | Allocate userdata to hold the actual uuid. |
The latter three functions will be exposed to Lua in luaopen_uuid()
:
int
luaopen_uuid(lua_State *L)
{
struct luaL_Reg luauuid[] = {
{ "generate", lua_uuid_generate },
{ "generate_random", lua_uuid_generate_random },
{ "generate_time", lua_uuid_generate_time },
/* more functions to come */
{ NULL, NULL }
};
luaL_newlib(L, luauuid);
return 1;
}
The more interesting part is the uuid metatable which defines all the functions
and metamethods we can call on a uuid value. It is also defined in
luaopen_uuid()
:
struct luaL_Reg uuid_methods[] = {
{ "clear", lua_uuid_clear },
{ "compare", lua_uuid_compare },
{ "data", lua_uuid_data },
{ "is_null", lua_uuid_is_null },
{ "time", lua_uuid_time },
{ "unparse", lua_uuid_unparse },
{ "__eq", lua_uuid_equal },
{ "__lt", lua_uuid_less },
{ "__le", lua_uuid_less_or_equal },
{ "__gc", lua_uuid_clear },
{ "__close", lua_uuid_clear },
{ "__tostring", lua_uuid_unparse },
{ "__concat", lua_uuid_concat },
{ "__len", lua_uuid_length },
{ NULL, NULL }
};
if (luaL_newmetatable(L, UUID_METATABLE)) {
luaL_setfuncs(L, uuid_methods, 0);
lua_pushliteral(L, "__index");
lua_pushvalue(L, -2);
lua_settable(L, -3);
lua_pushliteral(L, "__metatable");
lua_pushliteral(L, "must not access this metatable");
lua_settable(L, -3);
}
lua_pop(L, 1);
As an example of how we access the userdata value, let’s look at the
implementation of the unparse()
function:
static int
lua_uuid_unparse(lua_State *L)
{
char str[UUID_STR_SIZE + 1];
uuid_t *u;
u = luaL_checkudata(L, 1, UUID_METATABLE);
uuid_unparse(*u, str);
lua_pushstring(L, str);
return 1;
}
The remaining functions are no magic either, they just call different
functions of the libuuid library after they retrieved the uuid_t *
pointer.
The full source code of luauuid can be fetched from Github:
https://github.com/arcapos/luauuid
With this explanations and examples you should now be able to create your own bindings. But before doing so, it is well worth checking if a binding for the library you have in mind doesn’t already exist. You might be surprised how many different implementations e.g. of decoders for JSON you will find…
Lua Readers
To load Lua code into a Lua state, you will most likely load it from a file
or string using either the luaL_loadfile()
or luaL_loadstring()
function from the Lua auxiliary library.
Lua itself, however, uses a different approach to load Lua code: It repeatetly calls a function to return the code piece by piece. This function is called a Lua reader and it can either return the next piece of code plus its size in bytes or signal that the end of the Lua code has been reached.
So in order to load Lua code using only functions from the core library, you
use the lua_load()
function, supplying it with a Lua reader:
int
lua_load(lua_State *L,
lua_Reader reader, (1)
void *data, (2)
const char *chunkname,
const char *mode);
1 | This is the Lua reader function that will repeatly be called. |
2 | The data pointer will be handed to the reader function on each call.
It is not used by Lua. |
Please note that lua_load()
only loads the Lua code, but does not run it.
After lua_load()
successfully returns (with return code LUA_OK
) the
loaded Lua code will be on top of the stack.
But how does the Lua reader function look like and how is it called? The
function returns the next piece of Lua code as a const char *
pointer
and gets the Lua state, the data pointer and a pointer to a size_t
as
arguments:
const char *
myReader(lua_State *L, void *data, size_t size)
{
/* Return a piece of Lua source and put its size in size */
}
When there is no more Lua code to be returned, the reader function returns
either NULL or sets size
to zero.
The functions found in the Lua auxiliary library to load Lua code are in fact unsurprisingly implemented as Lua readers.
What makes this approach interesting? It allows us to supply a function that returns Lua code from any source, e.g. a network connection, a HTTP request, an object stored in a database, or, even synthesized Lua code that is built on the fly from parsing a different code format.
Reading Lua code from a .zip file
As an example of a working Lua reader, we assume that we want to load Lua code from a file that stored within a .zip file, without first decompressing the .zip file on the command line.
Let’s assume that we have a testzip.zip file that contains an entry testzip.lua with the code we want to load:
% zip -sf testzip.zip
Archive contains:
testzip.lua
Total 1 entries (1468 bytes)
%
We first define how we want our helper function to look like, i.e. the function
which we call to load Lua from a .zip file. This means, we design a function
similar to luaL_loadfile()
and `luaL_loadstring()
.
One approach is to pass the path to the .zip file and the path to the Lua file contained within the .zip file to the function:
int lua_loadzip(lua_State *L, const char *zipfile, const char *path)
Before we look at the implementation of lua_loadzip()
, lets write a small
command line tool which hosts the Lua environment, loads and calls the Lua
code contained in a .zip file. We call the utility ziplua
and it is called
with two arguments, the path to the .zip file and the path to the Lua code.
% ./ziplua
usage: ziplua <zipfile> <path>
% ./ziplua testzip.zip testzip.lua # Load and run testzip.lua
%
And here is the source code of the ziplua utility:
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
extern int lua_loadzip(lua_State *, const char *, const char *);
int
main(int argc, char *argv[])
{
lua_State *L;
if (argc != 3) {
fprintf(stderr, "usage: ziplua <zipfile> <path>\n");
exit(1);
}
L = luaL_newstate();
if (L == NULL) {
fprintf(stderr, "memory error\n");
exit(1);
}
luaL_openlibs(L);
switch (lua_loadzip(L, argv[1], argv[2])) {
case LUA_ERRSYNTAX:
fprintf(stderr, "syntax error: %s\n", lua_tostring(L, -1));
exit(1);
case LUA_ERRMEM:
fprintf(stderr, "memory error\n");
exit(1);
case LUA_ERRFILE:
fprintf(stderr, "file error: %s\n", lua_tostring(L, -1));
exit(1);
case LUA_OK:
;
}
switch (lua_pcall(L, 0, 0, 0)) {
case LUA_ERRRUN:
fprintf(stderr, "runtime error: %s\n",
lua_tostring(L, -1));
exit(1);
case LUA_ERRMEM:
fprintf(stderr, "memory allocation error: %s\n",
lua_tostring(L, -1));
exit(1);
case LUA_ERRERR:
fprintf(stderr, "error running message handler: %s\n",
lua_tostring(L, -1));
exit(1);
case LUA_OK:
;
}
lua_close(L);
return 0;
}
With that in place, let’s develop the corresponding Lua reader and the
lua_loadzip()
function. For the actual processing of .zip files we will rely
on the libzip library from https://libzip.org.
#include <stdlib.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <zip.h>
We will need some state information for the actual reader function, which
will be passed in the data *
pointer. In our case we will need a
pointer to the zip file from which we read plus the remaining size which
we still have to read. We define a zip_reader_stat_t
type to keep
this information.
typedef struct {
zip_file_t *zf; (1)
size_t size;
} zip_reader_stat_t;
1 | zip_file_t is a data type defined by libzip in zip.h. |
In our lua_loadzip()
function we initialize a zip_reader_stat_t
variable
and pass it to loa_load()
alongside with our reader function:
zip_reader_stat_t st;
st.zf = zip_fopen(zip, path, 0); (1)
st.size = sb.size;
error = lua_load(L, zip_Reader, &st, path, NULL);
1 | path contains the path to the Lua file within the .zip file. |
lua_load()
will now call our zip_Reader()
function, which will try to
read up to st.size
bytes from the .zip file and return them to lua_load
.
As reading can return fewer bytes than requested, it will also return the number
of bytes actually read and decrement st.size
by that number. Once
st.size
is zero, the function will return NULL, thus terminating the
loading process.
The complete function with the zip_Reader()
function now looks like this:
#include <stdlib.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <zip.h>
typedef struct {
zip_file_t *zf;
size_t size;
} zip_reader_stat_t;
static const char *
zip_Reader(lua_State *L, void *data, size_t *size)
{
zip_reader_stat_t *st = (zip_reader_stat_t *)data;
static char *buf = NULL;
free(buf);
if (st->size == 0)
return NULL;
buf = malloc(st->size);
*size = zip_fread(st->zf, buf, st->size);
st->size -= *size;
return buf;
}
int
lua_loadzip(lua_State *L, const char *zipfile, const char *path)
{
zip_t *zip;
zip_stat_t sb;
zip_reader_stat_t st;
int error;
zip = zip_open(zipfile, ZIP_RDONLY, &error);
if (zip == NULL) {
switch (error) {
case ZIP_ER_INVAL:
lua_pushstring(L, "invalid path");
break;
case ZIP_ER_MEMORY:
lua_pushstring(L, "memory error");
break;
case ZIP_ER_NOENT:
lua_pushstring(L, "file not found");
break;
case ZIP_ER_NOZIP:
lua_pushstring(L, "not a zip file");
break;
case ZIP_ER_OPEN:
lua_pushstring(L, "file open error");
break;
case ZIP_ER_READ:
lua_pushstring(L, "read error");
break;
default:
lua_pushstring(L, "unknown zipfile related error");
}
return LUA_ERRFILE;
}
if (zip_stat(zip, path, 0, &sb)) {
lua_pushstring(L, "can't stat zipfile entry");
zip_close(zip);
return LUA_ERRFILE;
}
st.zf = zip_fopen(zip, path, 0);
st.size = sb.size;
error = lua_load(L, zip_Reader, &st, path, NULL);
zip_fclose(st.zf);
zip_close(zip);
return error;
}
Useful Helper Functions
lua_vpncall()
The lua_vnpcall()
function calls a Lua function by name,
specifying parameters and return values in a printf()
like manner.
Parameters passed as variable arguments and the expected results must be indicated by strings in arg and ret containing the following characters:
Character | C Type | Lua Type |
---|---|---|
b |
int |
Lua |
f |
double |
Number |
d |
int |
Integer |
l |
long |
Integer |
L |
long long |
Integer |
s |
char * |
String |
To pass a string and number to a function called test, expecting an int to be returned, one would call the function as follows:
int rv;
if (lua_vnpcall(L, NULL, "test", "sd", "d", "Hello, Lua!", 42, &rv)) {
/*
* Something went wrong, add an error handler. The Lua error
* message is on top the Lua stack.
*/
}
And here is the function itself:
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <lua.h>
#include <lualib.h>
int
lua_vnpcall(lua_State *L, char *table, char *func, char *arg, char *ret, ...)
{
va_list ap;
int n, retval, args, rets, pop;
char *types, *t, *fnam, *f;
fnam = strdup(func);
if (fnam == NULL) {
lua_pushstring(L, strerror(errno));
return LUA_ERRMEM;
}
pop = 0;
if (table != NULL) {
lua_getfield(L, -1, table);
if (lua_isnil(L, -1)) {
lua_pushstring(L, "no such table");
lua_pop(L, 1);
return LUA_ERRRUN;
}
pop++;
}
f = fnam;
while ((t = strsep(&f, ".")) != NULL) {
lua_getfield(L, -1, t);
if (lua_isnil(L, -1)) {
free(fnam);
lua_pushstring(L, "no such function");
if (pop)
lua_pop(L, pop);
return LUA_ERRRUN;
}
pop++;
}
free(fnam);
va_start(ap, ret);
args = 0;
if (arg != NULL) {
for (types = arg; *types; types++)
switch (*types) {
case 'b':
lua_pushboolean(L, va_arg(ap, int));
args++;
break;
case 'f':
lua_pushnumber(L, va_arg(ap, double));
args++;
break;
case 'd':
lua_pushinteger(L, va_arg(ap, int));
args++;
break;
case 'l':
lua_pushinteger(L, va_arg(ap, long));
args++;
break;
case 'L':
lua_pushinteger(L,
va_arg(ap, long long));
args++;
break;
case 's':
lua_pushstring(L, va_arg(ap, char *));
args++;
break;
default:
lua_pop(L, 1 + args + pop);
lua_pushstring(L, "unknown return type");
va_end(ap);
return LUA_ERRRUN;
}
}
rets = ret != NULL ? strlen(ret) : 0;
if ((retval = lua_pcall(L, args, rets, 0))) {
va_end(ap);
if (pop)
lua_pop(L, pop);
return retval;
}
if (ret != NULL) {
for (n = rets, types = ret; *types; n--, types++)
switch (*types) {
case 'b':
*(va_arg(ap, int *)) =
lua_toboolean(L, -n);
break;
case 'f':
*(va_arg(ap, double *)) =
lua_tonumber(L, -n);
break;
case 'd':
*(va_arg(ap, int *)) =
lua_tointeger(L, -n);
break;
case 'l':
*(va_arg(ap, long *)) =
(long)lua_tointeger(L, -n);
break;
case 'L':
*(va_arg(ap, long long *)) =
(long long)lua_tointeger(L, -n);
break;
case 's':
*(va_arg(ap, char **)) =
(char *)lua_tostring(L, -n);
break;
default:
lua_pop(L, lua_gettop(L));
lua_pushstring(L, "unknown return type");
va_end(ap);
return LUA_ERRRUN;
}
if (rets)
lua_pop(L, rets);
}
va_end(ap);
if (pop)
lua_pop(L, pop);
return 0;
}
lua_vpcall()
The lua_vpcall()
function is similar to lua_vnpcall()
, but instead
of passing a function name, it expects the function to be on top of the Lua
stack.
#include <stdarg.h>
#include <string.h>
#include <lua.h>
#include <lualib.h>
int
lua_vpcall(lua_State *L, char *arg, char *ret, ...)
{
va_list ap;
int n, retval, args, rets;
char *types;
va_start(ap, ret);
args = 0;
if (arg != NULL) {
for (types = arg; *types; types++)
switch (*types) {
case 'b':
lua_pushboolean(L, va_arg(ap, int));
args++;
break;
case 'f':
lua_pushnumber(L, va_arg(ap, double));
args++;
break;
case 'd':
lua_pushinteger(L, va_arg(ap, int));
args++;
break;
case 'l':
lua_pushinteger(L, va_arg(ap, long));
args++;
break;
case 'L':
lua_pushinteger(L,
va_arg(ap, long long));
args++;
break;
case 's':
lua_pushstring(L, va_arg(ap, char *));
args++;
break;
default:
lua_pop(L, 1 + args);
lua_pushstring(L, "unknown parameter type");
va_end(ap);
return LUA_ERRRUN;
}
}
rets = ret != NULL ? strlen(ret) : 0;
if ((retval = lua_pcall(L, args, rets, 0))) {
va_end(ap);
return retval;
}
if (ret != NULL) {
for (n = rets, types = ret; *types; n--, types++)
switch (*types) {
case 'b':
*(va_arg(ap, int *)) =
lua_toboolean(L, -n);
break;
case 'f':
*(va_arg(ap, double *)) =
lua_tonumber(L, -n);
break;
case 'd':
*(va_arg(ap, int *)) =
lua_tointeger(L, -n);
break;
case 'l':
*(va_arg(ap, long *)) =
(long)lua_tointeger(L, -n);
break;
case 'L':
*(va_arg(ap, long long *)) =
(long long)lua_tointeger(L, -n);
break;
case 's':
*(va_arg(ap, char **)) =
(char *)lua_tostring(L, -n);
break;
default:
lua_pop(L, lua_gettop(L));
lua_pushstring(L, "unknown return type");
va_end(ap);
return LUA_ERRRUN;
}
if (rets)
lua_pop(L, rets);
}
va_end(ap);
return 0;
}