Celixir Demo
Mix.install([{:celixir, path: "."}])
Basics
CEL is a non-Turing-complete expression language designed for simplicity and safety. Celixir is a pure Elixir implementation.
# Arithmetic
Celixir.eval!("1 + 2 * 3")
# String operations
Celixir.eval!("'hello' + ' ' + 'world'")
# Booleans and comparisons
Celixir.eval!("10 > 5 && 'abc'.startsWith('a')")
# Ternary
Celixir.eval!("true ? 'yes' : 'no'")
Variables
Pass data into expressions as variable bindings.
Celixir.eval!("user.age >= 18 && user.country == 'PL'", %{
user: %{age: 30, country: "PL"}
})
Celixir.eval!("price * double(quantity)", %{price: 9.99, quantity: 3})
Strings
name = "World"
[
Celixir.eval!("name.size()", %{name: name}),
Celixir.eval!("'hello world'.contains('world')"),
Celixir.eval!("'hello world'.upperAscii()"),
Celixir.eval!("'a,b,c'.split(',')"),
Celixir.eval!("'hello'.reverse()"),
Celixir.eval!("' padded '.trim()"),
Celixir.eval!("'hello'.substring(1, 3)"),
Celixir.eval!("'banana'.replace('a', 'o')"),
Celixir.eval!(~S|'test@example.com'.matches('[a-z]+@[a-z]+\\.[a-z]+')|)
]
Lists and Maps
# List operations
Celixir.eval!("[3, 1, 2].sort()")
Celixir.eval!("[1, [2, 3], [4]].flatten()")
# Map access and membership
Celixir.eval!("'key' in m && m.key == 42", %{m: %{"key" => 42}})
# Ranges
Celixir.eval!("lists.range(0, 5)")
Comprehensions
Filter, transform, and test collections with macros.
Celixir.eval!("[1, 2, 3, 4, 5].filter(x, x > 2)")
Celixir.eval!("[1, 2, 3].map(x, x * x)")
Celixir.eval!("[1, 2, 3].all(x, x > 0)")
Celixir.eval!("[1, 2, 3].exists(x, x == 2)")
Celixir.eval!("[1, 2, 3, 2].exists_one(x, x == 2)")
Math
[
math_least: Celixir.eval!("math.least(3, 1, 2)"),
math_greatest: Celixir.eval!("math.greatest(3, 1, 2)"),
ceil: Celixir.eval!("math.ceil(1.2)"),
floor: Celixir.eval!("math.floor(1.8)"),
round: Celixir.eval!("math.round(1.5)"),
abs: Celixir.eval!("math.abs(-42)"),
sign: Celixir.eval!("math.sign(-3.14)")
]
Type Conversions
[
to_int: Celixir.eval!("int('42')"),
to_double: Celixir.eval!("double(42)"),
to_string: Celixir.eval!("string(3.14)"),
to_bool: Celixir.eval!("bool('true')"),
to_bytes: Celixir.eval!("bytes('hello')"),
typeof: Celixir.eval!("type(42)")
]
Timestamps and Durations
Celixir.eval!("timestamp('2024-01-15T10:30:00Z') + duration('1h30m')")
now = Celixir.eval!("timestamp('2026-03-13T12:00:00Z')")
Celixir.eval!(
"timestamp('2026-12-25T00:00:00Z') > now",
%{now: now}
)
Celixir.eval!("duration('1h') + duration('30m') == duration('90m')")
Optional Values
Safely handle missing data without errors.
Celixir.eval!("optional.of('hello').hasValue()")
Celixir.eval!("optional.none().orValue('default')")
# Optional chaining — access fields that might not exist
Celixir.eval!("{'a': 1}.?b.orValue(0)")
Compile Once, Evaluate Many
For hot paths, parse once and evaluate with different bindings.
{:ok, program} = Celixir.compile("price * (1.0 - discount)")
for {price, discount} <- [{100.0, 0.1}, {50.0, 0.2}, {200.0, 0.0}] do
{:ok, result} = Celixir.Program.eval(program, %{price: price, discount: discount})
%{price: price, discount: "#{round(discount * 100)}%", final: result}
end
Custom Functions
Extend CEL with Elixir functions.
env =
Celixir.Environment.new(%{items: [3, 1, 4, 1, 5, 9, 2, 6]})
|> Celixir.Environment.put_function("median", fn list ->
sorted = Enum.sort(list)
len = length(sorted)
mid = div(len, 2)
if rem(len, 2) == 0 do
(Enum.at(sorted, mid - 1) + Enum.at(sorted, mid)) / 2.0
else
Enum.at(sorted, mid) / 1.0
end
end)
Celixir.eval!("median(items)", env)
# Namespaced functions
env =
Celixir.Environment.new()
|> Celixir.Environment.put_function("str.slugify", fn s ->
s |> String.downcase() |> String.replace(~r/[^a-z0-9]+/, "-") |> String.trim("-")
end)
|> Celixir.Environment.put_function("str.word_count", fn s ->
s |> String.split() |> length()
end)
[
Celixir.eval!(~S|str.slugify("Hello World!")|, env),
Celixir.eval!(~S|str.word_count("The quick brown fox")|, env)
]
Practical Example: Policy Engine
Use CEL as a rule engine to evaluate access policies.
policies = [
{"admin_access", "user.role == 'admin'"},
{"own_resource", "user.id == resource.owner_id"},
{"public_read", "request.method == 'GET' && resource.visibility == 'public'"},
{"business_hours", "request.hour >= 9 && request.hour < 17"},
{"rate_limit", "user.request_count < 1000"}
]
compiled_policies =
Enum.map(policies, fn {name, expr} ->
{:ok, program} = Celixir.compile(expr)
{name, program}
end)
context = %{
user: %{role: "editor", id: 42, request_count: 150},
resource: %{owner_id: 42, visibility: "private"},
request: %{method: "PUT", hour: 14}
}
Enum.map(compiled_policies, fn {name, program} ->
{:ok, result} = Celixir.Program.eval(program, context)
{name, result}
end)
Compile-Time Sigil
Parse expressions at compile time for zero runtime parsing overhead.
import Celixir.Sigil
ast = ~CEL|request.size < 1024 && request.content_type == "application/json"|
Celixir.eval_ast(ast, %{
request: %{size: 512, content_type: "application/json"}
})
Error Handling
CEL uses error-as-value semantics with short-circuit absorption.
# Errors are returned as {:error, message}
Celixir.eval("1 / 0")
# Short-circuit: error on left, but right is true → true wins
Celixir.eval("1/0 > 5 || true")
# Type mismatch
Celixir.eval("'hello' + 42")
# Undefined variable
Celixir.eval("missing_var > 0")