Lua is a scripting language that is commonly found in complex systems to provide powerful user configuration possibilities. Examples of systems that use Lua for scripting are the Awesome Window Manager and Minetest.
Getting And Using
Install from a stock Debian system.
apt install lua5.3
To run an interpreter, simply lua
.
Run a script the obvious sensible way.
lua my_script.lua
Modules
You can create a module by returning a table with the module’s functions and
variables, and then use require()
to load and use the module in other Lua
scripts.
-- mymodule.lua
local mymodule = {}
function mymodule.say_hello()
print("Hello from mymodule!")
end
return mymodule
-- main.lua
local mymodule = require("mymodule")
mymodule.say_hello() -- Output: Hello from mymodule!
Note that the .lua
suffix is assumed/appended when the require
hunts down the file.
The package.path
variable is a semicolon-separated list of file path
patterns, with a ?
as a placeholder for the module name. When you call
require with a module name, Lua replaces the question mark with the module name
and checks each pattern in the list to see if the corresponding file exists.
Here is an example package.path
.
./?.lua;/usr/local/share/lua/5.4/?.lua;/usr/local/share/lua/5.4/?/init.lua;/usr/local/lib/lua/5.4/?.lua;/usr/local/lib/lua/5.4/?/init.lua
By using dots instead of slashes to separate directory levels, Lua will
automatically convert them to the appropriate path separators for your
operating system. For example, if you want to load a module located at
mydirectory/mymodule.lua
, you would use require("mydirectory.mymodule")
.
Comments
There are single line comments with --
.
print(type(tbl)) -- Output will be "table".
And there are multi-line comments.
--[[ Note!
A long comment passage is contained here.
Here is ok too. ]]
Types
- nil
-
Represents the absence of a value or uninitialized variables. It is used to remove an element from a table by setting its value to nil.
- boolean
-
Set with
true
orfalse
. - number
-
Represents both integers and floating-point numbers. Newer versions may included a sub-type integer — safely ignored.
- string
-
Immutable. Can hold any data, e.g. binary data.
- table
-
The primary data structure in Lua. Can be used like an array, dictionary, or object.
- function
-
Functions are typed objects; they can be assigned to variables, passed as arguments, and returned by other functions.
- userdata
-
Arbitrary C data. Userdata is used to provide an interface to C functions or other low-level constructs not directly available in Lua.
- thread
-
A Lua coroutine. Not parallelized so more like a Python generator.
Casting
Nothing could possibly go wrong with this JavaScript smelling feature.
local a,b = 10,"20"
print(a .. b) -- Output: 1020 (concatenated as strings)
print(a + b) -- Output: 30 (coerced to numbers and added)
String Hassles
Awful string escaping is awful in Lua too.
local str = "Hello, \"World!\"\nThis is a string with escape sequences."
Need a HEREDOC?
local longStr = [[
This is a long string.
It can span multiple lines.
"Quotes" and other special characters don't need to be escaped.
]]
Basically sprintf
is alive and well in Lua.
local name,IQ = "xed",30
local str = string.format("Name: %s IQ: %d", name, IQ)
Operators
Lua has some interesting operators.
Concatenating strings is ..
local first_name = "Winston"
first_name .. " " .. "Smith"
Normal languages usually have a len()
function but Lua has the #
operator. This example checks for the existence of some rooms.
if #rooms > 0 then ...
Scope
By default variables are global. Using the local
keyword is
recommended. Scope blocks include functions and control structures
like if
/end
blocks. Locals declared in an outer block are visible
to inner blocks but not vice versa.
Branching
if condition1 then
-- Execute this block if condition1 is true
elseif condition2 then
-- Execute this block if condition1 is false and condition2 is true
else
-- Execute this block if both condition1 and condition2 are false
end
Miss those crazy C style ternary operators? Well, you can always do this.
local a,b = 5,10
local max_value = (a > b) and a or b -- a if a>b else b
Assertions
Note that the assert()
function is related. If its argument is not nil
or
true
then it returns the first argument. If the first argument is nil
or
false
then it returns the message string if it is supplied as a second
argument.
local a,b = 10,20
local result = assert(a < b, "a should be less than b")
print(result) -- Output: true
-- This will fail and raise an error, as the condition is false
local result2 = assert(a > b, "a should be greater than b")
Loops
Normal numeric for
loops.
for i = 1, 5 do
print(i)
end
Iterating Python style for
over tables.
local tbl = {a = 1, b = 2, c = 3}
-- Iterate over key-value pairs in the table
for key, value in pairs(tbl) do
print(key, value)
end
Note there is also ipairs()
which returns the iteration position and
the value. So 1 and 1, 2 and 2, 3 and 3, etc in the example above, and
yes, Lua seems to index things starting at 1 not 0.
But tables are only the most commonly used possibility. Here is an example of iterating over the first 10 Fibonacci numbers using a custom iterator.
function fib(n)
local a, b = 0, 1
return function()
if n <= 0 then
return nil
end
a, b, n = b, a + b, n - 1
return a
end
end
for num in fib(10) do
print(num)
end
There’s a sensible while
statement.
local i = 1
while i <= 5 do
print(i)
i = i + 1
end
And the slightly less sensible (redundant some might say) repeat
/until
.
local i = 1
repeat
print(i)
i = i + 1
until i > 5
File IO
Here is the slightly clumsy way to read the entire contents of a file.
local file = assert(io.open("input.txt", "r"))
local entire_content = file:read("*all")
file:close()
print(entire_content)
Or you can read the file line by line.
local file = assert(io.open("input.txt", "r"))
for line in file:lines() do
print(line)
end
file:close()
Writing.
local file = assert(io.open("output.txt", "w"))
file:write("Hello, World!\n")
file:close()
Appending.
local file = assert(io.open("output.txt", "a"))
file:write("Another line of text.\n")
file:close()
Error Handling And Exceptions
Lua provides a simple mechanism for error/exception handling using the
pcall
(protected call) and xpcall
(extended protected call) functions.
These functions allow you to call a function in a protected mode, so that if an
error occurs, it can be caught and handled without breaking your program.
function divide(a, b)
if b == 0 then
error("Division by zero")
end
return a / b
end
local success, result = pcall(divide, 10, 0)
if success then
print(result)
else
print("Error: " .. result)
end
Functions
Here’s the normal way to define functions.
function greet(name)
print("Hello, " .. name)
end
greet("Alice")
Anonymous functions are fine.
local greet = function(name)
print("Hello, " .. name)
end
greet("xed.ch")
Functions can return functions.
function make_multiplier(factor)
return function(x)
return x * factor
end
end
local double = make_multiplier(2)
print(double(5)) -- Output: 10
Functions passed around - callbacks - are fine.
function apply(func, x, y)
return func(x, y)
end
function add(a, b)
return a + b
end
print(apply(add, 3, 4)) -- Output: 7
Variable/arbitrary number of arguments.
function print_all(...)
local args = {...}
for i = 1, #args do
print(args[i])
end
end
print_all("A", "B", "C")
Recursion is fine.
Meta Tables And Awkward OOPs
Here is the normal semi-OOP style with a table containing attributes and methods.
local person = {
name = "Xed",
greet = function(self)
print("Hello, " .. self.name)
end
}
person:greet()
person.greet(person) -- Same as line above.
More complicated tricks with meta methods overloading an operator (the +
in
this case).
-- Define a metatable with a custom __add metamethod
local mt = {
__add = function(a, b)
return {value = a.value + b.value}
end
}
-- Create two tables with the metatable
local t1 = {value = 10}
local t2 = {value = 20}
setmetatable(t1, mt)
setmetatable(t2, mt)
-- Perform addition using the custom __add metamethod
local t3 = t1 + t2
print(t3.value) -- Output: 30
Metatables allow you to define custom behavior for tables. Metatables are regular tables that store metamethods, which are functions that dictate how tables behave in certain situations, such as when performing arithmetic operations, indexing non-existent keys, or comparing tables.
Some common use cases for metatables include:
-
Operator overloading: define custom behavior for arithmetic or comparison operators when applied to tables, such as adding or multiplying two tables.
-
Object-oriented programming: create classes and implement inheritance, building complex object-oriented systems (encapsulation, inheritance, and polymorphism, yada yada).
-
Default values: provide default values for non-existent keys in a table, useful when you want a table to return something besides
nil
for undefined keys.
Here is a simple "class" for 2D vectors using metatables.
local Vector = {}
Vector.__index = Vector
-- Constructor function
function Vector.new(x, y)
local self = setmetatable({}, Vector)
self.x = x or 0
self.y = y or 0
return self
end
-- Custom addition metamethod
function Vector.__add(a, b)
return Vector.new(a.x + b.x, a.y + b.y)
end
-- Custom tostring metamethod for pretty printing
function Vector.__tostring(v)
return "(" .. v.x .. ", " .. v.y .. ")"
end
-- Create two Vector objects
local v1 = Vector.new(1, 2)
local v2 = Vector.new(3, 4)
-- Use the custom addition metamethod to add the vectors
local v3 = v1 + v2
print(v3) -- Output: (4, 6)
Note the __add
function is a predefined thing for overloading the +
operator.
Here are some more.
Here’s a list of some common predefined metamethods in Lua:
-
__add
-+
operator -
__sub
--
operator -
__mul
-*
operator -
__div
-/
operator -
__mod
-%
operator -
__pow
-^
operator -
__unm
- unary-
operator -
__concat
-..
operator -
__len
-#
(length) operator -
__eq
-==
operator -
__lt
-<
operator -
__le
-<=
operator -
__index
- table indexing -
__newindex
- table assignments -
__call
- calling a table like a function -
__tostring
- converting a table to a string