Skip to content

feat(tokenizer): M1.B — full WHATWG named-character-reference table#3

Merged
akankov merged 2 commits into
mainfrom
feature/m1b-entity-table
Jun 12, 2026
Merged

feat(tokenizer): M1.B — full WHATWG named-character-reference table#3
akankov merged 2 commits into
mainfrom
feature/m1b-entity-table

Conversation

@akankov

@akankov akankov commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • Full 2231-entry WHATWG named-entity table, generated into Internal\Tokenizer\NamedCharacterReferences by bin/generate-entities.php from the vendored resources/entities.json (canonical spec data, sha256-stamped in the file header). Output is ASCII-only (\u{…} escapes from the spec's own codepoint lists), so regeneration is byte-stable and PHP-CS-Fixer-clean.
  • Matcher rewrite for scale: CharacterReference::matchNamed() now probes candidate lengths downward (longest match, ≤31 isset() lookups per &, bounded by MAX_LENGTH = 33) instead of scanning the table — O(1) in table size, protecting the committed ≤2× parse-time budget on entity-heavy input.
  • Spec rules that only matter at full table size (WHATWG §13.2.5.73): legacy semicolon-less matches in attribute values followed by =/alphanumeric stay literal (?x=1&copy=2 keeps &copy, no error); semicolon-less matches elsewhere emit missing-semicolon-after-character-reference.
  • CREDITS.md row updated; CHANGELOG under Unreleased. Error-catalog completion is deferred to M1.B part 2, alongside the html5lib-tests conformance runner which will exercise it.

Test plan

  • New CharacterReferenceTest (9 tests): generated table ≡ vendored JSON (all 2231 entries), length bounds, multi-codepoint + ligature entries, longest-match vs legacy prefix, attribute-context literal rule, missing-semicolon error in attribute and data contexts
  • Full suite 73 tests / 4585 assertions green; round-trip fidelity fixtures unchanged
  • make ci (Docker: tests 8.3/8.4/8.5, PHPStan max, Phan, Rector, CS-Fixer) green locally

akankov added 2 commits June 11, 2026 23:18
Replace the ~30-entry seed table with the complete 2231-entry WHATWG list,
codegen'd into NamedCharacterReferences by bin/generate-entities.php from
the vendored resources/entities.json (sha256-stamped, ASCII-only output so
regeneration is byte-stable under PHP-CS-Fixer).

matchNamed() switches from a full-table scan to a longest-match probe over
descending candidate lengths — ≤31 hash lookups per '&', O(1) in table size
— so the full table doesn't blow the committed performance budget.

Also implements the entity rules that only matter at full table size:
the WHATWG §13.2.5.73 historical exception (a legacy semicolon-less match
in an attribute value followed by '=' or an alphanumeric stays literal) and
the missing-semicolon-after-character-reference parse error.
…in entity test

Rector's AddArrayFunctionClosureParamTypeRector wants the TokenizerError
type on the error-mapping closures. Phan flags the test's access to
@internal classes (no notion of a package-owned test suite) and caps
array-shape inference on the 2231-entry TABLE constant, making unsampled
keys look like invalid offsets — both suppressed file-level with the
reasoning inline.
@akankov
akankov merged commit 036214e into main Jun 12, 2026
7 checks passed
@akankov
akankov deleted the feature/m1b-entity-table branch June 12, 2026 07:16
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