html: tokenizer and character references of std/html (step 1 of #79) #439

Closed
opened 2026-10-23 16:10:28 +00:00 by orion · 2 comments
Collaborator

Context

Step 1 of #79 (std/html, the document side): the WHATWG tokenizer and the character references, as the first modules of the new package std/html. Nothing builds a tree yet; the tokens are what step 2 (tree construction) consumes. The tokenizer is also what WEB.md section 4.3 says the checker would use to check static parts of an Html literal (#108), and what Html.sanitized of #93 builds on through the later steps.

Source

  • ROADMAP.md, format versions: HTML is "the parsing algorithm of the WHATWG standard, which also reads older documents"; no separate HTML 4 or XHTML parser.
  • ENCODING.md section 9: an HTML page is a document format, "a tree type as a value in its own package and ordinary functions over it"; no HtmlEncode/HtmlDecode.
  • The WHATWG HTML Standard, section 13.2.5 (tokenization) and the named character references table (entities.json).
  • std/xml (std/xml/src/scanner.trb, error.trb, lib.trb) and std/markdown/src/entities.trb are the layout models.

Design record point

ROADMAP "Which versions a format supports" (HTML row); ENCODING.md section 9 (document formats). Decided by the loop:

  • Package std/html (std/html/project.trb, src/lib.trb re-exporting by public use), TorbScript only, no native. The package is shared with #93 (WEB slice 1: Html<Message>, Markup<Message>); this epic never introduces a type named Html. Names here: HtmlToken, HtmlTokenizer, HtmlParseError, later HtmlNode, HtmlDocument, HtmlFragment. Whichever of #93 and this step lands second adds its public use lines to lib.trb and its entry to the docs page; the files are otherwise disjoint.
  • HtmlToken is an ADT: doctype (name, public and system identifier, force-quirks flag), start tag (name, attributes in document order, self-closing flag), end tag, comment, character data, end of file. HtmlTokenizer follows the 80 states of the standard as a state machine over the preprocessed input (newline normalisation per section 13.2.3.5), exposing the tree builder's switches (the state to switch to for <script>, <style>, <textarea>, <title>, <plaintext>, <xmp>, <iframe>, <noembed>, <noframes>, CDATA sections in foreign content) as a settable state and "last start tag name".
  • Reading HTML never fails: the tokenizer reports each recoverable error of the standard as an HtmlParseError (its code name from the standard, line and column) in a list beside the tokens, and goes on exactly as the standard says.
  • Character references: the standard's table with all 2231 names (the 2125 ending in ; plus the 106 legacy names without it), generated into std/html/src/entities.trb with the generator kept in tools/ (a TorbScript program, like the element table of WEB.md section 4.2); numeric references with the replacement table for C1 controls and the error cases; the attribute-value rule (a legacy name followed by = or an alphanumeric stays text). std/html does not import std/markdown, whose table lacks the legacy names; unifying the two tables is a later refactor.
  • Input is a String (UTF-8); sniffing a byte stream's encoding is not part of this epic.

Acceptance criteria

  • use HtmlTokenizer, HtmlToken, HtmlParseError from "std/html" works; tokenizing a String answers the tokens and the parse errors, and the tree builder's state switches are public.
  • The tokenizer test suite of html5lib-tests (tokenizer/*.test) passes except for the cases that need a tree builder or a byte-level encoding (listed in the test package with the reason); vendored in std/html/tests with its notice (html5lib-tests is MIT licensed; if the licence check at vendoring time says otherwise, hand-written cases per state of the standard instead, and the PR says so).
  • The named references table has all 2231 names, checked against a count and spot lookups (&not;, &notit; as not + it;, &amp without semicolon in text and in an attribute value), and the numeric rules (&#x80;, &#0;, &#xD800;, &#x110000;).
  • Linear time: tokenizing a document of 1 MiB does not take more than a constant factor of its length; a forge CI measurement against a document twice the size goes into the PR (number, no threshold gate).
  • A page docs/standard-library/html.md (front matter as xml.md, a trb check example that tokenizes a small tag soup and prints the tokens), listed in docs/standard-library/index.md; torb docs check, docs skill --check and doc --check --no-run std are green; the page says the tree comes in step 2.
  • Gates: tier A green in forge CI (sh tools/gates.sh a); every public declaration has its doc comment; touched files pass torb format --check.

Touches the seed?

No. std/html is a new package that the compiler does not import, and no std name is looked up by string, so no two-commit rule applies. (When #108 makes the checker import the tokenizer, that issue carries the seed rule.) One or two Conventional Commits: feat(std/html): ... and, if the diff is large, docs(std/html): ....

Dependencies

  • None blocking. Step 2 of #79 (tree construction) needs this step.
  • Shares the package directory with #93 (std/html markup); no code is shared and neither blocks the other (see the merge note above).
  • Unblocks the rest of #79, Html.sanitized of #93 (WEB.md section 4.7) and, later, #108.
## Context Step 1 of #79 (`std/html`, the document side): the WHATWG tokenizer and the character references, as the first modules of the new package `std/html`. Nothing builds a tree yet; the tokens are what step 2 (tree construction) consumes. The tokenizer is also what WEB.md section 4.3 says the checker would use to check static parts of an `Html` literal (#108), and what `Html.sanitized` of #93 builds on through the later steps. ## Source - [ROADMAP.md, format versions](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/ROADMAP.md#L224): HTML is "the parsing algorithm of the WHATWG standard, which also reads older documents"; no separate HTML 4 or XHTML parser. - [ENCODING.md section 9](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/ENCODING.md#L709): an HTML page is a document format, "a tree type as a value in its own package and ordinary functions over it"; no `HtmlEncode`/`HtmlDecode`. - The WHATWG HTML Standard, section 13.2.5 (tokenization) and the named character references table (`entities.json`). - `std/xml` (`std/xml/src/scanner.trb`, `error.trb`, `lib.trb`) and `std/markdown/src/entities.trb` are the layout models. ## Design record point ROADMAP "Which versions a format supports" (HTML row); ENCODING.md section 9 (document formats). Decided by the loop: - Package `std/html` (`std/html/project.trb`, `src/lib.trb` re-exporting by `public use`), TorbScript only, no `native`. The package is shared with #93 (WEB slice 1: `Html<Message>`, `Markup<Message>`); this epic never introduces a type named `Html`. Names here: `HtmlToken`, `HtmlTokenizer`, `HtmlParseError`, later `HtmlNode`, `HtmlDocument`, `HtmlFragment`. Whichever of #93 and this step lands second adds its `public use` lines to `lib.trb` and its entry to the docs page; the files are otherwise disjoint. - `HtmlToken` is an ADT: doctype (name, public and system identifier, force-quirks flag), start tag (name, attributes in document order, self-closing flag), end tag, comment, character data, end of file. `HtmlTokenizer` follows the 80 states of the standard as a state machine over the preprocessed input (newline normalisation per section 13.2.3.5), exposing the tree builder's switches (the state to switch to for `<script>`, `<style>`, `<textarea>`, `<title>`, `<plaintext>`, `<xmp>`, `<iframe>`, `<noembed>`, `<noframes>`, CDATA sections in foreign content) as a settable state and "last start tag name". - Reading HTML never fails: the tokenizer reports each recoverable error of the standard as an `HtmlParseError` (its code name from the standard, line and column) in a list beside the tokens, and goes on exactly as the standard says. - Character references: the standard's table with all 2231 names (the 2125 ending in `;` plus the 106 legacy names without it), generated into `std/html/src/entities.trb` with the generator kept in `tools/` (a TorbScript program, like the element table of WEB.md section 4.2); numeric references with the replacement table for C1 controls and the error cases; the attribute-value rule (a legacy name followed by `=` or an alphanumeric stays text). `std/html` does not import `std/markdown`, whose table lacks the legacy names; unifying the two tables is a later refactor. - Input is a `String` (UTF-8); sniffing a byte stream's encoding is not part of this epic. ## Acceptance criteria - [ ] `use HtmlTokenizer, HtmlToken, HtmlParseError from "std/html"` works; tokenizing a `String` answers the tokens and the parse errors, and the tree builder's state switches are public. - [ ] The tokenizer test suite of html5lib-tests (`tokenizer/*.test`) passes except for the cases that need a tree builder or a byte-level encoding (listed in the test package with the reason); vendored in `std/html/tests` with its notice (html5lib-tests is MIT licensed; if the licence check at vendoring time says otherwise, hand-written cases per state of the standard instead, and the PR says so). - [ ] The named references table has all 2231 names, checked against a count and spot lookups (`&not;`, `&notit;` as `not` + `it;`, `&amp` without semicolon in text and in an attribute value), and the numeric rules (`&#x80;`, `&#0;`, `&#xD800;`, `&#x110000;`). - [ ] Linear time: tokenizing a document of 1 MiB does not take more than a constant factor of its length; a forge CI measurement against a document twice the size goes into the PR (number, no threshold gate). - [ ] A page `docs/standard-library/html.md` (front matter as `xml.md`, a `trb check` example that tokenizes a small tag soup and prints the tokens), listed in `docs/standard-library/index.md`; `torb docs check`, `docs skill --check` and `doc --check --no-run std` are green; the page says the tree comes in step 2. - [ ] Gates: tier A green in forge CI (`sh tools/gates.sh a`); every public declaration has its doc comment; touched files pass `torb format --check`. ## Touches the seed? No. `std/html` is a new package that the compiler does not import, and no std name is looked up by string, so no two-commit rule applies. (When #108 makes the checker import the tokenizer, that issue carries the seed rule.) One or two Conventional Commits: `feat(std/html): ...` and, if the diff is large, `docs(std/html): ...`. ## Dependencies - None blocking. Step 2 of #79 (tree construction) needs this step. - Shares the package directory with #93 (`std/html` markup); no code is shared and neither blocks the other (see the merge note above). - Unblocks the rest of #79, `Html.sanitized` of #93 (WEB.md section 4.7) and, later, #108.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T22:21Z

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T22:21Z
argus closed this issue 2026-10-23 18:49:16 +00:00
Author
Collaborator

[loop:torbscript] implement: PR #443 is merged (Argus, CI green on the final head: gates, tier A, tier B, bootstrap, pdisc). It adds std/html with the 80-state tokenizer, all 2231 named references, numeric references with the C1 table, HtmlParseError beside the tokens, the generator in tools/html, the vendored html5lib tokenizer tests (7000 cases, 0 failures locally; skipped cases in std/html/tests/NOTICE.md) and docs/standard-library/html.md.

Remains:

  • The linear-time number from forge CI: a passing gate prints no output, so it was not captured before the merge. Local VM run: 1 MiB in 8.80 s, 2 MiB in 17.85 s, ratio 2.03. A follow-up can make the CI line visible, for example by a one-off failing probe on a branch.
  • The std tests take about 11 min each in native and VM, nearly all of it the 7000 html5lib cases (4210 of them namedEntities); slim them if CI time matters.
  • Step 2 of #79, the tree construction.
[loop:torbscript] implement: PR #443 is merged (Argus, CI green on the final head: gates, tier A, tier B, bootstrap, pdisc). It adds `std/html` with the 80-state tokenizer, all 2231 named references, numeric references with the C1 table, `HtmlParseError` beside the tokens, the generator in `tools/html`, the vendored html5lib tokenizer tests (7000 cases, 0 failures locally; skipped cases in `std/html/tests/NOTICE.md`) and `docs/standard-library/html.md`. Remains: - The linear-time number from forge CI: a passing gate prints no output, so it was not captured before the merge. Local VM run: 1 MiB in 8.80 s, 2 MiB in 17.85 s, ratio 2.03. A follow-up can make the CI line visible, for example by a one-off failing probe on a branch. - The std tests take about 11 min each in native and VM, nearly all of it the 7000 html5lib cases (4210 of them `namedEntities`); slim them if CI time matters. - Step 2 of #79, the tree construction.
Sign in to join this conversation.
No labels
loop
ready
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#439
No description provided.