refactor(#397): one call answers both sides of a link, and the clause's core claim is measured - #537
Merged
Merged
Conversation
…'s core claim is measured A connective is placed to join only where a name word stands on EACH side of it, so neither caller ever wanted one side's answer: `_name_word_beside(k, step, ...)` was asked once per side and the two answers ANDed, in `_group_segment`'s `frozen` loop and again in `_link_joins_inside_the_clause`. It is `_between_name_words(k, lo, hi, ...)` now and answers for both, so `step` and its ternary are gone and a generational connective costs one Python frame where it cost two. The left arm still short-circuits the right, and the neighbour arrays, the two bounds and the two piece tests are the same tests in the same order. FRAMES, py3.11, through tests/v2/test_benchmark.py's own `_frames_for` shape, b9ed142 -> 9fd8446 -> here: `Josep Carod i Rovira` 311 -> 314 -> 313, `Josep Lluis Carod i III` 377 -> 381 -> 380, `Jane Doe nee Puig i Soler` 315 -> 320 -> 319, `John Quincy Adams i MA Prof.` 438 -> 443 -> 442, and the clause guard's own pair 1,125 -> 875 -> 859 at sixteen links and 6,741 -> 2,651 -> 2,587 at sixty-four. Unmoved: `tools/perf/call_count.py` (parse=406.00 facade=443.00), `John Smith` 172, `Smith, John` 203, `Juan Garcia y Lopez` 291, `John and Jane Smith` 293, `Jane Doe nee Smith` 245, `Jane Doe nee Smith PhD` 340. TIMING is unmoved too: `"Josep " + "i " * n + "Rovira"` and `"Jane Doe nee Puig " + "i " * n + "Soler"` both read 1.96-2.02x per doubling from n=200 to n=1,600, 11.36ms and 9.39ms at the top end against 11.58ms and 9.58ms at 9fd8446. Byte-identical over 3,931,700 parses -- 157,188 names (corpus, cases.py, the property grids and a run-heavy generated set) under six lexicons and four policies, plus the facade surface and the #528 no-parse paths -- all twenty-six digest rows share their sha256 and the two dumps compare equal. THE FOLD BOUGHT A TEST. Per side, a mutation of the suffix or the title test hit both sides at once and the left-hand rows killed it; folded, the two sides mutate independently and both right-hand tests survived the whole suite, their own rows standing at the END of the name where `hi` refuses them first. test_a_credential_or_honorific_mid_name_on_the_right_too is the mid-name pair that kills them, and every arm of the folded predicate now dies by a named test. THE CLAUSE'S DELIMITER-CORE CLAIM IS REACHABLE AND TRUE, and it is not the whole story. Measured over corpus u cases.py u the property grids u a 50,925-name generated set with cores, under thirteen core-bearing policies: 25,536 of 596,392 maiden takes had a core standing between the marker run and `lo`, where the bound refuses it. PAST `lo` a core is not below it, is an ordinary index to `_run_neighbours` (which steps over connectives and nothing else), and DOES pass for the name word beside a link -- asked about a core in 51,072 of 900,023 calls, answering differently from a core-skipping reading in 8,094 parses over 1,278 texts, 1,824 of them moving `maiden` on 288 texts. None is a corpus or cases.py name and none is reachable at the default policy. Both readings are pinned by unit tests and the comment now says what happens; the repair threads `cores` through three call sites into `_run_neighbours` and is its own change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #537 +/- ##
=======================================
Coverage 98.78% 98.78%
=======================================
Files 45 45
Lines 3633 3636 +3
=======================================
+ Hits 3589 3592 +3
Misses 44 44 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… and a link's frame cost is pinned rules.md#M2 gives the link exception "a connective standing between two name words of the clause", and a delimiter core the caller declared through extra_suffix_delimiters is structure rather than a name word. Past the clause's first word one passes for that word anyway, so `Smith, John, PhD née Puig Mr. - i Soler` under a configured ' - ' reads maiden 'Puig Mr. i Soler' where its separator-less twin stops at 'Puig Mr.'. The preamble says a mismatch is classified, never defaulted, so the shape lands in M2 as a `deviates: #538` example rather than as prose: the runner asserts today's reading and the marker leaves in the PR that repairs it. The annotation needs a policy the registry had no entry for, so tests/v2/rules_doc.py gains `extra_suffix_delimiters-dash`, named after the Policy field and carrying the delimiter because the field holds a set, not a flag. A new dated bullet under decisions.md#M2 carries the measurement and names #538 as the open repair. The example's string enters corpus_rules.jsonl, where the gate parses it with the DEFAULT facade -- no delimiter declared, so #538's reading is off that path entirely and the name moves for the 2026-09-20 link fix instead. Classified as that at all five baselines: added to the fix(#397) clause-link alternation at the four 2.x ledgers (suffix 'PhD i Soler' -> 'PhD', maiden 'Puig Mr. -' -> 'Puig Mr. - i Soler', identical at each), and given its own rule at 1.4.0, where v1 had no maiden markers and read the whole suffix-comma tail as one suffix. Four _CORPUS_CLAIMS rows move, each by exactly one name, verified name by name; all five gates end 0 unexplained with radar counts unchanged. A LINK'S ABSOLUTE COST WAS UNPINNED. The clause-run guard bounds a ratio, and one more frame per link moves both of its ends -- so re-splitting the fold this branch just landed leaves it green. Measured: a copy of this tree carrying `git archive 9fd8446 nameparser` in place of its own runs 9,652 passed / 324 skipped / 4 xfailed with a single failure, the new `test_a_link_costs_what_it_is_pinned_at`. The pin is `_clause_run(64)` at 2,587 frames on py3.11 in a +-2% band, keyed by interpreter like `_CALL_BASELINE`; the 16-link end is not pinned because 2% of 859 is 17.2 and the regression is 16. An unmeasured interpreter skips rather than fails, and the reviewer's 3.12/3.13/3.14 figures are recorded in the comment rather than in the table, having no run here behind them. THREE COMMENT FIXES, all re-measured. _group.py said 'PhD née - i Jones' "under a configured ' - ' declines"; it does not -- with no comma there is no tail segment, so the dash is an ordinary word at either policy, and the text that declines is `Smith, John, PhD née - i Jones`. _group.py and the 2026-09-21 P3 bullet said the earlier run-of-64 pair was py3.11 and only the short names were not; measured, b9ed142 reads 6,741 on the nose while 6048eb5 reads 2,651 against the 2,652 recorded, so the splice ran through a single arrow. test_benchmark.py's unmeasured "one frame above that again" now carries the re-reading. The P3 bullet also points at the two dated entries that still spell the predicate `_name_word_beside`, so a grep closes the trail. Renames, both about what a name claims: the rough-edge pin is test_a_core_beside_a_link_wrongly_passes_for_a_word_until_538 with a docstring saying to update it when #538 lands, and test_properties.py's independent per-side mirror is `_name_word_on_the_side` -- its logic untouched, its name no longer the implementation's. No parser behavior change: `uv run python tools/perf/call_count.py` reads parse=406.00 facade=443.00, _group.py stays at 100% coverage, and the suite is 9,653 passed / 324 skipped / 4 xfailed against a 9,649 baseline (+1 doc example, +1 benchmark test, +2 corpus-parametrized rows the new name adds). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
The short follow-up agreed at the close of #536 (#397, #461). No behavior change at any default or documented configuration.
One call answers both sides of a link
_name_word_beside(k, step, …)was called twice per generational connective, once per side._between_name_words(k, lo, hi, …)answers both sides in one call: the left arm first, short-circuiting, then the right.stepand its ternary are gone, and both callers (the join'sfrozenset and the maiden clause's link exception) make one call.Josep Carod i Rovira314 → 313,Jane Doe nee Puig i Soler320 → 319, a clause run of 64 links 2,651 → 2,587. Reference frames 406.00 / 443.00 and every name without such a connective unchanged. Run shapes still double at ~2×._group.py,test_benchmark.pyanddecisions.mdre-measured on one interpreter and updated; the landed### P3bullet is amended by a dated bullet, not edited.The clause's delimiter-core claim, measured
_maiden_take's comment said a delimiter core between the marker and the clause's first word cannot pass for a name word. Measured over 802,282 parses under 13 core-bearing policies: reachable and true (25,536 of 596,392 maiden takes had such a core; the bound refuses it). Both readings are now pinned by unit tests and the comment states what happens.One thing found and reported, not fixed: past the clause's first word, a core is an ordinary index to the neighbour arrays and can pass as the name word beside a link —
Smith, John, PhD née Puig Mr. - i Solerunderextra_suffix_delimiters=(" - ",)reads maidenPuig Mr. i Soler, where the same name without the core stops the clause atPuig Mr.. Unreachable at the default policy (extra_suffix_delimitersis empty), 0 corpus or case-row names; the repair threadscoresthrough three call sites into_run_neighbours. Filed as #538; recorded in rules.md#M2 as adeviates: #538example line and a dated decisions bullet, pinned as it reads today by a test named for it.Review round
Three reviewers (code, tests, comments): two independent oracles found no diff and every mutation claim held. The round put the deviation on the record (#538), corrected two comment claims, added a banded pin on the absolute frame cost of a link-bearing name (fails on the pre-fold code), and renamed the property test's independent model so it is obviously not the implementation.
Verification
Suite 9653 passed / 324 skipped / 4 xfailed; mypy, ruff clean; coverage 100% on
_group.py; sphinx doctest 255 / 0; five differential gates 0 unexplained (one new example line, classified at every baseline).🤖 Generated with Claude Code