28 Extending Your Application

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.

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”,

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.

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.

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.

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”).

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>