[ LiB ] | ![]() ![]() |
As I mentioned, executions of lua are broken down into units called chunks. Chunks are simply sequences of statements, and are basically equivalent to program blocks. lua handles a chunk just like any language handles a function, so chunks can hold local variables and return values.
Chunks may be stored in a file or in a string inside the host program. When a chunk is executed, first it is precompiled into byte-code for the lua virtual machine, and then the compiled code is executed by an interpreter for the virtual machine. lua has no declarations, so a chunk may be as simple and short as a single statement:
chunk ::={single statement}
Or it can be big and complex:
Chunk ::={ event_buffer = nil, last_update_ticks = 0, begin_time = 0, elapsed_ticks = 0, frames = 0, update_period = 33 active = 1, screen = nil, background = nil, new_actors = {}, actors = {}, add_actor = function(self, a) assert(a) tinsert(self.new_actors, a) end }
Lua uses C- and Pascal-like punctuation. This takes a bit of getting used to, especially when you're just coming from Python. While Python uses spaces and tabs to keep statements separated, lua utilizes brackets, quotes, parentheses, squiggly lines, and other deliminators, and spaces and tabs are pretty much ignored, which can be confusing at first. A good practice is to use the interpreter often; because the interpreter expects code to be properly bracketed off, it
NOTE
I talked a bit about pascal in earlier chapters when discussing the history of computer languages. As you may recall, pascal is a high-level structured programming language, which forces design with a very regimented structure.
Statements in c are normally ended in a semicolon. In lua this is optional, but you will still see it commonly done:
a=1 b=2 --equivalent to a=1; b=2; --equivalent to a=1;b=2;
Lua is a dynamically typed language, so variables themselves do not have types; only the values of the variables have types. The basic types in lua are shown in Table 6.2:
Variables created in lua are visible within the blocks in which they are created and are considered global unless the area is specifically defined as local using the local keyword. After a code block is executed, local variables are destroyed.
In Lua, all values different from false or nil are considered true. This means that only nil and boolean false are considered false for the purposes of statement execution; everything else is considered true. As of Version 5.0, lua has a built-in boolean recognition of true and false.
Try running the following lines in the lua interpreter:
Name | Data Held |
---|---|
Boolean | Either false or true |
function | Function stored as a variable |
nil | Value nil |
number | Real numbers (double precision floating point) |
string | Character string |
table | Associative array (i.e., dictionary / hash) |
thread | Independent threads of execution |
userdata | C pointers stored as variable |
x = true print (x) print (not x)
You will see that the interpreter is smart enough to know that if something is not true, then it must be false. You can use lua to test boolean validity by using two equal signs to represent "is equal to," like so:
print (0==100) print (1 ==1)
Note that in Lua, true and false are not numerical values (0 and 1) like in some languages.
A really wonderful feature of lua is that you can assign functions to variables. In fact, when you define a function in Lua, you are basically assigning the text body of the function to a given variable. Functions are declared by using the function keyword, with the general syntax being:
function name(args) does_something end
where name is the name of the new function, args is any arguments the function takes, does_something represents what the function actually does, and end tells lua the function is over.
For example, here is a quick function that prints a statement to the screen:
function Myfunction() print("What's your function?") end
After creating a function, you can call it at will:
Myfunction()
You can also print the value of the function's memory address using print:
print (Myfunction)
When you run this last line in the interpreter, you can see that lua notices that it's dealing with a function as well as returning its memory address.
Functions can take arguments as well, like in this example that takes an argument and assigns it to X:
function Myfunction(X) print(X) end
When you call this function with Myfunction(1), the interpreter prints out what is assigned to Xin this case a 1. You could also assign the function a string with Myfunction("hello"). If no argument is passed to the function, lua automatically assigns nil to the argument, and in the case of Myfunction(), the interpreter prints nil.
Since functions can be stored as variables in Lua, they can then be passed as arguments to other functions or they can be returned. This makes them fairly powerful creatures in Lua-land.
Nil values mean that a variable has no value. You can set values to nil to delete them:
x = nil
and you can test to see whether a variable exists by checking to see if its value is nil:
print (x==nil)
Nil is the equivalent of no value, so if a variable is assigned nil, it ceases to exist.
Lua supports the standard add (+), subtract (-), multiply (*), and divide (/) operators. These can be fun to play with after firing up the lua.exe and using the print statement:
print (1+1) print (5*5) print (10/9)
If you run these lines in the interpreter, you will notice that lua automatically brings in floating point numbers and gives you 1.11111111 as an answer to the third chunk. lua doesn't bother with rounding off like many other languages do. All numbers in lua are "real" numbers stored in floating point format.
You can assign numbers to variables by using the = sign:
X=100 print (x)
Lua also supports multiple assignments:
x, y = 2, 4 print (x,y) x,y = y,x print (x,y)
NOTE
The act of setting the value of a variable is called an assignment.
Lua supports the standard arithmetic relational operators, including
+
-
*
/
^
==
~=
<
>
<=
>=
These should be pretty familiar to you by now. lua also understands logical and, or, and not. Logical not inverts a logical expression:
not true = false
while logical and and or can be used and combined to form the logical statements programmers often need:
true or false x = true and y = true
NOTE
CAUTION
Lua does exhibit some strange behavior when ordering precedence in an equation. This behavior shows up when running through equations from left to right and right to left. Normally, lua figures out the left side of the equals sign first, but the order in which multiple assignments are performed is actually undefined. For instance, if the same values or tables occur twice within an assignment list, then Lua may perform the equation from right to left. The order precedence may also be changed in future versions of Lua. This can be a hassle, but it simply means that you should always use separate assignment statements when possible.
An important topic for numbers and running equations is operator precedence, which is illustrated in Table 6.3.
Precedence | Operator |
---|---|
1.(highest) | ^(exponentiation) |
2. | not - (unary) |
3. | * / |
4. | + - |
5. | ..(string concatenation) |
6.(lowest) | < > <= >= ~= == |
Lua has an additional library that interfaces with the common c Math library functions. The library is available for access by lua with a luaopen_math function and include a number of fun math tricks that should look familiar to c users and Math whizzes. The functions are listed in Table 6.4.
Function | Use |
---|---|
math.abs | Absolute value |
math.acos | Arc cosine |
math.asin | Arc sine |
math.atan | Arc tangent |
math.atan2 | As atan but uses signs of the arguments to compute quadrant of the return value |
math.ceil | Ceiling, returns smallest integer no less than given argument |
math.cos | Cosine |
math.exp | Exponent |
math.floor | Returns largest integer no greater than given argument |
math.frexp | Turns argument number into mantissa and exponent |
math.ldexp | Returns X*(2^exp) |
math.log | Logarithm |
math.log10 | Base-10 logarithm |
math.mod | Splits given into integer and fraction parts |
math.pi | Pi (3.14) |
math.pow | Power, the base raised to exp power |
math.sin | Sine |
math.sqrt | Square root |
math.tan | Tangent |
math.random | Random number |
math.randomseed | Seed number for random |
These functions all follow a similar pattern when used. Let's say I wanted the value of pi. I'd do this:
MyPy = (math.pi) print (MyPy)
If I needed to find the tangent of a given number, I'd do this:
MyTan = (math.tan(10)) print (MyTan)
Lua supports strings as text variable types. You can assign strings just like you would numbers, but you must be sure to include the quotes and parentheses, like so:
myself = ("me") print (myself)
You cannot use operators like + to concatenate strings, but lua does allow you to concatenate strings using two periods, like in the following:
myself = ("me") print ("Hello to "..myself)
Besides double quotes, you can also set up strings using single quotes or double square brackets, as in the following:
--this myself = ("me") --is equivalent to this myself = ('me') --is equivalent to this myself = ([[me]])
Lua supports these various methods so that you can place quotes within strings without using nasty escape sequences:
Mystring = ([["quote"]]) print (Mystring)
But lua does support the standard C-type escape sequences when using strings. These sequences are listed in Table 6.5.
Sequence | Translates to |
---|---|
\a | System beep |
\b | Backspace, deletes the last character typed |
\f | Form feed |
\n | Newline |
\r | Carriage return |
\t | Horizontal tab |
\v | Vertical tab |
\\ | Backslash |
\" | Double quote |
\' | Single quote |
It is important to note that when indexing a string in Lua, the first character is at position 8-n-1 (not at 0, as with C).
Brackets have further uses when you're creating strings. For instance, they can be used to place strings on several lines of code, as shown in Figure 6.3.
Lua comes packaged with additional library string functions. These are not necessary to import lua but are very helpful if you are working on an application with heavy string handling. These functions are listed in Table 6.6; the library is opened with the luaopen_string function.
Function | Purpose |
---|---|
string.byte () | Returns the internal numerical code of the character |
string.char () | Returns a string of given length and internal numerical codes |
string.dump () | Returns binary representation for a given function |
string.find () | Uses pattern matching to find the first match of a given string |
string.len () | Returns a string's length |
string.lower () | Returns a copy of a given string in all lowercase letters |
string.rep () | Returns a string concatenated to specifications given |
string.sub () | Returns a substring of the given string |
string.upper () | Returns a copy of a given string in all uppercase letters |
string.format () | Returns a formatted version of a given string using C's printf style of arguments and rules |
string.gfind () | Used to iterate over strings to match pattern |
string.gsub () | Returns a copy of a given string after running given arguments over the specific string |
The string library also has built-in functions for pattern matching, allowing lua to search through long strings or tables, match up patterns, and return them (called capturing). These controls are normally preceded by modulus; they are outlined in Table 6.7.
Symbol | Pattern |
---|---|
. | All characters |
%a | All letters |
%c | All control characters |
%d | All digits |
%l | All lowercase letters |
%p | All punctuation characters |
%s | All space characters |
%u | All uppercase letters |
%w | All alphanumeric characters |
%x | All hexadecimal digits |
%z | Character with representation 0 |
Using these functions to find patterns and matches is relatively straightforward using string.find. For instance, here is a lua chunk that searches for the letter "o" in the given string:
MySearch = string.find('word', 'o') print (MySearch)
When this chunk is run in the lua interpreter, you are given the location of o in the string, which is the second character location, right after 'w' which is first.
Let's say that you wanted to find four-letter words that begin with s in a given string. You can use period (.) as a wildcard:
Mystring = 'Blah blah blah blah sand blah' Mystring2 = (string.find(Mystring, 's...')) print (Mystring2)
This chunk will find the word sand in the string at the 21st character location after the first four Blahs.
Tables are the main data structure in Lua. Let me repeat that, because it's important: Tables are the main data structure in Lua. Instead of lists or tuples or dictionaries, lua utilizes tables as its primary data holder. Tables are Lua's general-purpose data type and are capable of storing groups of objects, numbers, strings, or even other tables. Tables are created using curly brackets, like so:
Mytable = {}
If you were to print out Mytable (using print (MyTable)), you would get a funny number, something like 0032bb99. This is the unique identifier and memory address that lua has assigned to Mytable.
Tables are used everywhere in Lua. They are the basic building block to creating all of the important programming constructs like queues, linked lists, and arrays. Tables can also function more like hashes and dictionaries than arrays and lists. You can add hash-like objects to a table by assigning a key/value pair, like so:
Mytable = {Mynumber = 1, Myword = "Ikes!" }
You can then refer to the table with the familiar
print (Mytable.Mynumber) print (Mytable.Myword)
Tables can also be used in an array/list-type way. You do this by creating a comma-separated list of objects when creating the table. You can then access the table like an array, using brackets and numeric references, like so:
Mytable = { 1,2,3,4,5,6,7,8,9,0 } print (Mytable[1])
Notice, when you run this chunk in the interpreter, that the array/table starts at 1, not 0. The 0 value is actually assigned nil, or no value.
You can mix a dictionary-type table and array-type table together, making tables pretty versatile little buggers. Tables can also contain other tables:
Mytable = { table1= {a = 1, b = 2}, table2={c = 3, d = 4}}
Additional ways to manipulate tables are possible using the additional library functions listed in Table 6.8.
Function | Purpose |
---|---|
table.concat () | Returns concatenated tables |
table.foreach () | Used to execute a given function over all elements of a table |
table.foreachi () | Executes given function over numerical indices (only) of table |
table.getn () | Returns the size of the table |
table.sort () | Sorts tables elements in a given order |
table.insert () | Inserts element at a given position, shifting all other elements |
table.remove () | Removes element from given position, shifting elements down |
table.setn () | Updates the size of a table |
These functions all work in a similar way. For instance, you can use table.getn and table.insert to update a table entry, like so:
Mytablelength = table.getn(Mytable) --Inserts 22 into the end of the table table.insert(Mytable, 22)
You can insert elements at a chosen point in the list using table.insert:
table.insert(Mytable, 10,100)
You can print out the contents of the table using table.foreachi:
table.foreachi(Mytable, print)
Even though you can treat a table as an array, keep in mind that it is still table. You can store whatever you want:
Mytable[5] = "Hey, a string!"
So, if you were printing out a dictionary version of the table
Mytable = {Mynumber = 1, Myword = "Ikes!" }
you would use the foreach function to print out each key/value pair:
table.foreach(Mytable, print)
The next function can also be used to iterate over a table. next takes a table and an index and gives back the next key/value pair from the table:
next(Mytable,"key")
Tables are also objects in lua in the sense that they have state, independent identity, a life cycle, and operations that can be called upon them. The lua programming model also has ways of implementing traditional OOP in the form of inheritance, polymorphism, classes, and late binding with tables.
People considered tables in lua so foundationive that in the latest version metatables were added as well. Every table and userdata object in lua may now also have a metatable, which is an ordinary lua table that further defines behavior. The commands lua_getmetatable and lua_setmetatable allow you to manipulate the metatables of a given object.
Weak tables were also added with lua 5.0, which are tables whose elements are weak references. Unlike regular references weak references are ignored by Lua's garbage collector.. Since weak tables do not prevent garbage collection, they are useful for determining when other objects have been collected by the GC and for caching objects without impeding garbage collection.
Threads allow programs to do multiple things at once. In a multi-threading model, each task runs in a thread that is separate from other threads. There are many ways to implement multi-threading, and Lua's way is a bit unique. lua uses a "cooperative multi-threading," using coroutines that aren't actually operating-system threads but are instead just blocks of code that can be created and run in tandem.
To create a coroutine, you first must have a function that the coroutine runs:
function Myfunction() print ("do something") coroutine.yield() end
You then create a coroutine using coroutine.create:
Mythread = coroutine.create(Myfucntion)
Once you have established a coroutine, you can check its status with coroutine.status:
Mystatus = coroutine.status(Mythread) print(Mystatus)
When run in the interpreter, this code will show that Mythread is suspended. To start or resume a coroutine, use coroutine.resume. In this example, the interpreter will print do something, and then Mythread will exit by yielding.
Yielding is key to coroutines. Coroutines must be able to yield system resources and pass control to the next thread that needs it. The coroutine.yield is similar to the return function, and it exits the current thread and frees up any resources.
If you run the Mystatus code a second time:
Mystatus = coroutine.status(Mythread) print(Mystatus)
the status will show that the thread has already run by reporting dead.
Userdata is used to represent c values in Lua. There are two types of userdata: full userdata and light userdata. Full userdata represents a block of memory and is considered to be an object. A light userdata represents a pointer.
Identifiers in lua can be made up of letters, numbers, and underscores, but they cannot begin with a digit. lua is case-sensitive, so the strings HELLO and hello are considered different strings. There are a handful of reserved words that lua keeps for itself and cannot be used as identifiers; these are as follows:
and
break
do
else
elseif
end
false
for
function
if
in
local
nil
not
or
repeat
return
then
true
until
while
A standard convention in lua is that internal variables begin with an underscore and a capital letter, like Myvariable.
Control structures in lua are similar to those in Lua's syntactical parents c and Pascal. if, while, and repeat commands are very common. The traditional if statement looks like the following in Lua:
if true then block {elseif true then block} [else block] end
An example of an if statement that prints whether x is less than 10 would be:
x=1 if x<10 then print ("x is less than 10")end
You can add a second else statement in case x is greater than 10:
if x<10 then print ("x is less than 10")else print ("x is greater than 10")end
One extremely common looping statement is the while loop, which looks syntactically like the following:
while true do block end
A second common looping construct is the repeat loop:
repeat block until true
Here is a sample lua while loop that prints out a series of numbers:
x = 1 while x<10 do print (x) x=x+1 end
The sample is just as easy to implement using repeat:
x=1 repeat print (x) x=x+1 until x==10
The for loop, however, is what holds a special place in the programmer's heart. lua has two versions of the for loop. The first one is used with numbers:
for variable = var, var, var do block end
Like in a typical for loop, all three expressions aren't necessary:
for X=1, 10 do print(X) end
This loop prints X as it iterates through the loop 10 times.
The second version of for is used for traversing a table, and it is capable of iterating through each key/value pair of a given table:
for variable {, var} in explist do block end
An example of this version of for iterating over a given table is as follows:
Mytable = {1,2,3; word="hi, number=100000} for key,value in Mytable do print (key,value) end
Included with this fun for is also a pairs() function for iterating key/value pairs:
for key,value in pairs(Mytable) do print (key,value) end
In this instance, pairs() will iterate only over the array type table entries in the table:
for index,value in ipairs(Mytable) do print (index,value) end
Lua uses a return statement to return values from a function or a lua chunk. There is also a break statement that can be used to terminate the execution of a loop and skip to the next statement that follows. Both return and break must be the last statements in a given block.
Modules, packages, namespaces: all are mechanisms used by languages to organize global names and space and avoid collisions. In Lua, modules are implemented with the all-important and versatile (you guessed it) table. Identifiers become keys within tables instead of global variables. A package may look like this:
Mypackage = { function1 = function() dosomething{} end, function2 = function() dosomething{} end, function3 = function() dosomething{} end, function4 = function() dosomething{} end, }
Then the package can be called like this:
call = Mypackage.function1(arguments)
Lua has a set of standard libraries that provide useful and common routines. These are implemented directly through the standard API but aren't necessary to the language, and so are provided as separate c libraries. There is a basic library, a library for string manipulation, one for mathematical functions, one for system facilities and I/O, one for debugging, and one for tables. The functions are declared in lualib.h and must be opened with a corresponding function, like in the following examples:
luaopen_string luaopen_table luaopen_math luaopen_io
A few of the libraries (math and string) were covered in the previous sections. The others will be covered here.
The basic library provides much of Lua's base functionality. The commands involved are listed in Table 6.9.
The coroutinefunctions are actually part of a sublibrary of the basic library.
Input and output are handled by two file handles. These handles are stored in two global variables: _INPUT and _OUTPUT, the former for reading and the latter for writing. _INPUT and _OUTPUT are also equivalent to _STDIN and _STDOUT. The common I/O functions are listed in Table 6.10.
Function | Purpose |
---|---|
io.close () | Closes the given file |
io.flush () | Flushes over the default output file |
io.input () | Opens the named file in text mode and sets its handle to the default input file |
io.lines () | Opens the given file name in read mode and returns an iterator function that returns a new line from the file each time it is called |
io.open () | Opens a file in the mode specified and returns a new file handler |
io.output () | Opens named file in text mode and sets its handle to the default output file |
io.tmpfile () | Returns handle for a temporary file |
io.type () | Checks if object is a valid file handle |
file:close () | Closes file |
file:flush () | Saves any written data to file |
file:read () | Reads the file according to given formats |
file:lines () | Returns an integrator that returns a new line from the field each time it is called |
file:seek () | Sets and gets the file position |
file:write () | Writes the value of each of its arguments to the filehandle file |
Function | Purpose |
---|---|
assert () | Issues an error when its argument is nil |
collectgarbage () | Forces a garbage collection cycle and returns the number of objects collected |
coroutine.create () | Creates a new coroutine |
coroutine.resume () | Starts or continues coroutine execution |
coroutine.status () | Returns status for a coroutine |
coroutine.wrap () | Creates a new wrapped coroutine |
coroutine.yield () | Suspends coroutine execution |
dofile () | Opens a given file and executes its contents as a lua chunk or as precompiled chunks |
error () | Calls the error handler and then terminates the last protected function called |
_G | Holds the global environment |
getfenv () | Returns current environment in use by a given function |
getmetatable () | Returns objects' __metatable field value or else nil for no metatable |
gcinfo () | Returns dynamic memory use and garbage collector threshold in kbytes |
ipairs () | Iterates over a table |
loadfile () | Loads a file as a lua chunk |
loadlib () | Links a program to a c library |
loadstring () | Loads a string as a lua chunk |
newtag () | Returns a new tag - equivalent to the API function lua_newtag |
next () | Allows a program to traverse all fields of a table |
pairs () | Iterates over tables |
pcall () | Calls a function in protected mode with given arguments |
print () | Receives arguments and prints their values using the strings returned by tostring |
rawequal () | Checks to see if two values are equal |
rawget () | Gets the real value of an index within a table |
rawset () | Sets the real value of an index within a table |
require () | Loads a given package |
setenv () | Sets the environment to be used by a function |
setmetatable () | Sets the metatable for a given table |
tonumber () | Tries to convert an argument to a number |
tostring () | Tries to convert an argument to a string |
type () | Returns the type of its only argument |
tinsert () | Inserts an element at a given table position |
tremove () | Removes an element from a given table |
type () | Tests the type of a value |
unpack () | Returns all elements from a given list |
-VERSION | Holds the current interpreter version (i.e. lua 5.0) |
xpcall () | Calls a function in protected mode using err as the error handler |
There are also a few system utility functions that can be included with Lua's built-in library. They are listed in Table 6.11.
Function | Purpose |
---|---|
os.clock () | Returns an approximate CPU time, in seconds, used by the program |
os.date () | Returns the date and time according to given format |
os.difftime () | Returns the seconds between two given times |
os.execute () | Passes a command to be executed by the operating system. Equivalent to C's system |
os.exit () | Calls the c function exit to terminate a program |
os.getenv () | Returns the value of a given environment variable |
os.remove () | Deletes a given file |
os.rename () | Renames a given file |
os.setlocale () | Used as an interface to the ANSI c setlocale function |
os.time () | Returns current time |
os.tmpname () | Returns a string with a filename that can be used for a temporary file |
[ LiB ] | ![]() ![]() |