Lua

July 10, 2022

Comments

-- simple one liner
--[[ multiline
     comment ]]

Variables

In lua every variable is by default global, which is a historical decision. Nowadays this is considered bad practice (f.e. in moonscript everything is by default local).

In general you should always use local. Same goes for functions, which you can also declare as local.

local x = 4
y,z = 4,9

Operators

Relational operators

-- equal to
==

-- not equal to
~=

-- greater than
>

-- less than
<

-- greater than or equal to
>=

-- less than or equal to
<=

Arithmetic operators

-- Addition
+

-- Subtraction
-

-- Multiplication
*

-- Division
/

-- Modulus
%

-- Exponent
-- Use the built-in `^` operator rather than the `math.pow()` function.
^

Logic

nil and false  --> nil
false and nil  --> false
true           --> true
0 and 20       --> 20
10 and 20      --> 20

false and nil are distinct: false represents the logical false state, nil represents the absence of a value

Enumerations

local COLORS = {
  BLUE = 1,
  GREEN = 2,
  RED = 3
}

-- the above is equivalent to
local COLORS = {
  ["BLUE"] = 1,
  ["GREEN"] = 2,
  ["RED"] = 3
}

-- reading the integer back
local color = COLORS.RED -- the same as COLORS["RED"]

Tables and Arrays

Lua is descended from Sol, a language designed for petroleum engineers with no formal training in computer programming. People not trained in computing think it is damned weird to start counting at zero. By adopting 1-based array and string indexing, the Lua designers avoided confounding the expectations of their first clients and sponsors.

t = {}
t = { a = 1, b = 2 }
t = { ["hello"] = 200 }

-- arrays are also tables
array = { "a", "b", "c", "d"}
print(array[1]) -- "a" (one-indexed)
print(#array) -- 4 (length)

Every value in Lua can have a metatable. A metatable is also a Lua table that defines the behavior of the value under certain events. A group of related tables can share a common metatable, which describes their common behavior; a table can be its own metatable, so that it describes its own individual behavior. Any configuration is valid.

Lua always creates new tables without metatables.

t = {}
print(getmetatable(t)) --> nil

We can use setmetatable to set or change the metatable of a table.

t = {}
meta = {}
setmetatable(t, meta)
print(getmetatable(t) == meta) --> true

Have a look at this detailed list of operations controlled by metatables. By convention, all metatable keys used by Lua are composed by two underscores followed by lowercase Latin letters.

We will have a look at some of the operations in the following part.

__call

This metamethod is triggered when the table itself is called as a function. It is the only metamethod that allows multiple results.

local meta = {}

function meta.__call(...) print("You called __call", ...) end

-- create a testobject
local t = {}

-- set the metatable
setmetatable(t, meta)

print(t()) -- You called __call	table: 0x0112360f08
print(t("Apple")) -- You called __call	table: 0x01093ec000	Apple

_ _index and _ _newindex

Reading the content of the table. Note that the action is only triggered if the corresponding key is not present in the table.

-- first we want to set the __index method
-- this method gets called with the corresponding table and the used key
local meta = {}
meta.__index = function(object, index)
  print(string.format(
    "the key '%s' is not present in object '%s'",
    index, object))
  return -1
end

-- create a testobject
local t = {}

-- set the metatable
setmetatable(t, meta)

-- read a non-existend key
-- table[key] gets translated into meta.__index(table, key)
print(t["foo"]) -- the key 'foo' is not present in object  'table: 0x600002e8d2c0'

Writing the content of the table. Note that the action is only triggered if the corresponding key is not present in the table.

-- first we want to set the __newindex method
-- this method gets called with the corresponding table and the used key
local meta = {}
meta.__newindex = function(object, index, value)
  print(string.format(
    "writing the value '%s' to the object '%s' at the key `%s`",
    value, object, index))
  return -1
end

-- create a testobject
local t = {}

-- set the metatable
setmetatable(t, meta)

-- write a key (this triggers the method)
-- table[key] = value gets translated into meta.__newindex(table, key, value)
t.foo = 42

Loops

The numerical for loop

Do not use pairs() or ipairs() in critical code! For the performance tests, see here.

Try to save the table-size somewhere and use for i = 1, x do end.

-- i is a local control variable
-- the loop starts by evaluating the three control expressions:
-- initial value, limit, and the step
-- if the step is absent, it defaults to 1
for i = 1,5 do
end

-- the step value can be negative
for i = 20,-20,-1 do
end

-- here the step value is also defined (= delta)
for i = start,finish,delta do
end

-- iterable list of {key, value}
for k,v in pairs(tab) do
end

-- iterable list of {index, value}
for i,v in ipairs(tab) do
end

repeat
until condition

while x do
  if condition then break end
end

As mentioned in the upper code example, pairs() returns key-value pairs, while ipairs() returns index-value pairs. Be aware that pairs() and ipairs() behave slightly different. The ordering is NOT guaranteed in pairs(). If you do not use any keys in your table, pairs() and ipairs() are identical. Best explained with the following example:

animals = {}

animals[1] = "Frog"
animals[2] = "Sheep"
animals[3] = "Dog"
animals[4] = "Cat"
animals["Fish"] = "Fish"
animals[5] = "Monkey"

--[[ 1 Frog
     2 Sheep
     3 Dog
     4 Cat
     Fish Fish
     5 Monkey ]]
for k,v in pairs(animals) do
  print(k, v)
end

--[[ 1 Frog
     2 Sheep
     3 Dog
     4 Cat
     5 Monkey ]]
for i,v in ipairs(animals) do
  print(i, v)
end

Conditionals

if condition then
  print("right")
elseif condition then
  print("could be")
else
  print("no")
end

Mathematical Functions

math.floor(x)

Returns the largest integral value less than or equal to x.

math.random([m [, n]])

The function uses the xoshiro256** algorithm to produce pseudo-random 64-bit integers.

If no argument specified, then returns [0,1). If called with one positive argument n, then returns [1,n] If called with two arguments, then returns [m,n].

math.randomseed ([x [, y]])

When called with at least one argument, the integer parameters x and yare joined into a 128-bit seed. Equal seeds produce equal sequences of numbers. The default for y is zero.

Modulo

-- use of a modulo
local i = a%2 -- if a=2 then i=0; if a=3 then i=1

Strings

Quotes

You can use single or double quotations. In makes no difference.

Length

With the unary prefix operator # you can get the length of a string.

local name = "Peter"
local length = #name
print(length) -- 5 (number of bytes, each character is one byte)

toString(v)

v can be of any type and will be coverted into a string.

local number = 20
local string = tostring(number)
print(string) -- "20"

Concatenation of strings

local name = "pablo"
name = name .. "the number two" -- the two dots are called the string concetenation operator

string.format

Returns a formatted version of its variable number of arguments following the description given in its first argument. The format string follows the same rules as the ISO C function sprintf cpp printf. The only differences are that the conversion specifiers and modifiers *, h, L, l, and n are not supported and that there is an extra specifier, q.

local name = "Eric"
local age = 34
string.format("My name is %s and I am %d old.", name, age)

Table Manipulation

Length

With the unary prefix operator # you can get the length of a table.

local my_table = {2, 5, 10, 22}
local length = #my_table
print(length) -- 4

If you call for the length, it will return a border in that table. The border in the upper table my_table is 4 because it satisfies the following applied condition:

(border == 0 or my_table[border] ~= nil) and
(my_table[border + 1] == nil or border == math.maxinteger)

Here some table examples:

-- sequence, because only one border (5)
local table1 = {10, 20, 30, 40, 50}

-- not a sequence, has two borders (3, 5)
local table2 = {10, 20, 30, nil, 50}

-- sequence with one border (0)
local table3 = {}

Insert

table.insert(t,21) -- append (--> t[#t+1]=21)

Find

With this function you can find a value in a table and return the index

function tblFind(t,e)
  for i, v in pairs(t) do
    if v == e then
      return i
    end
  end
end

Classes

This is a simple example of how to create a class in lua.

local enemies = {}
local enemy = {}
function enemy:new(o)
  o = o or {}
  self.__index = self
  setmetatable(o, self)
  return o
end

function enemy:update()
  -- update enemy
end

function enemy:draw()
  -- draw enemy
end

Alternatively, you can pass predefined variables to the new class as follows:

function enemy:new(x, y, health)
  self.__index = self
  o = setmetatable({}, self)
  o.x = x
  o.y = y
  o.health = health
  return o
end

To create a new instance of the enemy, simple call enemy:new() and add it to your enemies array as follows:

table.insert(enemies, enemy:new(20, 40, 10))

To update and draw the enemies simple loop over it. You can also use ipairs.

for k,v in pairs(enemies) do
  v:update()
  v:draw()
end