Lua Programming Language Guide — Scripting and Embedded Development
Lua is a lightweight, embeddable scripting language designed for extensibility — used everywhere from video games to Redis scripts to configuration files, with a simple syntax that fits in a small C library.
What You’ll Learn
- Lua syntax fundamentals (no semicolons, 1-indexed arrays)
- Tables as the universal data structure
- Metatables and metamethods for operator overloading
- Coroutines for cooperative multitasking
- LuaJIT and performance optimization
Why It Matters
Lua is the most embedded language in the world — it powers World of Warcraft addons, Roblox game scripting, Redis custom commands, Nginx configuration (OpenResty), and Adobe Lightroom plugins. Durga Antivirus Pro uses Lua for its rule-based threat detection engine because Lua’s sandboxing makes it safe to run untrusted scripts. Understanding Lua gives you a scripting superpower that fits in 200KB and runs anywhere — from game consoles to IoT devices.
Learning Path
flowchart LR
A[Lua Basics<br/>You are here] --> B[Tables & Functions]
B --> C[Metatables & OOP]
C --> D[Coroutines & I/O]
D --> E[LuaJIT & Real-World Apps]
Your First Lua Script
Lua doesn’t require semicolons. Comments use -- for single-line and --[[ ]] for multi-line.
-- hello.lua
print("Hello, Lua!")
-- Variables are global by default
local name = "World" -- local keyword scopes the variable
print("Hello, " .. name)lua hello.lua
# Hello, Lua!
# Hello, World!Tables — Lua’s Only Data Structure
Everything in Lua is a table — arrays, dictionaries, objects, modules, and sets.
-- Array-style table (1-indexed!)
local fruits = {"apple", "banana", "cherry"}
print(fruits[1]) -- apple (not fruits[0]!)
print(#fruits) -- 3 (length operator)
-- Dictionary-style table
local person = {
name = "Alice",
age = 30,
job = "Engineer"
}
print(person.name) -- Alice
print(person["age"]) -- 30
-- Mixed table
local mixed = {
"first",
key = "value",
[10] = "ten"
}
print(mixed[1]) -- first
print(mixed.key) -- valueapple
3
Alice
30
first
valueArrays are 1-indexed. The # operator returns the length of the array portion (contiguous integer keys starting from 1).
Functions as First-Class Values
Functions can be stored in variables, passed as arguments, and returned from other functions.
-- Store function in a variable
local greet = function(name)
return "Hello, " .. name
end
print(greet("Lua")) -- Hello, Lua
-- Higher-order function
local function apply(func, value)
return func(value)
end
local result = apply(function(x) return x * x end, 5)
print(result) -- 25
-- Lua's sugary syntax for OOP
local obj = {}
function obj:say(text)
print(self.name .. " says: " .. text)
end
obj.name = "Bot"
obj:say("Hello") -- Bot says: HelloHello, Lua
25
Bot says: HelloMetatables
Metatables let you change the behavior of a table — similar to operator overloading in C++ or Python’s magic methods.
-- Vector addition using metatables
local Vector = {}
Vector.__index = Vector
function Vector:new(x, y)
return setmetatable({x = x, y = y}, self)
end
function Vector.__add(a, b)
return Vector:new(a.x + b.x, a.y + b.y)
end
function Vector.__tostring(v)
return "(" .. v.x .. ", " .. v.y .. ")"
end
local v1 = Vector:new(3, 4)
local v2 = Vector:new(1, 2)
local v3 = v1 + v2
print(v3) -- (4, 6)(4, 6)Common metamethods: __add, __sub, __mul, __index (for inheritance), __newindex, __tostring, __gc (garbage collection), __call.
Coroutines
Coroutines allow cooperative multitasking — functions that can pause and resume.
local function counter()
local i = 0
while true do
i = i + 1
coroutine.yield(i) -- pause and return i
end
end
local co = coroutine.create(counter)
print(coroutine.resume(co)) -- true 1
print(coroutine.resume(co)) -- true 2
print(coroutine.resume(co)) -- true 3
print(coroutine.status(co)) -- suspended
-- Closing the coroutine
coroutine.close(co)true 1
true 2
true 3
suspendedCoroutines are not preemptive — only one coroutine runs at a time. They yield control explicitly.
File I/O
-- Write to a file
local file = io.open("data.txt", "w")
file:write("Line 1\n")
file:write("Line 2\n")
file:close()
-- Read from a file
file = io.open("data.txt", "r")
for line in file:lines() do
print(line)
end
file:close()Line 1
Line 2LuaJIT
LuaJIT is a Just-In-Time compiler for Lua that achieves C-like performance for numeric code.
-- LuaJIT automatically JIT-compiles hot paths
local function sum(n)
local s = 0
for i = 1, n do
s = s + i
end
return s
end
local start = os.clock()
local result = sum(10000000)
local elapsed = os.clock() - start
print("Sum: " .. result)
print("Time: " .. elapsed .. " seconds")Sum: 50000005000000
Time: 0.035 secondsLuaJIT can match C speed for tight loops. Use jit.dump to see which traces the JIT compiler produces.
Common Mistakes
1. Using index 0 instead of 1
Lua arrays start at 1. fruits[0] returns nil, not an error. Always start loops at 1.
2. Forgetting local for variables
Without local, variables are global. Large scripts leak globals and cause subtle bugs. Declare local inside functions.
3. Confusing = with == in conditions
if x = 5 then -- ERROR: assignment in condition
if x == 5 then -- correct4. Modifying a table while iterating
for k, v in pairs(t) do
t[k] = nil -- dangerous during iteration
endCollect keys in a separate list first, then remove.
5. Assuming tables copy by value
Tables are passed by reference. local t2 = t1 creates an alias, not a copy. Use a loop or table.clone in Lua 5.4+.
6. Forgetting return in module files
Lua modules must return the module table. Without it, require returns nil.
Practice Questions
Why are Lua arrays 1-indexed instead of 0-indexed? Lua was designed for non-programmers (domain experts, artists) who naturally count from 1. The language designers prioritized approachability.
What is a metatable and when would you use one? A metatable controls table behavior through metamethods. Use it for operator overloading (vectors, matrices), inheritance, and custom index fallback.
How do coroutines differ from threads? Coroutines are cooperative — they yield explicitly. Threads are preemptive — the OS scheduler can interrupt them at any time. Coroutines share the same thread with no race conditions.
What is the difference between
pairsandipairs?ipairsiterates over integer keys from 1 to the first nil.pairsiterates over all keys (including non-integer). Useipairsfor arrays,pairsfor dictionaries.What makes LuaJIT faster than standard Lua? LuaJIT uses a tracing JIT compiler that identifies hot loops and compiles them to native machine code, achieving near-C performance for numeric workloads.
Challenge: Write a Lua script that reads a CSV file, parses it into a table of records, and prints a summary (total rows, column names, and the average of the first numeric column).
Mini Project — Config File Parser
Build a Lua script that parses INI-style configuration files:
-- config_parser.lua
local function parse_ini(filename)
local config = {}
local current_section = nil
for line in io.lines(filename) do
-- Remove whitespace
line = line:match("^%s*(.-)%s*$")
-- Skip empty lines and comments
if line ~= "" and not line:match("^[;#]") then
-- Section header
local section = line:match("^%[(.-)%]$")
if section then
current_section = section
config[current_section] = {}
else
-- Key=value pair
local key, value = line:match("^(.-)%s*=%s*(.-)$")
if key and current_section then
-- Try to convert numeric values
local num = tonumber(value)
config[current_section][key] = num or value
end
end
end
end
return config
end
-- Example INI file
-- [[
-- [database]
-- host = localhost
-- port = 5432
-- name = myapp
--
-- [logging]
-- level = debug
-- file = /var/log/app.log
-- ]]
local config = parse_ini("config.ini")
print(config.database.host) -- localhost
print(config.database.port) -- 5432 (number)
print(config.logging.level) -- debuglocalhost
5432
debugFAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro