Expand description
The when expression language — bounded DSL for gating rules on facts.
Grammar (hand-written recursive-descent; no parser combinator):
expr := or_expr
or_expr := and_expr ('or' and_expr)*
and_expr := not_expr ('and' not_expr)*
not_expr := ['not'] cmp_expr
cmp_expr := primary [cmp_op primary]
cmp_op := '==' | '!=' | '<' | '<=' | '>' | '>=' | 'in' | 'matches'
primary := literal | ident_or_call | '(' expr ')'
literal := STRING | INT | BOOL | 'null' | list
list := '[' [expr (',' expr)*] ']'
ident_or_call := NS '.' NAME ['(' [expr (',' expr)*] ')']
NS := 'facts' | 'vars' | 'iter'Design choices (all load-bearing):
- No arithmetic. Only comparison.
- Function calls limited to a fixed set on the
iternamespace.iter.has_file("Cargo.toml")is supported; arbitrary user-defined calls are not. Use declaredfacts:for repo-level computation. iter.*is only meaningful in iteration contexts (per-iterationwhen_iter:onfor_each_*, and nested rules’when:). Outside those,iter.Xevaluates tonullanditer.has_file(_)tofalse.matchesRHS must be a string literal. This lets us compile the regex at parse time; dynamic patterns stay out of the hot path.- Short-circuit
and/or. Unevaluated branches don’t even touch their subtree. - Type coercion is explicit, not silent. Comparing
InttoStringis an error, notfalse.
Structs§
- IterEnv
- Iteration context exposed to
when:expressions through theiter.*namespace. Built once per iterated entry byfor_each_*rules and threaded into both the outerwhen_iter:filter and any nested rule’swhen:. - WhenEnv