The complete Predicator expression language: data types, operators, builtin functions, decompile formatting, and error shapes. For the grammar with precedence, see Architecture.

Data Types

  • Numbers: 42, -17 (integers), 3.14, -2.5 (floats)
  • Strings: 'hello', 'world' (single-quoted) or "hello", "world" (double-quoted, with escape sequences)
  • Booleans: true, false (or plain identifiers like active, expired)
  • Dates: #2024-01-15# (ISO 8601 date format)
  • DateTimes: #2024-01-15T10:30:00Z# (ISO 8601 datetime format with timezone)
  • Durations: Natural units for time spans (e.g., 3d, 2h, 15m)
    • In relative expressions: 3d ago, 2w from now, next 1mo, last 1y
    • In arithmetic: #2024-01-10# + 5d, #2024-01-15T10:30:00Z# - 2h
  • Lists: [1, 2, 3], ['admin', 'manager'] (homogeneous collections)
  • Objects: {}, {name: "John", age: 30}, {user: {role: "admin"}} (JavaScript-style object literals)
  • Identifiers: score, user_name, is_active, user.profile.name, user['key'], items[0] (variable references with dot notation and bracket notation for nested data)

Arithmetic Operators

OperatorDescriptionExample
+Additionscore + bonus, 2 + 3 * 4
-Subtractiontotal - discount, 100 - 25
*Multiplicationprice * quantity, 3 * 4
/Division (integer)total / count, 10 / 3
%Moduloid % 2, 17 % 5
-Unary minus-amount, -(x + y)

Comparison Operators

OperatorDescriptionExample
>Greater thanscore > 85, #2024-01-15# > #2024-01-10#
<Less thanage < 30, created_at < #2024-01-15T10:00:00Z#
>=Greater than or equalpoints >= 100
<=Less than or equalcount <= 5
==Equalstatus == 'active', date == #2024-01-15#
!=Not equalrole != 'guest'
===Strict equal (no type coercion)count === 5
!==Strict not equal (no type coercion)count !== "5"
=Equal - deprecated, use ==status = 'active'

Deprecated: = as equality. Using = for equality still works and still compiles to ["compare", "EQ"], but parsing one emits a deprecation warning. Predicator 4.0 makes expression-position = a parse error - = is being reserved for assignment in the forthcoming statement grammar. Migrate to == before upgrading. Silence the warning with config :predicator, deprecation_warnings: false. See ADR-0002 for the reasoning.

Logical Operators

OperatorDescriptionExample
ANDLogical AND (case-insensitive)score > 85 AND age >= 18
ORLogical OR (case-insensitive)role == 'admin' OR role == 'manager'
NOTLogical NOT (case-insensitive)NOT expired

Membership Operators

OperatorDescriptionExample
inElement in collectionrole in ['admin', 'manager']
containsCollection contains element[1, 2, 3] contains 2

Builtin Functions

String Functions

FunctionDescriptionExample
len(string)String lengthlen(name) > 3
upper(string)Convert to uppercaseupper(role) == 'ADMIN'
lower(string)Convert to lowercaselower(name) == 'alice'
trim(string)Remove surrounding whitespacelen(trim(input)) > 0
starts_with(string, prefix)Prefix teststarts_with(email, 'admin')
ends_with(string, suffix)Suffix testends_with(file, '.csv')
substring(string, start[, len])Substring by offsetsubstring(code, 0, 3) == 'ABC'
index_of(string, sub)Index of substring, or -1index_of(path, '/') == 0
iex> Predicator.evaluate("len('hello')", %{})
{:ok, 5}

iex> Predicator.evaluate("upper('world')", %{})
{:ok, "WORLD"}

iex> Predicator.evaluate("starts_with('hello world', 'hello')", %{})
{:ok, true}

iex> Predicator.evaluate("ends_with('hello world', 'world')", %{})
{:ok, true}

iex> Predicator.evaluate("substring('hello world', 6)", %{})
{:ok, "world"}

iex> Predicator.evaluate("substring('hello world', 0, 5)", %{})
{:ok, "hello"}

iex> Predicator.evaluate("index_of('hello world', 'world')", %{})
{:ok, 6}

iex> Predicator.evaluate("index_of('hello world', 'nope')", %{})
{:ok, -1}

Numeric Functions

FunctionDescriptionExample
Math.abs(number)Absolute valueMath.abs(balance) < 100
Math.max(a, b)Maximum of two numbersMath.max(score1, score2) > 85
Math.min(a, b)Minimum of two numbersMath.min(age, 65) >= 18
Math.pow(base, exp)ExponentiationMath.pow(2, 10) == 1024
Math.sqrt(number)Square rootMath.sqrt(144) == 12
Math.floor(number)Round downMath.floor(3.9) == 3
Math.ceil(number)Round upMath.ceil(3.1) == 4
Math.round(number)Round to nearest integerMath.round(3.5) == 4
iex> Predicator.evaluate("Math.abs(-5) == 5", %{})
{:ok, true}

iex> Predicator.evaluate("Math.max(1, 2) == 2", %{})
{:ok, true}

iex> Predicator.evaluate("Math.pow(2, 10) == 1024", %{})
{:ok, true}

Date Functions

FunctionDescriptionExample
Date.year(date)Extract yearDate.year(created_at) == 2024
Date.month(date)Extract monthDate.month(birthday) == 12
Date.day(date)Extract dayDate.day(deadline) <= 15
iex> Predicator.evaluate("Date.year(created_at) == 2024", %{"created_at" => ~D[2024-03-15]})
{:ok, true}

Numeric and date functions moved under the Math. and Date. namespaces to avoid colliding with likely user variable names; the unnamespaced string functions predate that convention and were left as-is.

Decompiling and Formatting Options

Predicator.decompile/2 converts a parsed AST back to source, preserving quote style, with formatting options:

iex> {:ok, ast} = Predicator.parse("score > 85")
iex> Predicator.decompile(ast, spacing: :compact)
"score>85"

iex> {:ok, ast} = Predicator.parse("score > 85")
iex> Predicator.decompile(ast, spacing: :verbose)
"score  >  85"

iex> {:ok, ast} = Predicator.parse("score > 85")
iex> Predicator.decompile(ast, parentheses: :explicit)
"(score > 85)"

Error Shapes

Predicator returns errors as structs under Predicator.Errors, never as bare strings, and never raises at a leaf:

iex> {:error, err} = Predicator.evaluate("score >> 85", %{})
iex> {err.__struct__, err.line, err.column}
{Predicator.Errors.ParseError, 1, 8}

iex> {:error, err} = Predicator.evaluate("score AND", %{})
iex> {err.__struct__, err.column}
{Predicator.Errors.ParseError, 10}

A function that errors mid-evaluation surfaces as an EvaluationError:

iex> custom_functions = %{"divide" => {2, fn [a, b], _ctx ->
...>   if b == 0, do: {:error, "Division by zero"}, else: {:ok, a / b}
...> end}}
iex> {:error, err} = Predicator.evaluate("divide(10, 0)", %{}, functions: custom_functions)
iex> {err.__struct__, err.message}
{Predicator.Errors.EvaluationError, "Division by zero"}

See the location expressions guide for Predicator.Errors.LocationError, the third error struct.