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
evolveblocks 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
decideblocks.
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.