luaXroot Wiki

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

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.

go back to top


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

go back to top


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.

go back to top


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

go back to top

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.

go back to top

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

go back to top

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

go back to top


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 way

   local 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.

go back to top


Functions

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

go back to top

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).

go back to top

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).

go back to top


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.

go back to top