Lua
Lua is a dynamically-typed, garbage collected programming language that can easily embedded as a scripting language in other programs.
Concepts
Basics
- Variables are global by default. Use
localto circumvent. - Optional semicolons
- No curly braces
{}for blocks - Comments:
-- single lineand--[[ multi-line --]] - Data Types: nil, true/false, number (double float), Integers (Lua 5.3+), string (immutable), function
- Multiple return values:
function foo() return 1, "two" end; local x, y = foo() Variadic functions:
function sum(...) local total = 0; for _, v in ipairs{...} do total = total + v end return total end print(sum(4,4))8
- Error Handling:
error("message"): Throws an error (likethrow). pcall(function, args...): "Protected call.", similar totry-catch.local status, result_or_error = pcall(function() -- some_risky_operation() return "Success!" end) if status then print("Succeeded:", result_or_error) else print("Failed:", result_or_error) endSucceeded: Success!
Table Data Structure
Primary data structure is the table. Can represent arrays, dicts, and objects.
local mixed = {
10,
"hello",
[5] = "explicit index",
key = "value",
foo = "bar",
}
print(mixed[0]) -- nil because of 1-Based Indexing
print(mixed[5]) -- "explicit index"
print(#mixed) -- length of the sequence part (2)
print(mixed["key"])
print(mixed.key) -- syntactic sugar
mixed.thing = nil -- deletes key from table
print(mixed["foo"])
mixed = {} -- set to empty table
nil explicit index 2 value value bar
Metatables and Metamethods
Every table can have a metatable. It contains special functions called
metamethods. This is the mechanism used for operator overloading, inheritance,
and customizing table behavior.
Metamethods include:
__indexfor prototypal inheritance:local defaults = { x = 0, y = 0 } local point = { x = 10 } setmetatable(point, { __index = defaults }) print(point.x) -- 10 (from point itself) print(point.y) -- 0 (from defaults via __index)10 0
__newindex: Called when trying to assign to a non-existent key in a table__call: Allows table to be called like a function:myTable(arg1, arg2)__add,__sub,__mul,__div, etc. for operator overloading.__tostring: Called byprint()ortostring()setmetatable(table, metatable_or_nil),getmetatable(table)
OOP (Methods) in Lua
Vector = {} -- Our "class" (just a table)
Vector.__index = Vector -- Instances will look up methods in Vector itself
function Vector:new(x, y) -- Colon : is syntactic sugar for methods
-- self:method(...) is like self.method(self, ...)
local obj = {x = x or 0, y = y or 0}
setmetatable(obj, Vector)
return obj
end
function Vector:magnitude()
return math.sqrt(self.x^2 + self.y^2)
end
local v1 = Vector:new(3, 4)
print(v1:magnitude()) -- 5
print(v1.x) -- 3
Coroutines
Lua has cooperative coroutines (yield control explicitly). They are useful for iterators, generators, state machines, non-blocking I/O (in event loops).
local co = coroutine.create(function(start_val)
print("Coroutine started with:", start_val)
local val = start_val
for i = 1, 3 do
val = val + i
print("Coroutine yielding:", val)
coroutine.yield(val) -- Suspend and return val
end
print("Coroutine finished")
return val + 100 -- Final return value
end)
print(coroutine.status(co)) -- suspended
local status, res
status, res = coroutine.resume(co, 10) -- Start it with 10
print("Main received:", status, res) -- true, 11
status, res = coroutine.resume(co)
print("Main received:", status, res) -- true, 13 (11+2)
status, res = coroutine.resume(co)
print("Main received:", status, res) -- true, 16 (13+3)
status, res = coroutine.resume(co)
print("Main received:", status, res) -- true, 116 (16+100)
print(coroutine.status(co)) -- dead (finished correctly)
suspended Coroutine started with: 10 Coroutine yielding: 11 Main received: true 11 Coroutine yielding: 13 Main received: true 13 Coroutine yielding: 16 Main received: true 16 Coroutine finished Main received: true 116 dead
Modules and require
require("modulename")loads modules- A module returns a table containing its public functions and data
- Lua looks for modules in
package.pathandpackage.cpath(for C modules)
-- mymodule.lua
local M = {} -- Table for our module
function M.greet(name)
return "Hello, " .. name .. "!"
end
M.version = "1.0"
return M -- Export the table
-- main.lua
local mymod = require("mymodule")
print(mymod.greet("Lua User")) -- Hello, Lua User!
print(mymod.version) -- 1.0
Lua C API
The Lua C API for embedding Lua into C/C++ (or extending Lua with C functions) is stack-based. You push values onto a virtual stack, call Lua functions, and retrieve results from the stack.
#include <stdio.h> #include <string.h> // lua headers #include "lua.h" #include "lauxlib.h" #include "lualib.h" // ------------------- C function to be exposed to Lua ------------------- // Signature for C functions callable by Lua: // int function_name(lua_State *L); // Returns number of return values pushed onto the stack static int c_add(lua_State *L) { // 1. Check and get arguments from Lua stack if (lua_gettop(L) != 2) // returns number of items on the stack (also the index of the top element) { return luaL_error(L, "C: c_add expects 2 arguments"); // Throws a Lua error } if (!lua_isnumber(L, 1) || !lua_isnumber(L, 2)) { return luaL_error(L, "C: c_add expects two numbers"); } double num1 = lua_tonumber(L, 1); // Get first argument (index 1) double num2 = lua_tonumber(L, 2); // Get second argument (index 2) double sum = num1 + num2; // 2. Perform the operation lua_pushnumber(L, sum); // 3. Push the result onto the Lua stack return 1; // 4. Return the number of results pushed } // ------------------- Error handler for lua_pcall ------------------- // This function is pushed onto the stack before calling lua_pcall. // If an error occurs during lua_pcall, Lua calls this function with the error message on top of the stack. static int msgh (lua_State *L) { const char *msg = lua_tostring(L, 1); // Get error message if (msg == NULL) { // Does it have a metamethod that yields a string instead? if (luaL_callmeta(L, 1, "__tostring") && // Call __tostring metamethod lua_type(L, -1) == LUA_TSTRING) return 1; // Return the result of __tostring else msg = lua_pushfstring(L, "(error object is a %s value)", luaL_typename(L, 1)); } luaL_traceback(L, L, msg, 1); // Append a traceback return 1; // Return the new error message (with traceback) } int main() { lua_State *L = luaL_newstate(); // Create a new Lua state if (L == NULL) { fprintf(stderr, "C: Error creating Lua state.\n"); return 1; } luaL_openlibs(L); // Load standard Lua libraries (print, math, string, etc.) // --- Expose C function to Lua --- lua_pushcfunction(L, c_add); // Push our C function onto the stack lua_setglobal(L, "c_add"); // Set it as a global Lua variable named "c_add" // Alternatively: lua_register(L, "c_add", c_add); // macro that does both printf("C: --- Running script.lua ---\n"); // Load and run the Lua script file // We'll use lua_pcall for safer execution, so we need an error handler. // Push error handler function lua_pushcfunction(L, msgh); int msgh_idx = lua_gettop(L); // Get stack index of error handler if (luaL_loadfile(L, "script.lua") != LUA_OK) { // Error loading file (e.g., file not found, syntax error) fprintf(stderr, "C: Error loading script.lua: %s\n", lua_tostring(L, -1)); lua_pop(L, 1); // Pop error message lua_close(L); return 1; } // luaL_loadfile pushes the compiled chunk onto the stack. // Now call it with lua_pcall(L, num_args, num_results, error_handler_index) // error_handler_index is the stack index of our error handler function. if (lua_pcall(L, 0, LUA_MULTRET, msgh_idx) != LUA_OK) { fprintf(stderr, "C: Error running script.lua: %s\n", lua_tostring(L, -1)); // Error message is on top of the stack, error handler (msgh) already processed it. lua_pop(L, 1); // Pop error message lua_close(L); return 1; } lua_remove(L, msgh_idx); // Remove error handler from stack printf("C: --- Script finished. Accessing Lua global variable ---\n"); lua_getglobal(L, "lua_message"); // Pushes the value of global 'lua_message' onto the stack if (lua_isstring(L, -1)) { // -1 refers to the top of the stack const char *msg_from_lua = lua_tostring(L, -1); printf("C: Value of 'lua_message': %s\n", msg_from_lua); } else { printf("C: 'lua_message' is not a string or not found.\n"); } lua_pop(L, 1); // Pop the value from the stack printf("C: --- Calling Lua function 'lua_multiply' from C ---\n"); lua_getglobal(L, "lua_multiply"); // Get the function if (!lua_isfunction(L, -1)) { fprintf(stderr, "C: Error: 'lua_multiply' is not a function in Lua.\n"); lua_pop(L, 1); // Pop non-function } else { // Push error handler again for this pcall lua_pushcfunction(L, msgh); lua_insert(L, -2); // Move msgh to just before the function on stack msgh_idx = lua_gettop(L) - 1; lua_pushnumber(L, 7); // Push first argument lua_pushnumber(L, 6); // Push second argument // Call: lua_pcall(L, num_args, num_results, error_handler_index) if (lua_pcall(L, 2, 1, msgh_idx) != LUA_OK) { fprintf(stderr, "C: Error calling 'lua_multiply': %s\n", lua_tostring(L, -1)); lua_pop(L, 1); // Pop error } else { if (lua_isnumber(L, -1)) { double product = lua_tonumber(L, -1); printf("C: lua_multiply(7, 6) returned: %f\n", product); } else { printf("C: 'lua_multiply' did not return a number.\n"); } lua_pop(L, 1); // Pop the result } lua_remove(L, msgh_idx); // Remove error handler } printf("C: --- Testing Lua error handling from C ---\n"); lua_getglobal(L, "lua_error_func"); if (lua_isfunction(L, -1)) { lua_pushcfunction(L, msgh); lua_insert(L, -2); msgh_idx = lua_gettop(L) - 1; if (lua_pcall(L, 0, 0, msgh_idx) != LUA_OK) { // 0 arguments, 0 results fprintf(stderr, "C: Caught error from 'lua_error_func': %s\n", lua_tostring(L, -1)); lua_pop(L, 1); // Pop the error message } lua_remove(L, msgh_idx); } else { lua_pop(L,1); // pop non-function } lua_close(L); // close Lua state, free resources printf("C: Lua state closed.\n"); return 0; }
Dialects
There are Lua dialects and variations that can differ in their implementation (e.g., JIT compiler vs. standard interpreter) and language features.
LuaJIT
- Based on Lua 5.1 + extensions
- Features a very fast interpreter and a JIT compiler that can speed up Lua code
- FFI (Foreign Function Interface) for calling C functions directly from Lua
- Gradually typed superset of Lua developed by Roblox.
- Has its own VM, interpreter, and compiler.
- Features sandboxing & syntax additions
- Statically typed, compiled-to-C, manually memory managed language
- Mostly it's own language with "Lua flavor"
- Can interoperate with Lua and C easily
- Can also compile to Lua 5.1 bytecode for embedding or to WebAssembly.
- Compile-time metaprogramming features