 | Level: Introductory Martin Streicher (martin.streicher@linux-mag.com), Editor-in-Chief, Linux Magazine
28 Apr 2006 Compiled programming languages and scripting languages each have unique advantages, but what if you could use both to create rich applications? Lua is an embeddable scripting language that is small, fast, and very powerful. Before you create yet another configuration file or resource format (and yet another parser to accompany it), try Lua.
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 Resources 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 Resources) 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 Resources 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:
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 Resources).
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:
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.value b 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 Resources) 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 Resources) 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.
Resources Learn
Get products and technologies
-
Get the Lua 5.0.2 or Lua 5.1 source code to build Lua from scratch.
-
At the Lua-users wiki, browse a variety of pre-built, installable Lua binaries.
-
At LuaForge, discover a vast library of code, including many language bindings and specialized computation libraries.
-
Order the SEK for Linux, a two-DVD set containing the latest IBM trial software for Linux from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
-
With IBM trial software, available for download directly from developerWorks, build your next development project on Linux.
Discuss
About the author  | |  | Martin Streicher is the Editor-in-Chief of Linux Magazine. He earned a master's degree in computer science from Purdue University and has been programming UNIX-like systems since 1982 in the Pascal, C, Perl, Java, and (most recently) Ruby programming languages. |
Rate this page
|  |