Companion film
Parse, don’t split
Change a valid arithmetic expression to invalid syntax and watch Grid return structured rejection evidence instead of turning the parse into a model error.
47 secGrid 0.63.2
Open film page →Canonical Grid model
First-class grammars and parsers: CFG and PEG declarations, generator values, total PARSE results, and parse-tree extraction.
What this model gives you
35 min to study · Source reviewed 2026-08-26
Continue with guided practiceExpected checkpoint
With the source strings exactly as authored and the default process ceilings, lowered only by the 4,096-byte DFA generator bound. These semantic outputs follow the documented parser contract; the current verification scenario certifies compilation only.
The arithmetic grammar names tokens, whitespace, start rule, precedence layers, and a value capture. Applying LALR produces a parser value; left recursion remains grammar structure rather than a copied formula parser.
PARSE returns success or language rejection as a value. Check PARSE_RESULT_OK before projecting TREE or ERROR; a rejected sentence is not a failed workbook evaluation, while a budget breach remains a diagnostic.
Successful trees retain the original source and nested node records. PARSE_TREE_CAPTURE returns every matching node in source order, so ordinary vector and record operations can traverse the syntax without reparsing text.
The command PEG tries start, stop, and status in order and captures both action and identifier. Packrat supplies deterministic memoized parsing under the same authored work and node ceilings.
The regular identifier grammar uses a generator value whose 4,096-byte input limit can lower but never raise the host ceiling. Persisted models store grammar definitions and recipes; process-local automata are reconstructed after hydration.
MODEL "Grammar Parsing"
DESCRIPTION "First-class grammars and parsers: CFG and PEG declarations, generator values, total PARSE results, and parse-tree extraction."
VERSION "1.0.0"
AUTHOR "Grid Team"
TAGS "canonical", "grammar", "parser", "parse-tree"
# A CFG for arithmetic: unordered alternatives with named captures on numbers.
GRAMMAR arithmetic IS CFG
TOKEN NUMBER = /(?:0|[1-9][0-9]*)(?:\.[0-9]+)?/
SKIP /\s+/
START expression
expression = expression "+" term | term
term = term "*" primary | primary
primary = value:NUMBER | "(" expression ")"
END
# Applying a generator to the grammar produces a parser value.
fast = arithmetic USING LALR
# PARSE is total: a language rejection is a parse-result value, not a failure.
accepted = PARSE("2 * (3 + 4)", fast)
accepted_ok = PARSE_RESULT_OK(accepted)
tree = PARSE_RESULT_TREE(accepted)
# Successful trees retain their source and expose nested node records.
source_text = PARSE_TREE_SOURCE(tree)
root = PARSE_TREE_ROOT(tree)
root_symbol = root.symbol
child_count = VECTOR_LENGTH(root.children)
# Named captures return matching node records in source order.
numbers = PARSE_TREE_CAPTURE(tree, "value")
number_count = VECTOR_LENGTH(numbers)
number_texts = MAP(numbers, node => node.text)
# Rejection produces a structured error record with exact coordinates.
rejected = PARSE("2 + * 3", fast)
rejected_ok = PARSE_RESULT_OK(rejected)
failure = PARSE_RESULT_ERROR(rejected)
failure_offset = failure.offset
failure_found = failure.found
# A PEG command language: choice is ordered, first success wins.
GRAMMAR command IS PEG
SKIP /\s+/
START command
command = action:("start" / "stop" / "status") name:/[a-z_]+/
END
controller = command USING PACKRAT
issued = PARSE("stop feed_pump", controller)
issued_tree = PARSE_RESULT_TREE(issued)
issued_action = VECTOR_GET(PARSE_TREE_CAPTURE(issued_tree, "action"), 1).text
issued_name = VECTOR_GET(PARSE_TREE_CAPTURE(issued_tree, "name"), 1).text
# Generators are values: authored limits lower, never raise, process ceilings.
bounded = GENERATOR DFA(max_input_bytes = 4096)
GRAMMAR identifier IS REGULAR
MATCH /[A-Za-z_][A-Za-z0-9_]*/
END
checker = identifier USING bounded
identifier_ok = PARSE_RESULT_OK(PARSE("feed_pump_21", checker))
summary = `parsed={accepted_ok} numbers={number_count} action={issued_action} name={issued_name}`
END MODEL