Skip to content

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 or nil raises; use tostring.
  • There is no automatic string-to-number conversion in arithmetic: "42" + 0 raises. Use tonumber("42"), which returns nil for anything that is not a number.
  • string.match returns the whole match, even when the pattern has captures. Use string.captures for the groups and string.matchall instead of gmatch.
  • string.format supports %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 }
  • ipairs and pairs only work written directly in a for loop. There are no other iterators and no next.
  • pairs visits 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 change t.
  • The standard library adds helpers such as table.map, table.filter, table.reduce, table.keys, table.values, table.merge, table.size and table.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 local or assignment list, or pass through a return 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 ​

  1. Reading an undefined global raises instead of returning nil.
  2. Integer overflow raises instead of wrapping.
  3. Multiple returns do not expand in argument lists or table constructors.
  4. ipairs/pairs are loop syntax only; pairs order is defined.
  5. Metatables support only a table-valued __index: no operator overloading, __call or __newindex.
  6. No loading code from strings (load, loadstring, dofile).
  7. string.match returns the whole match; use string.captures and string.matchall.
  8. table.sort returns a new array.
  9. No string-to-number coercion in arithmetic.
  10. No bitwise operators (& | ~ << >>); use the bit32 library.
  11. Script functions cannot be variadic.
  12. Library tables are read-only.
  13. Not available: os, io, debug, coroutine, package, require, select, next, rawequal, xpcall, collectgarbage, _G, getfenv/setfenv, math.random, string.gmatch and goto.

For what the sandbox provides instead, see Sandbox environment.

Connect machines, automate workflows, and build custom apps for print production.