1 of 47

Image source: Wikimedia Commons

2 of 47

Outline

  • Basic characteristics of Lua
  • Data types
  • Operators
  • Control flow
  • Functions
  • Data structures

3 of 47

Resources

4 of 47

Characteristics of the Lua language

  • Used primarily as a scripting language
  • Dynamically typed
  • Procedural syntax
  • Automatic memory management (garbage collector)

5 of 47

Syntax

  • Free-form: no statement separators, no semantic whitespace��local lowest = math.huge�for index, value = ipairs(elements) doif value < lowest then� lowest = value� endendreturn lowest��local lowest = math.huge for index, value = ipairs(elements) do if value < lowest then lowest = value end end return lowest

6 of 47

Syntax

  • Comments: line and block��--[[�Finds lowest value among elements�]]local lowest = math.huge�for index, value = ipairs(elements) doif value < lowest then� lowest = value -- replace lowest found valueendendreturn lowest

7 of 47

Data types

  • nil
  • boolean: true, false
  • numeric: -12, 3.14, 1e6, 0xdeadbeef
  • strings: ”Hello world!”
  • functions: function foo(args) --[[ body ]] end
  • tables: { 1, 2, 3 }, { name = ”Bugatti Veyron”, speed = 400 }
  • userdata
  • coroutines/threads

8 of 47

Variables

  • Assignment: =
    • The value of an unassigned variable is nil
    • print(a) -- nil�a = ”Hello”�print(a) -- “Hello”
  • Multiple assignment
    • a, b, c = 12, ”Hello World”�print(a, b, c) -- 12, “Hello World”, nil�a, b = b, a -- concise swap

9 of 47

Variables

  • Locals
    • All variables are global unless otherwise specified
    • It’s possible to declare a variable as local, then it’s local to the current scope
    • Each body of a condition, loop or function definition creates a new nested scope
    • It is also possible to explicitly create a new scope using the do keyword
    • A scope is usually terminated with the keyword end
    • a = 12�do� a = a + 2 -- changes the global a� print(a) -- 14local a = 42 -- creates a new variable also called a, which is local� print(a) -- 42end -- local a goes out of scopeprint(a) -- 14

10 of 47

Arithmetic and bitwise operators

  • The usual: +, -, *, /, unary -
  • % - modulo
  • ^ - exponentiation (even with non-integral exponents)
  • // - floor division (Lua 5.3+)
    • a / b == (a // b) + (a % b)
  • Bitwise operations: (Lua 5.3+)
    • Bitwise AND, OR, XOR: &, |, ~
    • Bitwise NOT: unary ~
    • Arithmetic shift: >>, << (vacant bits are zeroed)

11 of 47

Relational operators

  • Equality: ==
  • Inequality: ~=
    • Variables of different types are not equal
    • Variables of reference types (tables, functions, userdata, threads) are equal iff they refer to the same object
  • Comparison: >, >=, <, <=
    • Numbers are ordered by their value
    • Strings are ordered lexicographically according to the current locale
    • Other types are unordered, and comparison will result in a runtime error

12 of 47

Logical operators

  • and, or, not
  • The return value of and and or is one of its operands
    • nil counts as false, all other values of non-boolean types count as true
    • If a is false or nil, then a and b returns a, otherwise it returns b
    • If a is true, then a or b returns a, otherwise it returns b
      • This can be used for optional/default arguments:�local upperBound = userSpecifiedUpperBound or defaultUpperBound�If userSpecifiedUpperBound is defined, its value is assigned to upperBound; otherwise defaultUpperBound is used instead
      • This can be used in place of a ternary conditional operator:�local max = (x > y) and x or y�This is equivalent to: if x is greater than y, use x, otherwise use y

13 of 47

Logical operators

  • Short-circuit evaluation
    • false and error() -- never calls error
    • This can be useful in conjunction with the shorthand described in the previous slide:�local targetHealth = target and target.health
    • target may be nil, in which case accessing target.health would result in a runtime error
    • By putting the target.health on the right side of an and, we make sure it is accessed only when target is not nil
    • As a side effect, when target is nil, targetHealth is also set to nil, which is a sensible default

14 of 47

Other operators

  • String concatenation: ..
    • Converts both operands to string, if possible, and joins them together in a new string
    • print(”I caught ” .. count .. ” fish today”)
  • Length operator: #
    • If the operand is an array, returns the number of its elements
    • friends = { ”Bajza”, ”Bejval”, ”Jirsák”, ”Kemlink”, ”Zilvar” }�print(”Bylo nás ” .. #friends)

15 of 47

Conditions

Expressed using the familiar if, then, else and elseif keywords

if range < minRange then-- ...elseif range > maxRange then-- ...else-- ...end

16 of 47

Conditions

As with logical operators:

  • nil counts as false, all other values of non-boolean types count as true
  • This can be useful to determine if a variable has been defined:�if target then-- use targetelse-- there is no targetend

17 of 47

Loops - for (numeric)

for var = start, end[, step] do� -- bodyend

var is the variable to be used as the loop index, initially set to start�The loop body is run�If var ~= end, step is added to var, and the process repeats (until var == end)

start, end and step must be numbers

step may be omitted, in which case it’s assumed to be equal to 1

18 of 47

Loops - for (numeric)

Go over each element of an array, one by one, and print it:for i = 1, #elements do� print(elements[i])�end

Go over every odd-numbered element of an array (step = 2):for i = 1, #elements, 2 doend

Go over every element of an array in reverse order:for i = #elements, 1, -1 doend

19 of 47

Loops - for (iterator)

for index, value in ipairs(array) do-- bodyend

for key, value in pairs(table) do-- bodyend

Looping over ipairs(array) will run the loop for each element of array in order, setting index to the index of the element and value to the element itself.

Looping over pairs(table) will run the loop for each element of table, setting key to key the element is stored under and value to the element itself. The order of iteration is unspecified.

20 of 47

Loops - while, repeat

while #elements > 0 do� print(elements[#elements])� elements[#elements] = nilend

repeat� print(elements[#elements])� elements[#elements] = niluntil #elements == 0

21 of 47

Loops - break

The break statement can be used to terminate any for, while or repeat loop early

for key, value in pairs(table) doif key == searchedKey then� print(value)� breakendend

22 of 47

Functions

A function is created using the keyword function, followed by a list of arguments, and the function definition. The resulting function is then assigned into a variable:printArray = function(elements)� for _, element in ipairs(elements) do print(element) endend

There is alternate syntax for this, that more resembles function declarations in C-like languages:�function printArray(elements)� for _, element in ipairs(elements) do print(element) endend

Functions can be local variables as well:�local function printArray(elements)� -- ...end

23 of 47

Functions - return value

function findIf(elements, predicate)� for _, element in ipairs(elements)� if predicate(element) then return element end� end�end

firstRoundSquare = findIf(� {1, 4, 9, 16, 25, 36, 49},� function(x)� return x % 5 == 0� end�)

24 of 47

Functions - multiple return values

function findIf(elements, predicate)� for index, element in ipairs(elements)� if predicate(element) then return index, element end� end�end

firstRoundSquareIndex, firstRoundSquare = findIf(� {1, 4, 9, 16, 25, 36, 49},� function(x)� return x % 5 == 0� end�)

25 of 47

Functions - optional parameters

As with unassigned variables, parameters not passed in are equal to nil�This can be used to emulate optional function parameters

function takeFiltered(elements, predicate, count)� local result = {}� for _, element in ipairs(elements) doif predicate(element) then result[#result] = element endif count ~= nil and #result == count then break endendreturn result�end��odds = takeFiltered({1, 2, 3, 4, 5, 6, 7, 8, 9}, isOdd)�firstTwoOdds = takeFiltered({1, 2, 3, 4, 5, 6, 7, 8, 9}, isOdd, 2)

26 of 47

Functions - variadic arguments

function concatenate(...)� local result = ””� for _, arg in ipairs({...}) do result = result .. arg endreturn result�end

print(concatenate(”I caught ”, count, “ fish”))

27 of 47

Functions - closures

function counter(start, step)� return function() -- captures the variables start and step insidelocal previousValue = start� start = start + step� return previousValue� endend

oneCounter = counter(1, 1)�twoCounter = counter(10, 2)�print(oneCounter()) -- 1�print(twoCounter()) -- 10�print(oneCounter()) -- 2�print(twoCounter()) -- 12

28 of 47

Lua standard library

  • basic
    • pairs
    • ipairs
    • print
    • tostring
    • type
  • table
    • table.insert
    • table.concat
    • table.sort
    • table.pack
    • table.unpack

  • math
    • math.huge
    • math.max
    • math.min
    • math.floor
    • math.random
    • math.pi
  • string
    • string.reverse
    • string.lower
    • string.upper
    • string.sub
    • string.match

The host application that Lua is running in may choose to not include these libraries, therefore these functions may not always be available

29 of 47

Coroutines

  • A coroutine is a resumable function
  • Normally, when a function is called, it runs all the way to completion and returns a value
  • A coroutine can suspend (pause) its execution at any point; this is called yielding
  • A suspended coroutine can be resumed later, with all of its local variables intact

30 of 47

Coroutines

coro = coroutine.create(function(v)� print(v) -- print the passed-in value� coroutine.yield(v + 4) -- yield v + 4 and wait for resume� return v * 5�end)��running, x = coroutine.resume(coro, 10) -- coro prints 10�-- running is true, because coro hasn’t finished yet�-- x is the value yielded from coro�print(running, x) -- true, 14�running, x = coroutine.resume(coro) -- coro finishes running�print(running, x) -- false, 50

31 of 47

Coroutines

counter = coroutine.create(function()� local x, continue = 0, truewhile continue do� x = x + 1� continue = coroutine.yield(x)� endreturn x�end)�print(coroutine.resume(counter)) -- true, 1�print(coroutine.resume(counter, true)) -- true, 2�print(coroutine.resume(counter, false)) -- false, 2�print(coroutine.resume(counter, false)) -- false, <error message>

32 of 47

Tables

  • Tables in Lua serve as an array type and a dictionary / hash map type
  • Enclosing a list of values in curly braces ({}) creates the table as an array�a = { 1, 4, 9 }
  • Array elements can be accessed with the subscript operator ([])
  • Indices start at 1
  • Accessing indices not stored in the array results in nil, as with unassigned variablesprint(a[1], a[2], a[3], a[4]) -- 1, 4, 9, nil
  • The length operator (#) can be used to determine the number of elements stored in an arrayprint(#a) -- 3

33 of 47

Tables

  • When creating a table, values can be paired with string keyscar = { name = ”Bugatti Veyron”, speed = 400 }
  • Table elements can be accessed with the index operator (.)�print(car.name, car.speed) -- ”Bugatti Veyron”, 400
  • Accessing keys not stored in the table results in nilprint(car.legs) -- nil
  • The length operator (#) does not give useful results with tables that are not arrays�print(#car) -- usually 0, but not always

34 of 47

Tables

  • When creating a table, if the key is an expression enclosed in square brackets, then the result of that expression is useda = { [1] = 1, [2] = 4, [3] = 9 }�-- same as a = { 1, 4, 9 }�car = { [”name”] = ”Bugatti Veyron”, [”speed”] = 400 }�-- same as car = { name = ”Bugatti Veyron”, speed = 400 }
  • When accessing table elements, any expression can be used in the square brackets; the result of the expression is used as the key�print(car[”name”], car[”speed”]) -- ”Bugatti Veyron”, 400

35 of 47

Tables

  • Each set of curly braces creates a new table
  • These tables are distinct from each other, and will never compare equal, even if their contents match�a, b = {}, {}�print(tostring(a == b)) -- false

36 of 47

Tables

  • Tables can be used as arrays�a = { 1, 4, 9, 16, 25, 36, 49 }�for i = 1, #a do print(a[i]) endfor _, e in ipairs(a) do print(e) end
  • Tables can be used as associative arrays / dictionaries�speeds = {� [”Bugatti Veyron”] = 400,� [”Lamborghini Countach”] = 300�}�print(speeds[myCar])

37 of 47

Tables

  • Tables can be used as a stand-in for records / structs�bugatti = { name = ”Bugatti Veyron”, speed = 400 }�lambo = { name = ”Lamborghini Countach”, speed = 300 }�if bugatti.speed > lambo.speed then-- ...end

38 of 47

Tables

  • Tables can be used as more complicated data structures�cz = { name = ”Czechia” }�pl = { name = ”Poland” }�de = { name = ”Germany” }�at = { name = ”Austria” }�cz.neighbors = { pl, de, at }�pl.neighbors = { cz, de }�de.neighbors = { pl, cz, at }�at.neighbors = { cz, de }

39 of 47

Tables

  • Tables can be used as objects
    • You can store both data and behavior in a table

bugatti = {� name = ”Bugatti Veyron”,� speed = 400,� drive = function(self, destination) --[[ … ]] end,� refuel = function(self) --[[ … ]] end�}�bugatti.drive(bugatti, prague)�bugatti:drive(prague) -- syntax sugar for the above line

40 of 47

Tables

Alternate syntax for defining functions in tables:bugatti = {� name = ”Bugatti Veyron”,� speed = 400�}�function bugatti.drive(self, destination)� -- ...end�-- or equivalently:function bugatti:drive(destination)� -- parameter “self” is added implicitlyend

41 of 47

Metatables

  • Any table can set its own metatable with the setmetatable function
  • A metatable defines functions (metamethods) that can be called in place of normal behavior when an operator is invoked

__add

+

__band

&

__len

#

__sub

-

__bor

|

__eq

==

__mul

*

__bxor

~

__lt

<

__div

/

__bnot

unary ~

__le

<=

__mod

%

__shl

<<

__index

[] (read)

__pow

^

__shr

>>

__newindex

[] (write)

__unm

unary -

__concat

..

__call

()

__idiv

//

42 of 47

Metatables

deepComparableArray = {� __eq = function(lhs, rhs)� -- compare arrays element by elementend�}�numbers = { 1, 3, 5 }�-- normally, two distinct arrays are never equal�print(tostring(numbers == { 1, 3, 5 })) -- false�setmetatable(numbers, deepComparableArray)�-- now deepComparableArray.__eq is used for this comparison instead�print(tostring(numbers == { 1, 3, 5 })) -- true

43 of 47

Metatables - __index

  • When a table element is accessed with [], . or : operators, and the key is not present, the __index metamethod is called to determine the result of the access
  • This is often used as follows to emulate classes:�car = {}�car.prototype = { drive = function() --[[ … ]] end }�car.__index = function(index) return car.prototype[index] endbugatti = {}�setmetatable(bugatti, car) -- bugatti “is a” car�bugatti:drive() -- bugatti.drive is nil, car.__index is consulted instead, fetching car.prototype.drive
  • This is a common enough pattern that the car.prototype table may be used as the __index directly

44 of 47

Metatables - __index

car = {}�function car:drive(destination) --[[ … ]] endfunction car:refuel() --[[ … ]] end��bugatti = {� name = ”Bugatti Veyron”,� speed = 400�}�bugatti:drive(prague) -- error, cannot find bugatti.drive�setmetatable(bugatti, { __index = car })�bugatti:drive(prague) -- calls car:drive with self = bugatti

45 of 47

Metatables - __index

vehicle = {}�function vehicle:position() --[[ … ]] end

car = {}�function car:drive(destination) --[[ … ]] endfunction car:refuel() --[[ … ]] end�setmetatable(car, { __index = vehicle })

bugatti = {� name = ”Bugatti Veyron”,� speed = 400�}�setmetatable(bugatti, { __index = car })�print(tostring(bugatti:position())) -- falls back to car:position, which falls back to vehicle:position

46 of 47

Userdata

  • Data types provided by the hosting environment
  • Can also have a metatable set
    • And so usually provide their own functions or operator overloads
    • For example, a 3D vector type may be provided by the hosting environment that supports adding or subtracting two vectors with the + and - operators, or multiplying a vector by a number with the * operator

47 of 47

Questions?