Introduction
The best introduction to Lua is of course the one written by the authors themselves.
In a Nutshell, Lua is a scripting language which means that it is not a language by itself. It works as an additional layer to another program.
This allows to be able to modify the properties and functionalities of the program without having to recompile the source code.
It is also dynamic which means that you can modify it while the program is running. Everything happens in modules which can be added or removed extremely easily, encouraging experimentation.
The following sub-sections are explaining the basic concepts of the Lua scripting language. Anyone slightly experienced with other programming language should get confortable with it easily.
Index:
- Variables and types
- The Environment
- Tables
- Operators and Statements
- Reading tables
- Functions
- Modules and Packages
Variables and types
Unlike a language like C, Lua variables do not *need* to be declared nor typed. For instance in C you would need to write the following to assign 3 variables to respectively integer, string of character and floating point number
int i = 5; string s = "This is a string"; float f = 3.14157;
In Lua you would write directly
i = 5 s = "This is a string" f = 3.14157
The types are deduced from the assigned value. They are also not definitive. You could reassigned the variable i later on to something else with a different type. Note that you also do not need anything at the end of a line to terminate it. You can though use a semi-colon to separate different statements on the same line
i = 5; s = "This is a string"; f = 3.14157
There are 8 basic types in Lua: nil, boolean, number, string, function, userdata, thread, and table
nil
Have only one value which is... nil
It means the absence of any useful value. It is used to reset a variable, to kind of "delete" it from the environment.
boolean, number ans string
They are the same than their C counterpart types.
boolean are either true or false.
number are either integer or floating point, without distinction.
string are chain of characters of variable length terminated by '\0' or the NULL character.
string can be concatenated together using the operator ".."
print("Hello".." World".." !!")
would print on screen "Hello World !!".
Please note that if you need to sequentially concatenate strings this might not be very efficient. For instance it is not recommended to do the following
local s = "Hello" s = s.." World" s = s.." !!"
In this case you are copying the value of the string s several times while it is unnecessary. This won't be an issue with short strings but may become one if performances are important while dealing with long strings. If you need to sequentially append strings at the end of another string you should look into the table.concat function discussed a bit later.
table
table are associative arrays.
They can behave like a standard C array or as maps with an association of a key and a value.
More about table here and here.
function
Self explanatory. Functions are described into more details here.
userdata
userdata are used as a link to C pointers. They are particularly useful for example to associate a C++ class to an object that can be manipulated in Lua.
More about the userdata later.
thread
We don't really care here.
The Environment
Variables are scoped in Lua as they are in other languages. For instance a C snippet that would be
-- [assuming a boolean x has been set earlier in the code] int a; // a is declared here if(x == true) { a = 1; // a is in scope because declared outside and before the if string message1 = "a is equal to 1"; // message1 is declared here cout << message1 << endl; } // end of message1 scope else { a = 2; // a is in scope because declared outside and before the if string message2 = "a is equal to 2"; // message2 is declared here cout << message2 << endl; } // end of message2 scope // a is still in scope but message1 and message2 are not
Writing something similar in Lua using the variable definition described above would not give quite the same result
-- [assuming a boolean x has been set earlier in the code] a = 0 -- a is **globally** declared here if x then a = 1 -- a is in scope because declared **globally** before message1 = "a is equal to 1" -- message1 is **globally** declared here print(message1) else a = 2 -- a AND message1 are in scope because declared **globally** before message2 = "a is equal to 2" -- message2 is **globally** declared here print(message2) end -- a, message1 and message2 are still in scope because **globally** declared
By default, Lua variables will be global meaning that once defined, they are here to stay. If you want to scope them, you have to use the keyword local
-- [assuming a boolean x has been set earlier in the code] local a = 0 -- a is locally declared here if x then a = 1 -- a is in scope because declared outside and before the if statement local message1 = "a is equal to 1" -- message1 is locally declared within the if statement print(message1) else -- message1 falls out of scope here a = 2 -- a is in scope because declared outside and before the if statement local message2 = "a is equal to 2" -- message2 is locally declared within the if statement print(message2) end -- message2 falls out of scope here -- a is still in scope because locally declared outside of the if statement
Tables
The tables are one of the main tool for any Lua script. They can be used as C arrays or vectors, or as some kind of dynamic structures.
Let's consider the following C statements
int array[3]; array[0] = 3; array[1] = 1; array[2] = 4;
would be
local array = {} -- this "declares" the table array[1] = 3 array[2] = 1 array[3] = 4
or
local array = { 3, 1, 4 } -- this "declares" and populates the table
Please note that in Lua, arrays starts at index 1 and not 0.
Another way to achieve the say thing and that would give a behavior closer to a C vector would be
local array = {} -- this "declares" the table table.insert(array, 3) -- append 3 at the end of the array, here element number 1 table.insert(array, 1) -- append 1 at the end of the array, here element number 2 table.insert(array, 4) -- append 4 at the end of the array, here element number 4
The function table.insert is behaving similarly to the push_back function with C vectors.
When tables are populated as arrays or vector, the size can be retrieved using the # operator
print(#array) -- would print 3 because we inserted 3 elements in our table
Now a table could be used as a struct.
struct detector{ unsigned long long entry = 0; double energy = 0; int strip = 0; };
Would be
local detector = { entry = 0, energy = 0, strip = 0 }
Please note that in this case the operator # would not return the size of the table. There is not easy way to get how many elements are in a table using key-value association.
When the table is initialized with elements, these elements are separated with a coma (,).
As we already said, tables are dynamic, so nothing prevents you to then add members to that structure (something that would not be possible in C.
detector.timestamp = 0
And now your detector structure has a new member called timestamp.
Note that the dot (.) is used as an accessor to table members, similarly to how one would access a struc member in C. But this is just a helper syntax for the actual accessor []
detector["timestamp"] = 0 -- this would produce the same result as above
Actually tables are really the *base* of Lua since everything is registered in a global table called _G
When a variable is declared globally like explained earlier, what is happening is that this variable is actually registered in that global table.
var = 3.14157 -- a global number is declared here print(var) -- will print on the screen "3.14157" print(_G["var"]) -- will print on the screen... "3.14157", congratulation
The way to process through a table is slightly different according to the way it has been filled (as an array or as an key-value struct like object). These ways are discussed in the next sub-section.
Concatenating strings from a table
Tables offer another nice functionality: the concatenation of all the strings contained within a table. For this to work, the table needs to be used as a sorted array
local str = { "Hello", " ", "World", " !!"} print(table.concat(str)) -- will print "Hello World !!"
Copying table
In Lua, tables are passed as references and cannot be copied trivialy.
local tbl1 = { 8, true, "Hello", 123.456, "World"} local tbl2 = tbl1 -- we actually did not copy tbl1 to tbl2 here, just made a reference tbl2[3] = "Goodbye" -- we modify the 3rd field of tbl2 print(tbl1[3]) -- will print "Goodbye" and not "Hello" since tbl2 is a reference to tbl1 -- so by modifying tbl2[3] we modified tbl1[3] as well
To do actual copy of tables, please see this section.
Operators and Statements
Statements are quite similar to C statements, just with slightly different syntax.
The operators
The operators are very similar to the C operators.
The following list give the correspondence between the C operator (on the left) and the Lua operator (on the right)
C operator | Lua operator | Comments |
---|---|---|
+ | + | addition |
- | - | substraction |
/ | / | division |
* | * | multiplicatiom |
= | = | equal ASSIGNMENT |
== | == | equal to COMPARISON |
!= | ~= | not equal |
< | < | less than |
<= | <= | less than or equal to |
> | > | more than |
>= | >= | more than or equal to |
The AND, OR, NOT operator are though very different
C operator | Lua operator |
---|---|
&& | and |
|| | or |
! | not |
Please note that the C "!=" equivalent in Lua is not "not=" or "not =" or "not ==". It is written as stated above "~="
Lua also offer a convenient power operator ^
2^3 = 8
Bitwise operators are also available
operator | Functionality |
---|---|
& | bitmask AND |
| | bitmask OR |
<< | bitshift left |
>> | bitshift right |
And a modulo operator % (integer rest of the euclidean division)
14%3 = 2
if statement
the if statement is written in the following way.
C if statement
if(a == b) { [do something] }
Lua if statement
if a == b then [do something] end
elseif, else statments
the else statements are written the following way
C if-else if-else statement
if(a == b) { [do something] } else if(a < b) { [do something else] } else { [do something else] }
Lua if-elseif-else statement
if a == b then [do something] elseif a < b then [do something else] else [do something else] end
Please note that the elseif statement takes no space between else and if. Also the elseif statement needs a then after the condition while the else does not.
for statement
The for statement is very similar as well
C for statement
for(int i =0; i <= 9; i++) { [do something] }
Lua for statement
for i=0,9 do [do something] end
The Lua for statement can take an extra parameter to specify the step
for i=1,10,2 do [do something] end
will produce a loop that will use the following values of i: 1, 3, 5, 7, 9.
The next value would be 11 since the step is 2 but since we specified that we want i to be maximally 10, it stops at 9.
The step could also be negative
for i = 100,-50,-15 do [some fancy stuffs] end
while loop
The while loop is again very similar
C while
bool b = false int x, y = 14941, 9642 while(!b) { if(x%y < 5) b = true; else x = x-y; }
Lua while loop
local b = false local x, y = 14941, 9642 while not b do if x%y < 5 then b = true else x = x-y end end
Reading tables
The content of a table can be read in different ways. If the table is a simple array/vector, one could do it the C waylocal tbl = { 3, 1, 4, 1, 5, 7 } for i = 1 , #tbl do print(tbl[i]) end
would print on the screen
3 1 4 1 5 7
Lua offer another way to do that though
local tbl = { 3, 1, 4, 1, 5, 7 } for i, v in ipairs(tbl) do print(v) end
would print exactly the same thing.
The ipairs relates to the fact that tables are associative containers with each entry being a key and a value. When tables are used as arrays, the keys are merely the element
number (in the order they have been inserted in the table).
We are here considering the pairs of key-value in the table *tbl*, and assign the key to i and the value to ipairs.
Something similar can be done for tables not used as arrays. For instance
local detector = { entry = 987654, energy = 666.666, strip = 3, timestamp = 14157 } for k, v in pairs(detector) do print(k, v) end
would print
entry 987654 strip 3 timestamp 14157 energy 666.666
Note that I put a random order for the print out. This is because when the *keys* for a table are not integer of increasing order starting from 1 separated by steps of 1,
the order in which they are processed is random.
Note also that this time we are calling *pairs* and not *ipairs*. The keys are not integers anymore.
Functions
Function Definition
Function Definition
Function definitions in Lua are similar to their C siblings but at the same time very different.
For the similarities:
A function in C
int AddNumbers(int a, int b) { return a+b; }
The same function in Lua
local function AddNumbers(a, b) return a+b end -- an equivalent function definition would be local AddNumbers = function(a, b) return a+b end
In the end functions are just like any other more classic variables (numbers, booleans, strings, ...). They can be either global or local, they can be registered in tables, their assignment can be changed, ...
Functions can be called from within other functions as you would expect:
local function AddNumbers(a, b) return a+b end local function AddAndSquareResult(a, b) local add = AddNumbers(a, b) return a^2 end
Function Overriding
Something very handy is the ability to override an existing function. This is particularly useful for several reasons. Let's imagine we have an existing function doing the addition between 2 numbers, included in a module previously loaded. Now we would like this function to add together these numbers only under a specific condition, and would like it to be retroactive on all the scripts using it, even the ones called from outside of our scripts.
-- MODULE A previously loaded local function AddNumbers(a, b) return a+b end
-- Our own Module local OriginalAddNumbers = AddNumbers -- we assigned to the variable OriginalAddNumbers the current function AddNumbers local function AddNumbers(a, b) if a > b then return OriginalAddNumbers(a, b) else return nil end end
What the function AddNumbers is now doing is calling the old value of this function (merly adding together a and b) only if a is biger than b, and doing nothing otherwise. As long as this override happens after the original AddNumbers function is loaded, any call to this function will now call our overridden version, even calls outside of our script(s).
Function as table members
As mentioned ealier, function are variables like any other. The can therefore be a member of a table.
Taking again the example of a table that would be a struct for a stripped detector
-- assuming we calibrated our detector and obtained -- a slope of 'a' and an offset of 'b' for each of the strips local detector = { entry = 9173, energy = 123.456, strip = 3, GetEnCal = function(self) return a[self.strip] * self.energy + b[self.strip] end } local en3 = detector.GetEnCal(detector) [...] detector.strip = 2 detector.energy = 987.654 local en2 = detector.GetEnCal(detector) print(en2) print(en3)
would print the calibrated energy using first the parameters for the strip 3 then for the strip 2.
What happens is that the function GetEnCal which is stored in the table detector gets called by detecotr.GetEnCal. The argument passed is the table detector itself,
so in the function, self is replaced by detector. You can do the rest of the math yourself.
You will notice it is not a very elegant syntax. Lua has a nice way of dealing with this. The functions calls could be replaced by
local en3 = detector:GetEnCal() [...] detector.strip = 2 detector.energy = 987.654 local en2 = detector:GetEnCal()
The colon (:) instead of the dot (.) when calling a function is actually implicitly passing what is on the left of the ':' as the first argument of the function without having to specify it. It is easy here to follow that the table detector will then be passed as the first argument in the function GetEnCal. This table actually starts to look like a the equivalent of a C++ class with its own methods. There would still be some more job to do to have a class-like behaviors with this table but that's not the subject here (but it is the subject of this section).
Modules and Packages
As said previously, Lua revolves around modules and packages. But what are these?
These are merely scripts that are loaded to provide functions or definitions to the main script or other sub-scripts. You can see them as "#include" for a C/C++ script.
There are several ways to load modules, the main ones being
-- Assuming we have a module in /home/tdrump/science.lua -- Option 1: dofile dofile("/home/tdrump/science.lua") -- Option 2: require require("science")
The 2 options are different in what the achieve eventually.
The dofile will load the given file no matter where it is as long a a path (absolute or relative) is provided and execute that script, effectively loading whatever is inside.
If global variables or functions are declared in the loaded script, then from now on they are available to any other scripts running in the same environment
(as long as you try to use them after the script has been loaded).
The require is a more demanding beast. You notice that you do not specify here anymore the path to the script. This is because require use what you pass as an argument as a hint
to find the corresponding script. For it to work you need to have included the path where the script is located in the list of path the *require* function will use to search for it. Also
a major difference with dofile* is that require register the script you loaded in the global table package and keep track of all the modules you included. This way
if several sub-modules calls require on the same module, it will effectively be loaded only once. This is not the case with dofile.
More info about this topic can be found here.