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:

  1. Global variables
  2. Local variables (includes the parameters of a function)
  3. 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

generated by LDoc 1.4.3 Last updated 2024-11-18 11:27:45