Skip to content

Homogenize the QVR grammar surface and parse through the vendored grammar#47

Merged
aaronstevenwhite merged 35 commits into
mainfrom
feat/dsl-grammar-ergonomics
Jul 14, 2026
Merged

Homogenize the QVR grammar surface and parse through the vendored grammar#47
aaronstevenwhite merged 35 commits into
mainfrom
feat/dsl-grammar-ergonomics

Conversation

@aaronstevenwhite

Copy link
Copy Markdown
Contributor

Summary

Homogenizes the QVR grammar surface so malformed input can no longer be silently reinterpreted, sands down the ergonomic sharp edges the grammar audit surfaced, and moves parsing onto the grammar now vendored in panproto-grammars-all.

Motivation

A deep audit of the grammar and compiler found that the surface was homogenized but its boundaries were sharp: tree-sitter error recovery plus lenient AST walkers would rewrite malformed input into a different valid model (a dropped minus sign, a vanished sample step) instead of rejecting it; misplaced or misspelled options were silently ignored; roughly half the language had no end-to-end coverage; and the vendored grammar lagged head-of-tree, forcing a dev-only local-grammar override. This PR fixes all of that.

Refs panproto/panproto#213 (grammar revendored), panproto/panproto#214 (walker now surfaces MISSING anonymous tokens).

Changes

  • grammars/qvr/ (grammar): optional option blocks with a kernel-default role; brace-delimited constructor options (Real 1 {low=-1.0, high=1.0}), which makes the grammar conflict-free; signed and scientific number literals; a single |- turnstile; bundle/decoder headers use :; composition level moves into the option block; define binds top-level morphism expressions; plural-name object/morphism/lexicon declarations; parenthesized sample/observe tuples; keyword-led encoder op rules; the nine algebra-tagged compose operators removed.
  • dsl/parser/ (hardening): parsing rejects any ERROR or missing node anywhere in the tree with the offending token's line and column; walkers cover the new node shapes; slash direction is read from the grammar field; bracket indices must be bare identifiers.
  • dsl/compiler/: closed per-declaration option-key sets with did-you-mean suggestions; kernel-default role with a clear error on a deterministic initializer; plural-name expansion; the cut operators removed from dispatch.
  • dsl/emit.py: a complete source emitter (every AST node) with a parse -> emit -> parse canonical-fixed-point suite; strings emit verbatim.
  • cli/migrations/: a v0.14.0 -> v0.15.0 hop migrates every repository .qvr file and fenced doc block; the CLI reports an unmigratable source and exits non-zero.
  • stochastic/: the FIFO agenda merges pending contributions via the semiring's plus (cyclic deductions converge in linearly many pops); bounded rules carry a joint sub-stochastic cap.
  • Packaging: parsing goes through the qvr grammar vendored in panproto-grammars-all; the floor moves to panproto>=0.58.0, and the QVR_USE_LOCAL_GRAMMAR dev override is gone.
  • Examples, tooling, docs: four new gallery examples with end-to-end tests (schema-bundled chart parser, bilinear tensor contraction, term autoencoder, parametric partial pooling); the tree-sitter corpus, editors, and Pygments lexer track the surface; the semantics, guide, and tutorial prose, the README, and the contributor guide describe the current pipeline.

API impact

  • Breaking change. Migration notes: the .qvr surface changes across the items above. qvr migrate --from v0.14.0 --to v0.15.0 <paths> rewrites sources automatically with byte-preserving span edits; every repository source and fenced doc block is already migrated. The compose operators >=>, *>, ~>, ||>, ?>, &&>, +>, $>, %> have no rewrite and must be expressed via .change_base(...) or a composition declaration.

Tests

  • pytest -x passes (2560 passed, via the standard vendored-grammar path).
  • pyright src/quivers is clean (0 errors; the remaining warnings are the established didactic / mixin-MRO baseline).
  • ruff check and ruff format --check pass.
  • New tests added for new behaviour (diagnostics regression suite, per-construct end-to-end suites, emit round-trip, review-driven regressions).
  • Tree-sitter fixtures updated (the corpus is rewritten for the current surface; 50/50).

Documentation

  • Docstrings updated for changed public API.
  • User-facing pages under docs/ updated (grammar reference, guides, tutorials, examples, semantics, getting-started).
  • docs/developer/changelog.md has an entry for this change (0.15.0).
  • Denotational semantics under docs/semantics/ updated (composition operators, typing, expressions, composition rules).

Checklist

  • Commits are focused and have descriptive messages.
  • No backward-compatibility shims added (pre-1.0; the migration hop is the clean path forward).
  • Comments describe the code as it stands; none reference prior states or removed code.
  • No secrets, credentials, or large binary artefacts in the diff.

Grammar: signed and scientific number literals; optional option blocks
on morphism and contraction declarations; brace-delimited constructor
options; single |- turnstile for rules and deductions; bundle and
decoder headers use ':'; composition level moves into the option
block; top-level 'define' binds morphism expressions; plural-name
object/morphism/lexicon declarations; parenthesized variable
patterns for sample and observe; keyword-led encoder op rules;
unused compose operators removed. The grammar is now conflict-free.

AST: MorphismDecl/ObjectDecl carry names tuples; LexiconEntry carries
words; DefineDecl replaces the top-level let node; ObserveStep shares
the SampleStep pattern shape; constructor kwargs carry typed values.
Parsing now fails loudly on any ERROR or zero-width vertex anywhere in
the tree, with the innermost damaged span's line, col, and snippet.
Registry-level failures re-raise as ParseError; a program body without
a return step names the missing step instead of leaking an internal
message. Walkers cover plural names, define bindings with where
blocks, brace constructor options with typed keyword values, signed
and scientific option numbers, shared sample/observe patterns, option-
sourced composition levels, and multi-word lexicon entries.
module_to_source covers all statement, expression, let-arithmetic,
step, and structural node kinds; compiler-only IR without a surface
form raises a typed EmitError. The round-trip suite asserts a
canonical fixed point over every repository .qvr file and fenced doc
block.
Morphism and define tables expand plural declarations per name;
reverse composition expands right-to-left (the operator was previously
ignored when flattening chains); step arguments cross the transpile
boundary as the typed DrawArg union; ResolvedDist is a didactic model.
Fifty corpus cases cover every declaration, step, expression, and
comment form; editor indent and highlight rules, the pygments lexer,
and the package quick-start snippet match the shipped grammar.
Adds the adjacent-pair migrator for the homogenized surface (turnstile,
bundle and decoder headers, composition level option, define bindings,
parenthesized patterns, brace constructor options, keyword-led encoder
op rules) with span-preserving edits, registers it on the migration
chain, and rebuilds the grammar VCS store and parser snapshots. All
repository .qvr sources and fenced doc blocks are migrated; the
regression fixture drops a stray colon its old tolerant parse had
swallowed silently.
Every declaration and step kind validates its option keys against an
explicit set, with did-you-mean suggestions at the offending entry's
own position and a braces hint when a constructor key lands on a
declaration. A morphism without role= is a kernel; every other role
is explicit. Plural-name morphism and object declarations expand to
independent parameters; lexicon entries expand per word. Top-level
define bindings compile as the old value bindings did; observe takes
a single variable with a clear arity error; the algebra-tagged
composition operators are gone from expression dispatch in favor of
change_base and composition declarations.
Per-name definition targets and document symbols for plural
declarations; define where-blocks nest as child symbols; diagnostics
extract the parser's line and col contract exactly; formatting calls
the complete emitter directly. The compiler composes << as the
reversed pipeline of >>. Five example sources drop their inferable
role options.
Every role=kernel option across the example and benchmark corpus is
gone (kernel is the default); textually identical consecutive
declarations merge into plural-name families; markdown twins stay in
sync with their sources. All fifty corpus files parse, compile, and
round-trip through the emitter.
…rface

Where-bound names no longer leak into the module namespace after the
outer binding compiles. New end-to-end coverage: a term-autoencoder
example exercising the signature, encoder, decoder, and loss flow
with keyword-led op rules and doc comments; tests for file-loaded and
multi-word lexicons, curry and trace methods, doc-comment attachment,
and from_data tensors flowing through inference.
The parser() and chart_fold() walkers read their argument keywords
from anonymous-token field constraints; previously every argument was
dropped and no surface form of either expression could compile. A
schema-bundled categorial chart parser joins the example gallery with
end-to-end tests covering pattern schemas, bundle splicing, forward
scoring with gradients, and the unknown-rule diagnostic.
A ternary contraction joins predicate and argument embeddings through
a third-order interaction kernel, exercising operadic wiring that no
chain of binary composition expresses. Tests cover einsum-spec
inference, forward equivalence with the reference contraction,
gradient flow, and the unknown-rule and did-you-mean diagnostics.
A parametric program template instantiated at two spreads exercises
typed program parameters, labeled return tuples, score steps, and
export selection, with tests asserting the bound parameter value
changes the forward result and the score term moves log_joint by its
exact analytic value. A Program without an exported morphism now
raises a typed error naming its templates and the instantiation call
instead of an attribute error swallowed by the module getattr.
The formula compiler builds ObjectDecl and ObserveStep values
directly; didactic validation rejected the removed singular fields,
which broke every fit() call and the analysis-pipelines tutorial.
…suite

Embedded QVR snippets across the suite move to the current grammar;
AST assertions read plural names, define bindings, and observe vars;
cut-operator dispatch tests become declared-algebra composition tests
with a positive reversed-composition case; the local-grammar skip
gates are gone and CI builds the in-tree grammar unconditionally. A
new diagnostics suite pins the rejected-input catalogue, message
quality, and line accuracy.
… REPL

Plate graphs, chain shapes, scope graphs, and the REPL's declaration
lookup, statement replacement, and step renderers all consume the
plural-name and vars-tuple AST shapes; observe sites no longer vanish
from scope graphs or render with placeholder names.
…ded rules

The FIFO agenda merges contributions pushed for an already-pending
item through the semiring's plus, so contractive cycles reach their
tolerance fixed point with at most one queue entry per item per
wavefront instead of geometrically many. Bounded rule weights carry a
joint sub-stochastic cap of one over the deduction's bounded-rule
count, keeping interlocking cycles contractive for every parameter
value.
…n prose

Rewrites the composition-operator sections of the typing and
expressions references to the two shipped operators, redirecting
cross-algebra composition to change_base; converts composition-level,
bundle, decoder, object, rule-turnstile, and top-level binding
references throughout the guides, tutorials, and remaining example
prose to the current surface.
Kernel is the default role, so the example, guide, and tutorial prose
no longer writes it explicitly; the grammar-summary EBNF in the DSL
overview and grammar reference now shows define bindings, brace
constructor options, the level option, the single turnstile, and the
colon-headed bundle and decoder forms.
…d bracket indices

The object-slash walker read its direction from the grammar's
direction field instead of scanning source bytes, so a comment
carrying a backslash between the operator and its right operand no
longer flips the residuation direction. A bracket index that is not a
bare identifier now raises at parse time instead of silently dropping
to an empty index tuple.
A role-less morphism defaults to a kernel; a deterministic
'~ <expression>' initializer on one now raises with the fixing roles
instead of silently discarding the initializer and building a random
kernel.
String inner text is stored verbatim by the parser, so the emitter
wraps it without re-escaping, keeping the round-trip a fixed point for
strings that contain backslashes or quotes. An index draw argument
with no indices raises rather than emitting the unparseable 'name[]'.
The per-file migrate loop catches MigrationError and exits 2 with a
message instead of an uncaught traceback, and the per-decl parse gate
raises MigrationError so callers can handle it uniformly.
…rride

The QVR grammar is vendored in panproto-grammars-all, so parsing goes
through the standard panproto registry. The QVR_USE_LOCAL_GRAMMAR
override and its env gate are gone; the grammar-build helpers the
Pygments lexer needs move to _grammar_build.py.
…butor guide

The README requirements and feature bullets, the CONTRIBUTING DSL
pipeline and distribution-family sections, and the architecture module
map now describe the current tree-sitter-via-panproto parser, didactic
AST models, and the 0.58.0 dependency floor.
@aaronstevenwhite
aaronstevenwhite merged commit b589d94 into main Jul 14, 2026
3 checks passed
@aaronstevenwhite
aaronstevenwhite deleted the feat/dsl-grammar-ergonomics branch July 14, 2026 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant