Appearance
Lua dialect guide
CoCoCo scripts are written in SwiftLua, a subset of Lua built for sandboxed, deterministic execution. Most everyday Lua works unchanged. A few constructs are missing, and a few behave differently. This page covers the basics and then lists the differences.
Values and variables
The types are nil, boolean, integer (64-bit), number (64-bit float), string, table and function. There is no userdata and no coroutine type.
lua
local name = "press-1"
local count = 3 -- integer
local ratio = 0.75 -- number (float)
local active = true
local nothing = nil
local half = 7 // 2 -- 3: integer floor division stays an integer
local exact = 7 / 2 -- 3.5: `/` always produces a float
return { name = name, count = count, ratio = ratio, active = active, half = half, exact = exact, none = nothing }Always declare variables with local. The type checker flags an assignment that creates a global, and reading a global that does not exist raises unknownGlobal instead of returning nil.
Only nil and false are false. 0 and "" are true.
Strings
lua
local line = " batt=55% "
local trimmed = string.trim(line)
local parts = string.split("a,b,c", ",")
local joined = string.join("|", parts)
local label = "count=" .. 42 -- numbers concatenate
local formatted = string.format("%6.2f", 7.5)
local value = nil
local groups = string.captures(trimmed, "batt=(%d+)") -- nil when nothing matches
if groups ~= nil then
value = groups[1] -- "55"
end
return { trimmed = trimmed, joined = joined, label = label, formatted = formatted, value = value }..concatenates strings and numbers. Concatenating a boolean ornilraises; usetostring.- There is no automatic string-to-number conversion in arithmetic:
"42" + 0raises. Usetonumber("42"), which returnsnilfor anything that is not a number. string.matchreturns the whole match, even when the pattern has captures. Usestring.capturesfor the groups andstring.matchallinstead ofgmatch.string.formatsupports%d %i %f %g %s %x %%and at most 4 value arguments.
Tables
lua
local jobs = { "JOB-1", "JOB-2" }
table.insert(jobs, "JOB-3")
local job = { id = "JOB-1", quantity = 500 }
job.status = "READY"
local names = {}
for index, id in ipairs(jobs) do
names[#names + 1] = tostring(index) .. ":" .. id
end
local keys = {}
for key, _ in pairs(job) do
keys[#keys + 1] = key
end
return { count = #jobs, names = names, keys = keys }ipairsandpairsonly work written directly in aforloop. There are no other iterators and nonext.pairsvisits the array part in order, then the other keys in insertion order. The order never changes between runs.table.sort(t)returns a new sorted array and does not changet.- The standard library adds helpers such as
table.map,table.filter,table.reduce,table.keys,table.values,table.merge,table.sizeandtable.contains. Callbacks receive(value, index).
lua
local sorted = table.sort({ 3, 1, 2 })
local doubled = table.map(sorted, function(v) return v * 2 end)
local total = table.reduce(doubled, function(acc, v) return acc + v end, 0)
return { sorted = sorted, doubled = doubled, total = total }Control flow
lua
local function classify(temperature)
if temperature > 80 then
return "hot"
elseif temperature > 40 then
return "warm"
else
return "cold"
end
end
local labels = {}
for i = 1, 3 do
labels[i] = classify(i * 30)
end
local n = 0
while n < 3 do
n = n + 1
end
return { labels = labels, n = n }There is no goto, no continue and no +=. Use break to leave a loop early.
Functions
lua
local function add(a, b)
return a + b
end
local function divmod(a, b)
return a // b, a % b
end
local q, r = divmod(9, 2)
return { sum = add(3, 4), q = q, r = r }- Functions cannot be variadic:
...is only allowed in the main chunk, where it holds the script's input. - Multiple return values only spread into the last position of a
localor assignment list, or pass through areturn f()that has no other expressions.g(f())passes one value and{f()}has one element. Bind the values to locals first. - The
obj:method()call syntax works, and metatables support one feature: a table-valued__index, which is enough for the class pattern.
lua
local Counter = {}
Counter.__index = Counter
function Counter.new(start)
local self = setmetatable({}, Counter)
self.value = start
return self
end
function Counter:increment()
self.value = self.value + 1
return self.value
end
local c = Counter.new(10)
c:increment()
return c:increment()Errors
lua
local function parseQuantity(text)
local n = tonumber(text)
if n == nil then
error("not a number: " .. tostring(text))
end
return n
end
local ok, result = pcall(function() return parseQuantity("12x") end)
if not ok then
ctx.log.warn("bad quantity", { reason = tostring(result) })
end
return { ok = ok }pcall(f, ...) returns true, result or false, message. There is no xpcall. Budget errors (time, memory, instruction limits) cannot be caught.
Type annotations
The type checker (in the console editor and the validateLua mutation) reads optional type annotations. They have no runtime cost:
lua
type Line = { sku: string, qty: number }
local function total(lines: { Line }): number
local sum = 0
for _, line in ipairs(lines) do
sum = sum + line.qty
end
return sum
end
return total({ { sku = "A", qty = 2 }, { sku = "B", qty = 3 } })Differences from stock Lua
- Reading an undefined global raises instead of returning
nil. - Integer overflow raises instead of wrapping.
- Multiple returns do not expand in argument lists or table constructors.
ipairs/pairsare loop syntax only;pairsorder is defined.- Metatables support only a table-valued
__index: no operator overloading,__callor__newindex. - No loading code from strings (
load,loadstring,dofile). string.matchreturns the whole match; usestring.capturesandstring.matchall.table.sortreturns a new array.- No string-to-number coercion in arithmetic.
- No bitwise operators (
& | ~ << >>); use thebit32library. - Script functions cannot be variadic.
- Library tables are read-only.
- Not available:
os,io,debug,coroutine,package,require,select,next,rawequal,xpcall,collectgarbage,_G,getfenv/setfenv,math.random,string.gmatchandgoto.
For what the sandbox provides instead, see Sandbox environment.