Repository navigation
Configure a Lexicon by reading; its set fields are internal (#754, 4i-b) - #991
Merged
Merged
Conversation
Lexicon holds a base (the shipped index by reference, none, or a pickled release's rows), an ordered log of add/remove edits stored frozen, and the surnames, given_names and capitalization_exceptions fields. add(*words, kind, **properties) and remove(*words, kind=None) take the vocabulary files' kinds; readings_of returns the public LexiconReading; the compiled sets are internal, read through Lexicon._sets. The 2.x set spellings are refused at add/remove, the constructor and dataclasses.replace, and as an attribute read, each naming the calls for the words given. The generation, credentials and patronymic views read a word's kind from the lexicon (rules/R8, rules/R9 amended). A 2.x pickle loads onto the nearer named base and a per-word log, refused where readings cannot compile it back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, 4i-b) docs/customize.rst, locales.rst and migrate.rst (with a row per 2.x Lexicon field), the release-log bullet, LexiconReading in the API reference, AGENTS.md's layering, pickling, invariant and Gotchas paragraphs, the 4i-b decisions entry and the RFC's status. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, 4i-b) Review of 4i-b: add() accepted an agnomen reading alone, which the build's check 11 refuses in a file and which read as a credential; and _apply_choice dropped a caller's reading that a chosen file's reading clashed with, where 3b's delta raised. Both now refuse, naming the fix. decisions/R8 and R9 gain their 4i-b entries and Standing lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 4i-b) Codecov's patch check: the malformed-argument refusals, a 2.x state 3.0 readings cannot express, and the translation keeping a held suffix kind and reading the kinds a choice gives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A 2.x marker entry its base no longer holds, which 2.x kept and never
read, is dropped as inert (_lexicon._inert_dropped) on both sides of
every compare: remove(conjunctions={'e'}) and remove(titles={'air'})
translate, and their 2.8 pickles load as the removal.
- Refusals: a list's offered code names the caller's words and runs; no
'already reads' wording where a call is needed, no empty 'nearest'; a
long attaches list bounded; the agnomen refusal says family first.
- add's kind is required to a type checker; a malformed snapshot row is
refused by name; stale lexicon.titles comments in _vocab.
- The caseless-connective classify test is back, its sets built by hand.
- The choice sweep is a committed test (161 tags x 3 lexicons).
- Docs: mechanisms.md's three recipes and pointer, locales.rst's fragment
section, AGENTS.md's two attribute names, migrate.rst's agnomen order
and the limit that a 2.8 pickle keeps 2.8's vocabulary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…754, 4i-b) Codecov's project check: the refusal translations for modifiers, styles, one-case connectives, gated suffix words and honorifics, the bounded phrase list and the two no-exact-reading outcomes, which the parse no longer reaches through the index lookup it replaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…row by name (#754, 4i-b) Decided 2026-11-02: a 2.x call or pickle that took a word out of a set but left it in a marker over it reads as though the marker went with the word, for every marker set. The two readings 2.x gave such an orphan move, accepted: Sir. Smith after remove(titles={'sir'}) reads family Smith (given in 2.8.0), and John Smith, AIR after remove(titles={'air'}) reads suffix AIR (given AIR in 2.8.0). The decisions entry, the code comments, migrate.rst and the release bullet say so, and _LEFT_BEHIND pins the parses. Also: a snapshot row's tag, kind, property names and values are refused by name; add's docstring states kind is required; remove(agnomens=...) translates to the generation the gated class holds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 2, 2026
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.
Sub-phase 4i-b of #754: the public
Lexiconbreak, decided on 2026-11-02 (decisions/vocabulary-model's 2026-11-02 #754, theLexiconsurface entry). A caller configures vocabulary by reading, and the compiled sets become internal. Built on 4i-a (#989).What changes
The state.
Lexiconholds a base, an ordered edit log, and three fields:surnames,given_namesandcapitalization_exceptions._setsfield.Lexiconyet; that is 4i-c.Configuring.
add(*words, kind, **properties)andremove(*words, kind=None)take kinds and properties as the vocabulary files spell them.removealso takes a category.Querying.
readings_of(word)returns the new publicLexiconReading.The repr counts readings by kind, e.g.
Lexicon(default + given: -1).|concatenates the logs, the left one's first.The title warning (2026-11-01, the title warning entry) fires when an
addcreates or re-adds a title reading that agivenreading keeps a title only inside a phrase. The warning namesremove(w, kind="given").The build's checks 3, 4, 8, 9 and 11's agnomen clause run on each
add, and each raises naming the call that fixes it.Three refusals, each translated for the words named:
add/removewith a set keyword;Lexicon(...)anddataclasses.replacewith one;For example,
Lexicon.default().add(titles={"dean"})raises and names.remove('dean', kind='given'). A 2.x pickle is translated the same way (pickle option (b)). The 2.8 fixture'sLexicon.empty().add(titles={"dean"})loads equal toLexicon.empty().add("dean", kind="title").The views read the lexicon's kinds. These are rules/R8 (the generation and credentials views) and rules/R9 (the patronymic view), both amended.
_compiled.py:GENERATION_WORDS,CREDENTIAL_WORDSandPATRONYMIC_PARTICLES.add("secundus", kind="generation")putsSecundusin the generation view.The default stays lazy.
Lexicon.default()reads_compiled.py, andparse("John Smith")loads no index; a test holds this._parser._apply_choicekeeps the 3b delta until 4i-c retires it. It now writes the delta as readings appended to the log.test_a_choice_written_as_readings_gives_the_deltas_sets.Found by review, fixed here
Two reviews ran, one of the code and one of the design docs.
add("bisneto", kind="agnomen")with no name reading went through and read as a credential. The build's check 11 refuses that in a file, soaddnow refuses it too, naming.add(w, kind="family").parser_for(locales.CS, base=Parser(lexicon=Lexicon.default().add("st", kind="credential")))built a parser without the caller's credential. On the base, 3b's delta raised its suffix set guard for this input._apply_choicenow compiles its edits and refuses a result that falls short of the delta's sets, wrapped with the languages chosen (rules/D2). 4i-c replaces this with the decided raise that names the edit.Choices made inside the decided design
Each is recorded in the new decisions entry, and each is easy to reverse if wrong:
add/removerefusesurnamesandgiven_namestoo. The refusal nameslexicon | Lexicon(surnames={...}). The RFC's item 3 says the lists change "through|... ordataclasses.replace", and it rewrites customize.rst'sadd(surnames=...)example that way. Its migrate table, though, calls the lists "unchanged".addof a kind the word holds only off by default turns that reading on. For example,add("st", kind="generation")turns on the generation readings of the cs, hr, sk and sl files.readings_ofleaves outdefaultandcased.LexiconReading.propertiesis a tuple of sorted pairs.What 2.x could state that readings cannot
These turned up while translating the tests:
chas);may_be_initialon a caseless letter;A 2.x pickle holding any of these is refused, naming the fields. An orphan marker, a marker entry whose base set no longer holds the word (for example,
remove(titles={"air"})leavingaira modifier), reads as though the marker went with the word. This was decided on 2026-11-02 and is recorded in the decisions file. 2.x still read such an orphan in two places, and both readings change; the change is accepted:John Smith, AIRgives suffixAIR, where 2.8.0 gave givenAIR;remove(titles={"sir"}),Sir. Smithgives familySmith, where 2.8.0 gave givenSmith._LEFT_BEHINDpins both readings.A loaded 2.8 lexicon keeps the vocabulary it was pickled with. A pickled 2.8
Parser()loads asLexicon(default + credential: -23; capitalization_exceptions: -13)and readsJane Doe KCas 2.8.0 did; migrate.rst and the release bullet say so.Measured
tests/v2/test_snapshot.pyandtest_cldr_agreement.pypass unchanged.tools/perf/call_count.py --against 3804b1a1)._CALL_BASELINEis unchanged and the serial benchmark passes.Lexicon.default().add("st", kind="suffix")readsStas a post-nominal. 4b's index lookup read it as the cs file's generation, which is written off by default. This is the R8 amendment.Docs
docs/customize.rstis rewritten in reading terms:|;docs/migrate.rstgains "Lexicon fields → readings", one row per 2.x field.docs/locales.rst, the API reference (LexiconReading), the release-log bullet, nameparser/vocab/README.md, and AGENTS.md's layering, pickling, invariants and Gotchas paragraphs are updated.Verification
uv run pytestuv run pytest -n0 -m serialuv run mypyuv run ruff checksphinx-build -b htmland-b doctestpython -m doctest README.rst--python 3.15Left for later
Lexicon, and_apply_choiceretired.parser_for's wrap of a choice error re-raisestype(exc)(msg), which fails on_lexicon._MaskValueError(three-argument__init__). No shipped choice changes a mask, so nothing reaches it today.Part of #754.
🤖 Generated with Claude Code