Lua Learning Notes
Lua is a powerful, efficient, lightweight, embeddable scripting language. It supports procedural programming, object-oriented programming, functional programming, data-driven programming, and data description. [1]
-
Lua combines simple procedural syntax with powerful data description constructs based on associative arrays and extensible semantics.
-
Lua is dynamically typed, runs by interpreting bytecode with a register-based virtual machine, and has automatic memory management with a generational garbage collection, making it ideal for configuration, scripting, and rapid prototyping.
-
Lua is implemented as a library, written in clean C, the common subset of standard C and C++.
-
The Lua distribution includes a host program called
lua
, which uses the Lua library to offer a complete, standalone Lua interpreter, for interactive or batch use. -
Lua is intended to be used both as a powerful, lightweight, embeddable scripting language for any program that needs one, and as a powerful but lightweight and efficient stand-alone language.
-
As an extension language, Lua has no notion of a "main" program: it works embedded in a host client, called the embedding program or simply the host.
1. The stand-alone interpreter
The stand-alone interpreter (also called lua.c
due to its source file or simply lua
due to its executable) is a small program that allows the direct use of Lua. [2]
# Debian
apt install lua5.4
# Windows
winget install DEVCOM.Lua --version 5.4.6
-
When the interpreter loads a file, it ignores its first line if this line starts with a hash (
#
).#!/usr/bin/env lua print("Hello World!")
-
Without arguments the interpreter enters the interactive mode.
$ lua Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio > math.pi / 4 0.78539816339745 > os.exit() $
-
A script can retrieve its arguments through the predefined global variable
arg
.In a call like
% lua script a b c
, the interpreter creates the tablearg
with all the command-line arguments, before running any code.-
The script name goes into index 0; its first argument ("a" in the example) goes to index 1, and so on.
-
Preceding options go to negative indices, as they appear before the script.
For instance, consider this call:
% lua -e "sin=math.sin" script a b
The interpreter collects the arguments as follows:
arg[-3] = "lua" arg[-2] = "-e" arg[-1] = "sin=math.sin" arg[0] = "script" arg[1] = "a" arg[2] = "b"
-
2. Lexical
-- Lua is case-sensitive: and is a reserved word, but And and AND are two different identifiers.
and break do else elseif
end false for function goto
if in local nil not
or repeat return then true
until while
-
Naming conventions in Lua
-
Variables and Functions: Lower camel case (e.g.,
userName
,calculateArea
) -
Table Keys: Lower camel case or underscore separated (e.g.,
userData.name
,user_data["age"]
) -
Constants: Uppercase with underscores (e.g.,
MAX_PLAYERS
)
-
-
A chunk is simply a sequence of commands (or statements), that is a piece of code that Lua executes, such as a file or a single line in interactive mode. [2]
-
A comment starts anywhere with two consecutive hyphens (
--
) and runs until the end of the line. Lua also offers long comments, which start with two hyphens followed by two opening square brackets and run until the first occurrence of two consecutive closing square brackets, like here:--[[A multi-line long comment ]]
-
Lua needs no separator (i.e. semicolon,
;
) between consecutive statements.a = 1 b = a * 2 a = 1; b = a * 2; a = 1; b = a * 2 a = 1 b = a * 2 -- ugly, but valid
-
It is not an error to access a non-initialized variable (
nil
).$ lua -e 'print(x)' nil
2.1. Local variables and blocks
By default, variables in Lua are global. Unlike global variables, a local variable has its scope limited to the block where it is declared.
-
A block is the body of a control structure, the body of a function, or a chunk (the file or string where the variable is declared):
x = 10 local i = 1 -- local to the chunk while i <= x do local x = i * 2 -- local to the while body print(x) --> 2, 4, 6, 8, ... i = i + 1 end if i > 20 then local x -- local to the "then" body x = 20 print(x + 2) -- (would print 22 if test succeeded) else print(x) --> 10 (the global one) end print(x) --> 10 (the global one)
-
In interactive mode, each line is a chunk by itself (unless it is not a complete command).
$ lua Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio > local x = 10 > print(x) nil > do >> local x = 20 >> print(x) >> end 20 >
-
The do-end blocks are useful to finer control over the scope of some local variables:
local x1, x2 do local a2 = 2 * a local d = (b ^ 2 - 4 * a * c) ^ (1 / 2) x1 = (-b + d) / a2 x2 = (-b - d) / a2 end -- scope of 'a2' and 'd' ends here print(x1, x2) -- 'x1' and 'x2' still in scope
-
It is good programming style to use local variables whenever possible.
-
Local variables avoid cluttering the global environment with unnecessary names; they also avoid name clashes between different parts of a program.
-
Moreover, the access to local variables is faster than to global ones.
-
Finally, a local variable vanishes as soon as its scope ends, allowing the garbage collector to release its value.
-
-
The Lua distribution comes with a module
strict.lua
for global-variable checks; it raises an error if we try to assign to a non-existent global inside a function or to use a non-existent global. -
A common idiom in Lua is
local foo = foo
to create a local variable,foo
, and initializes it with the value of the global variablefoo
.
2.2. Control structures
Lua provides a small and conventional set of control structures, with if
for conditional execution and while
, repeat
, and for
for iteration.
-
All control structures have a syntax with an explicit terminator:
end
terminatesif
,for
andwhile
structures;until
terminatesrepeat
structures. -
The condition expression of a control structure can result in any value.
2.2.1. if then else
An if statement tests its condition and executes its then-part or its else-part accordingly.
if op == "+" then
r = a + b
elseif op == "-" then
r = a - b
elseif op == "*" then
r = a * b
elseif op == "/" then
r = a / b
else
error("invalid operation")
end
2.2.2. While
A while loop repeats its body while a condition is true. As usual, Lua first tests the while condition; if the condition is false, then the loop ends; otherwise, Lua executes the body of the loop and repeats the process.
local i = 1
while a[i] do
print(a[i])
i = i + 1
end
2.2.3. repeat
A repeat–until statement repeats its body until its condition is true. It does the test after the body, so that it always executes the body at least once.
-- print the first non-empty input line
local line
repeat
line = io.read()
until line ~= ""
print(line)
-- computes the square root of 'x' using Newton-Raphson method
local sqr = x / 2
repeat
sqr = (sqr + x / sqr) / 2
local error = math.abs(sqr ^ 2 - x)
until error < x / 10000 -- local 'error' still visible here
2.2.4. For
The for statement has two variants: the numerical for and the generic for.
-
A numerical for has the following syntax:
for var = from, to, step = 1 do -- something end
for i = 0, 3 do io.write(i .. '\t') end -- 0 1 2 3
for i = 0, 10, 2 do io.write(i .. '\t') end -- 0 2 4 6 8 10
-
The generic for loop traverses all values returned by an iterator function, with
pairs
,ipairs
,io.lines
, etc. -
Unlike the numerical for, the generic for can have multiple variables, which are all updated at each iteration. The loop stops when the first variable gets
nil
.
2.2.5. break, return, and goto
The break and return statements are used to jump out of a block, and the goto statement is used jump to almost any point in a function.
In Lua, the syntax for a goto statement is quite conventional: it is the reserved word goto followed by the label name, which can be any valid identifier: it has two colons followed by the label name followed by more two colons, like in ::name::
, which is intentional, to highlight labels in a program.
3. Values and Types
Lua is a dynamically typed language, which means that variables do not have types; only values do. [1]
-
All values carry their own type.
-
All values in Lua are first-class values, which means that all values can be stored in variables, passed as arguments to other functions, and returned as results.
There are eight basic types in Lua: nil
, boolean
, number
, string
, function
, userdata
, thread
, and table
.
The type userdata
is provided to allow arbitrary C data to be stored in Lua variables. A userdata value represents a block of raw memory. There are two kinds of userdata: full userdata, which is an object with a block of memory managed by Lua, and light userdata, which is simply a C pointer value. Userdata has no predefined operations in Lua, except assignment and identity test. By using metatables, the programmer can define operations for full userdata values.
The type thread
represents independent threads of execution and it is used to implement coroutines. Lua threads are not related to operating-system threads. Lua supports coroutines on all systems, even those that do not support threads natively.
Tables, functions, threads, and (full) userdata values are objects: variables do not actually contain these values, only references to them. Assignment, parameter passing, and function returns always manipulate references to such values; these operations do not imply any kind of copy.
3.1. Nil
The type nil
has one single value, nil
, whose main property is to be different from any other value; it often represents the absence of a useful value.
$ lua
Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio
> undefined
nil
> not undefined
true
>
3.2. Booleans
The type boolean
has two values, false
and true
.
-
Both
nil
andfalse
make a condition false; they are collectively called false values. Any other value makes a condition true. -
Despite its name,
false
is frequently used as an alternative tonil
, with the key difference thatfalse
behaves like a regular value in a table, while anil
in a table represents an absent key.-
Lua supports a conventional set of logical operators:
and
,or
, andnot
.Both
and
andor
use short-circuit evaluation, that is, they evaluate their second operand only when necessary.
-
-
The result of the
and
operator is its first operand if that operand is false; otherwise, the result is its second operand.4 and 5 --> 5 nil and 13 --> nil false and 13 --> false
-
The result of the
or
operator is its first operand if it is not false; otherwise, the result is its second operand:0 or 5 --> 0 false or "hi" --> "hi" nil or false --> false
-
The
not
operator always gives a Boolean value.not nil --> true not false --> true not 0 --> false not not 1 --> true not not nil --> false
3.3. Numbers
The type number
represents both integer numbers and real (floating-point) numbers, using two subtypes: integer and float.
-
Integers and floats with the same value compare as equal in Lua:
1 == 1.0 --> true -3 == -3.0 --> true 0.2e3 == 200 --> true
-
To distinguish between floats and integers, use
math.type
:math.type(3) --> integer math.type(3.0) --> float
-
If both operands are integers, the operation gives an integer result; otherwise, the operation results in a float. In case of mixed operands, Lua converts the integer one to a float before the operation:
13.0 + 25 --> 38.0 -(3 * 6.0) --> -18.0
-
To avoid different results between division of integers and divisions of floats, division always operates on floats and gives float results:
3.0 / 2.0 --> 1.5 3 / 2 --> 1.5 3 // 2 --> 1 -- floor division and denoted by //
-
Lua provides the following relational operators, and all these operators always produce a Boolean value:
< > <= >= == ~=
-
To force a number to be a float, simply add
0.0
to it.-3 + 0.0 --> -3.0 0x7fffffffffffffff + 0.0 --> 9.2233720368548e+18
-
To force a number to be an integer, OR it with zero:
2^53 --> 9.007199254741e+15 (float) 2^53 | 0 --> 9007199254740992
-- number has no integer representation 3.2 | 0 -- fractional part 2^64 | 0 -- out of range
3.4. Strings
The type string
represents immutable sequences of bytes.
-
Lua is 8-bit clean: strings can contain any 8-bit value, including embedded zeros ('\0').
-
Lua is also encoding-agnostic; it makes no assumptions about the contents of a string.
-
Get the length of a string using the length operator (denoted by
#
):hi = 'Hello 世界' print(#hi) --> 12 -- always counts the length in bytes
-
Concatenate two strings with the concatenation operator .. (two dots):
"Hello " .. "World" --> Hello World "result is " .. 3 --> result is 3
-
Multiple line literal strings can be delimited also by matching double square brackets, as with long comments. Moreover, it ignores the first character of the string when this character is a newline.
page = [[ <html> <head> <title>An HTML Page</title> </head> <body> <a href="http://www.lua.org">Lua</a> </body> </html> ]]
-
Lua provides automatic conversions between numbers and strings at run time.
-
To convert a string to a number explicitly, we can use the function
tonumber
, which returnsnil
if the string does not denote a proper number.tonumber(" -3 ") --> -3 tonumber(" 10e4 ") --> 100000.0 tonumber("10e") --> nil (not a valid number) tonumber("0x1.3p-4") --> 0.07421875
-
To convert a number to a string explicitly, call the function
tostring
:print(tostring(10) == "10") --> true
-
Since version 5.3, Lua includes a small library (
utf8
) to support operations on Unicode strings encoded in UTF-8.hi = 'Hello 世界' print(string.len(hi)) -- 12 print(utf8.len(hi)) -- 8
3.5. Tables
The type table
implements associative arrays, that is, arrays that can have as indices not only numbers, but any Lua value except nil
and NaN
.
-
Tables can be heterogeneous; that is, they can contain values of all types (except
nil
). -
Any key associated to the value
nil
is not considered part of the table. Conversely, any key that is not part of a table has an associated valuenil
. -
Lua uses tables to represent packages and objects as well. For Lua, the
math.sin
means “index the table math using the string "sin" as the key”. -
Lua stores global variables in ordinary tables.
-
Tables are created by means of a constructor expression, which in its simplest form is written as
{}
:a = {} -- create a table and assign its reference a['x'] = 10 -- new entry, with key="x" and value=10 print(a.x) --> 10
3.5.1. Table Indices
-
Each table can store values with different types of indices, and it grows as needed to accommodate new entries.
a = {} -- empty table -- create 1000 new entries for i = 1, 1000 do a[i] = i*2 end a[9] --> 18 a['x'] = 10 a['x'] --> 10 a['y'] --> nil
-
Lua supports to use the field name as an index by providing
a.name
as syntactic sugar fora['name']
.a = { x = 10 } a.x == a['x'] --> true -- indexed by the string 'x' a.x == a[x] --> false
3.5.2. Table Constructors
Constructors are expressions that create and initialize tables, and the simplest constructor is the empty constructor, {}
.
-- empty constructor
a = {}
-- record-style and list-style initializations
days = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" } -- initialize a list
a = { x = 10, y = 20 } -- initialize a record-like table
-- explicitly write each index as an expression, between square brackets, to
-- initialize fields with negative indices, nor with string indices
opnames = {
["+"] = "add",
["-"] = "sub",
["*"] = "mul",
["/"] = "div"
}
3.5.3. Arrays, Lists, and Sequences
To represent a conventional array or a list, simply use a table with integer keys.
-- read 10 lines, storing them in a table
a = {}
for i = 1, 10 do
a[i] = io.read()
end
-
Sequences are lists without holes.
-
For sequences, Lua offers the length operator (
#
) to give the length of the sequence represented by a table. -
The length operator (
#
) is unreliable for lists with holes (nils).a = { [1] = 1, [3] = 3, } print(#a) -- 1
-
3.5.4. Table Traversal
-
Tables can be traversed all key–value pairs with the
pairs
iterator, the order that elements appear in a traversal is undefined.t = { 10, print, x = 12, k = "hi" } for k, v in pairs(t) do print(k, v) end -- 1 10 -- 2 function: 0x5595d1eb1730 -- k hi -- x 12
-
For lists, they can be traversed by using the
ipairs
iterator:t = { 10, print, 12, "hi" } for k, v in ipairs(t) do print(k, v) end -- 1 10 -- 2 function: 0x558e75c75730 -- 3 12 -- 4 hi
Or, with a numerical for:
t = { 10, print, 12, "hi" } for k = 1, #t do print(k, t[k]) end -- 1 10 -- 2 function: 0x561090ff8730 -- 3 12 -- 4 hi
3.5.5. The table library
-
The function
table.insert
inserts an element in a given position of a sequence, moving up other elements to open space. Without a position, it inserts the element in the last position of the sequence, moving no elements.t = { 10, 20, 30 } table.insert(t, 1, 50) for k, v in ipairs(t) do print(k, v) end -- 1 50 -- 2 10 -- 3 20 -- 4 30
-
The function
table.remove
removes and returns an element from the given position in a sequence, moving subsequent elements down to fill the gap. Without a position, it removes the last element of the sequence.t = { 10, 20, 30 } table.remove(t) for k, v in ipairs(t) do print(k, v) end -- 1 10 -- 2 20
3.6. Functions
Functions are the main mechanism for abstraction of statements and expressions in Lua.
print(8*9, 9/8) -- as a statement
a = math.sin(3) + math.cos(10) -- as an expression
print(os.date())
If a function has one single argument and that argument is either a literal string or a table constructor, then the parentheses in the call are optional:
print "Hello World" --> print("Hello World")
dofile 'a.lua' --> dofile ('a.lua')
print [[a multi-line --> print([[a multi-line
message]] message]])
f{x=10, y=20} --> f({x=10, y=20})
type{} --> type({})
-
A Lua program can use functions defined both in Lua and in C (or in any other language used by the host application).
-
A function definition in Lua has a conventional syntax, like here:
-- add the elements of sequence 'a' function add(a) local sum = 0 for i = 1, #a do sum = sum + a[i] end return sum end
-
Lua adjusts the number of arguments to the number of parameters by throwing away extra arguments and supplying nils to extra parameters.
function f(a, b) print(a, b) end f() -- nil nil f(3) -- 3 nil f(3, 4) -- 3 4 f(3, 4, 5) -- 3 4 (5 is discarded)
3.6.1. Multiple results
-
Functions that we write in Lua also can return multiple results, by listing them all after the
return
keyword.function maximum(a) local mi = 1 -- index of the maximum value local m = a[mi] -- maximum value for i = 1, #a do if a[i] > m then mi = i; m = a[i] end end return m, mi end print(maximum({ 8, 10, 23, 12, 5 })) -- 23 3
-
Lua always adjusts the number of results from a function to the circumstances of the call.
-
When call a function as a statement, Lua discards all results from the function.
-
When use a call as an expression (e.g., the operand of an addition), Lua keeps only the first result.
-
Lua gives all results only when the call is the last (or the only) expression in a list of expressions: multiple assignments, arguments to function calls, table constructors, and return statements.
function foo0() end -- returns no results function foo1() return "a" end -- returns 1 result function foo2() return "a", "b" end -- returns 2 results
-
In a multiple assignment, a function call as the last (or only) expression produces as many results as needed to match the variables:
x, y = foo2() -- x="a", y="b" x = foo2() -- x="a", "b" is discarded x, y, z = 10, foo2() -- x=10, y="a", z="b" -- In a multiple assignment, if a function has fewer results than we -- need, Lua produces nils for the missing values: x, y = foo0() -- x=nil, y=nil x, y = foo1() -- x="a", y=nil x, y, z = foo2() -- x="a", y="b", z=nil -- A function call that is not the last -- element in the list always produces exactly one result: x, y = foo2(), 20 -- x="a", y=20 ('b' discarded) x, y = foo0(), 20, 30 -- x=nil, y=20 (30 is discarded)
-
When a function call is the last (or the only) argument to another call, all results from the first call go as arguments.
print(foo0()) --> (no results) print(foo1()) --> a print(foo2()) --> a b print(foo2(), 1) --> a 1 print(foo2() .. "x") --> ax
-
A constructor also collects all results from a call, without any adjustments:
t = { foo0() } -- t = {} (an empty table) t = { foo1() } -- t = {"a"} t = { foo2() } -- t = {"a", "b"} t = { foo0(), foo2(), 4 } -- t[1] = nil, t[2] = "a", t[3] = 4
-
Finally, a statement like
return f()
returns all values returned byf
:function foo(i) if i == 0 then return foo0() elseif i == 1 then return foo1() elseif i == 2 then return foo2() end end print(foo(1)) --> a print(foo(2)) --> a b print(foo(0)) -- (no results) print(foo(3)) -- (no results) -- force a call to return exactly one result by enclosing it in an -- extra pair of parentheses: print((foo0())) --> nil print((foo1())) --> a print((foo2())) --> a
-
-
3.6.2. Variadic Functions
A function in Lua can be variadic (…
), that is, it can take a variable number of arguments.
-
To iterate over its extra arguments as a sequence, a function can use the expression
{…}
ortable.pack
to collect them all in a table.function add(...) local s = 0 for _, v in ipairs { ... } do s = s + v end return s end print(add(3, 4, 10, 25, 12)) --> 54
function nonils(...) local arg = table.pack(...) for i = 1, arg.n do if arg[i] == nil then return false end end return true end print(nonils(2, 3, nil)) --> false print(nonils(2, 3)) --> true print(nonils()) --> true print(nonils(nil)) --> false
-
The three-dot expression is a vararg expression, which behaves like a multiple return function, returning all extra arguments of the current function.
function echo(...) return ... end print(echo(1, 3, 5, 7)) -- 1 3 5 7
4. The I/O library
The I/O library provides two different styles for file manipulation. The first one uses implicit file descriptors; that is, there are operations to set a default input file and a default output file, and all input/output operations are over these default files. The second style uses explicit file descriptors.
When using implicit file descriptors, all operations are supplied by table io
. When using explicit file descriptors, the operation io.open
returns a file descriptor and then all operations are supplied as methods of the file descriptor.
The table io
also provides three predefined file descriptors with their usual meanings from C: io.stdin
, io.stdout
, and io.stderr
. The I/O library never closes these files.
Unless otherwise stated, all I/O functions return nil
on failure (plus an error message as a second result and a system-dependent error code as a third result) and some value different from nil
on success.