Skip to main content

Module expr

Module expr 

Source
Expand description

The branch-condition expression language: a small, TOTAL, non-Turing-complete language that gives meaning to the opaque string inside a crate::document::BranchCondition::Expression.

§Why deliberately weak

A branch condition is evaluated inside a durable, replayed run: the same expression is re-evaluated against the same routed value during replay and again during a fork, so its result must be identical every time and forever. Two properties follow, and they shape every decision here:

  • Total. Every syntactically valid expression produces a bool for every possible JSON value, with no panic, no error, and no undefined case. There is no runtime failure mode, so a valid condition can never turn a replay into an error.
  • Non-Turing-complete. No loops, no function calls, no arithmetic, no regex. Evaluation is a single linear walk of an abstract syntax tree whose size is bounded by the MAX_EXPRESSION_LEN-character source cap, so eval time is bounded. A Turing-complete language in a durable log would make replay time unbounded.

§The grammar

or         := and ( "||" and )*
and        := unary ( "&&" unary )*
unary      := "!" unary | atom
atom       := "(" or ")" | comparison
comparison := operand ( cmp_op operand )?
cmp_op     := "==" | "!=" | "<" | "<=" | ">" | ">="
operand    := literal | path
literal    := string | number | "true" | "false" | "null"
path       := ident ( "." segment )*
segment    := ident | integer

Precedence, loosest to tightest: ||, then &&, then !, then a comparison. A comparison therefore binds tighter than any boolean operator (!score > 0.8 parses as !(score > 0.8)), and comparisons do not chain (a < b < c is a parse error). Parentheses group a whole boolean sub-expression; they are not part of a comparison operand, so (a > b) > c is a parse error rather than a comparison of a boolean.

A bare operand used where a boolean is expected (ready, or ready && ok) is true only when it resolves to the JSON boolean true; every other value, and a missing path, is false.

§Paths and array indexing

A path is dot-separated segments walked from the root of the routed value: score, output.score, items.0.score. Array indexing IS supported because routed values routinely carry lists (a tool that returns an array, a map join), and without it a branch could not reach into one at all. The container type decides how a segment is read: on a JSON object a segment is a key, and on a JSON array a purely numeric segment is a zero-based index. A numeric segment therefore never indexes an object, so an object key spelled with digits ({"0": ...}) is not reachable; that ambiguity is traded away deliberately for a single, deterministic rule.

§Total semantics (these are forever: replay and fork re-run them verbatim)

  • Missing path. A path that does not resolve (an absent key, an index past the end, or a descent into a non-container) is MISSING, which is distinct from JSON null. Any comparison with a missing operand is false, and a missing operand in boolean position is false. Rationale: JSON null is a value an author chose, so conflating it with “absent” would let x == null fire on a field that simply is not there; keeping them distinct means a branch never silently fires on absent data. ! still lets an author test for absence deliberately (!(score > 0.8) is true when score is missing).
  • Type mismatch. Equality (==, !=) is defined across all types: values of different types are never equal, so "5" == 5 is false. Ordering (<, <=, >, >=) is defined only for two numbers or two strings; any other ordered comparison (a number against a string, anything against a bool/null/object/array) is false. Rationale: ordering has an obvious total meaning only for numbers and strings, and yielding false everywhere else means a mismatched type can never satisfy an ordering branch.
  • Number comparison. Two numbers compare by mathematical value. When both are integers they compare exactly through i128, so two distinct large integers never collide; when either is a floating-point number both are compared as f64 (so 1 == 1.0 is true). Rationale: exact integer comparison is the safe default, and dropping to f64 only when a fractional value is actually involved confines f64’s precision limit to the case that inherently needs it. JSON numbers are always finite (JSON cannot encode NaN or infinity), so an unordered f64 result cannot arise; the code treats it as false regardless, keeping eval total for any constructed value.

Structs§

Expr
A parsed, ready-to-evaluate condition expression.
ExprError
A failure to parse an expression. Its Display message is the human-readable diagnostic, and names the offending character position where one is known.
Reference
A parsed reference into a routed value: a dot-separated path with array indexing, the same path grammar the expression language uses for an operand, standing alone as a value reference rather than inside a boolean.

Constants§

MAX_EXPRESSION_LEN
The maximum length, in Unicode scalar values, of a condition expression.

Functions§

parse
Parses a condition expression, or reports why it is not well-formed.
parse_reference
Parses a bare reference path (items, output.items, results.0.items), the standalone counterpart of a path operand in the expression grammar.