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 or false.

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