Contents


Embeddable scripting with Lua

Lua offers high-level abstraction without losing touch with the hardware

Comments

While interpreted programming languages such as Perl, Python, PHP, and Ruby are increasingly favored for Web applications -- and have long been preferred for automating system administration tasks -- compiled programming languages such as C and C++ are still necessary. The performance of compiled programming languages remains unmatched (exceeded only by the performance of hand-tuned assembly), and certain software -- including operating systems and device drivers -- can only be implemented efficiently using compiled code. Indeed, whenever software and hardware need to mesh seamlessly, programmers instinctively reach for a C compiler: C is primitive enough to get "close to the bare metal" -- that is, to capture the idiosyncrasies of a piece of hardware -- yet expressive enough to offer some high-level programming constructs, such as structures, loops, named variables, and scope.

However, scripting languages have distinct advantages, too. For example, after a language's interpreter is successfully ported to a platform, the vast majority of scripts written in that language run on the new platform unchanged -- free of dependencies such as system-specific function libraries. (Think of the many DLL files of the Microsoft® Windows® operating system or the many libcs of UNIX® and Linux®.) Additionally, scripting languages typically offer higher-level programming constructs and convenience operations, which programmers claim boost productivity and agility. Moreover, programmers working in an interpreted language can work faster, because the compilation and link steps are unnecessary. The "code, build, link, run" cycle of C and its ilk is reduced to a hastened "script, run."

Lua novelties

Like every scripting language, Lua has its own peculiarities:

  • Lua types. In Lua, values have types, but variables are dynamically typed. The nil, boolean, number, and string types work as you might expect.
    • Nil is the type of the special value nil and is used to represent the absence of a value.
    • Boolean is the type of the constants true and false. (Nil also represents false, and any non-nil value represents true.)
    • All numbers in Lua are doubles (but you can easily build code to realize other numeric types).
    • A string is an immutable array of characters. (Hence, to append to a string, you must make a copy of it.)
  • The table, function, and thread types are all references. Each can be assigned to a variable, passed as an argument, or returned from a function. For instance, here's an example of storing a function:

    -- example of an anonymous function
    -- returned as a value
    -- see http://www.tecgraf.puc-rio.br/~lhf/ftp/doc/hopl.pdf
    function add(x)
      return function (y) return (x + y) end
    end
    f = add(2)
    print(type(f), f(10))
    function  12
  • Lua threads. A thread is a co-routine created by calling the built-in function coroutine.create(f), where f is a Lua function. Threads do not start when created; instead, a co-routine is started after the fact, using coroutine.resume(t), where t is a thread. Every co-routine must occasionally yield the processor to other co-routines using coroutine.yield().
  • Assignment statements. Lua allows multiple assignments, and expressions are evaluated first and are then assigned. For example, the statements:

    i = 3
    a = {1, 3, 5, 7, 9}
    i, a[i], a[i+1], b = i+1, a[i+1], a[i]
    print (i, a[3], a[4], b, I)

    produce 4 7 5 nil nil. If the list of variables is larger than the list of values, excess variables are assigned nil; hence, b is nil. If there are more values than variables, extra values are simply discarded. In Lua, variable names are case-sensitive, explaining why I is nil.
  • Chunks. A chunk is any sequence of Lua statements. A chunk can be stored in a file or in a string in a Lua program. Each chunk is executed as the body of an anonymous function. Therefore, a chunk can define local variables and return values.
  • More cool stuff. Lua has a mark-and-sweep garbage collector. As of Lua 5.1, the garbage collector works incrementally. Lua has full lexical closures (like Scheme and unlike Python). And Lua has reliable tail call semantics (again, like Scheme and unlike Python).

Find more examples of Lua code in Programming in Lua and in the Lua-users wiki (for links, see the Related topics section below).

As in all engineering pursuits, choosing between a compiled language and an interpreted language means measuring the pros and cons of each in context, weighing the trade-offs, and accepting compromises.

Mixing the best of both worlds

But what if you could enjoy the best of both worlds: close to bare metal performance and high-level, powerful abstractions? Better yet, what if you could optimize algorithms and functions that are processor intensive and system dependent as well as separate logic that's system independent and highly susceptible to changes in requirements?

Balancing the need for high-performance code and high-level programming is the purview of Lua, an embeddable scripting language. Applications that include Lua are a combination of compiled code and Lua scripts. Compiled code can drop to the metal when necessary, yet can call Lua scripts to manipulate complex data. And because the Lua scripts are separate from the compiled code, you readily and separately revise the scripts. With Lua, the development cycle is more akin to "Code, Build, Run, Script, Script, Script...".

For example, the Lua Web site "Uses" page (see Related topics) lists several mass-market computer games, including World of Warcraft and (the home console version of arcade classic) Defender, that integrate Lua to run everything from the user interface to the artificial intelligence of foes. Other applications of Lua include an extension mechanism for the popular Linux software update tool apt-rpm and the control of the "Crazy Ivan" Robocup 2000 champion. Many of the testimonials on that page laud Lua's small size and excellent performance.

Getting started with Lua

Lua version 5.0.2 was the current version at the time of this writing, although version 5.1 was released recently. You can download Lua as source code from lua.org, and you can find a variety of pre-built binaries at the Lua-users wiki (see Related topics for links). The entire Lua 5.0.2 core, including the standard libraries and the Lua compiler, is less than 200KB in size.

If you use Debian Linux, you can install Lua 5.0 quickly and easily by running this command:

# apt-get install lua50

as the superuser. All the examples shown here were run on Debian Linux "Sarge" using Lua 5.0.2 and the 2.4.27-2-686 Linux kernel.

After you've installed Lua on your system, give the stand-alone Lua interpreter a try. (All Lua applications must be embedded in a host application. The interpreter is simply a special kind of host, useful for development and debugging.) Create a file called factorial.lua, and enter the lines:

-- defines a factorial function
function fact (n)
  if n == 0 then
    return 1
  else
    return n * fact(n-1)
  end
end

print("enter a number:")
a = io.read("*number")
print(fact(a))

The code in factorial.lua -- more specifically, any sequence of Lua statements -- is called a chunk, as described in the Lua novelties section above. To execute the chunk you just created, run the command lua factorial.lua:

$ lua factorial.lua
enter a number:
10
3628800

Or, as in other interpreted languages, you can add a "shebang" (#!) line at the top of the script, make the script executable, and then run the file as a stand-alone command:

$ (echo '#! /usr/bin/lua'; cat factorial.lua) > factorial 
$ chmod u+x factorial
$ ./factorial
enter a number:
4
24

The Lua language

Lua has many of the conveniences found in a modern scripting language: scope, control structures, iterators, and a host of standard libraries for processing strings, emitting and collecting data, and performing mathematical operations. A complete description of the Lua language is in the Lua 5.0 Reference Manual (see Related topics).

In Lua, only values have a type, but variables are dynamically typed. There are eight fundamental types (of values) in Lua: nil, boolean, number, string, function, thread, table, and userdata. The first six types are largely self-descriptive (see the Lua novelties section above for the exceptions); the last two need some explanation.

Lua tables

Tables are the catch-all data structure in Lua. Indeed, tables are the only data structure in Lua. You can use a table as an array, a dictionary (also called a hash or an associative array), a tree, a record, and so on.

Unlike other programming languages, the contents of a Lua table need not be homogeneous: The table can include any combination of types and can contain a mix of array-like elements and dictionary-like elements. Moreover, any Lua value -- including a function or another table -- can serve as a dictionary element key.

To explore tables, start the Lua interpreter and type the lines shown in bold in Listing 1.

Listing 1. Experimenting with Lua tables
$ lua
> -- create an empty table and add some elements
> t1 = {}
> t1[1] = "moustache"
> t1[2] = 3
> t1["brothers"] = true

> -- more commonly, create the table and define elements
> all at once
> t2 = {[1] = "groucho", [3] = "chico", [5] = "harpo"}
> t3 = {[t1[1]] = t2[1], accent = t2[3], horn = t2[5]}
> t4 = {}
> t4[t3] = "the marx brothers"
> t5 = {characters = t2, marks = t3}
> t6 = {["a night at the opera"] = "classic"}

> -- make a reference and a string
> i = t3
> s = "a night at the opera"

> -- indices can be any Lua value
> print(t1[1], t4[t3], t6[s])
moustache   the marx brothers classic

> -- the phrase table.string is the same as table["string"]
> print(t3.horn, t3["horn"])
harpo   harpo

> -- indices can also be "multi-dimensional"
> print (t5["marks"]["horn"], t5.marks.horn)
harpo   harpo

> -- i points to the same table as t3
> = t4[i]
the marx brothers

> -- non-existent indices return nil values
> print(t1[2], t2[2], t5.films)
nil     nil     nil

>  -- even a function can be a key 
> t = {}
> function t.add(i,j)
>> return(i+j)
>> end
> print(t.add(1,2))
3
> print(t['add'](1,2))
3
>  -- and another variation of a function as a key 
> t = {}
> function v(x)
>> print(x)
>> end
> t[v] = "The Big Store"
> for key,value in t do key(value) end
The Big Store

As you might expect, Lua also provides a fair number of iterator functions to process tables. The global variable table provides the functions (yes, Lua packages are just tables, too). Some functions, like table.foreachi(), expect a contiguous range of integer keys starting at 1 (the numeral one):

> table.foreachi(t1, print)
1 moustache
2 3

Others, such as table.foreach(), iterate over an entire table:

> table.foreach(t2,print)
1       groucho
3       chico
5       harpo
> table.foreach(t1,print)
1       moustache
2       3
brothers        true

While some iterators are optimized for integer indices, all simply process (key, value) pairs.

For fun, create a table, t, with elements {2, 4, 6, language="Lua", version="5", 8, 10, 12, web="www.lua.org"}, and run table.foreach(t, print) and table.foreachi(t, print).

Userdata

Because Lua is intended to be embedded in a host application written in a language such as C or C++ and is intended to cooperate with the host application, data must be shared between the C environment and Lua. As the Lua 5.0 Reference Manual says, the userdata type allows "arbitrary C data to be stored in Lua variables." You can think of userdata as an array of bytes -- bytes that might represent a pointer, a structure, or a file in the host application.

The contents of userdata originates with C, so it cannot be modified in Lua. Of course, because the userdata originates in C, there are no pre-defined operations for userdata in Lua. However, you can create operations that operate on userdata using another Lua mechanism, called metatables.

Metatables

Because tables and userdata are so flexible, Lua permits you to overload operations for objects of either type. (You cannot overload the six other types.) A metatable is a (normal) Lua table that maps standard operations to custom functions that you provide. The keys of the metatable are called events; the values (in other words, the functions) are called metamethods.

The functions setmetatable() and getmetatable() modify and query an object's metatable, respectively. Each table and userdata object can have its own metatable.

For example, one event is __add, for addition. Can you deduce what this chunk does?

-- Overload the add operation
-- to do string concatenation
--
mt = {}

function String(string)
  return setmetatable({value = string or ''}, mt)
end

-- The first operand is a String table
-- The second operand is a string
-- .. is the Lua concatenate operator
--
function mt.__add(a, b)
  return String(a.value..b)
end

s = String('Hello')
print((s + ' There ' + ' World!').value )

This chunk emits the following text:

Hello There World!

The function String() takes a string, string, wraps it in a table ({value = s or ''}), and assigns the metatable mt to the table. Function mt.__add() is a metamethod that appends the string b to the string found in a.valueb times. The line print((s + ' There ' + ' World!').value ) invokes the metamethod twice.

__index is another event. The metamethod for __index is called whenever a key doesn't exist in a table. Here's an example that "memoizes," or remembers, the value of a function:

-- code courtesy of Rici Lake, rici@ricilake.net
function Memoize(func, t)
  return setmetatable(
     t or {},
    {__index =
      function(t, k)
        local v = func(k);
        t[k] = v;
        return v;
        end
    }
  )
end

COLORS = {"red", "blue", "green", "yellow", "black"}
color = Memoize(
  function(node)
    return COLORS[math.random(1, table.getn(COLORS))]
    end
)

Put the code into the Lua interpreter, and then type print(color[1], color[2], color[1]). You should see something like blue black blue.

This code, given a key, node, looks up the color for the node. If it doesn't exist, the code permanently assigns node a new, randomly chosen color. Otherwise, the node's assigned color is returned. In the former case, the __index metamethod is executed once to assign a color. The latter case is a simple and fast hash lookup.

The Lua language offers many other powerful features, and all of them are well documented. But in case you ever run into trouble or want to talk to a wizard, head over to the Lua Users Chat Room IRC Channel (see Related topics) for some very enthusiastic support.

Embed and extend

Beyond Lua's simple syntax and powerful table structure, Lua's real power is evident when mixing it with a host language. As has been intimated, Lua scripts can extend the host language's own capabilities. But the reciprocal is true as well: The host language can simultaneously extend Lua. C functions can call Lua functions and vice versa, for example.

At the heart of the symbiotic relationship between Lua and its host language is a virtual stack. The virtual stack -- like a real stack -- is a last in-first out (LIFO) data structure that temporarily stores function arguments and function results. To make a call from Lua to its host language and vice versa, the caller pushes values onto the stack and calls the target function; the callee pops the arguments (verifying the type and value of each argument, of course), processes the data, and pushes the result on the stack. When control returns to the caller, the caller extracts the return values from the stack.

Virtually all the C application program interface (API) for Lua operations operate through the stack. The stack can hold any Lua value; however, the type of the value must be known to the caller and callee, and specific functions push and pop each type from the stack (such as lua_pushnil() and lua_pushnumber().

Listing 2 shows a simple C program (taken from Chapter 24 of the Programming in Lua book cited in Related topics) that implements a minimal but functional Lua interpreter.

Listing 2. A simple Lua interpreter
 1 #include <stdio.h>
 2 #include <lua.h>
 3 #include <lauxlib.h>
 4 #include <lualib.h>
 5
 6 int main (void) {
 7   char buff[256];
 8   int error;
 9   lua_State *L = lua_open();   /* opens Lua */
10   luaopen_base(L);             /* opens the basic library */
11   luaopen_table(L);            /* opens the table library */
12   luaopen_io(L);               /* opens the I/O library */
13   luaopen_string(L);           /* opens the string lib. */
14   luaopen_math(L);             /* opens the math lib. */
15
16   while (fgets(buff, sizeof(buff), stdin) != NULL) {
17     error = luaL_loadbuffer(L, buff, strlen(buff), "line") ||
18             lua_pcall(L, 0, 0, 0);
19     if (error) {
20       fprintf(stderr, "%s", lua_tostring(L, -1));
21       lua_pop(L, 1);  /* pop error message from the stack */
22     }
23   }
24
25   lua_close(L);
26   return 0;
27 }

Lines 2 through 4 include the Lua standard functions, several convenience functions that are used in all Lua libraries, and functions to open libraries, respectively. Line 9 creates a Lua state. All states are initially empty; you add libraries of functions to the state using luaopen_...(), as shown in Lines 10 through 14.

Line 17 and luaL_loadbuffer() take the input from stdin as a chunk and compile it, placing the chunk on the virtual stack. Line 18 pops the chunk from the stack and executes it. If an error occurs during execution, a Lua string is pushed on the stack. Line 20 accesses the top of the stack (the top of the stack has an index of -1) as a Lua string, prints the message, and then removes the value from the stack.

Using the C API, your application can also "reach" into the Lua state to extract information. This snippet grabs two global variables from the Lua state:

..
if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))
  error(L, "cannot run configuration file: %s", lua_tostring(L, -1));

lua_getglobal(L, "width");
lua_getglobal(L, "height");
..
width = (int) lua_tonumber(L, -2);
height = (int) lua_tonumber(L, -1);
..

Again, notice that the stack enables the transfer. Calling any Lua function from C is similar to this code: Grab the function using lua_getglobal(), push arguments, make a lua_pcall(), and then process the results. If a Lua function returns n values, the first value is at location -n in the stack, and the last value is at position -1.

The inverse -- calling a C function from Lua -- is similar. If your operating system supports dynamic loading, Lua can load and call functions on demand. (In operating systems where static loading is a necessity, extending the Lua engine to call a C function requires you to rebuild Lua.)

Lua is out of this world

Lua is an incredibly easy language to pick up, but its simple syntax disguises its power: The language supports objects (which are similar to Perl's), metatables make its table type quite malleable, and the C API allows great integration and extension between scripts and the host language. Lua has been hosted in the C, C++, C#, Java™, and Python languages.

Before you create yet another configuration file or resource format (and yet another parser to accompany it), try Lua. The Lua language -- like its community -- is robust, inventive, and ready to help.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux, Open source
ArticleID=110233
ArticleTitle=Embeddable scripting with Lua
publish-date=04282006