Skip to content

fix(executor): constraints compile in class context — charset aliases resolve per-character - #124

Merged
ronaldtse merged 1 commit into
mainfrom
fix/executor-constraints
Nov 8, 2026
Merged

ronaldtse merged 1 commit into
mainfrom
fix/executor-constraints

Conversation

@ronaldtse

Copy link
Copy Markdown
Contributor

Summary

Contextual (before/after-constrained) rules were silently inert whenever they referenced an imported charset alias — alalc-ell-Grek-Latn-2010's medial γκ rules never fired, so TS rendered "gk" where the map's embedded tests and the RS runtime render "nk" (5 golden-capture divergences).

Root cause

compileItem had a single alias-expansion context. An imported charset alias resolving to a plain string (dependency map unicode's greek, ~400 codepoints) compiled via the string case to regexpEscape(<whole charset>) — a literal sequence. In a constraint position that sequence became the lookbehind (?<=(?:<400 chars…>)), which no input can ever satisfy, so rules like

sub { from "γ" to "n"
      before any(greek)
      not_before boundary
      after any("κΚ") + any(greek)
      not_after any("κΚ") + boundary }

never matched and the unconstrained parallel γ → g won.

Ruby/RS resolve aliases in two contexts (RS builder.rs: materialize_class vs materialize; convert_class_alias): constraints and any() arguments are CLASS context, where an imported Item::Str charset expands one entry per character; from/to stay sequence context; a local string alias stays a sequence even in class context. The fix mirrors exactly that with a classCtx parameter on compileItem; the executor's constraint compile sites pass class context, any() class-resolves its alias arguments, capture innards and group parts stay sequence-resolved.

Fix

  • src/runtime/compile-item.ts: compileItem(item, ctx, classCtx = false); imported charset alias in class position → per-character set (stringCharset); any() arguments class-resolve alias entries; capture innards forced to sequence context (RS parity).
  • src/runtime/executor.ts: executeSubRule and the parallel megaregexp path compile before/not_before/after/not_after with classCtx = true.

Verification

  • Golden delta (pre-fix, measured on a worktree at ff173b0): exactly 5 divergent rows, all on alalc-ell-Grek-Latn-2010 — γκέγκε gkegke→gkenke, Λαγκαδάς Lagkadas→Lankadas, Παγκράτι Pagkrati→Pankrati, Κονγκό Kongko→Konnko, Νταγκεστάν Ḏagkestan→Ḏankestan.
  • Golden re-run (post-fix): 11,048 / 11,048 rows byte-identical — no other map's output changes (the bug sweep is the sweep itself: zero deltas elsewhere).
  • RS side untouched: cargo test --release --test golden_capture passes; the rs crate and maps corpus were only read.
  • Full suite: vitest run — 310 passed / 4 skipped (pre-existing), 0 failed. npm run lint, npm run format:check, npm run build all clean.

New specs (9 tests)

  • test/golden-isc-wf0.test.ts — TS mirror of the RS golden_capture additivity gate: replays all 11,048 rows, requires byte-identical output (skips without the sibling interscript-rs/maps checkouts).
  • test/isc/constraints.test.ts — per-shape regression units, each verified to fail pre-fix: imported charset in class position (root-cause shape), from any(charset) per-char, to any(charset) nth_string, sum constraint with charset part, boundary-sum word-final protection, before+after combined with capital-vs-small variants, local-alias sequence preservation, plus the five ell rows (and Χονγκ Κονγκ / Γκαμπόν guards) through the full path.

Test plan

  • npx vitest run — full suite green (310 passed / 4 skipped)
  • npx vitest run test/golden-isc-wf0.test.ts test/isc/constraints.test.ts — 9/9 with sibling checkouts present
  • Pre-fix baseline via worktree: golden sweep fails on exactly the 5 ell rows; constraint units fail 6/8
  • cd ../interscript-rs && cargo test --release --test golden_capture — rust additivity gate green
  • npm run lint && npm run format:check && npm run build

…t aliases resolve per-character

Root cause of the alalc-ell-Grek-Latn-2010 divergences (medial γκ
rendered "gk" instead of "nk"): compileItem had a single alias
expansion context, so an imported charset alias resolving to a plain
string (unicode's greek, ~400 codepoints) compiled to
regexpEscape(<whole string>) — a literal sequence. In a constraint
position that sequence became a lookbehind/lookahead no input could
ever satisfy, silently disabling every rule built on it: the map's
before any(greek) ... after any("κΚ") + any(greek) γ→n rules never
fired and the unconstrained parallel γ→g won.

Ruby/RS resolve aliases in two contexts (RS builder.rs
materialize_class vs materialize, convert_class_alias): constraints
and any() arguments are CLASS context, where an imported Item::Str
charset expands one entry per character; from/to stay sequence
context; local string aliases stay sequences even in class context.
This port mirrors that with a classCtx parameter on compileItem:
constraint compilation sites (executeSubRule + parallel megaregexp)
pass class context, the any() case class-resolves its alias arguments,
capture innards and group parts stay sequence-resolved.

Golden capture (interscript-rs tests/golden/isc-wf0.jsonl): pre-fix
exactly 5 divergent rows, all on alalc-ell-Grek-Latn-2010
(γκέγκε, Λαγκαδάς, Παγκράτι, Κονγκό, Νταγκεστάν); post-fix all
11,048 rows byte-identical — no other map's output changes.

New specs:
- test/golden-isc-wf0.test.ts — the TS mirror of the RS
  golden_capture additivity gate (skips without sibling checkouts);
- test/isc/constraints.test.ts — per-shape regression units (imported
  charset in class position, sum constraints, before+after combined,
  capital variants, local-alias sequence preservation, nth_string
  replacement) plus the five ell rows through the full path.
@ronaldtse
ronaldtse merged commit c2b3e39 into main Nov 8, 2026
9 checks passed
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