Skip to content

feat(loomgen): integrate generated line lexer skeleton#706

Merged
dowdiness merged 10 commits into
mainfrom
fix/699-line-lexer-skeleton
Jul 13, 2026
Merged

feat(loomgen): integrate generated line lexer skeleton#706
dowdiness merged 10 commits into
mainfrom
fix/699-line-lexer-skeleton

Conversation

@dowdiness

@dowdiness dowdiness commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • Emit line-mode helpers as generated_lex_<mode> and delegate through lexer_skeleton.g.mbt override points.
  • Migrate only exact generated skeleton stubs; preserve handwritten mode implementations and retain --force-lexer as the explicit overwrite path.
  • Make --regenerate-fixtures the canonical fixture generator; propagate generation failures to the CLI and enforce formatted generated-artifact drift checks in CI.
  • Add a compiled Markdown regression fixture with generated dispatch, deterministic regeneration, and output-path validation that keeps helpers and skeleton in one MoonBit package.
  • Write generated line helpers before skeleton delegates, preventing a failed helper write from installing a dangling generated call.
  • Document the generated-file contract and record the ADR.

Verification

  • moon fmt --check
  • moon check --deny-warn --target native
  • moon test --target native — 3436 passed, 0 failed
  • moon test loomgen --target native — 182 passed, 0 failed
  • moon test fixtures/line_lexer_regression --target native — 2 passed, 0 failed
  • Canonical regeneration script rewrote all six owned artifacts with git diff --exit-code clean.
  • CLI smoke test: --regenerate-fixtures exits 1 when its fixture source is unavailable.
  • Independent exact-range MoonBit reviews: PASS.

Closes #699

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Line-mode lexer generation now emits generated_lex_<mode> helpers, integrates them into lexer skeleton dispatch, preserves handwritten overrides, validates compatible output paths, regenerates fixtures through propagated errors, and adds regression coverage, CI checks, and documentation.

Changes

Line lexer integration

Layer / File(s) Summary
Generated helper and skeleton delegation contract
loomgen/emit_lexer_skeleton.mbt, loomgen/emit_line_lexer.mbt, loomgen/fixtures/line_pattern_fixture.g.mbt
Generated line-mode helpers use generated_lex_<mode> names; skeleton rendering delegates recognized line modes, while mode collection is deterministic.
CLI skeleton integration and regeneration pipeline
loomgen/main.mbt, loomgen/regenerate_fixtures.mbt
CLI preflight validates package paths and propagates line modes into skeleton creation or migration; fixture regeneration now returns and propagates errors.
Regression fixture artifacts and CI verification
fixtures/line_lexer_regression/*, .github/workflows/ci.yml
The regression fixture separates generated line-mode helpers from the public skeleton dispatcher and verifies deterministic regeneration, compilation, and native tests.
Emitter and integration regression coverage
loomgen/emit_lexer_wbtest.mbt, loomgen/regression_wbtest.mbt
Tests cover generated naming, selective delegation, exact-stub migration, handwritten override preservation, idempotence, path matching, output construction, and regeneration failures.
Documented integration contract
loomgen/README.md, docs/decisions/..., docs/superpowers/specs/..., docs/archive/completed-phases/..., docs/README.md
Documentation records helper naming, skeleton ownership, migration behavior, path constraints, validation requirements, and completed implementation evidence.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • dowdiness/loom#526 — Adds durable regeneration, compilation, and test validation for generated lexer fixtures, matching this PR’s CI coverage.

Possibly related PRs

  • dowdiness/loom#698 — Introduced the line-lexer generation pipeline and generated helper output that this PR integrates with the skeleton.
  • dowdiness/loom#553 — Established lexer skeleton dispatch behavior extended here with line-mode delegation.
  • dowdiness/loom#705 — Added the related line-lexer regression fixture setup extended here with regeneration and CI assertions.

Sequence Diagram(s)

sequenceDiagram
  participant LoomgenCLI
  participant LineLexerEmitter
  participant SkeletonIntegrator
  participant FixtureFiles
  participant CI
  LoomgenCLI->>LineLexerEmitter: preflight line modes and emit helpers
  LineLexerEmitter->>SkeletonIntegrator: provide generated_lex_<mode> names
  SkeletonIntegrator->>FixtureFiles: create or migrate lexer_skeleton.g.mbt
  FixtureFiles->>CI: regenerate and compile regression artifacts
  CI->>FixtureFiles: verify generated files and run native tests
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR satisfies #699 by adding generated helpers, skeleton delegation, handwritten override preservation, documentation, and fixture validation.
Out of Scope Changes check ✅ Passed The changes appear in scope for the line-lexer skeleton integration, documentation, fixtures, tests, and regeneration workflow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly and concisely describes the main change: integrating the generated line lexer skeleton.
Description check ✅ Passed The description matches the template well with Summary and Verification sections, plus the ADR/docs implications are covered in the bullets.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/699-line-lexer-skeleton

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
loomgen/main.mbt (1)

1023-1081: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Skeleton delegate is written before the file it delegates to — an I/O failure mid-run leaves a non-compiling package.

write_outputs(...) (line 1023) writes/integrates lexer_skeleton.g.mbt, whose lex_<mode> functions now call generated_lex_<mode> (see lines 493-499, 516-518). That helper function is only written afterward, at lines 1069-1081. If @fs.write_string_to_file for line_lexer_job fails after write_outputs already succeeded, the committed/working-tree lexer_skeleton.g.mbt ends up delegating to a function that was never written, breaking compilation — exactly the "half-written output" hazard the surrounding comments (e.g. line 897-899) explicitly try to avoid for other jobs.

Contrast with regenerate_line_lexer_regression_fixture in regenerate_fixtures.mbt (lines 82-89), which correctly writes the dependency (line_lexer_path) before the dependent (skeleton_path). The CLI path here has the order reversed.

Move the line-lexer write before the write_outputs(...) call so a write failure never leaves a dangling skeleton reference.

🔧 Proposed fix: write the line-mode lexer before write_outputs()
+  // Write the preflighted line-mode lexer (already built + validated above). `#561`
+  // Written before write_outputs() so a write failure here never leaves
+  // lexer_skeleton.g.mbt delegating to a helper that was never written.
+  match line_lexer_job {
+    Some((path, content, _)) => {
+      `@fs.write_string_to_file`(path, content) catch {
+        _ => {
+          println("Error writing " + path)
+          `@sys.exit`(1)
+          return
+        }
+      }
+      println("  " + path)
+    }
+    None => ()
+  }
+
   match
     write_outputs(
       te,
@@
     Ok(_) => ()
   }
 
   // Write the preflighted step lexer (already built + validated above). `#521`
   match lexer_job {
     ...
   }
 
-  // Write the preflighted line-mode lexer (already built + validated above). `#561`
-  match line_lexer_job {
-    Some((path, content, _)) => {
-      `@fs.write_string_to_file`(path, content) catch {
-        _ => {
-          println("Error writing " + path)
-          `@sys.exit`(1)
-          return
-        }
-      }
-      println("  " + path)
-    }
-    None => ()
-  }
-
   // Write the preflighted GrammarIr (already built + validated above). `#522`
   match grammar_ir_job {
     ...
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@loomgen/main.mbt` around lines 1023 - 1081, Move the line_lexer_job write
block before the write_outputs(...) call so the generated line-mode lexer exists
before lexer_skeleton.g.mbt is written. Preserve the existing error handling,
exit behavior, and output messages for line_lexer_job, and remove its later
duplicate block after write_outputs.
🧹 Nitpick comments (2)
loomgen/regenerate_fixtures.mbt (2)

150-186: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Deeply nested match pyramid hurts readability; consider flattening.

Five levels of nested match just to run five sequential fixture jobs and stop at the first error. A loop over an array of thunks would be flatter and easier to extend when a sixth fixture is added.

♻️ Proposed refactor using a loop over closures
fn regenerate_fixtures() -> Result[Unit, String] {
  let jobs : Array[() -> Result[Unit, String]] = [
    fn() {
      regenerate_grammar_ir_fixture(
        "loomgen/fixtures/error_node_fixture.mbt",
        "loomgen/fixtures/error_node_fixture.g.mbt", "error_node_grammar_ir",
      )
    },
    fn() {
      regenerate_grammar_ir_fixture(
        "loomgen/fixtures/rule_grammar_fixture.mbt",
        "loomgen/fixtures/rule_grammar_fixture.g.mbt", "mini_grammar_ir",
      )
    },
    fn() {
      regenerate_grammar_ir_fixture(
        "loomgen/fixtures/separated_list_fixture.mbt",
        "loomgen/fixtures/separated_list_fixture.g.mbt",
        "separated_list_grammar_ir",
      )
    },
    fn() {
      regenerate_line_pattern_fixture(
        "loomgen/fixtures/line_pattern_fixture.mbt",
        "loomgen/fixtures/line_pattern_fixture.g.mbt",
      )
    },
    fn() {
      regenerate_line_lexer_regression_fixture(
        "fixtures/line_lexer_regression/src/line_lexer_src.mbt",
        "fixtures/line_lexer_regression/line_lexer.g.mbt",
        "fixtures/line_lexer_regression/lexer_skeleton.g.mbt",
      )
    },
  ]
  for job in jobs {
    match job() {
      Err(msg) => return Err(msg)
      Ok(_) => ()
    }
  }
  Ok(())
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@loomgen/regenerate_fixtures.mbt` around lines 150 - 186, Refactor
regenerate_fixtures to replace the nested match chain with an ordered array of
zero-argument jobs and a loop. Execute each fixture regeneration sequentially,
immediately return the first Err message, and return Ok(()) only after all five
jobs succeed; preserve the existing fixture arguments and execution order.

7-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded source filename in error messages instead of a parameter.

Unlike regenerate_grammar_ir_fixture and regenerate_line_pattern_fixture (which format errors with the source_path parameter they receive), build_line_lexer_regression_outputs hardcodes "line_lexer_src.mbt" in its error strings (lines 21, 24) even though it has no source_path parameter to keep it in sync. If the fixture's source path ever changes, these messages go stale silently.

♻️ Proposed fix: thread source_path through
 fn build_line_lexer_regression_outputs(
   source : String,
   existing_skeleton : String,
+  source_path : String,
 ) -> Result[(String, String), String] {
   let annotations = match parse_annotations(source) {
     Ok(annotations) => annotations
     Err(msg) => return Err(msg)
   }
   guard annotations.token_enum is Some(token_enum) else {
-    return Err("line_lexer_src.mbt has no `#loom.token` enum")
+    return Err(source_path + " has no `#loom.token` enum")
   }
   guard annotations.term_enum is Some(term_enum) else {
-    return Err("line_lexer_src.mbt has no `#loom.term` enum")
+    return Err(source_path + " has no `#loom.term` enum")
   }

(and update the call site in regenerate_line_lexer_regression_fixture to pass source_path)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@loomgen/regenerate_fixtures.mbt` around lines 7 - 44, Thread a source_path
parameter through build_line_lexer_regression_outputs and update
regenerate_line_lexer_regression_fixture to pass it. Use source_path when
constructing the missing token-enum and term-enum error messages, matching the
parameterized error handling in the other fixture builders.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@loomgen/main.mbt`:
- Around line 1023-1081: Move the line_lexer_job write block before the
write_outputs(...) call so the generated line-mode lexer exists before
lexer_skeleton.g.mbt is written. Preserve the existing error handling, exit
behavior, and output messages for line_lexer_job, and remove its later duplicate
block after write_outputs.

---

Nitpick comments:
In `@loomgen/regenerate_fixtures.mbt`:
- Around line 150-186: Refactor regenerate_fixtures to replace the nested match
chain with an ordered array of zero-argument jobs and a loop. Execute each
fixture regeneration sequentially, immediately return the first Err message, and
return Ok(()) only after all five jobs succeed; preserve the existing fixture
arguments and execution order.
- Around line 7-44: Thread a source_path parameter through
build_line_lexer_regression_outputs and update
regenerate_line_lexer_regression_fixture to pass it. Use source_path when
constructing the missing token-enum and term-enum error messages, matching the
parameterized error handling in the other fixture builders.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c96b8654-110d-44c9-ba32-c39fac745d0b

📥 Commits

Reviewing files that changed from the base of the PR and between 6932b71 and 92bcd7d.

📒 Files selected for processing (17)
  • .github/workflows/ci.yml
  • docs/README.md
  • docs/archive/completed-phases/2026-07-13-line-lexer-skeleton-integration.md
  • docs/decisions/2026-07-13-line-lexer-skeleton-integration.md
  • docs/superpowers/specs/2026-07-13-line-lexer-skeleton-design.md
  • fixtures/line_lexer_regression/lexer_skeleton.g.mbt
  • fixtures/line_lexer_regression/line_lexer.g.mbt
  • fixtures/line_lexer_regression/line_lexer_support.mbt
  • fixtures/line_lexer_regression/regenerate.sh
  • loomgen/README.md
  • loomgen/emit_lexer_skeleton.mbt
  • loomgen/emit_lexer_wbtest.mbt
  • loomgen/emit_line_lexer.mbt
  • loomgen/fixtures/line_pattern_fixture.g.mbt
  • loomgen/main.mbt
  • loomgen/regenerate_fixtures.mbt
  • loomgen/regression_wbtest.mbt
💤 Files with no reviewable changes (1)
  • fixtures/line_lexer_regression/line_lexer_support.mbt

@dowdiness dowdiness merged commit 285c848 into main Jul 13, 2026
38 checks passed
@dowdiness dowdiness deleted the fix/699-line-lexer-skeleton branch July 13, 2026 03:37
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.

loomgen: generated line-mode lexer needs integration path with lexer skeleton (#561 follow-up)

1 participant