An important use of Lua is as a configuration language. In this chapter, we will illustrate how we can use Lua to configure a program, starting with a simple example and evolving it to perform increasingly complex tasks.
As our first task, let us imagine a simple configuration scenario: our C program has a window and we want the user to be able to specify the initial window size. Clearly, for such a simple task, there are several options simpler than using Lua, like environment variables or files with name-value pairs. But even using a simple text file, we have to parse it somehow; so, we decide to use a Lua configuration file (that is, a plain text file that happens to be a Lua program). In its simplest form, this file can contain something like the following:
-- define window size width = 200 height = 300
Now, we must use the Lua API to direct Lua to parse this file
and then to get the values of the global variables width
and height
.
The function load
, in Figure 28.1, “Getting user information from a configuration file”, does this job.
Figure 28.1. Getting user information from a configuration file
int getglobint (lua_State *L, const char *var) { int isnum, result; lua_getglobal(L, var); result = (int)lua_tointegerx(L, -1, &isnum); if (!isnum) error(L, "'%s' should be a number\n", var); lua_pop(L, 1); /* remove result from the stack */ return result; } void load (lua_State *L, const char *fname, int *w, int *h) { if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0)) error(L, "cannot run config. file: %s", lua_tostring(L, -1)); *w = getglobint(L, "width"); *h = getglobint(L, "height"); }
It assumes that we have already created a Lua state,
following what we saw in the previous chapter.
It calls luaL_loadfile
to load the chunk from the file fname
,
and then calls lua_pcall
to run the compiled chunk.
In case of errors
(e.g., a syntax error in our configuration file),
these functions push the error message onto the stack
and return a non-zero error code;
our program then uses lua_tostring
with index -1
to get the message from the top of the stack.
(We defined the function error
in the section called “A First Example”.)
After running the chunk,
the program needs to get the values of the
global variables.
For that,
it calls the auxiliary function getglobint
(also in Figure 28.1, “Getting user information from a configuration file”) twice.
This function first calls lua_getglobal
,
whose single parameter (besides the omnipresent lua_State
)
is the variable name,
to push the corresponding global value onto the stack.
Next, getglobint
uses lua_tointegerx
to convert this value to an integer,
ensuring that it has the correct type.
Is it worth using Lua for that task? As I said before, for such a simple task, a simple file with only two numbers in it would be easier to use than Lua. Even so, the use of Lua brings some advantages. First, Lua handles all syntax details for us; our configuration file can even have comments! Second, the user is already able to do some complex configurations with it. For instance, the script may prompt the user for some information, or it can query an environment variable to choose a proper size:
-- configuration file if getenv("DISPLAY") == ":0.0" then width = 300; height = 300 else width = 200; height = 200 end
Even in such simple configuration scenarios, it is hard to anticipate what users will want; but as long as the script defines the two variables, our C application works without changes.
A final reason for using Lua is that now it is easy to add new configuration facilities to our program; this ease fosters an attitude that results in programs that are more flexible.
Let us adopt that attitude: now, we want to configure a background color for the window, too. We will assume that the final color specification is composed of three numbers, where each number is a color component in RGB. Usually, in C, these numbers are integers in some range like [0,255]. In Lua, we will use the more natural range [0,1].
A naive approach here is to ask the user to set each component in a different global variable:
-- configuration file width = 200 height = 300 background_red = 0.30 background_green = 0.10 background_blue = 0
This approach has two drawbacks:
it is too verbose
(real programs may need dozens of different colors,
for window background, window foreground, menu background, etc.);
and there is no way to predefine common colors,
so that, later,
the user can simply write something like background = WHITE
.
To avoid these drawbacks, we will use a table to represent a color:
background = {red = 0.30, green = 0.10, blue = 0}
The use of tables gives more structure to the script; now it is easy for the user (or for the application) to predefine colors for later use in the configuration file:
BLUE = {red = 0, green = 0, blue = 1.0}
other color definitions
background = BLUE
To get these values in C, we can do as follows:
lua_getglobal(L, "background"); if (!lua_istable(L, -1)) error(L, "'background' is not a table"); red = getcolorfield(L, "red"); green = getcolorfield(L, "green"); blue = getcolorfield(L, "blue");
We first get the value of the global variable background
and ensure that it is a table;
then we use getcolorfield
to get each color component.
Of course, the function getcolorfield
is not part of the Lua API;
we must define it.
Again, we face the problem of polymorphism:
there are potentially many versions of getcolorfield
functions,
varying the key type, value type, error handling, etc.
The Lua API offers one function, lua_gettable
,
that works for all types.
It takes the position of the table on the stack,
pops the key from the stack,
and pushes the corresponding value.
Our private getcolorfield
,
defined in Figure 28.2, “A particular getcolorfield
implementation”,
Figure 28.2. A particular getcolorfield
implementation
#define MAX_COLOR 255 /* assume that table is on the top of the stack */ int getcolorfield (lua_State *L, const char *key) { int result, isnum; lua_pushstring(L, key); /* push key */ lua_gettable(L, -2); /* get background[key] */ result = (int)(lua_tonumberx(L, -1, &isnum) * MAX_COLOR); if (!isnum) error(L, "invalid component '%s' in color", key); lua_pop(L, 1); /* remove number */ return result; }
assumes that the table is on the top of the stack;
so, after pushing the key with lua_pushstring
,
the table will be at index -2.
Before returning,
getcolorfield
pops the retrieved value from the stack,
leaving the stack at the same level that it was before the call.
We will extend our example a little further and introduce color names for the user. The user can still use color tables, but she can also use predefined names for the more common colors. To implement this feature, we need a color table in our C application:
struct ColorTable {
char *name;
unsigned char red, green, blue;
} colortable[] = {
{"WHITE", MAX_COLOR, MAX_COLOR, MAX_COLOR},
{"RED", MAX_COLOR, 0, 0},
{"GREEN", 0, MAX_COLOR, 0},
{"BLUE", 0, 0, MAX_COLOR},
other colors
{NULL, 0, 0, 0} /* sentinel */
};
Our implementation will create global variables with the color names and initialize these variables using color tables. The result is the same as if the user had the following lines in her script:
WHITE = {red = 1.0, green = 1.0, blue = 1.0}
RED = {red = 1.0, green = 0, blue = 0}
other colors
To set the table fields,
we define an auxiliary function, setcolorfield
;
it pushes the index and the field value on the stack,
and then calls lua_settable
:
/* assume that table is on top */ void setcolorfield (lua_State *L, const char *index, int value) { lua_pushstring(L, index); /* key */ lua_pushnumber(L, (double)value / MAX_COLOR); /* value */ lua_settable(L, -3); }
Like other API functions,
lua_settable
works for many different types,
so it gets all its operands from the stack.
It takes the table index as an argument
and pops the key and the value.
The function setcolorfield
assumes that before the call
the table is on the top of the stack (index -1);
after pushing the index and the value,
the table will be at index -3.
The next function, setcolor
, defines a single color.
It creates a table, sets the appropriate fields,
and assigns this table to the corresponding global variable:
void setcolor (lua_State *L, struct ColorTable *ct) { lua_newtable(L); /* creates a table */ setcolorfield(L, "red", ct->red); setcolorfield(L, "green", ct->green); setcolorfield(L, "blue", ct->blue); lua_setglobal(L, ct->name); /* 'name' = table */ }
The function lua_newtable
creates an empty table
and pushes it on the stack;
the three calls to setcolorfield
set the table fields;
finally, lua_setglobal
pops the table and
sets it as the value of the global with the given name.
With these previous functions, the following loop will register all colors for the configuration script:
int i = 0; while (colortable[i].name != NULL) setcolor(L, &colortable[i++]);
Remember that the application must execute this loop before running the script.
Figure 28.3, “Colors as strings or tables” shows another option for implementing named colors.
Figure 28.3. Colors as strings or tables
lua_getglobal(L, "background"); if (lua_isstring(L, -1)) { /* value is a string? */ const char *name = lua_tostring(L, -1); /* get string */ int i; /* search the color table */ for (i = 0; colortable[i].name != NULL; i++) { if (strcmp(colorname, colortable[i].name) == 0) break; } if (colortable[i].name == NULL) /* string not found? */ error(L, "invalid color name (%s)", colorname); else { /* use colortable[i] */ red = colortable[i].red; green = colortable[i].green; blue = colortable[i].blue; } } else if (lua_istable(L, -1)) { red = getcolorfield(L, "red"); green = getcolorfield(L, "green"); blue = getcolorfield(L, "blue"); } else error(L, "invalid value for 'background'");
Instead of global variables,
the user can denote color names with strings,
writing her settings as background = "BLUE"
.
Therefore, background
can be either a table or a string.
With this design,
the application does not need to do anything before running
the user’s script.
Instead, it needs more work to get a color.
When it gets the value of the variable background
,
it must test whether the value is a string,
and then look up the string in the color table.
What is the best option? In C programs, the use of strings to denote options is not a good practice, because the compiler cannot detect misspellings. In Lua, however, the error message for a misspelt color will probably be seen by the author of the configuration “program”. The distinction between programmer and user is blurred, and so the difference between a compilation error and a run-time error is blurred, too.
With strings,
the value of background
would be the misspelled string;
hence, the application can add this information to the error message.
The application can also compare strings regardless of case,
so that a user can write "white"
, "WHITE"
, or even "White"
.
Moreover, if the user script is small
and there are many colors,
it may be inefficient to register hundreds of colors
(and to create hundreds of tables and global variables)
when the user needs only a few.
With strings, we avoid this overhead.
Although the C API strives for simplicity, Lua is not radical. So, the API offers short cuts for several common operations. Let us see some of them.
Because indexing a table with a string key is so common,
Lua has a specialized version of lua_gettable
for this case: lua_getfield
.
Using this function, we can rewrite the two lines
lua_pushstring(L, key); lua_gettable(L, -2); /* get background[key] */
in getcolorfield
as
lua_getfield(L, -1, key); /* get background[key] */
(As we do not push the string onto the stack,
the table index is still -1 when we call lua_getfield
.)
Because it is common to check the type
of a value returned by lua_gettable
,
in Lua 5.3 this function
(and similar ones like lua_getfield
)
now returns the type of its result.
Therefore,
we can simplify further
the access and the check in getcolorfield
:
if (lua_getfield(L, -1, key) != LUA_TNUMBER) error(L, "invalid component in background color");
As you might expect,
Lua offers also a specialized version of lua_settable
for string keys,
called lua_setfield
.
Using this function, we can rewrite our previous
definition for setcolorfield
as follows:
void setcolorfield (lua_State *L, const char *index, int value) { lua_pushnumber(L, (double)value / MAX_COLOR); lua_setfield(L, -2, index); }
As a small optimization,
we can also replace our use of lua_newtable
in the function setcolor
.
Lua offers another function,
lua_createtable
,
where we create a table and pre-allocate space for entries.
Lua declares these functions like this:
void lua_createtable (lua_State *L, int narr, int nrec); #define lua_newtable(L) lua_createtable(L, 0, 0)
The parameter narr
is the expected number of elements
in the sequence part of the table
(that is, entries with sequential integer indices),
and nrec
is the expected number of other elements.
In setcolor
,
we could write lua_createtable(L, 0, 3)
as a hint that the table will get three entries.
(Lua code does a similar optimization
when we write a constructor.)
A great strength of Lua is that a configuration file can define functions to be called by the application. For instance, we can write in C an application to plot the graph of a function and define in Lua the function to be plotted.
The API protocol to call a function is simple:
first, we push the function to be called;
second, we push the arguments to the call;
then we use lua_pcall
to do the actual call;
finally, we get the results from the stack.
As an example, let us assume that our configuration file has a function like this:
function f (x, y) return (x^2 * math.sin(y)) / (1 - x) end
We want to evaluate, in C,
z = f(x, y)
for given x
and y
.
Assuming that we have already opened the Lua library and run
the configuration file,
the function f
in Figure 28.4, “Calling a Lua function from C”
evaluates that code.
Figure 28.4. Calling a Lua function from C
/* call a function 'f' defined in Lua */ double f (lua_State *L, double x, double y) { int isnum; double z; /* push functions and arguments */ lua_getglobal(L, "f"); /* function to be called */ lua_pushnumber(L, x); /* push 1st argument */ lua_pushnumber(L, y); /* push 2nd argument */ /* do the call (2 arguments, 1 result) */ if (lua_pcall(L, 2, 1, 0) != LUA_OK) error(L, "error running function 'f': %s", lua_tostring(L, -1)); /* retrieve result */ z = lua_tonumberx(L, -1, &isnum); if (!isnum) error(L, "function 'f' should return a number"); lua_pop(L, 1); /* pop returned value */ return z; }
The second and third arguments to lua_pcall
are the number of arguments we are passing
and the number of results we want.
The fourth argument indicates a message-handling function;
we will discuss it in a moment.
As in a Lua assignment,
lua_pcall
adjusts the actual number of results
to what we have asked for,
pushing nils or discarding extra values as needed.
Before pushing the results,
lua_pcall
removes from the stack
the function and its arguments.
When a function returns multiple results,
the first result is pushed first;
for instance, if there are three results,
the first one will be at index -3
and the last at index -1.
If there is any error while lua_pcall
is running,
lua_pcall
returns an error code;
moreover, it pushes the error message on the stack
(but still pops the function and its arguments).
Before pushing the message, however,
lua_pcall
calls the message handler function,
if there is one.
To specify a message handler function,
we use the last argument of lua_pcall
.
Zero means no message handler function;
that is, the final error message is the original message.
Otherwise, this argument should be the index on the stack where
the message handler function is located.
In such cases,
we should push the handler on the stack
somewhere below the function to be called.
For normal errors, lua_pcall
returns the
error code LUA_ERRRUN
.
Two special kinds of errors deserve different codes,
because they never run the message handler.
The first kind is a memory allocation error.
For such errors, lua_pcall
returns LUA_ERRMEM
.
The second kind is an error
while Lua is running the message handler itself.
In this case, it is of little use to call the handler again,
so lua_pcall
returns immediately with a code LUA_ERRERR
.
Since version 5.2, Lua differentiates a third kind of error:
when a finalizer raises an error,
lua_pcall
returns the code LUA_ERRGCMM
(error in a GC metamethod).
This code indicates that the error
is not directly related to the call itself.
As a more advanced example,
we will build a wrapper for calling Lua functions,
using the stdarg
facility in C.
Our wrapper function, let us call it call_va
,
takes the name of a global function to be called,
a string describing the types of the arguments and results,
then the list of arguments, and finally a list of pointers to
variables to store the results;
it handles all the details of the API.
With this function, we could write our example
in Figure 28.4, “Calling a Lua function from C” simply like this:
call_va(L, "f", "dd>d", x, y, &z);
The string "dd>d"
means
“two arguments of type double, one result of type double”.
This descriptor can use the letters d
for double,
i
for integer,
and s
for strings;
a >
separates arguments from the results.
If the function has no results, the >
is optional.
Figure 28.5, “A generic call function” shows the implementation of call_va
.
Figure 28.5. A generic call function
#include <stdarg.h> void call_va (lua_State *L, const char *func, const char *sig, ...) { va_list vl; int narg, nres; /* number of arguments and results */ va_start(vl, sig); lua_getglobal(L, func); /* push function */push and count arguments (Figure 28.6, “Pushing arguments for the generic call function”)
nres = strlen(sig); /* number of expected results */ if (lua_pcall(L, narg, nres, 0) != 0) /* do the call */ error(L, "error calling '%s': %s", func, lua_tostring(L, -1));retrieve results (Figure 28.7, “Retrieving results for the generic call function”)
va_end(vl); }
Despite its generality, this function follows the same steps of our first example: it pushes the function, pushes the arguments (Figure 28.6, “Pushing arguments for the generic call function”), does the call, and gets the results (Figure 28.7, “Retrieving results for the generic call function”).
Figure 28.6. Pushing arguments for the generic call function
for (narg = 0; *sig; narg++) { /* repeat for each argument */ /* check stack space */ luaL_checkstack(L, 1, "too many arguments"); switch (*sig++) { case 'd': /* double argument */ lua_pushnumber(L, va_arg(vl, double)); break; case 'i': /* int argument */ lua_pushinteger(L, va_arg(vl, int)); break; case 's': /* string argument */ lua_pushstring(L, va_arg(vl, char *)); break; case '>': /* end of arguments */ goto endargs; /* break the loop */ default: error(L, "invalid option (%c)", *(sig - 1)); } } endargs:
Figure 28.7. Retrieving results for the generic call function
nres = -nres; /* stack index of first result */ while (*sig) { /* repeat for each result */ switch (*sig++) { case 'd': { /* double result */ int isnum; double n = lua_tonumberx(L, nres, &isnum); if (!isnum) error(L, "wrong result type"); *va_arg(vl, double *) = n; break; } case 'i': { /* int result */ int isnum; int n = lua_tointegerx(L, nres, &isnum); if (!isnum) error(L, "wrong result type"); *va_arg(vl, int *) = n; break; } case 's': { /* string result */ const char *s = lua_tostring(L, nres); if (s == NULL) error(L, "wrong result type"); *va_arg(vl, const char **) = s; break; } default: error(L, "invalid option (%c)", *(sig - 1)); } nres++; }
Most of its code is straightforward,
but there are some subtleties.
First, it does not need to check whether func
is a function:
lua_pcall
will trigger that error.
Second, because it pushes an arbitrary number of arguments,
it must ensure that there is enough stack space.
Third, because the function can return strings,
call_va
cannot pop the results from the stack.
It is up to the caller to pop them,
after it finishes using any string results
(or after copying them to appropriate buffers).
Exercise 28.1:
Write a C program that reads a Lua file defining a function f
from numbers to numbers and plots that function.
(You do not need to do anything fancy;
the program can plot the results printing ASCII asterisks
as we did in the section called “Compilation”.)
Exercise 28.2:
Modify the function call_va
(Figure 28.5, “A generic call function”)
to handle Boolean values.
Exercise 28.3: Let us suppose a program that needs to monitor several weather stations. Internally, it uses a four-byte string to represent each station, and there is a configuration file to map each string to the actual URL of the corresponding station. A Lua configuration file could do this mapping in several ways:
a bunch of global variables, one for each station;
a table mapping string codes to URLs;
a function mapping string codes to URLs.
Discuss the pros and cons of each option, considering things like the total number of stations, the regularity of the URLs (e.g., there may be a formation rule from codes to URLs), the kind of users, etc.
Personal copy of Eric Taylor <jdslkgjf.iapgjflksfg@yandex.com>