Expression syntax
An expression is a single line that reads from the context, transforms values through methods and functions, and produces one value.
Literals
| Kind | Examples |
|---|---|
| String | "hello", 'hello' — single or double quotes are equivalent |
| Integer | 42, 0 |
| Float | 3.14, 0.5 |
| Boolean | true, false |
| Null | null |
Inside a string, the escapes \n, \t, \r, and \\ are recognised;
\ before any other character yields that character (so \" is a
literal quote).
Paths
An identifier starts a path; further segments follow with .. Segments
are context keys or numeric indexes.
trigger
trigger.user.email
trigger.items.0.name
nodes.step_2.output
Identifiers match [A-Za-z_][A-Za-z0-9_]*. A path that can't be resolved
is null, or throws in strict mode.
Property and method chaining
trigger.user.name.trim().upper()
trigger.items.filter(current.active).first().name
.nameafter a value reads a property (array key)..name(args)calls a method on the value.?.before either makes it safe: anullleft-hand side short-circuits the rest of the chain tonullinstead of erroring.
Function calls
An identifier immediately followed by ( is a function
call, not a path:
now()
uuid()
coalesce(trigger.a, trigger.b, "fallback")
Operators
Arithmetic — + - * / %
trigger.price * trigger.qty
(trigger.subtotal + trigger.tax) / 2
10 % 3 → 1
10.5 % 3 → 1.5 (float operands keep precision)
/ and % by zero throw EvaluatorException.
+ is overloaded. If either side is a genuine (non-numeric) string,
+ concatenates; otherwise it adds. Numeric strings (e.g. "10" from a
form field) still add arithmetically, matching -, *, /.
"Hello " + trigger.user.name → "Hello Ada"
trigger.price + 5 → 15 (even if price is the string "10")
Comparison — == != > >= < <=
Return a bool. == and != are loose comparisons.
trigger.user.age >= 18
trigger.order.status == "paid"
Logical — && || !
&& and || short-circuit and evaluate to a bool. ! is prefix
negation.
trigger.user.active && trigger.user.age >= 18
!trigger.order.cancelled
Null-coalescing — ??
Evaluates to the left side unless it is null, in which case the right
side.
trigger.user.nickname ?? trigger.user.name ?? "friend"
Ternary — ? :
trigger.user.age >= 18 ? "adult" : "minor"
Unary minus — -
-trigger.balance
Applying - to a non-numeric string throws
EvaluatorException.
Grouping
Parentheses override precedence:
(1 + 2) * 3 → 9
Precedence
Lowest to highest — lower binds looser:
? :(ternary)??||&&==!=>>=<<=+-*/%- unary
!- .?.()(property / method / call chain)
a && b ?? c → (a && b) ?? c
1 + 2 * 3 → 1 + (2 * 3)
trigger.age > 18 && trigger.active → (trigger.age > 18) && trigger.active
What the language does not have
No variable assignment (outside set()/get()),
no loops, no function definitions, no object instantiation, no access to
PHP classes or globals. Expressions are read-and-transform only.
Errors
- Invalid characters or an unterminated string →
LexerException - Malformed structure (unbalanced parens, a trailing operator,
=instead of==) →ParserException
Both carry a position N in the message. See Error handling.