Cel Standard Library
cel.lua is the unified standard library for the ZERG project. It provides JSON, OOP, collections, vectors, shell quoting, and utilities through a single import that works identically on both LuaJIT (Luna agent) and Luerl (Sol plugin sandbox).
Import
local cel = require("core.cel")cel.json -- JSON Encode/Decode
JSON encoding and decoding with automatic backend selection. Prefers dkjson on LuaJIT, falls back to JSON_luerl in the plugin sandbox.
local cel = require("core.cel")
local encoded = cel.json.encode({name = "zerg", version = "0.91.0"})
-- '{"name":"zerg","version":"0.91.0"}'
local decoded, _, err = cel.json.decode('{"ok":true}')
-- {ok = true}
local bad, _, err = cel.json.decode("invalid")
-- nil, nil, "parse error"API
| Function | Signature | Returns |
|---|---|---|
encode | cel.json.encode(table) | JSON string |
decode | cel.json.decode(string) | table, nil, nil on success; nil, nil, error on failure |
The decode function returns three values for compatibility with dkjson's error reporting convention.
cel.class -- Object-Oriented Programming
Provides middleclass v4.1.1 for class-based OOP with inheritance, mixins, and metamethods.
local cel = require("core.cel")
local Animal = cel.class("Animal")
function Animal:initialize(name)
self.name = name
end
function Animal:speak()
return "..."
end
local Dog = cel.class("Dog", Animal)
function Dog:speak()
return self.name .. " says woof"
end
local d = Dog:new("Rex")
d:speak()
-- "Rex says woof"API
| Function | Description |
|---|---|
cel.class(name) | Define a new class |
cel.class(name, parent) | Define a subclass |
Class:initialize(...) | Constructor |
instance = Class:new(...) | Create instance |
instance:isInstanceOf(Class) | Type check |
Sub:subclassOf(Parent) | Inheritance check |
Class:include(mixin) | Include a mixin |
cel.vec -- 2D Vector Math
A 2D vector library for spatial calculations. Uses a plain metatable (no __call) for Luerl compatibility.
local cel = require("core.cel")
local v1 = cel.vec.new(3, 4)
cel.vec.mag(v1)
-- 5
local v2 = cel.vec.from_angle(math.pi / 2)
cel.vec.heading(v2)
-- 1.5707...
local sum = cel.vec.add(v1, v2)
local scaled = cel.vec.mul(2, v1)
local normalized = cel.vec.norm(v1)
local limited = cel.vec.limit(v1, 3)API
| Function | Signature | Returns |
|---|---|---|
new | vec.new(x, y) | Vector {x, y} |
from_angle | vec.from_angle(theta) | Unit vector at angle |
random | vec.random() | Random unit vector |
is | vec.is(t) | Boolean |
to_table | vec.to_table(v) | {x=, y=} |
from_table | vec.from_table(t) | Vector |
clone | vec.clone(v) | New vector copy |
mag | vec.mag(v) | Magnitude |
mag_sq | vec.mag_sq(v) | Squared magnitude |
set_mag | vec.set_mag(v, m) | Vector with new magnitude |
norm | vec.norm(v) | Unit vector |
limit | vec.limit(v, max) | Clamped vector |
add | vec.add(a, b) | Component-wise sum |
sub | vec.sub(a, b) | Component-wise difference |
mul | vec.mul(a, b) | Scalar or component-wise multiply |
div | vec.div(v, s) | Scalar division |
neg | vec.neg(v) | Negated vector |
eq | vec.eq(a, b) | Boolean equality |
dot | vec.dot(a, b) | Dot product |
dist | vec.dist(a, b) | Euclidean distance |
heading | vec.heading(v) | Angle in radians |
rotate | vec.rotate(v, theta) | Rotated vector |
Eager Collections
Collection operations backed by lume. These process entire tables immediately.
local cel = require("core.cel")
local items = {1, 2, 3, 4, 5}
local doubled = cel.map(items, function(x) return x * 2 end)
-- {2, 4, 6, 8, 10}
local evens = cel.filter(items, function(x) return x % 2 == 0 end)
-- {2, 4}
local sum = cel.reduce(items, function(acc, x) return acc + x end, 0)
-- 15
cel.each(items, function(x) print(x) end)API
| Function | Signature | Description |
|---|---|---|
map | cel.map(t, fn) | Transform each element |
filter | cel.filter(t, fn) | Keep matching elements |
each | cel.each(t, fn) | Iterate with side effects |
reduce | cel.reduce(t, fn, init) | Fold into single value |
any | cel.any(t, fn) | True if any element matches |
all | cel.all(t, fn) | True if all elements match |
Lazy Iterators
Lazy pipeline operations backed by fun.lua. These create iterators that compute values on demand.
local cel = require("core.cel")
local pipeline = cel.iter.range(1, 100)
pipeline = cel.iter.filter(function(x) return x % 3 == 0 end, pipeline)
pipeline = cel.iter.map(function(x) return x ^ 2 end, pipeline)
pipeline = cel.iter.take(5, pipeline)
local results = cel.iter.to_table(pipeline)
-- {9, 36, 81, 144, 225}API
| Function | Description |
|---|---|
iter.range(start, stop) | Integer range iterator |
iter.map(fn, iter) | Transform iterator |
iter.filter(fn, iter) | Filter iterator |
iter.reduce(fn, init, iter) | Fold iterator |
iter.each(fn, iter) | Consume iterator |
iter.take(n, iter) | First n elements |
iter.drop(n, iter) | Skip first n elements |
iter.zip(it1, it2) | Pair elements from two iterators |
iter.chain(it1, it2) | Concatenate iterators |
iter.enumerate(iter) | Index-value pairs |
iter.cycle(iter) | Infinite repetition |
iter.duplicate(val) | Infinite value |
iter.wrap(it) | Wrap as iterator |
iter.unwrap(it) | Unwrap iterator |
iter.to_table(iter) | Collect into array table |
String Utilities
local cel = require("core.cel")
cel.split("a,b,c", ",")
-- {"a", "b", "c"}
cel.trim(" hello ")
-- "hello"
cel.keys({a = 1, b = 2})
-- {"a", "b"}
cel.values({a = 1, b = 2})
-- {1, 2}
cel.clamp(15, 0, 10)
-- 10
cel.lerp(0, 100, 0.5)
-- 50API
| Function | Description |
|---|---|
split(str, sep) | Split string by separator |
trim(str) | Remove leading/trailing whitespace |
format(str, ...) | String formatting |
wordwrap(str, width) | Word-wrap text |
keys(t) | Table keys as array |
values(t) | Table values as array |
clone(t) | Shallow table copy |
find(t, value) | Find value in table |
pick(t, ...) | Select keys from table |
extend(t, ...) | Merge tables |
clamp(val, min, max) | Clamp to range |
lerp(a, b, t) | Linear interpolation |
uuid() | Generate UUID string |
serialize(t) | Serialize table to string |
deserialize(str) | Deserialize string to table |
random(t) | Random element from table |
cel.shell_quote -- Shell Argument Safety
Wraps shell arguments in single quotes to prevent injection from LLM-generated parameters. Required for all os.execute and shell command calls.
local cel = require("core.cel")
local user_input = '$(rm -rf /)'
local safe = cel.shell_quote(user_input)
-- "'$(rm -rf /)'"
os.execute("echo " .. cel.shell_quote(user_input))The function wraps arguments in single quotes and escapes any embedded single quotes using '\''. This prevents $(), backticks, $VAR, and other shell expansions.
Utility Functions
deep_copy
local copy = cel.deep_copy(original)Recursive deep copy of a table.
deep_merge
local merged = cel.deep_merge(base, overlay)Recursive deep merge. Arrays are concatenated; maps are merged recursively.
is_array
cel.is_array({1, 2, 3})
-- true
cel.is_array({a = 1})
-- falsesplit_lines
local lines = cel.split_lines("line1\nline2\nline3")
-- {"line1", "line2", "line3"}Split text into lines, handling trailing newlines correctly.