Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Expressions

RuleFlow’s expression language is the small, total, side-effect-free language used inside decision_table cells, expression nodes, switch cases and output bindings. It is a bespoke safe subset, not an embedded general-purpose interpreter (not Rhai, not Lua, not JS) — deliberately, for two reasons:

  • Determinism / auditability. A decision must replay to the identical result forever, and produce the same answer in the browser (WASM) and on the server. That rules out clocks, randomness and IO.
  • Stability. Once a rule references an operator or function, changing or removing it can silently alter a customer’s logic. The surface is small, explicit and versioned alongside the model.

Guarantees

  • Total & pure. No IO, clock or randomness. No loops, no recursion, no user-defined functions — the only callable surface is the fixed function library below. Evaluation always terminates.
  • Decimal arithmetic. Numbers are exact decimals, never f64 — financial precision by construction, not by convention.
  • Cross-target parity. The same evaluator compiles to native and to WebAssembly, so a browser preview and a server run evaluate identically.

Types

null, bool, number (decimal), string, date, array, object. Literals: numbers (42, 0.35), double-quoted strings with escapes (\" \\ \n \t), true, false, null, array literals ([a, b, c]). There is no date or object literal — both enter only via input data.

Grammar

Lowest to highest precedence:

or      → and  ( "or"  and )*
and     → eq   ( "and" eq  )*
eq      → cmp  ( ( "=" | "!=" ) cmp )*
cmp     → add  ( ( "<" | "<=" | ">" | ">=" | "in" ) add )*
add     → mul  ( ( "+" | "-" ) mul )*
mul     → unary( ( "*" | "/" | "%" ) unary )*
unary   → ( "-" | "not" ) unary | postfix
postfix → primary ( "." IDENT | "[" or "]" )*
primary → NUMBER | STRING | "true" | "false" | "null"
        | "[" args "]" | IDENT | IDENT "(" args ")" | "(" or ")"

a.b reads an object field; a[i] indexes an array or string by an integer.

Operators

  • Arithmetic + - * / % on numbers. + also concatenates two strings ("a" + "b""ab"); mixing a number and a string is an error.
  • Equality = / != is type-aware and numeric-tolerant: 1 = 1.0 is true. (There is no === alone is equality.)
  • Ordering < <= > >= compares numbers, dates and strings (lexicographically); comparing incomparable types is an error, not a silent false.
  • Membership x in [a, b, c] is true when x equals any element.
  • Logical and / or short-circuit; not negates a boolean. A non-boolean operand to a logical operator is an error.

Functions

FunctionArityNotes
if(cond, a, b)3lazy — only the taken branch is evaluated
min(…), max(…)≥1 numericvariadic
abs(n)1 numeric
round(n), floor(n), ceil(n)1 numeric
len(x)1string (chars), array or object length
contains(hay, needle)2substring (string) or element membership (array)
startsWith(s, prefix)2strings
lower(s), upper(s)1strings
coalesce(…)≥0first non-null argument, else null
dateDiffDays(a, b)2 datesa - b in whole days
isBefore(a, b), isAfter(a, b)2 dates

Calling an unknown function, the wrong arity, or the wrong argument type is a typed error surfaced to the author — never a silent default.

Decision-table cell conditions

Table cells use a compact predicate mini-language, one per input column, tested against that column’s value:

FormMeaning
- (or empty)wildcard, always matches
>= 18, < 100, != 0, = "GOLD"comparison / (in)equality
[18..65], (0..100], [1..10)range; [/] inclusive, (/) exclusive
in ["A", "B"]set membership
bare expression ("GOLD", 5, tier)equality against the column value

The right-hand side of a cell is a full expression, so it may reference other inputs — e.g. <= creditLimit in one column reading a value computed in another.

Where it’s used

expression nodes, switch case when predicates, output bindings ({ "expr": … }), and decision-table cells all share one parser and evaluator — semantics are identical everywhere the language appears.

Versioning

The expression language version is coupled to the project’s model_version — there is no separate version number. Additive changes (a new pure function, a new operator) are a minor, backward-compatible bump. Removing a function/operator, or changing evaluation semantics, is a breaking, major bump. A stored release pins a model_version, and the engine refuses to run a model whose version line it does not implement.