slop-stuff cheatsheets & experiments git

slop-stuff / Languages

Lua

Small, fast, embeddable.

Tables, metatables, patterns, modules, and embedding.

languagetablespatternsrequire

Lua is a tiny scripting language you drop into games, servers, and tools. One table to rule every data structure, a handful of types, and C-level speed — with luajit on top when you need more.

Quick reference

The essentials you reach for most, in one pass. Each links to its deep-dive section.

  • local t = {1, 2, 3} — the one container: arrays and maps are the same table (1-indexed).
  • setmetatable(t, {__index = mt})__index fills missing keys, __newindex traps writes — metatables.
  • for i, v in ipairs(t) — array order 1..n; pairs(t) walks every key — iterators.
  • s:match("(%w+)=(%w+)") — Lua patterns (%d %s %w ()), not regex — strings.
  • s:gsub("%s+", " ") — collapse runs of whitespace to one space.
  • local f = function(a) return a, a * 2 end — first-class + multiple returns — functions.
  • return function() n = n + 1 end — closures capture surrounding locals — closures.
  • local m = require("mymod") — runs once, caches, returns the module — require.
  • coroutine.resume(co)create / resume / yieldcoroutines.

%d %s %w %a # .. : // nil

⚡: Truthiness: only nil and false are falsy — 0 and "" are true. Prefer local: bare names become globals.

Run & basics

Run a file with lua script.lua or a snippet with -e. Comments use --, and semicolons are optional everywhere.

1. Run a script

lua script.lua
luajit script.lua

2. One-liner

lua -e 'print(2 + 2)'   # → 4
lua -e 'print(2 ^ 10)'  # → 1024.0

3. Comments

-- single line
--[[ multi
     line ]]

4. Variables

local x = 10     -- local
count = 0        -- global
-- no semicolons

lua script.lua run a file lua -e 'print(1)' inline snippet luajit script.lua JIT runtime

KEY: Prefer local. Bare assignments create globals; local scopes the name to its block and is faster to access. In the REPL, exit with Ctrl+D or os.exit().

Types & values

Eight types, but you touch six daily. Only nil and false are falsy — 0 and "" are both true.

TypeExampleNotes
nilnilabsent value; falsy
booleantrue, falseonly false + nil are falsy
number3.14, 2, 0x1Fdoubles; integers in 5.3+
string"hi", 'hi', [[raw]]immutable; both quote styles
functionfunction() endfirst-class values
table{}, {1, 2}, {k = "v"}the only container type

type("hi")string #{"a","b","c"}3 type(nil)nil

!: Truthiness: only nil and false are false. 0, "", and {} are all truthy, so if 0 then print("yes") end prints yes.

Tables

The one container type: arrays, maps, objects, and namespaces are all tables. Arrays are 1-indexed.

Array (1-indexed)

local t = {"a", "b", "c"}
print(t[1])        -- "a"
t[#t + 1] = "d"    -- append
print(#t)          -- 4

Map / record

local p = {name = "Ada", age = 36}
print(p.name)      -- "Ada"
p["role"] = "admin"
print(p.role)      -- "admin"

table.* helpers

table.insert(t, "x")   -- append
table.remove(t, 1)     -- pop front
table.concat(t, ", ")  -- join
table.sort(t)          -- sort in place
IteratorWalksUse for
ipairs(t)keys 1, 2, 3… until nilcontiguous arrays, in order
pairs(t)every key, any ordermaps, string keys, sparse tables

t.foo (read a key) → __index (metamethod) → mt.foo (fallback value)

MT: Metatables hook into table behavior. __index supplies missing keys (defaults, inheritance), __newindex intercepts writes. Set with setmetatable(t, {__index = mt}).

Strings & patterns

Strings are immutable and concatenate with ... Pattern matching uses Lua patterns, not regular expressions.

Concatenate & format

local s = "Hello" .. " " .. "world"
string.format("%s: %05d", "id", 42)
-- "id: 00042"

Extract

s:sub(1, 5)        -- "Hello"
s:find("world")    -- start, end or nil
s:match("%d+")     -- first number or nil

Replace

s:gsub("l", "L")       -- all l → L
s:gsub("%s+", " ")     -- collapse space
s:gsub("(%w+)=(%w+)", "%2=%1")
  • s:upper() — uppercase copy.
  • s:lower() — lowercase copy.
  • #s — byte length.
  • s:rep(3) — repeat the string.
  • s:find("x") — first match index.
  • s:gsub("a", "b") — substitute all.
PatternMatchesExample
%da digit"a1b2":match("%d+")1
%walphanumeric"ab_c":match("%w+")ab_c
%swhitespacegsub("%s+", " ")
%aa letter"lua":match("%a+")lua
()capture group"k=v":match("(%w+)=(%w+)")
^ / $start / end anchor"lua":match("^l")l

%d %w %s %a %c %p %l %u %x

⚠: Lua patterns are not regex. No | alternation, no {n,m} repetition, no ? quantifier. You get ., *, +, -, classes %x, and captures () — enough for most parsing.

Functions & closures

Functions are first-class values with multiple returns and varargs. Closures capture their surrounding local variables.

Multiple returns

local function divmod(a, b)
  return a // b, a % b
end
local q, r = divmod(17, 5)
print(q, r)   -- 3  2

Varargs …

local function sum(...)
  local total = 0
  for _, v in ipairs({...}) do
    total = total + v
  end
  return total
end
print(sum(1, 2, 3, 4))  -- 10

Closures

local function counter()
  local n = 0
  return function()
    n = n + 1
    return n
  end
end
local next = counter()
print(next(), next())   -- 1  2
Function extras

Anonymous as arguments

table.sort(names, function(a, b)
  return a < b
end)

Recursion needs a name

local fact
fact = function(n)
  if n == 0 then return 1 end
  return n * fact(n - 1)
end

Multiple assignment

local a, b = b, a     -- swap
local x, y = returns2()

Colon syntax

function obj:method() end
-- sugar for obj.method(self)

fn: Functions are first-class. Store them in variables, pass them as arguments, return them from other functions. A closure keeps its local variables alive after the outer function returns.

Modules & require

require loads a file once, runs it, and returns whatever it returns — usually a table of functions.

my_mod.lua

local M = {}
function M.greet(name)
  return "hi " .. name
end
return M

main.lua

local my_mod = require("my_mod")
print(my_mod.greet("Ada"))  -- hi Ada
  1. Search package.path — Turn "my_mod" into a file path like ./my_mod.lua.
  2. Load the chunk — Compile and run the file as a fresh chunk.
  3. Return the table — Whatever the module returns becomes the value of require.
  4. Cache in package.loaded — Later require calls return the same table.
Module patterns
local M = {}
M.hello = function() print("hi") end
return M

Cache manually

package.loaded["mymod"] = M
-- require("mymod") returns M

req: Prefer returning a table. The old module() function is deprecated — it hides globals. A module that returns a table is explicit and plays well with package.path and package.loaded.

Coroutines & embedding

Coroutines give cooperative multitasking; the C API and LuaJIT FFI give you the host machine.

Coroutines

local co = coroutine.create(function()
  for i = 1, 3 do
    coroutine.yield(i)
  end
end)
print(coroutine.resume(co))  -- true  1
print(coroutine.resume(co))  -- true  2
print(coroutine.status(co))  -- suspended

LuaJIT FFI

local ffi = require("ffi")
ffi.cdef[[ int abs(int n); ]]
print(ffi.C.abs(-42))  -- 42
  • running — Executing now; the one active coroutine.
  • suspended — Paused at yield or fresh from create.
  • normal — It resumed another coroutine.
  • dead — Finished; resume returns false.
  • luaL_newstate() — create an interpreter.
  • luaL_openlibs(L) — load the standard library.
  • lua_pushnumber(L, 42) — push a value onto the stack.
  • lua_getglobal(L, "fn") — push a global onto the stack.
  • lua_pcall(L, 0, 0, 0) — protected call.
  • lua_tonumber(L, -1) — read a number off the stack.

co: Cooperative, not preemptive. A coroutine only switches when it calls coroutine.yield. No locks, no data races — but one tight loop can hog the whole program.

Pitfalls

Small behaviors that surprise people coming from other languages.

Arrays start at 1

t[1] is the first element — there is no t[0]. Loops run 1, #t.

local t = {"a", "b"}
print(t[1])   -- "a"
print(t[0])   -- nil

No continue statement

Use goto with a label, or restructure the loop with an if.

for i = 1, 10 do
  if i == 5 then goto next end
  print(i)
  ::next::
end

nil ends iteration

ipairs and # stop at the first nil, so holes hide the rest of the array.

local t = {1, nil, 3}
print(#t)      -- 1 or 3 (undefined)

# is undefined on holes

The length of a table with nil gaps is unspecified. Count manually instead.

local n = 0
for _ in pairs(t) do n = n + 1 end

String methods need a colon

s:match("x") is sugar for s.match(s, "x"). A dot drops the receiver and errors on a nil index.

s:upper()      -- ok
s.upper(s)     -- equivalent
s.upper()      -- error: nil value

and / or return operands

They yield values, not booleans — idiomatic for defaults and guards.

local x = maybe or "default"
local y = flag and "yes" or "no"

⚠: Globals by default. Assigning to an undeclared name creates a global — visible everywhere, easy to clobber. Declare with local at the top of each block.