Skip to content

ADR-013: Replace ExpressionFragment with Operator-Precedence Expression Grammar

Context

The initial grammar represented guard and assignment expressions as ExpressionFragment — an opaque token sequence that greedily consumed all following tokens until a structural keyword. This made multi-field evolve blocks unparseable: the parser could not distinguish “end of expression, start of next field” from “continuation of current expression.”

Example failure with ExpressionFragment:

evolve(Unregistered, Registered) -> Active {
email: email
name: name // "name" consumed as part of email's expression
}

The deferred item “Expression grammar for evolve assignments” in packages/language/specs/parsing.allium identified this as a known gap. The expression grammar in ADR-013 resolves it. Commit d5d7b17 contains the implementation.

Decision

Replace ExpressionFragment with a proper operator-precedence expression grammar using standard precedence levels: comparison, additive, multiplicative, unary, primary. Each level is a discrete Langium rule, giving the parser explicit expression boundaries. Match expressions inside decide are supported via a dedicated MatchExpression rule at the primary level.

Consequences

Positive

  • Multi-field evolve blocks parse correctly; the field name on the next line terminates the current expression.
  • Unblocks semantic analysis of guard expressions (required for ADR-015 and guard consistency checking).
  • Unblocks postcondition derivability checking, which must inspect evolve assignment right-hand sides.
  • Enables match expression support inside decide blocks.

Negative

  • The grammar is more complex; the single opaque rule became approximately five precedence rules.
  • Parser tests required updating to reflect the richer AST shape.
  • Downstream consumers (validators, generators) must handle the full expression tree rather than an opaque string.