Lua
Lua is a quite simple and yet powerful scripting language. It was chosen for it's simplicity and has a lot to offer. If you're familiar with programming it shouldn't be hard to follow along.
See the official Lua documentation to learn
about all aspects of the language.
The Lua version used in TheoTown is 5.2.x
.
To test small Lua code snippets I recommend to use services like the online Lua demo for testing in a separate environment. You can output text there by using the print function.
Variables
A variable is a named place in memory that can contain data. To assign data to a variable, you can use the assignment operator =. This looks like:
-- This is a comment local a = 42 -- variable a has a value of 42 now local b = 'Text' -- assign a text to a variable b local c = 7.5 + a -- calculate 7.5 + a and store it into c local d = {} -- create a table and store it into d d.x = b -- put value of b into the table d using name x
Don't mind the local
statement. It's important when it comes to scoping in
the next chapter.
Comments are prefixed by -- and won't be interpreted by the compiler.
Lua supports a wide range of numbers in a single data type. Internally a 64 bit floating point representation is used for all of them. For example in Java this data type is referred to as double.
Text is handled by a data type called string. Such strings can be defined in code by prefixing and appending either single or double quotes (which won't be part of the string itself).
Scopes
The scope of a variable describes from where is can be accessed/where it's located. In Lua there are basically three types of scopes for variables:
- Global variables
- Local variables (includes the parameters of a function)
- Variables inside a table
Whenever you assign a value to or try to read a variable that hasn't been
defined with local
before it will be global:
local x = 42 -- Local y = 42 -- Global since it wasn't defined as local (before) x = 32 -- Still local since x was already defined function foo(z) z = 7 -- local since parameter variables are always local x = 32 -- Still local; nested scope end
This distinction is important when it comes to isolation. While local variables can only be accessed within the context in which they have been defined global variables can be accessed from everywhere (including other scripts). To avoid hard to debug errors in your code we recommend to always use local variables if possible.
Sidenote: Global variables are actually just syntactic sugar for variables in a special table.
Tables
The main data type in Lua are tables. A table can by created like that:
local t = {}
You can just put values into it:
t[42] = 7 t['xyz'] = 'ok'
And then also retrieve them:
print(t[42]) --Prints 7 print(t['xyz']) --Prints ok
A more readable syntax for string valued keys allows us to write:
print(t.xyz) --Prints ok
So a table is basically what's called a dictionary in other languages. However, thanks to its syntactic sugar it allows us to use them like classes in other languages:
local complex = {} complex.real = 7 complex.imag = 2
A table with contents can be created directly:
local complex = { real = 7, imag = 2 }
If no keys are provided the entries will be assigned to numbers 1,...,n in the order they are defined:
local list = { 1, 2, 3 }
Nesting is possible, too:
local table = {
list = { 'a', 'b', 'c' },
pos = {
x = 7,
y = 8
},
[42] = 42 -- For non regular keys we can use [...]
syntax to specify it
}
All in all this syntax allows us to define data in a way similar to json. This is not a coincidence as one of the design goals of the Lua programming language is to make data definition easy.
Functions
In Lua functions are first class objects. That means you can put them into variables or pass them around as arguments in function calls.
function foo(x, y) print(x, y) end local function foo(x, y) print(x, y) end local fun = foo -- here foo will refer to the local foo that was defined before fun(42, 43)
function foo()
- Global functions
The most basic way to define a function in Lua is the following:
function foo(x) print(x + 42) end foo(7) -- This calls the function foo and provides a value of 7 for the parameter x
When defining the function we can define how many parameters the function should
take and optionally return a value using the return
keyword:
function add(x, y) return x + y end print(add(39, 3)) -- This will print 42
return
ends the execution of the function and returns the given value to
the caller (here the result of calculating x + y
).
If return
is not called the function will return the nil
value.
Since functions are first class values the code
function foo(x) ... end
is equivalent to
foo = function(x) ... end
which creates an anonymous function (that is a function that has no name) and puts it into a global variable called foo.
In the context of TheoTown plugins we don't recommend the use of global functions because any script will be able to call/override them. A solution to this are local functions as described below.
local function foo()
- Local functions
A local function has the same scope as local variables and can be defined like this:
local function foo(x) ... end
In fact the above code is syntactic sugar for writing
local foo foo = function(x) ... end
Here the definition is done before the assigment to allow the function to access itself by using the then defined outer local variable foo.
Note that with local functions order matters. The following code would not work for that reason:
local function foo() bar() -- Here this would refer to global variable bar which is likely nil end local function bar() ... end foo()
So, if possible, we recommend to order the functions so that needed functions were defined before. In casees where this is not possible (e.g. because the functions depend on each other) you cen define their local variables in advance:
local foo local bar foo = function() bar() end bar = function() ... end foo()
Note that you may not write local
again in front of the functions as that
would define a new local variable that would be inaccessible from previous
usage sites.
function x.foo()
- Table functions
Like with objects in other languages you may want to put functions into tables. Lua has an easy syntax to do exactly that:
local x = {} function x.foo() print('Hi') end x.foo()
The function definition above is simply syntactic sugar for:
x.foo = function() print('Hi') end
function x:foo()
- Table functions with receiver
If you are familiar with object oriented programming you are probably also familiar with the concept of methods. These are functions that are defined on an object and can operate directly on that object wihout having to provide it as an (explicit) parameter. In Lua this can be achieved by using the colon operator:
local obj = { x = 0 } function obj:inc(y) self.x = self.x + y end function obj:print() print(self.x) end obj:print() -- This will print 0 obj:inc(1) obj:print() -- This will print 1
Note that you have to use the colon operator in both places, the definition and the call site. The reason for this is that behind the scenes the code above gets transformed into:
function obj.inc(self, y) ... end function obj.print(self) ... end obj.print(obj) obj.inc(obj, 1) obj.print(obj)
In other words, instance methods in Lua are implemented by syntactic sugar which can still help us with readability and conciseness. For that reason you can also mix the colon syntax with the dot syntax as long as you are consistent with the parameters.
The colon syntax can help us to avoid some typing e.g. if we retrieve an object from somewhere else and don't want to store it temporarily:
local id = Draft .getDraft('$someid') :getTitle() -- getTitle() operates on the draft directly
Control structures
Control structures generally describe language features to control the flow
in which the program gets executed.
Functions are a special type of control flow mechanism that was covered in the
section above.
Some control structures use the concept of conditions.
Conditions are expressions that can be evaluated to some value that is then
interpreted as true or false.
In Lua a condition is considered to be true if the expression evaluates
to something else than nil
or false
.
This implies that 0
, 'false'
and ''
(the latter two are strings) are all
considered true in Lua.
There are various logical operators available that can be nested to build up complex conditions (optionally by using parentheses to not rely of automatic order as defined by precedence similar to mathematical operations):
- a and b → b if a is considered true, otherwise a
- a or b → a if a is considered true, otherwise b
- not a → false if a is considered true, otherwise true
- a == b → true if a is equal to b, otherwise false
- a ~= b → false if a is equal to b, otherwise true
- a < b, b > a → true if a is smaller than b, otherwise false
- a <= b, b >= a → a < b or a == b
if
- conditional branching
If statements execute some code only if a given condition is met. An example:
local x = 42 if x == 42 then print('yes') -- This will be executed end
You can chain multiple if statements by using elseif. Code in a closing else-end block will only be executed if none of the previous if or elseif conditions was met:
local type = 'b' if type == 'a' then print('it is a') elseif type == 'b' then print('it is b') else print('it was something else ('..type..')') end
while
- conditional looping
While conditions are related to if statements in that they only execute some code if the given condition is true. However, a while condition will call the code repeatedly as long as the condition holds true.
local x = 1 while x <= 10 do print(x) -- Prints the numbers from 1 to 10 x = x + 1 end
for
- numerical and generic enumeration
Numerical for loops in Lua use fixed start, end and step parameters to execute the looping. The step parameter can be omitted if it is 1.
for i = 0, 10, 2 do -- Start at 0, end at 10, step 2 print(i) -- Prints the numbers 0, 2, 4, 6, 8, 10 end
It's worth noting that the iteration variable (here i
) is local and newly
created for every iteration of the loop.
Because of this you don't have to worry about capturing a changing loop variable
in a function within the loop as it will behave like a constant.
A more generic way of for-loops is iterating using an iterable object.
To iterate over a table of key-value pairs you can iterate over the result
of the pairs()
function:
local tbl = { x = 42, y = 1773 } for key, val in pairs(tbl) do print(key..' = '..val) -- prints x = 42 and y = 1773 end
For tables of type Array we recommend to use the :forEach(function(e) ... end)
method to iterate or its elements as this handles the for loop for you.
do
- code blocks
Not really a structured but more of a logical control structure is the
do ... end
syntax that can be used to surround a block of code.
All it does is keep local variables that were defined within it inside of it.
Here's a short example of what that means:
do local x = 42 end print(x) -- This will print nil as it is not aware of the local x in the code block
Since all do/then ... end
blocks behave like this it's something you should
be aware of.
A fix for the code above (assuming we want to set x) is defining the local
variable before the block:
local x if true then x = 42 end print(x)
If we would repeat local
within the code block that would create a new local
variable x that only exists within the block and would not affect the outer x.
Garbage collection
To motivate this topic have a look at this short example:
for i = 1, 10000000 do local t = { x = i } -- Let's create a table that takes up some space end
It basically creates a new table 10,000,000 times without cleaning anything up. So it will eventually run out of memory, right? Actually not, because Lua is a memory managed language. This means that memory is freed up when it's not in use anymore/cannot be accessed. Here the tables created in previous iterations of the loop aren't accessible anymore and therefore can be freed up by the so called garbage collector.
In conclusion you usually don't have to worry about when the garbage collector
will free up memory nor do you have to clear up yourself.
However, for the garbage collector to work properly you have to ensure that
you don't keep references to objects that you don't use anymore.
In the example above we would run into issues if we would store the tables t
in a common list.
Libraries
A programming language gains its true power from the libraries that are available for it. Regarding TheoTown scripting you'll find all of the relevant libraries listed in the left sidebar (they are called Modules there).
However, when it comes to more basic features Lua has its own set of standard libraries that are available in any Lua environment. For a full list of all available modules and their function see the Lua documentation index. Here are some examples that use the standard libraries:
print(math.sin(math.pi)) -- Really close to 0 print(os.clock()) -- Used CPU time in seconds (should be close to 0 right here) print(os.date()) -- Formatted date print(os.time()) -- Unix timestamp (a big number) print(string.upper('hello')) -- HELLO print(table.concat({'a', 'b', 'c'})) -- abc