title logo

back contents forward

Chapter 8

metaprogramming

Lua is provided as a C library, for incorporation into programs written in C. Whether you think of this as adding Lua sauce to another dish, or as adding an envelope to contain Lua, the implications are much the same. Programming Lua at the API level means writing programs in C to provide an application, say an interpreter, that can act on Lua programs. Programming at the Lua level means writing programs in Lua for the interpreter to run.

Lua has been designed to be portable and to be extendable. Most possibilities for extension require programming at the API level, which is not within our scope. However, there are many ways of altering the language even at the Lua level. This is loosely described as metaprogramming - programming that tells the interpreter not so much what to do as how to understand the program. The most straightforward use of this is to extend the syntax; in particular, the way that the standard operation symbols are interpreted

events

The term event is used to denote circumstances that occur when the interpreter runs. For example, while parsing a Lua program, it may encounter an operator. Associated to the operator is a string which labels such an event.

operator event
+ add
- sub
* mul
/ div
% mod
^ pow
- unm
.. concat
# len
== eq
< lt
<= le
& bit_and
| bit_or
^^ bit_xor
~ bit_not
<< bit_lsh
>> bit_rsh

The events starting with bit_ are particular to RiscLua. The unm event is for the unary minus operator, whereas sub is for the binary operation of subtraction. We will see how we can extend the meaning of these operators when applied to certain kinds of table. For example, we might want to use a list of two doubles as a complex number. It would be nice to extend the arithmetic operators so that they did the right things with them. Or we might use boolean-valued tables for sets; then it would be nice to be able to use the bit-operators for the corresponding operations on sets. At the API level such extensions can be applied to any sort of object. There are also other events, not associated with operators.

event
index
newindex
call
tostring
metatable
mode
gc

These can be used for purposes more far-reaching than just extending the syntax.

The index event is when the parser comes across the evaluation of a table at an index that it cannot find. The newindex event occurs when the parser comes across an assignment to a non-existent index of a table. The call event occurs when the parser finds a table being applied to an argument as if it were a function. The tostring event occurs when it finds the print function being applied to a table (or userdata). The remaining events we will leave for later. In the next section we will see how to use events.

metatables

To every table it is possible to associate a special table called its metatable. You do this with a statement of the form  setmetatable(  table, metatable )  . The function  getmetatable  takes a table as an argument and returns its metatable, if it has one, and  nil  otherwise. Actually every kind of value can have a metatable, but apart from tables they can only be set at the API level.

If the metatable of an object has a key that is the name of an event, prefixed by two underscores, then the corresponding value of the metatable is taken as a function for interpreting the event. This is easiest to understand by example.

example: gaussian integers

Gaussian integers are complex numbers whose real and imaginary parts are integers. We could represent one as

         z = { re = x; im = y; }
In that case, addition and multiplication would be given by

  complex_add = \(z,w)
       => {
            re = z.re + w.re;
            im = z.im + w.im;
          }
               end
  complex_mul = \(z,w)
   => {
        re = z.re * w.re - z.im * w.im;
        im = z.re * w.im + z.im * w.re;
      }
              end
If we define

    complex_mt = {
       __add = complex_add;
       __mul = complex_mul;
                 }
and set this as the metatable for the tables that we deem to represent Gaussian integers then we will be able to use the standard operators  +  and  *  with them. So we define

  GaussInt = \(x,y)
        local z = { re = x; im = y }
        setmetatable(z,complex_mt)
        => z
             end

Of course, a bit more detail should be added so that we can treat numbers as Gaussian integers (test the type of the arguments in complex_add and complex_mul ), and so that Gaussian integers are displayed prettily (use the tostring event). I hope the general idea is clear.

To see how prettyprinting of complex numbers is not entirely straightforward, here is a program to run in a taskwindow that lets you enter the real and imaginary parts of a Gaussian integer and then prints out its factorization into irreducible Gaussian integers. The program does not use the scheme described above, which was given simply to illustrate a use of metatables.

do
local fmt1 = [[

%s factorizes as the product
of the irreducibles:

]]
local fmt2 = "\ntimes the unit %s.\n"

local prettyformat = \(x,y)
 if y == 0 then => tostring(x) end
 if y == 1 then
   if x == 0 then => "i"
   else => tostring(x).." + i" end
 end -- if
 if y == -1 then
   if x == 0 then => "-i"
   else => tostring(x).." - i" end
 end -- if
 if x == 0 then
   => tostring(y).."i" end
 if y > 0 then
   => tostring(x).." +
    "..tostring(y).."i"
 else
   => tostring(x).." -
    "..tostring(-y).."i"
 end
     end

local norm = \(x,y) => x*x + y*y end

local smallestprime = \(n)
    if n%2 == 0 then => 2 end
    local p,k = 1
    repeat
     p = p+2
     k = n%p
    until p*p > n or k == 0
    if k == 0 then => p
    else => n
    end -- if
                end

local divide = \(x,y,a,b)
         local d = norm(a,b)
         => (x*a+y*b)/d, (y*a-x*b)/d
         end

local irredfact = \(x,y,p)
    local a,b = 1,1
    while norm(a,b) < p do
     a = a + 1
     if norm(a,b) > p then
       a,b = 1,b + 1
     end -- if
     end -- while
     if (x*a+y*b)%p == 0 and
        (y*a-x*b)%p == 0 then
       => a,b
     else
       => b,a
     end -- if
     end

print  [[
Enter a Gaussian integer as two integers
(real and imaginary parts)]]
print  "separated by a space."
local x,y = io.read("*n","*n")
io.write(fmt1:format(prettyformat(x,y)))
local a,b -- complex irreducible factor
local n = norm(x,y)
while n > 1 do
 local p = smallestprime(n)
 if p == 2 then
   print (prettyformat(1,1))
   x,y = divide(x,y,1,1)
 elseif p%4 == 3 then
   print (prettyformat(p,0))
   x,y = divide(x,y,p,0)
 else a,b = irredfact(x,y,p)
   print (prettyformat(a,b))
   x,y = divide(x,y,a,b)
 end -- if
 n = norm(x,y)
end -- while
io.write(fmt2:format(prettyformat(x,y)))
end -- do

The index event is very interesting. If we set the value of __index to a table called, say, parent in the metatable of a table called, say, child then child will appear to inherit from parent all the keys it does not itself have. In this way we can program any variant of the notion of inheritance that the ideas of object oriented programming may suggest to us. The colon notation for strings is a consequence of the fact that every string has for its metatable

                   { __index = string; }
A similar device is used for file handles and for doubles. However, only tables can have their metatables set by the user from within Lua.

back contents forward