Goal: Build a full mental model of Luaβs semantics.
-
1.1 Syntax & Primitives
-
Lexical structure
-
Dynamic typing, coercion rules
-
Numbers, strings, booleans, nil
-
-
1.2 Tables: The Core Data Structure
-
Arrays, dictionaries
-
Metatables and metamethods
-
Deep/shallow copies, serialization patterns
-
-
1.3 Functions
-
First-class functions
-
Closures and upvalues
-
Varargs and multiple return values
-
-
1.4 Control Flow
-
Conditionals, loops
-
goto, labeled statements -
Error handling:
pcall,xpcall, and custom error strategies
-
-
1.5 Object-Oriented Patterns
-
Manual prototypes
-
Class-like structures with metatables
-
Inheritance and composition
-
-
1.6 Modules & Scope
-
require, package loader mechanism -
Environment tables (
_ENV) -
Sandboxing patterns
-
-
1.7 Coroutines and Asynchrony
-
coroutine.create,yield,resume -
Producer/consumer, generator patterns
-
State machines
-
-
1.8 Luau & LuaJIT Differences (Optional)
-
Extended features (e.g., type annotations in Luau)
-
Performance caveats and limitations
-
Goal: Establish clean, modular Lua project workflows.
-
2.1 File/Directory Structure Patterns
-
src/,lib/,test/,scripts/,bin/ -
Versioning, vendoring, and layering
-
-
2.2 Interpreters
-
Using
lua,luajit, andluau -
Managing multiple versions with Docker or asdf
-
-
2.3 Package Management
-
Using luarocks
-
Declaring dependencies via
rockspec -
Local dev rocks vs global installations
-
Publishing packages
-
-
2.4 Build & Execution Automation
-
Creating CLI scripts with entry points
-
Using Makefiles or shell scripts
-
Integrating with CI tools
-
Goal: Efficient Lua development using modern IDEs.
-
**3.1 Visual Studio Code
- Extensions: Lua Language Server, Luacheck
- Debugging with breakpoints and console
- Linting, formatting, snippets
-
3.2 IntelliJ (via plugins)
- Using EmmyLua plugin
- Code completion, documentation, unit testing
-
3.3 Luacheck & LDoc
- Static analysis
- Documenting Lua codebases
Goal: Maintain robust, testable Lua code.
-
4.1 Unit Testing
-
Using busted
-
Mocking and stubbing patterns
-
-
4.2 Benchmarking & Profiling
-
Measuring performance
-
LuaJIT profiling tools
-
-
4.3 Code Quality
-
Style guides
-
Dead code analysis
-
CI/CD integration for Lua
-
Goal: Deepen Lua mastery and apply in large-scale contexts.
-
5.1 Embedding & Interfacing
-
Embedding Lua in C/C++/Rust
-
FFI and shared libraries
-
-
5.2 DSL Design with Lua
-
Designing configuration and scripting interfaces
-
Declarative API patterns
-
-
5.3 Building Custom Interpreters
- Using Lua as a scripting engine for your apps
-
Install Lua (including LuaJIT + LuaRocks)
-
Create a structured local Lua project
-
Validate everything works with a βHello, Luaβ and utility module
-
Prepare tooling for the rest of the course
sudo apt update
sudo apt install lua5.4 luarockssudo apt install luajitCheck versions:
lua -v # Lua 5.4.x
luajit -v # LuaJIT 2.x luarocks --version`Create a base project folder:
mkdir lua-tutorial-project && cd lua-tutorial-project # recommended layout
mkdir -p src/lib
mkdir tests
touch main.luaBasic structure:
lua-tutorial-project/
βββ src/
β βββ lib/
β βββ utils.lua
βββ tests/
βββ main.lua
βββ .luarc.json (later)`
src/lib/utils.lua
local utils = {}
function utils.greet(name)
return "Hello, " .. name .. "!"
end
return utilsmain.lua
package.path = "./src/?.lua;" .. package.path
local utils = require("lib.utils")
print(utils.greet("John"))Run it:
lua main.luaCreate a rockspec if you want dependency management:
luarocks init lua-tutorial-project 1.0-1 --lua-versions="5.4"`
Edit the generated .rockspec file to define source folders:
source = {
url = "git://github.com/yourname/lua-tutorial-project"
}
build = {
type = "builtin",
modules = {
["lib.utils"] = "src/lib/utils.lua"
}
}-
Exercise 1: Add a function
utils.add(a, b)and test it inmain.lua. -
Exercise 2: Create a new module
lib.mathxand export a factorial function. -
Exercise 3: Use
lua -ito open an interactive shell, manually requirelib.utils, and callgreet. -
Exercise 4: Try replacing
luawithluajitin your run commands β observe any differences. -
Exercise 5: Write a simple shell script
run.shto runmain.luausing LuaJIT.
-
Understand Lua's syntax rules
-
Get comfortable with literals and types
-
Explore dynamic typing and coercion
-
Practice input/output and inline testing
-
All statements end with no semicolon (they're optional)
-
Blocks are defined by keywords (
do ... end,if ... end, etc.) -
Functions, tables, and blocks are first-class citizens
| Type | Example |
|---|---|
nil |
x = nil |
boolean |
true, false |
number |
3.14, -2, 1e6 |
string |
"hello" or 'hi' |
table |
{} (associative arrays) |
function |
function() ... end |
userdata, thread β advanced topics |
local s1 = "double quoted"
local s2 = 'single quoted'
local s3 = [[
Multi-line
String
]]local n = "123"
print(tonumber(n) + 10) --> 133
local b = 1
if b then
print("truthy")
end --> prints`print("Enter your name:")
local name = io.read()
print("Hello, " .. name)print(type(nil))
print(type(true))
print(type(42))
print(type("hi"))
print(type({}))
print(type(function() end))
Print whether a value is truthy, its type, and its string representation.
local input = "12.5"
print(tonumber(input) * 10) --> 125.0`local first = "Lua"
local second = "Rocks"
print(first .. " " .. second)print("Enter text:")
local text = io.read()
print(string.upper(text))-
Understand table creation and indexing
-
Use tables as arrays and dictionaries
-
Apply metatables for custom behavior
-
Implement deep copying
-
Prepare table utility functions for reuse
In Lua:
-
Tables are the only data structuring mechanism (like arrays, dicts, objects).
-
Tables are mutable, reference types.
-
Functions, objects, and modules are implemented with tables.
local t = {} -- empty table
t["name"] = "Paul" -- string key
t.age = 42 -- sugar for t["age"]
t[1] = "first element" -- numeric index (array-style)`Tables can be declared inline:
local user = {
name = "John",
age = 30,
skills = { "Lua", "C", "JS" }
}Arrays (1-based indexing):
local fruits = { "apple", "banana", "cherry" }
print(fruits[2]) -- bananaDictionaries:
local config = {
port = 8080,
host = "localhost"
}Mixed tables:
local t = {
"index0", "index1",
foo = "bar",
[true] = "yes"
}local a = { x = 1 }
local b = { y = 2 }
local mt = {
__add = function(lhs, rhs)
return { x = lhs.x + (rhs.x or 0), y = lhs.y + (rhs.y or 0) }
end
}
setmetatable(a, mt)
setmetatable(b, mt)
local c = a + b
print(c.x, c.y) -- 1, 2Other metamethods: __index, __newindex, __tostring, __eq, etc.
function deepcopy(orig)
local copy = {}
for k, v in pairs(orig) do
if type(v) == "table" then
copy[k] = deepcopy(v)
else
copy[k] = v
end
end
return copy
endfunction table.len(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
function table.keys(t)
local ks = {}
for k in pairs(t) do
table.insert(ks, k)
end
return ks
end-
Exercise 1: Create a table with mixed keys (string, number, boolean), iterate using
pairs, print each key type. -
Exercise 2: Write a metatable that enables two tables to be concatenated via
+. -
Exercise 3: Implement
table.map(t, fn)that returns a new table wherefn(value)was applied to each value. -
Exercise 4: Write a table that acts like a default dictionary (returns
0for missing keys). -
Exercise 5: Write
table.to_string(t)that recursively prints a table's structure (stringify deeply nested tables).
-
Understand function declaration and expression styles
-
Use closures and upvalues
-
Use varargs and multiple return values
-
Apply functional programming patterns
function greet(name)
return "Hello, " .. name
endlocal greet = function(name)
return "Hello, " .. name
endfunction repeatAction(action, times)
for i = 1, times do
action(i)
end
endfunction counter()
local count = 0
return function()
count = count + 1
return count
end
end local inc = counter()
print(inc()) -- 1
print(inc()) -- 2function printAll(...)
local args = {...}
for i, v in ipairs(args) do
print(i, v)
end
end
function divmod(a, b)
return math.floor(a / b), a % b
end
local q, r = divmod(17, 5)
print(q, r) -- 3 2src/lib/funcs.lua
local funcs = {}
function funcs.counter()
local count = 0
return function()
count = count + 1
return count
end
end
function funcs.map(fn, list)
local results = {}
for i, v in ipairs(list) do
results[i] = fn(v)
end
return results
end
return funcsUse in main.lua
local funcs = require("lib.funcs")
local c = funcs.counter()
print(c(), c(), c()) -- 1 2 3
local squared = funcs.map(function(x) return x * x end, {1,2,3})
for _, v in ipairs(squared) do
print(v)
end-
Create a function
adder(n)that returns a function which addsnto its argument. -
Make a logger function that returns a closure logging a prefixed message:
local logInfo = logger("[INFO]")
logInfo("System ready.")-
Write a filter function that takes a predicate and a list, returning only items that match.
-
Wrap
divmodas a single return value:
function divmodWrap(a, b)
local q, r = divmod(a, b)
return {q = q, r = r}
end- Build a pipeline function:
function pipe(f1, f2)
return function(x)
return f2(f1(x))
end
end-
Use conditionals and branching
-
Write loops with control statements
-
Understand
gotoand labels -
Handle errors using
pcall,xpcall, anderror -
Build non-linear flows using
gotoresponsibly
local x = 5
if x > 10 then
print("Greater than 10")
elseif x > 3 then
print("Greater than 3")
else
print("3 or less")
end- Lua treats
falseandnilas falsey; everything else (including0and"") is truthy.
for i = 1, 5 do
print("Count:", i)
end```
### **Generic `for`**
```lua
for k, v in pairs({a = 1, b = 2}) do
print(k, v)
endlocal i = 1
while i <= 3 do
print(i)
i = i + 1
endlocal x = 1
repeat
print(x)
x = x + 1
until x > 3for i = 1, 10 do
if i == 5 then
break
end
print(i)
endlocal i = 1
::start::
print("i is", i)
i = i + 1
if i <= 3 then
goto start
endlocal ok, result = pcall(
function()
error("Something bad happened!")
end
)
if not ok then
print("Caught error:", result)
endlocal function handler(err)
return "Custom handler: " .. err
end
local ok, result = xpcall(
function()
error("Oops")
end,
handler
)
print(result)-
Exercise 1: Write a loop from 1 to 20, printing only even numbers using
ifandcontinuepattern (goto). -
Exercise 2: Implement a simple retry mechanism using
pcall, retrying a function 3 times if it fails. -
Exercise 3: Re-implement
repeat...untilusingwhileandbreak. -
Exercise 4: Write a factorial function using a
forloop withbreakif input is negative. -
Exercise 5: Create a toy state machine with
gotoand labels foridle,processing, anddonestates.
-
Understand how to implement classes and instances manually
-
Learn metatables for method resolution
-
Use constructor functions and inheritance patterns
-
Handle
selfidioms and method calls (:vs.)
Everything is a table in Lua. "Objects" are just tables with methods.
local Dog = {}
Dog.__index = Dog
function Dog:new(name)
local o = setmetatable({}, self)
o.name = name
return o
end
function Dog:speak()
print(self.name .. " says woof!")
end
local myDog = Dog:new("Buddy")
myDog:speak() -- Buddy says woof!Inheritance is done by chaining __index:
local Animal = {}
Animal.__index = Animal
function Animal:new(name)
return setmetatable({ name = name }, self)
end
function Animal:speak()
print(self.name .. " makes a sound.")
end
local Dog = setmetatable({}, { __index = Animal })
Dog.__index = Dog
function Dog:speak()
print(self.name .. " barks!")
end
local d = Dog:new("Fido")
d:speak() -- Fido barks!obj:method(args) == obj.method(obj, args)
Always use : when defining object methods unless you're building static functions.
-
Exercise 1:
Create aPersonclass withname,age, and agreet()method. -
Exercise 2:
Derive aStudentclass fromPerson. AddstudentIDand overridegreet()to include it. -
Exercise 3:
Create aShapebase class and implementRectangleandCirclesubclasses. Add anarea()method. -
Exercise 4:
Simulate private fields by prefixing with_, and write agetAge()method that exposes it. -
Exercise 5:
Write a utility functionclass()that creates a new class with optional inheritance:
local Dog = class("Dog", Animal)You can use libraries like middleclass if you want more βtypicalβ OOP behavior, but rolling your own helps you understand metatables and Luaβs flexibility.
-
Understand how
require,package.path, andmodule tableswork -
Learn how Lua's global and local scopes affect modularity
-
Explore
_ENVand sandboxing -
Build reusable, testable Lua modules
Lua doesnβt have built-in "modules"βinstead, you define a table, populate it, and return it.
-- mymodule.lua
local M = {}
function M.sayHello()
print("Hello from module!")
end
return MThen use:
local mod = require("mymodule")
mod.sayHello()Lua uses package.path to locate modules:
print(package.path)
-- Example: "./?.lua;./?/init.lua;/usr/share/lua/5.4/?.lua;..."You can extend it in main.lua:
package.path = "./src/?.lua;" .. package.path`Avoid polluting the global scope!
-- BAD: global function
function greet()
print("Hi")
end
-- GOOD: local scope
local function greet()
print("Hi")
endUsed to sandbox or override global scope.
_ENV = { print = print, math = math } -- restrict scope
print(math.sqrt(16)) -- OK
-- print(os.date())
-- ERROR: os not in _ENVUseful for sandboxing plugin code or untrusted scripts.
You can override require to load encrypted files, remote content, etc.
table.insert(
package.searchers, 1,
function(name)
if name == "custom" then
return function()
return { hello = function() print("Hi custom") end }
end
end
end
)β
Exercise 1: Create a module lib.strings with a function reverse(s) that reverses a string.
β
Exercise 2: Add a new loader to package.searchers that returns a dummy module named mock.
β
Exercise 3: Create _ENV in a file to restrict access to only math and print, then try using os.date().
β
Exercise 4: Write a module lib.counter with a private count variable (closure), and public inc() and get() functions.
β
Exercise 5: Set package.path dynamically in main.lua to import from a modules/ folder outside src/.
-
Understand what coroutines are and how they differ from threads
-
Learn how to create, resume, and yield coroutines
-
Design asynchronous control flows
-
Build real examples like producers/consumers or cooperative schedulers
Coroutines are resumable functions. You control when they pause (yield) and when they resume (resume).
coroutine.create(f) -- creates a coroutine (suspended)
coroutine.resume(co) -- starts or resumes a coroutine
coroutine.yield() -- pauses execution
coroutine.status(co) -- returns "suspended", "running", "dead"`Unlike threads, coroutines run in the same thread and donβt preempt each other.
local co = coroutine.create(
function()
print("A")
coroutine.yield()
print("B")
end
)
print(coroutine.status(co)) --> suspended
coroutine.resume(co) --> prints A
print(coroutine.status(co)) --> suspended
coroutine.resume(co) --> prints B
print(coroutine.status(co)) --> dead`π Exercise: Modify it to print A, B, C with two yields.
local co = coroutine.create(
function(x)
print("received:", x)
local y = coroutine.yield(x + 1)
print("resumed with:", y)
return y * 2
end
)
local status, val = coroutine.resume(co, 10) --> received: 10
print("yielded:", val)
status, val = coroutine.resume(co, 5) --> resumed with: 5
print("returned:", val)π Exercise: Pass two arguments to the coroutine and perform arithmetic with both.
function producer()
return coroutine.create(
function()
for i = 1, 3 do
coroutine.yield(i * 10)
end
end
)
end
function consumer(prod)
while coroutine.status(prod) ~= "dead" do
local ok, val = coroutine.resume(prod)
print("Consumed:", val)
end
end
consumer(producer())π Exercise: Extend this with random delays and simulate throttling.
local function task(name, delay)
return coroutine.create(
function()
for i = 1, 3 do
print(name .. " step " .. i)
coroutine.yield()
end
end
)
end
local tasks = { task("A"), task("B") }
for i = 1, 6 do
local t = tasks[(i % 2) + 1]
coroutine.resume(t)
endπ Exercise: Make it round-robin any number of tasks.
function wait(n)
for i = 1, n do
coroutine.yield()
end
end
local co = coroutine.create(
function()
print("Start")
wait(3)
print("End")
end
)
for _ = 1, 5 do
coroutine.resume(co)
endπ Exercise: Integrate this into a fake game loop.
Write a utility to "spawn" coroutines like tasks:
local function spawn(fn)
local co = coroutine.create(fn)
return function()
coroutine.resume(co)
end
end
local t1 = spawn(
function()
print("Running t1")
end
)
t1()-
Understand Luauβs extensions to standard Lua
-
Learn what LuaJIT changes or optimizes
-
Evaluate when to use each
-
Spot compatibility concerns between environments
Luau is a Roblox-specific dialect of Lua 5.1 with type annotations, performance tweaks, and extended syntax.
-
Based on Lua 5.1
-
Supports gradual typing
-
Extended syntax: type aliases,
continue,if let, etc. -
No C API, runs only in Roblox
| Feature | Luau Support |
|---|---|
| Type annotations | β |
continue in loops |
β |
typeof() operator |
β |
table.freeze() |
β |
| Destructuring assignment | β |
Built-in assert(x: T?) |
β |
| Enforced strict mode | β (opt-in) |
LuaJIT is a Just-In-Time compiled Lua interpreter based on Lua 5.1, designed for speed.
-
Fully compatible with Lua 5.1
-
Extremely fast (near C speed)
-
Adds FFI for calling C libraries
-
Often used in games, real-time apps, embedded
| Feature | LuaJIT Support |
|---|---|
| FFI (foreign function interface) | β |
| JIT compilation | β |
bit library (before Lua 5.3) |
β |
| Interoperable with C | β |
| Lua 5.2/5.3 compatibility | β (partial) |
| Feature | LuaJIT | Luau |
|---|---|---|
| Target audience | Native devs | Roblox devs |
| Typing system | β | β Gradual |
| Performance | β‘ Fast | π Roblox-optimized |
| JIT Compilation | β | β |
| FFI | β | β |
| Built-in types | β | β
Vector3, CFrame |
| Language spec | Lua 5.1-ish | Lua 5.1-ish + extensions |
| Interoperability | β (C libs) | β Roblox-only |
| Runs outside Roblox | β | β |
- Luau Only: Write a typed function in Luau:
local function greet(name: string): string
return "Hello, " .. name
end- Luau Only: Use
continuein a loop:
for i = 1, 5 do
if i == 3 then
continue
end
print(i)
end- LuaJIT Only: Call a C function using FFI:
local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("Hello from C!\n")`-
Compare Performance: Write a loop-heavy script and benchmark it in
luavsluajit. -
Compatibility Exercise: Take a Roblox Luau script and try to run it in
lua. Identify what fails and why.
-
Understand idiomatic Lua project structures
-
Automate builds and scripts
-
Use Luarocks for dependencies
-
Run Lua versions flexibly
-
Prepare for clean CI-style development
A recommended directory structure:
lua-tutorial-project/
βββ src/ # All code lives here
β βββ lib/ # Submodules
βββ tests/ # Unit tests
βββ bin/ # CLI scripts
βββ .luarc.json # Language server config (for VS Code)
βββ lua/ # Optional custom Lua scripts
βββ run.sh # Shortcut runner
βββ main.lua # Entry point
βββ lua-tutorial-project-1.0-1.rockspec
βββ README.md
Add this to run.sh to make executing easier:
#!/bin/bash
lua main.lua "$@"`Then:
chmod +x run.sh
./run.sh`asdf plugin-add lua https://github.com/Stratus3D/asdf-lua.git
asdf install lua 5.4.6
asdf global lua 5.4.6sudo apt install luajitNow you can test code like this:
luajit main.lua
lua main.lualuarocks install lua-cjsonUse it in code:
local cjson = require("cjson")
print(cjson.encode({foo = "bar"}))Declare dependencies in your rockspec:
dependencies = {
"lua >= 5.4",
"lua-cjson"
}To generate .rockspec:
luarocks init myproject 1.0-1 --lua-versions="5.4"`Create a Makefile:
.PHONY: run test lint clean
run:
lua main.lua
test:
busted tests/
lint:
luacheck src/
clean:
rm -rf *.rock* *.lua~ *.bak`Run:
make run-
Exercise 1: Create a new script in
bin/hello.luathat prints CLI args. Run it vialua bin/hello.lua arg1. -
Exercise 2: Add a dependency like
Penlight(luarocks install penlight) and usepl.pretty.dump(). -
Exercise 3: Create and install your own local rock:
luarocks make-
Exercise 4: Write a
Makefilerule to install all dependencies. -
Exercise 5: Create a Dockerfile that installs Lua 5.4, luarocks, and runs
main.lua.
-
Get productive using VS Code or IntelliJ with Lua Language Server
-
Enable linting, type hints, and debugging
-
Add support for code formatting and static analysis
-
Explore IntelliJ Lua support via EmmyLua
-
Prep environment for large project workflows
| Feature | Tool | Notes |
|---|---|---|
| Language Server | lua-language-server |
Fast, feature-rich LSP |
| Linting | luacheck |
Detects unused vars, shadowing, globals |
| Docs | ldoc |
Lua docgen |
| Debugging | VSCode Lua Debugger | For step-through and breakpoints |
| IntelliJ | EmmyLua Plugin | Best IntelliJ Lua support (not official) |
-
Lua Language Server β Sumneko Lua
-
Lua Debug β if you want breakpoints
-
Lua Formatter β optional for style
Place in project root:
{
"runtime": {
"version": "Lua 5.4",
"path": ["src/?.lua", "src/?/init.lua"]
},
"workspace": {
"library": ["./src"]
},
"diagnostics": {
"globals": ["vim"]
}
}Adjust
globalsfor your environment (e.g.,love,vim,game, etc.)
luarocks install luacheckCreate .luacheckrc in project root:
files = { "src", "tests", "main.lua" }
globals = {
"describe", "it", "before_each", "after_each" -- for busted
}
unused_args = false`Run it:
luacheck .In .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Lua",
"type": "lua",
"request": "launch",
"program": "${workspaceFolder}/main.lua"
}
]
}You'll need the VSCode extension that supports Lua debug (or use
mobdebugif working with ZeroBrane-style remote debugging).
-
Install EmmyLua plugin.
-
Configure SDK as system Lua (
/usr/bin/lua) -
Enable annotations like:
---@param name string
---@return string
function greet(name)
return "Hello, " .. name
endEmmyLua parses these and provides IntelliSense + static checking.
Install:
luarocks install ldocAdd a doc comment in utils.lua:
--- Say hello to someone
-- @param name string The name to greet
-- @return string A greeting message
function utils.greet(name)
return "Hello, " .. name
endGenerate docs:
ldoc src-
Exercise 1: Add
.luarc.jsonto your project and verify that auto-complete and path resolution work in VS Code. -
Exercise 2: Annotate 3 functions with EmmyLua-style comments and check IntelliSense.
-
Exercise 3: Trigger and fix 3 lint warnings using
luacheck. -
Exercise 4: Set a breakpoint in
main.luaand step into a utility function. -
Exercise 5: Generate HTML documentation using
ldoc, and open the generated file in a browser.
-
Set up and use a test framework (
busted) -
Write unit and integration tests
-
Benchmark performance
-
Maintain code quality with static analysis (
luacheck) -
Integrate all into a maintainable workflow
Install busted and luacheck:
luarocks install busted
luarocks install luachecklua-tutorial-project/
βββ src/
β βββ lib/
β βββ utils.lua
βββ tests/
β βββ test_utils.lualocal utils = require("lib.utils")
describe(
"utils.greet",
function()
it(
"returns a proper greeting",
function()
assert.is_equal(utils.greet("Paul"), "Hello, Paul!")
end
)
end
)Run:
busted tests/local socket = require("socket")
local t0 = socket.gettime()
for i = 1, 1e6 do
local x = i * 2
end
local t1 = socket.gettime()
print("Elapsed: " .. (t1 - t0) .. " seconds")You can also try lua-benchmark, busted has built-in performance assertions too.
Create a config file: .luacheckrc
files = {
"**.lua"
}
globals = {
"describe", "it", "assert" -- busted globals
}Run:
luacheck .Makefile
test:
busted tests
lint:
luacheck .
bench:
lua benchmarks/mymodule.luaThen:
make test
make lint-
Exercise 1: Add a new test for
utils.add(a, b)ensuring it handles negative numbers. -
Exercise 2: Write a test that fails and verify busted output.
-
Exercise 3: Create a
benchmarks/folder and benchmark recursive vs iterative factorial. -
Exercise 4: Use Luacheck to fix a warning in one of your modules.
-
Exercise 5: Add a new Makefile target
qa:that runs bothtestandlint.
Run Lua inside C/C++/Rust apps, and call native code from Lua.
-
Linking Lua runtime into host applications
-
Calling Lua from C/C++
-
Calling C/C++ from Lua via LuaJIT FFI or manual bindings
C code embedding Lua:
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
int main() {
lua_State* L = luaL_newstate();
luaL_openlibs(L);
luaL_dostring(L, "print('Hello from Lua inside C!')");
lua_close(L);
return 0;
}Use LuaJIT FFI to call C libraries directly from Lua.
-
Defining C declarations using
ffi.cdef -
Loading shared libraries with
ffi.load -
Direct memory manipulation
local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("Hello from LuaJIT FFI!\n")Use Lua as a domain-specific language engine.
-
Declarative APIs
-
Metatables for DSL constructs
-
Table-as-code strategies
config = {
window = { width = 800, height = 600 },
title = "My Game"
}
print(config.window.width)`Or dynamically:
function define(cfg)
return cfg
end
local game = define {
name = "LuaRunner",
players = 2
}Create sandboxed or modified Lua runtimes.
-
Custom
_ENVenvironments -
Controlling available globals
-
Sandboxed
load()andloadstring()execution
local sandbox_env = {
print = print,
math = math
}
local code = "return math.sqrt(16)"
local fn = load(code, "sandbox", "t", sandbox_env)
print(fn()) --> 4Create your own interactive Lua shell or a custom command DSL.
-
Reading user input
-
Evaluating Lua safely
-
Command routing using metatables or dispatch tables
while true do
io.write("> ")
local line = io.read()
local fn = load("return " .. line)
if fn then
print(fn())
end
end-
Write a C/C++ program that executes Lua code to generate a value and pass it back.
-
Use LuaJIT FFI to bind a shared C library (e.g., zlib or your own
.so). -
Write a DSL to declare UI layout using only tables and metatables.
-
Sandbox a Lua script to restrict access to I/O and
osfunctions. -
Build a mini REPL that supports variable definitions and math operations with error handling.
Learn idiomatic data manipulation and functional constructs in Lua.
-
Using tables as hashmaps and arrays
-
Emulating map, reduce, filter
-
indexBy,groupBy, and other utility patterns -
for,ipairs,pairs,nextidioms
function map(tbl, fn)
local out = {}
for i, v in ipairs(tbl) do
out[i] = fn(v)
end
return out
endUse Luaβs built-in libraries effectively.
-
string,math,table,os,coroutine,debug -
load,require,dofile,module(legacy) -
Pattern matching (not full regex)
print(string.match("abc123", "%d+")) --> 123-
io.open,io.read,io.write,io.lines -
Reading binary files
-
Line-by-line processing
for line in io.lines("file.txt") do
print(line)
end-
JSON: Use
dkjson,cjson, orlunajson -
CSV: Use
lua-csvor parse manually -
XML:
LuaExpat,LuaXML -
YAML:
lyaml,yaml.lua
local json = require("dkjson")
local data = json.decode('{"x": 1}')-
HTTP clients:
socket.http,lua-http,http.request(LuaSocket) -
Servers:
copas,lua-http,luv
local http = require("socket.http")
local body = http.request("https://example.com")-
SQLite:
lsqlite3 -
Postgres:
luapgsql -
MySQL:
luasql.mysql,luamysql -
Oracle: limited, via C FFI or ODBC
local sqlite3 = require("lsqlite3")
local db = sqlite3.open("test.db")-
SDL, OpenGL, Dear ImGui via FFI
-
Love2D (game dev with graphics)
-
IUP β multi-platform UI
-
wxLua β bindings to wxWidgets
-
LΓVE (Love2D) β game + UI toolkit
-- Love2D GUI app layout
function love.draw()
love.graphics.print("Hello, GUI", 100, 100)
end-
lcursesβ terminal-based UIs -
blessedandcursesvia FFI or LuaRocks
-
Install with
sudo apt install luarocks -
Find packages at https://luarocks.org
-
Create
.rockspecfiles for publishing
luarocks install luafilesystem-
Write map/filter/reduce functions using
tablein idiomatic Lua style. -
Parse a JSON file, modify it, and write it back.
-
Create a simple HTTP downloader using LuaSocket or LuaHTTP.
-
Write a SQLite-backed task tracker CLI app.
-
Build a TUI app that shows CPU and memory stats using
lcurses.