Replace tree-sitter parser with nom - #1
Merged
Merged
Conversation
- All positive/negative test cases are passing. - Error reporting has not been tested yet; this is mission-critical, and the underlying justification for changing the parser substrate. - Restructured test modules.
- Fixed an ordering bug in `primary` that caused misparses in certain cases.
rand 0.10 renamed the `gen` family of methods to avoid conflicting with the `gen` keyword reserved in Rust edition 2024: - `thread_rng()` → `rand::rng()` - `Rng::gen_range()` → `Rng::random_range()` Updated all call sites in evaluator internals (`roll_range`, `roll_standard_dice`, `roll_custom_dice`) and all 22 doctests across `lib.rs`, `compiler.rs`, and `evaluator.rs`. Also resolved 4 `mismatched_lifetime_syntaxes` warnings in the parser by making elided lifetimes explicit (`Span<'_>`, `Function<'_>`, `ParseError<'_>`) where the compiler flagged inconsistent usage. cargo check, clippy, and test all pass cleanly (76/76 tests, 0 warnings).
Implement `CodeGenerator`, a crate-internal component that walks the
nom parser's AST (`ast::Function`) and emits byte-identical IR to the
existing tree-sitter compiler. This is the critical-path item for
replacing tree-sitter with nom in the compilation pipeline.
Also fix three parser bugs discovered during verification:
- Constant overflow now saturates to `i32::MAX`/`i32::MIN` instead of
failing, matching tree-sitter behavior.
- Identifiers now accept `.` and inline whitespace, matching the
tree-sitter grammar (`[\p{L}_][\p{L}\p{N}\p{Z}._-]*`).
- Exponentiation now binds tighter than unary negation (`-a^2` is
`-(a^2)`), matching tree-sitter precedence.
Fix five pre-existing broken rustdoc links across the codebase.
Replace tree-sitter parsing in compile() and compile_unoptimized() with parser::parse() followed by CodeGenerator::generate(). The old tree-sitter Compiler struct remains for now (decoupling is xdy-ddf). CompilationError gains a lifetime and carries ParseError, giving callers full diagnostic fidelity. EvaluationError updated to match. Both lose Copy (ParseError contains Vec). Fix i32::MIN boundary in the parser: add negative_overflowed_constant combinator to unary, which parses sign+digits as a unit when digits overflow positive i32. Without this, 2147483648 clamps to i32::MAX before negation, producing -2147483647 instead of i32::MIN. Add top-level justfile with verify, fmt, clippy, test, bench, doc.
Introduce a two-layer error reporting system: enhanced parser error
propagation via `cut` at commit points, and a new diagnostics module
with a fix-and-retry doctor that collects all parse errors in a single
pass.
Parser changes (errors.rs, combinators.rs):
- Add `cut` after binary operators, delimiters, d/D operator, and
`drop` keyword so errors propagate instead of being swallowed by
`many0`/`fold_many0`.
- Restructure `dice` to factor out common `dice_count d_operator`
prefix; add `drop_clause` combinator.
- Add context classifiers for closing delimiters, right operand,
and drop direction.
- Fix EOF TODO: `ErrorKind::Eof` now produces "end of input" instead
of the incorrect incomplete-function-body mapping.
Diagnostics module (diagnostics.rs):
- Structured types: Diagnostic, DiagnosticKind (11 variants),
SourceSpan, Suggestion, Placeholder, DiagnoseResult.
- Fix-and-retry doctor: parse, analyze error, apply fix, re-parse
until clean or unfixable. OffsetMap tracks position adjustments
across iterations.
- Error patterns: UnclosedDelimiter, MissingRightOperand,
MissingLeftOperand, MissingDiceFaces, IncompleteDropClause,
BareIdentifier, IncompleteParameterDefinition, TrailingInput,
EmptyExpression, UnexpectedToken, UnexpectedEof.
- Suggested fixes include complete corrected source strings and
placeholder regions with valid_kinds for UI integration.
- Missing operand diagnostics offer both insert-placeholder and
drop-operator suggestions.
- Bare identifier detection handles d/D splitting (xD6 → {x}D6
or {xD6}) and expression-context variables (4 + hello → 4 +
{hello}).
Test infrastructure:
- Data-driven test_errors.txt with 33 canonical test cases.
- read_error_test_cases() helper for parsing the test format.
- Tests: error diagnostics (exact match on kind, span, message,
all suggestions and placeholders), corrected source validity,
valid program contract, OffsetMap unit tests, performance
(2000 diagnose() calls < 1s).
Introduce ASTVisitor trait in ast module with accept() dispatch on Expression, DiceExpression, and ArithmeticExpression. Rebuild CodeGenerator as an ASTVisitor<Output=AddressingMode> implementation. Make codegen module and CodeGenerator public for power users who want to drive the parse → AST → codegen pipeline manually.
Remove tree-sitter, tree-sitter-xdy, and cc dependencies from the xdy crate. Delete the old tree-sitter Compiler struct, SyntaxTreeVisitor trait, and CanVisitSyntaxTree trait (~800 lines). Move CodeGenerator into compiler.rs and rename it to Compiler, with Compiler::compile as the primary entry point. Delete codegen.rs and the redundant test_codegen_matches_tree_sitter test. Rename all source-text lifetime parameters from 'a to 'src (and 's/'e/'r to 'src/'env/'rng in evaluate functions) for self-documenting code. Add Function, Expression, ParseError, and Span. Add "run just verify before review" guardrail to AGENTS.md. tree-sitter-xdy crate is preserved untouched.
…e constants. Parser: negative constants are now always Constant(-N), not Neg(Constant(N)). The negative_constant combinator handles all negated integer literals directly, bailing on d/D and ^ so negation remains looser than dice and exponentiation. Removed dice_after_count (no longer needed). Replaced dead code paths with unreachable!(). Simplified exponent combinator. S-expressions: added read_s_expr() reader that roundtrips perfectly with the existing writer. Variables and parameters containing whitespace are now quoted with double quotes in both directions. Fixed Constant::size_s_expr panic on i32::MIN. Tests: added test_parse.txt (93 data-driven cases) covering all expressions from test_compile_unoptimized.txt plus overflow boundaries, Unicode, embedded spaces, operator precedence, and whitespace tolerance. Replaced 6 inline boilerplate test functions with test_parse_to_s_expr and test_s_expr_roundtrip. Renamed test_errors.txt to test_parser_errors.txt.
Benchmark 316 expressions across the tree-sitter and nom parser regimes. Nom delivers 48% aggregate speedup (median 62% per expression), with 99% of cases faster. Update README performance section with findings, remove stale tree-sitter references from Safety and Planned Work sections. Fix missing #[cfg(test)] gates on error diagnostic support types in support.rs.
…diagrams. Differentiate the two READMEs: the root README is the project brochure (language features, examples, performance, planned work), while the crate README is now a technical deep-dive covering the compilation pipeline, ISA reference, VM model, optimizer internals, histogram engine, and diagnostics. Add railroad diagram generation via ebnsf: the EBNF grammar in xdy/grammar/xdy.ebnf is compiled to an SVG at build time and inlined into the parser module's Rustdoc. Add Mermaid diagrams via aquamarine to the compiler, optimizer, and evaluator Rustdoc. Fix stale tree-sitter references in the crate README (safety section, error reporting planned work). Resolve all cargo doc warnings in diagnostics.rs.
Update the Performance section of README.md with current nom parser benchmarks measured on a 2026 MacBook Pro. Remove .beads/ from Git tracking and add it to .gitignore.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replace the tree-sitter parser with a hand-written nom combinator parser, delivering a complete rewrite of the parsing layer while preserving the existing compiler backend (IR, optimizer, evaluator) byte-for-byte.
What changed
New nom parser (
parser/combinators.rs,parser/errors.rs)xDy), custom dice (xD[...]), arithmetic (+,-,*,/,%,^), grouping, drop lowest/highest, formal parameters, environmental variables, and dynamic expressions.-a^2is-(a^2)), matching tree-sitter behavior.-2147483648(i32::MIN) parses correctly via a dedicatednegative_overflowed_constantcombinator that handles the sign+digits boundary as a unit.i32::MAX/i32::MINinstead of failing..and inline whitespace, matching the tree-sitter grammar ([\p{L}_][\p{L}\p{N}\p{Z}._-]*).cutat commit points (after binary operators, delimiters,d/Doperator,dropkeyword), so errors propagate instead of being silently swallowed.Typed AST (
ast.rs)Function,Expression,DiceExpression,ArithmeticExpression,DropDirection, andSpantypes.ASTVisitortrait withaccept()dispatch on all expression types, enabling clean tree-walking passes.AST-walking compiler (
compiler.rs)Compilerwalks the nom AST and emits byte-identical IR to the old tree-sitter compiler.'ato'src(and's/'e/'rto'src/'env/'rng) for self-documenting code.Diagnostics module (
diagnostics.rs)UnclosedDelimiter,MissingRightOperand,MissingLeftOperand,MissingDiceFaces,IncompleteDropClause,BareIdentifier,IncompleteParameterDefinition,TrailingInput,EmptyExpression,UnexpectedToken,UnexpectedEof.Diagnostictype withSourceSpan,Suggestion(including complete corrected source), andPlaceholderregions withvalid_kindsfor UI integration.OffsetMaptracks position adjustments across fix-and-retry iterations.d/Dsplitting (xD6→{x}D6or{xD6}) and expression-context variables (4 + hello→4 + {hello}).S-expression support (
s_expr.rs)read_s_expr()) that roundtrips perfectly with the writer.Comprehensive test infrastructure
tests/parser.rs) covering all grammar constructs, edge cases, Unicode, whitespace tolerance, and operator precedence.tests/test_parse.txt) covering all expressions fromtest_compile_unoptimized.txtplus overflow boundaries, Unicode, embedded spaces, and whitespace tolerance.tests/test_parser_errors.txt) with exact match on diagnostic kind, span, message, suggestions, and placeholders.tests/diagnostics.rs) including corrected source validity and performance (2000diagnose()calls < 1s).compiler,diagnostics,evaluator,histogram,optimizer,parser.tree-sitter decoupling
tree-sitter,tree-sitter-xdy, andccdependencies from the xdy crate.Compilerstruct,SyntaxTreeVisitortrait, andCanVisitSyntaxTreetrait (~800 lines).tree-sitter-xdycrate preserved untouched as a standalone package.Documentation overhaul
ebnsf: EBNF grammar inxdy/grammar/xdy.ebnfcompiled to SVG at build time and inlined into parser Rustdoc.aquamarinein compiler, optimizer, and evaluator Rustdoc.# Lifetimessections on all major types.Dependency upgrades
rand0.8 → 0.10 (thread_rng()→rand::rng(),gen_range()→random_range()).criterion0.5 → 0.8.Build & tooling
justfilewithverify,fmt,clippy,test,bench,docrecipes.CompilationErrornow carriesParseErrorwith lifetime parameter for full diagnostic fidelity.EvaluationErrorupdated correspondingly. Both loseCopy(ParseError containsVec).Performance
Benchmarked across 316 expressions: nom delivers 48% aggregate speedup (median 62% per expression), with 99% of cases faster than tree-sitter.
Compatibility
compile,compile_unoptimized,evaluate) preserved with the same signatures (modulo lifetime onCompilationError).Test plan
tests/*.txt)cargo clippycleancargo testall pass