ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Introducing Lua

by Keith Fieldhouse
02/16/2006

There's no reason that web developers should have all the fun. Web 2.0 APIs enable fascinating collaborations between developers and an extended community of developer-users. Extension and configuration APIs added to traditional applications can generate the same benefits.

Of course, extensibility isn't a particularly new idea. Many applications have a plugin framework (think Photoshop) or an extension language (think Emacs). What if you could provide a seamlessly integrated, fully dynamic language with a conventional syntax while increasing your application's size by less than 200K on an x86? You can do it with Lua!

Lua Basics

Roberto Ierusalimschy of the Pontifical Catholic University of Rio de Janeiro in Brazil leads the development of Lua. The most recent version (5.0.2; version 5.1 should be out soon) is made available under the MIT license. Lua is written in 99 percent ANSI C. Its main design goals are to be compact, efficient, and easy to integrate with other C/C++ programs. Game developers (such as World of Warcraft developer Blizzard Entertainment) are increasingly using Lua as an extension and configuration language.

Virtually anyone with any kind of programming experience should find Lua's syntax concise and easy to read. Two dashes introduce comments. An end statement delimits control structures (if, for, while). All variables are global unless explicitly declared local. Lua's fundamental data types include numbers (typically represented as double-precision floating-point values), strings, and Booleans. Lua has true and false as keywords; any expression that does not evaluate to nil is true. Note that 0 and arithmetic expressions that evaluate to 0 do not evaluate to nil. Thus Lua considers them as true when you use them as part of a conditional statement.

Finally, Lua supports userdata as one of its fundamental data types. By definition, a userdata value can hold an ANSI C pointer and thus is useful for passing data references back and forth across the C-Lua boundary.

Despite the small size of the Lua interpreter, the language itself is quite rich. Lua uses subtle but powerful forms of syntactic sugar to allow the language to be used in a natural way in a variety of problem domains, without adding complexity (or size) to the underlying virtual machine. The carefully chosen sugar results in very clean-looking Lua programs that effectively convey the nature of the problem being solved.

The only built-in data structure in Lua is the table. Perl programmers will recognize this as a hash; Python programmers will no doubt see a dictionary. Here are some examples of table usage in Lua:

a      = {}       -- Initializes an empty table
a[1]   = "Fred"   -- Assigns "Fred" to the entry indexed by the number 1
a["1"] = 7        -- Assigns the number 7 to the entry indexed by the string "1"

Any Lua data type can serve as a table index, making tables a very powerful construct in and of themselves. Lua extends the capabilities of the table by providing different syntactic styles for referencing table data. The standard table constructor looks like this:

t = { "Name"="Keith", "Address"="Ballston Lake, New York"}

A table constructor written like

t2 = { "First", "Second","Third"}

is the equivalent of

t3 = { 1="First", 2="Second", 3="Third" }

This last form essentially initializes a table that for all practical purposes behaves as an array. Arrays created in this way have as their first index the integer 1 rather than 0, as is the case in other languages.

The following two forms of accessing the table are equivalent when the table keys are strings:

t3["Name"] = "Keith"
t3.Name    = "Keith"

Tables behave like a standard struct or record when accessed in this fashion.

Variations on a Theme

The following variations on the venerable "Hello World" program illustrate the malleable and extensible nature of the language. First, the minimal implementation:

print("Hello World")

Here's a function declaration that accepts the name of the person to greet. The double "dot" operator does string concatenation:

function sayhello(person)
   print("Hello "..person)
end

This form of function definition masks a powerful feature of Lua: functions themselves are first-class data types. The underlying form of function definition is actually the following:

sayhello = function(person) 
    print("Hello "..person)
end

You may assign functions to variables, pass them to other functions as parameters, place them in tables, and generally deal with them in the same way as any other Lua value. Functions in Lua do not have names. Rather, you apply the () operator to variables that hold function references in order to call the function. To call a function reference stored in a table element, use the following:

t = {}
function t.sayhello(name)
    print (Hello..name)
end

t.sayhello("Fred")

This starts to look a lot like a method invocation except that there's no self or this reference for the function to access. Lua takes care of this by adding another piece of syntactic sugar, the : operator. Referencing the table element with a : instead of a . causes Lua to automatically pass a parameter, self, to the function. So:

t      = {}
t.Name = Fred

function t:sayhello()
   print (Hello..self.Name)
end

t:sayhello()
-- Or, other expressions that mean the same thing:
t.sayhello(t)
t['sayhello'](t)

This is object encapsulation: a way to combine an object's data with the code that operates on that data. Thus far, there isn't any way to create instances of an object. You must assemble objects by hand individually.

Lua uses a powerful meta table concept that among other things can bridge this final gap to object oriented programming (still without changing the underlying VM). A meta table is a Lua table that has been attached to a given table with the setmetatable built-in function. Lua uses the meta table for certain special functions that control how its associated table behaves.

Producing Open Source Software

Related Reading

Producing Open Source Software
How to Run a Successful Free Software Project
By Karl Fogel

Several special entries can go into a meta table. In this example, the most important one is __index. This entry contains a table or a function that Lua will consult if its associated table does not have a requested entry. Consider a meta table M whose __index entry is a table P. If M is the meta table for table T, whenever code requests an entry in T that does not exist, Lua will consult table P to see if it has the requested entry. P of course could have its own meta table data. Also, a table can be its own meta table, in which case entries such as __index will be in the table and will show up in table iterators, for example, but will otherwise behave in the same way.

Given this, the Lua programmer can now indulge in some actual object-oriented programming by providing a way to create "instances" of a Hello object. The base object looks like this:

Hello = {}
function Hello:sayhello()
    print("Hello "..self.Name)
end

Add a constructor, New, with:

function Hello:New(name)
    -- Create a new, empty table as in instance of the hello object
    local instance = {}

    -- Initialize the Name member
    instance.Name  = name

    -- Set the Hello object as the metatable and
    -- __index table of the instance.  This way 
    -- the Hello object is searched for any member 
    -- (typically methods) the instance doesn't have.
    setmetatable(instance,self)
    self.__index   = self

    -- Return the instance 
    return instance
end

fred = Hello:New("Fred");
fred:sayhello()

In this constructor, the Hello table is both the meta table and the __index table of the instance object. Even though the instance table does not have, for example, the sayhello function in it, Lua can find it through the auspices of the meta table's __index entry. Object-oriented programming cognoscenti will recognize this as a prototype-based object system similar to that of the Self and JavaScript languages.

In addition to the language itself, Lua comes with a set of runtime libraries. These libraries are also written in ANSI C and are generally linked with the Lua interpreter. As a result, there's no need to set PATH environment variables or to deliver ancillary files when deploying an application with Lua integrated.

Extending Lua

Lua is a powerful language that can express solutions to problems in a variety of domains. Yet Python, Ruby and Perl are also quite powerful in their way. Lua's primary advantage over other languages is its compact, efficient size. This size, and the ease of integrating and extending Lua into a particular programming problem is the reason Lua is worthy of examination.

Lua will probably be easy to integrate into a project build system because it's ANSI C code that requires little in the way of configuration and depends on no external libraries (beyond the C runtime library). It is likely that it will be possible to simply add the Lua interpreter code from the distribution directly into a pre-existing build system. A review of the Makefiles that come with Lua will reveal any useful configuration settings. It's easy to configure Lua for virtually any platform that supports ANSI C development.

An easy use for Lua in a program is to process a Lua file as configuration file and retrieve global values set by the Lua code to configure the behavior of the application.

Imagine a program that does some lengthy processing. The user needs to be able to set a maximum time that that processing should continue before being canceled if necessary. Call this value maxtime. Here's a function that represents a loadconfig function the program can call to process the user's configuration file:

#include <lua.h>
#include <lualib.h>

void loadconfig(char *file, int *maxtime)
{
    /* Start the lua library */
    lua_State *L = lua_open();

    /* We'll open the Math Library for them for calculations */
    lua_pushcfunction(L,luaopen_math);
    lua_pushstring(L,LUA_MATHLIBNAME);
    lua_call(L,1,0);

    /* Load and compile the file, the use lua_pcall to interpret it */
    if (luaL_loadfile(L,file) || lua_pcall(L,0,0,0))
      /* my_lua_error simply prints an error message and exits */
      my_lua_error(L,"cannot load file: %s",lua_tostring(L,-1));

    /* Get the Lua maxtime global variable onto the stack */
    lua_getglobal(L,"maxtime");

    /* Check that it is in fact, a number. The -1 indicates the
     * first stack position from the top of the stack. */
    if (!lua_isnumber(L,-1))
       my_lua_error(L,"maxtime should be a number\n");

    /* Take the value off the stack and keep it */
    *maxtime = (int)lua_tonumber(L,-1);

    /* And we're done */
    lua_close(L);
}

The user can use os.getenv to consult the HOSTNAME environment variable and set maxtime to values that are consistent with the use policies associated with given machines.

Eventually it will be useful to provide more than global variables for users to manipulate. Extending Lua so that there's an available API, tailored to your program, provides an increased level of flexibility for the user. One thing to keep in mind is that the Lua runtime library contains an excellent supply of sample code to review.

Every Lua extension function written in C has the same signature. It accepts a Lua context object, traditionally called L, and then returns an integer that indicates the number of return values. (Lua functions can return more than one value.) You can obtain parameters and return values through a simple stack-based API that operates on the context object. The API provides the ability to push and pop any of the Lua data types off or on the stack. There is also a set of API functions for manipulating tables (passed by reference) and strings.

To show off extending Lua, here's an example that makes the gethostbyaddr function, which returns information about TCP/IP hostnames given an IP address, available in Lua. In C, the gethostbyaddr function returns a hostent struct. The members of that struct include the hostname, the address type, the size of the address, any aliases associated with the hostname, and some other information. The Lua function takes a host address as a dotted decimal string and returns two values, the primary hostname and an array of host aliases. It could instead return a single table duplicating the hostent struct, but this provides a simpler and hopefully clearer example.

The code takes the host address off of the stack (the first parameter). Then it calls the C version of gethostbyaddr. Finally, it pulls out the fields of the struct and places them on the stack as return values.

/* Return a hostname and a table of aliases given an IP 
 * address in dotted decimal notation */
static int l_gethostnames(lua_State *L)
{
  /* Obtain our argument and verify that it is a string */
  const char *addr = luaL_checkstring(L,1);

  /* Convert the argument into the addr gethostbyaddr expects */
  unsigned long iaddr = inet_addr(addr);

  /* We'll return a table starting at t[1] in the lua style */
  int tableIndex = 1;
  
  /* Make the call to gethostbyaddr */
  struct hostent *h = gethostbyaddr((char *)&iaddr,sizeof(iaddr),AF_INET);

  if (!h) {
    /* If gethostbyaddr fails, we'll just return two nils */
    lua_pushnil(L);
    lua_pushnil(L);
  } else {
    /* gethostbyaddr has returned a value.  Push the primary name */
    lua_pushstring(L,h->h_name);
    
    /* Now, create a table.  So far we've pushed a string
     * and an empty table on the stack */
    lua_newtable(L);

    /* Loop over the aliases if any and put them in the table we've
     * created on the stack.  Remember to adjust the indices between Lua and C
     */
    while (h->h_aliases[tableIndex - 1]) {
      /* First we push the key and the value for this table entry on
       * the stack.
       */
      lua_pushnumber(L,tableIndex);
      lua_pushstring(L,h->h_aliases[tableIndex - 1]);

      /* lua_settable takes the last two items on the stack and 
       * treats them as a key,value pair.  This pair is added 
       * to the table at index.  Negative indices are back from the 
       * top of the stack. In this case the key is at -2, the value at
       * -1 and the table we create with lua_newtable is at -3
       */
      lua_settable(L,-3);
      tableIndex++;
    }
  }

  /* Through either path we've left two items on the stack to 
   * return. */
  return 2;
}

Add calls to lua_pushcfunction(L,l_gethostnames); lua_setglobal(L,"gethostnames"); somewhere after initializing the Lua interpreter to make the gethostnames function available from Lua.

Conclusion

At this point, you have a reasonable idea of what the Lua language is like. You also have seen how readily you can integrate it into your application. If you're interested in pursuing Lua, you should avail yourself of these resources. First and most importantly, spend some time with Roberto Ierusalimschy's book Programming in Lua, which is available both as a printed volume and for free online. This excellent resource goes well beyond the Lua Reference Manual and illuminates the language and its use very effectively. The Lua user community provides excellent resources including a wiki and a mailing list. Finally, LuaForge is an online repository of Lua-based projects and libraries.

Keith Fieldhouse is a software developer and writer living in upstate New York with his wife and two young daughters.


Return to ONLamp.com.

Copyright © 2009 O'Reilly Media, Inc.