Skip to main content

Module parser

Module parser 

Source
Expand description

Textual formula parser: "Revenue = Price * Quantity" -> Formula.

Hand-rolled tokenizer + recursive-descent parser (same shape as improv_nl_formula), for the symbolic v1 grammar in AGENT_FORMULA_LANGUAGE.md §4. Measure/category names resolve against a Model (like NlContext); unknown names are a clean ParseError, never a panic.

§Grammar (AGENT_FORMULA_LANGUAGE.md §4)

Formula      = Identifier "=" Expression ;
Expression   = OrExpr ;
OrExpr       = AndExpr { "OR" AndExpr } ;
AndExpr      = NotExpr { "AND" NotExpr } ;
NotExpr      = "NOT" NotExpr | Comparison ;        (* NOT a > b == NOT (a > b) *)
Comparison   = Concat [ CmpOp Concat ] ;          (* == <> != < <= > >= *)
Concat       = Additive { "&" Additive } ;
Additive     = Term { ("+" | "-") Term } ;
Term         = Unary { ("*" | "/") Unary } ;
Unary        = "-" Unary | Power ;
Power        = Postfix [ "^" Unary ] ;            (* right-assoc; -2^2 = -4 *)
Postfix      = Primary { "%" } ;                  (* x% = x / 100 *)
Primary      = Literal | Call | Special | MeasureRef | Local
             | "(" Expression ")" ;
Literal      = Number | Text | "TRUE" | "FALSE" | DateLiteral ;  (* #2025-01-31# *)
MeasureRef   = Name [ "[" Name { "," Name } "]" ] ;
Local        = Identifier ;                       (* bound by LET / LAMBDA *)

Call         = FuncName "(" [ Arg { Sep Arg } ] ")" ;  (* Sep: "," or ";" (de) *)
Arg          = Expression [ OverClause ] [ IntoClause ] ;
OverClause   = "OVER" ( Cats [ Window ] | "ALL" [ "EXCEPT" Cats ] )
               [ "WHERE" Expression ] ;
Window       = "[" Bound ".." Bound "]" ;          (* FIRST | LAST | THIS[+-n] *)
IntoClause   = "INTO" Cats ;                      (* a list result, row-major *)
Cats         = Name { "," Name } ;

Special      = "LET" "(" { Identifier "," Expression "," } Expression ")"
             | Lambda "(" Expression { "," Expression } ")"   (* called on the spot *)
             | "MAP" "(" Expression { "," Expression } "," Lambda ")"
             | ("SCAN" | "REDUCE") "(" Expression "," MeasureRef "OVER" Name "," Lambda ")"
             | ("BYROW" | "BYCOL") "(" MeasureRef "OVER" Name "," Lambda ")"
             | "FIXPOINT" "(" Expression "," Lambda [ "," Expression [ "," Expression ] ] ")"
             | "MAKEARRAY" "(" Name "," Name "," Lambda ")"
             | "SEQUENCE" "(" Name [ "," Expression [ "," Expression ] ] ")"
             | NamedLambda "(" [ Expression { "," Expression } ] ")"
               (* an argument for a category parameter is a category Name *) ;
Lambda       = "LAMBDA" "(" Identifier { "," Identifier } "," Expression ")" ;

Definition   = Formula
             | Identifier "=" "SQL" "(" Text ")"          (* SQL-sourced input *)
             | Identifier "=" "CALL" "(" Name { "," Name } ")"  (* external fn *)
             | Identifier "=" Lambda ;                   (* a named LAMBDA *)

§Quoted identifiers ('Unit Price')

Anywhere a measure or category name may appear, it may instead be written inside single quotes — the Quantrix spelling. This is the only way to name a measure a CSV header produced (import_csv takes the header verbatim, so Unit Price, Cost/Unit and 2024 Total are all real measure names), and the only way to name one that collides with a grammar keyword ('Over', 'SUM', 'NOT').

  • Double quotes are untouched: "..." is still a text literal, '...' is a name. They never overlap.
  • A literal ' inside a quoted name is written doubled: 'Bob''s Rate' names the measure Bob's Rate. (Quantrix/SQL convention; chosen over backslashes so the quoted form needs no escape character at all.)
  • A quoted name is never a keyword, a function, TRUE/FALSE or OVER: it resolves against the model as a measure/category name, full stop.
  • '' (empty) and an unterminated ' are clean parse errors.

§Dotted qualification (Matrix.Measure) — parsed, not yet resolvable

Quantrix qualifies a name by its owning matrix ('Defined Input & Outputs'.'Income tax rate (Corporate)'). Improv has no matrix concept yet: a measure belongs to the model, not to a matrix, so there is nothing a qualifier could name. The tokenizer and parser therefore accept the form and reject it with an error that names the unknown qualifier, instead of the old unexpected character: '.'. When multiple matrices per view land (GUI plan Step 3), Parser::parse_name is the one place resolution has to learn about.

§The = ambiguity (assignment vs. equality)

= is only the top-level assignment separator: parse_formula splits Identifier "=" ... first, then parses the RHS as an Expr. Inside an expression, equality comparison is spelled == and not-equal is <> (or !=). This keeps Revenue = Price * Quantity unambiguous while still allowing flag = Price == 10.

§Aggregation DimensionSpec convention

SUM(Revenue OVER Time) -> Call(FuncId(1), [Ref(revenue, DimensionSpec { over: [Time], by: [], except: [] })]). The compiler (engine::compiler) reads the arg ref’s over to pick the collapsed category, so only over is set. Function ids come from crate::functions (SUM is 1).

§Not implemented (deferred, per §4/§11.3)

  • (Date literals #2025-01-01# and #...T...Z# ARE supported — see parse_date_literal.)
  • Nothing else material: named scalar calls (ABS, SQRT, MIN2, …) and the whole-RHS source forms CALL(...) / SQL("...") (via parse_definition) are supported.

Structs§

FormulaText
A parsed formula: the target measure name (LHS of =) plus its expression.
LambdaText
A parsed named-LAMBDA definition.
ParseError
A formula parse failure. position is a byte offset into the source token stream’s originating text when known.

Enums§

Definition
A parsed measure definition — either an ordinary formula or a source form (SQL("...") / CALL(fn, m1, m2, ...)) that describes a host-side source rather than a differential-dataflow expression. Source forms carry the metadata a refresh consumes; they never enter the engine’s expression graph, preserving the deterministic core.

Constants§

FUNC_AVG
FUNC_MAX
FUNC_MIN
FUNC_SUM

Functions§

parse_definition
Parse a measure definition: an ordinary Target = <expr> formula, or a source form Target = SQL("...") / Target = CALL(fn, m1, m2, ...). The source forms are recognized only as the entire RHS (they are measure sources, not sub-expressions), keeping them out of the engine’s expression grammar and thus off the deterministic hot path.
parse_expr
Parse just an expression (the RHS), with no target/assignment.
parse_formula
Parse a full Target = Expression formula. The LHS identifier is the target measure name (returned as-is; it need not exist yet). The RHS is resolved against model.
scalar_func
The built-in a name calls as NAME(args...) (aggregations excluded): its id and parameter count, from crate::functions::all (case-insensitive, aliases included).