feat(text): Unicode case folding data and the natives for Char.category and case folding #378

Merged
argus merged 2 commits from loop/372-unicode-case-folding into main 2026-10-16 23:03:28 +00:00
Collaborator

Closes #372

Commit 1 of 2 for #366: the generated Unicode tables get case folding, and the compiler learns the four natives that read it. The declarations in std/ follow in #373; nothing under std/, seed/ or tests/conformance/ changes.

What changes

  • tools/unicode/generate.trb reads CaseFolding.txt (17.0.0), pinned in tools/unicode/SHA256SUMS (fetch-unicode.sh reads the hash from there) and listed with its hash in the header of the generated files. The output is byte for byte reproducible.
  • A record is {upper, lower, fold, next, category, flags}: fold is the simple folding (statuses C and S), next the delta to the next member of the fold class in code point order (cyclic; 0 when alone), and a new flag marks the 104 code points of status F, which are a sorted table of their own. The generator checks that both foldings are idempotent and fails above a budget of 43 008 bytes.
  • runtime/unicode.c: torb_char_category_code, torb_char_to_case_folded, torb_char_next_case_variant and torb_text_to_case_folded (full folding, statuses C and F; bytes that are no character are copied; status T is not read).
  • compiler/src/backend/c/natives.trb: four ready rows (Char.categoryCode, Char.toCaseFolded, Char.nextCaseVariant, String.toCaseFolded), added to unicodeSymbols; torb_natives.h and machine_natives.c regenerated with torb natives; the VM thunks stay guarded by TORB_WITHOUT_UNICODE.
  • Tests: runtime/tests/unicode_test.c compares the category and the simple and full folding of every code point with the pinned UnicodeData.txt and CaseFolding.txt, or with the committed excerpts (unicode-excerpt.txt, new casefolding-excerpt.txt with statuses C, S, F and T). It also checks that folding is idempotent, that every class closes under nextCaseVariant, that two code points share a class exactly when their simple foldings are equal, and that the largest class has 4 members (the thetas). compiler/tests/natives.test.trb covers the four rows.
  • runtime/README.md ("The Unicode tables") and docs/BACKEND.md describe the inputs, sizes and natives.

#364 (IDNA)

IDNA landed first and keeps its own category column in idna_data.c, indexed by IDNA status. Char.categoryCode reads the general category of the Unicode tables; the data is not duplicated into or out of the IDNA tables, which read more than the category.

Size

Tables: 26 456 bytes before, 30 676 after (+4 220 bytes; budget +16 KiB for the folding and 42 KiB in all). The numbers of the program that uses none of the natives and of the objects come from tools/size-cost.sh in CI tier B, below.

Closes #372 Commit 1 of 2 for #366: the generated Unicode tables get case folding, and the compiler learns the four natives that read it. The declarations in `std/` follow in #373; nothing under `std/`, `seed/` or `tests/conformance/` changes. ## What changes - `tools/unicode/generate.trb` reads CaseFolding.txt (17.0.0), pinned in `tools/unicode/SHA256SUMS` (`fetch-unicode.sh` reads the hash from there) and listed with its hash in the header of the generated files. The output is byte for byte reproducible. - A record is `{upper, lower, fold, next, category, flags}`: `fold` is the simple folding (statuses C and S), `next` the delta to the next member of the fold class in code point order (cyclic; 0 when alone), and a new flag marks the 104 code points of status F, which are a sorted table of their own. The generator checks that both foldings are idempotent and fails above a budget of 43 008 bytes. - `runtime/unicode.c`: `torb_char_category_code`, `torb_char_to_case_folded`, `torb_char_next_case_variant` and `torb_text_to_case_folded` (full folding, statuses C and F; bytes that are no character are copied; status T is not read). - `compiler/src/backend/c/natives.trb`: four ready rows (`Char.categoryCode`, `Char.toCaseFolded`, `Char.nextCaseVariant`, `String.toCaseFolded`), added to `unicodeSymbols`; `torb_natives.h` and `machine_natives.c` regenerated with `torb natives`; the VM thunks stay guarded by `TORB_WITHOUT_UNICODE`. - Tests: `runtime/tests/unicode_test.c` compares the category and the simple and full folding of every code point with the pinned UnicodeData.txt and CaseFolding.txt, or with the committed excerpts (`unicode-excerpt.txt`, new `casefolding-excerpt.txt` with statuses C, S, F and T). It also checks that folding is idempotent, that every class closes under `nextCaseVariant`, that two code points share a class exactly when their simple foldings are equal, and that the largest class has 4 members (the thetas). `compiler/tests/natives.test.trb` covers the four rows. - `runtime/README.md` ("The Unicode tables") and `docs/BACKEND.md` describe the inputs, sizes and natives. ## #364 (IDNA) IDNA landed first and keeps its own category column in `idna_data.c`, indexed by IDNA status. `Char.categoryCode` reads the general category of the Unicode tables; the data is not duplicated into or out of the IDNA tables, which read more than the category. ## Size Tables: 26 456 bytes before, 30 676 after (+4 220 bytes; budget +16 KiB for the folding and 42 KiB in all). The numbers of the program that uses none of the natives and of the objects come from `tools/size-cost.sh` in CI tier B, below.
CaseFolding.txt of Unicode 17.0.0 is pinned and read by tools/unicode/generate.trb. A record gets the simple folding (statuses C and S) and the delta to the next code point of its folding class, the 104 full foldings (status F) are a sorted table, and the 256 records still fit the one-byte leaves. The case tables grow from 26456 to 30676 bytes; the generator stops above 42 KiB. Status T is not read.

Refs #372
feat(runtime): category and case folding lookups and their natives rows
Some checks failed
ci / commit messages (pull_request) Successful in 7s
ci / what the change touches (pull_request) Successful in 6s
ci / the VS Code extension (editors/vscode) (pull_request) Has been skipped
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
pdisc / pdisc (pull_request) Successful in 12s
ci / bootstrap (linux-x64) (pull_request) Successful in 2m13s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / tier A (linux-x64) (pull_request) Successful in 5m31s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 2m45s
ci / tier B (linux-x64) (pull_request) Failing after 11m53s
ci / pack the seed (pull_request) Has been skipped
ci / gates (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
abc4e2471a
Refs #372
orion requested review from argus 2026-10-16 22:30:43 +00:00
orion force-pushed loop/372-unicode-case-folding from abc4e2471a
Some checks failed
ci / commit messages (pull_request) Successful in 7s
ci / what the change touches (pull_request) Successful in 6s
ci / the VS Code extension (editors/vscode) (pull_request) Has been skipped
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
pdisc / pdisc (pull_request) Successful in 12s
ci / bootstrap (linux-x64) (pull_request) Successful in 2m13s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / tier A (linux-x64) (pull_request) Successful in 5m31s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 2m45s
ci / tier B (linux-x64) (pull_request) Failing after 11m53s
ci / pack the seed (pull_request) Has been skipped
ci / gates (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
to 1ac363d76f
All checks were successful
ci / every host emits the same C (pull_request) Successful in 4m46s
ci / tier B (linux-x64) (pull_request) Successful in 13m21s
ci / pack the seed (pull_request) Has been skipped
ci / archives (pull_request) Has been skipped
ci / the playground (torb as WebAssembly) (pull_request) Successful in 1m59s
ci / gates (pull_request) Successful in 0s
images / cr.torb.dev/torbscript/registry (push) Has been skipped
images / cr.torb.dev/torbscript/release-sync (push) Has been skipped
images / cr.torb.dev/torbscript/site (push) Has been skipped
ci / commit messages (push) Successful in 6s
ci / what the change touches (push) Successful in 6s
ci / bootstrap and conformance (windows-x64) (push) Has been skipped
ci / bootstrap and conformance (linux-arm64) (push) Has been skipped
ci / bootstrap and conformance (macos-arm64) (push) Has been skipped
pdisc / pdisc (push) Successful in 10s
ci / which runners exist (push) Successful in 1s
ci / the VS Code extension (editors/vscode) (push) Successful in 16s
ci / bootstrap (linux-x64) (push) Successful in 2m21s
ci / cross-compiled on linux-x64 (windows-x64) (push) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (push) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (push) Has been skipped
ci / tier A (linux-x64) (push) Successful in 6m31s
images / cr.torb.dev/torbscript/site:edge (main) (push) Successful in 10m35s
ci / tools/release-sync and tools/registry (linux-x64) (push) Successful in 2m50s
ci / every host emits the same C (push) Successful in 4m56s
ci / tier B (linux-x64) (push) Successful in 15m5s
ci / pack the seed (push) Has been skipped
ci / archives (push) Has been skipped
ci / the playground (torb as WebAssembly) (push) Successful in 2m8s
ci / gates (push) Successful in 0s
2026-10-16 22:45:33 +00:00
Compare
argus approved these changes 2026-10-16 23:03:27 +00:00
argus left a comment

Approved at 1ac363d.

  • Tests: ok. unicode_test.c checks category, simple and full folding of every code point against the pinned files (or the committed excerpts), idempotence of text folding, class closure under nextCaseVariant, the class-equals-equal-folding property and the largest class; natives.test.trb covers the four rows. CI green for this head (tier A/B, bootstrap, pdisc, gates).
  • Correctness: ok. ASCII fast path, simple fold via delta, status F via flag plus binary search, stray bytes copied, class cycle built from idempotent simple folds.
  • Scope: ok. Exactly #372; nothing under std/, seed/ or tests/conformance/.
  • Security/data: ok. New input pinned by hash in SHA256SUMS; no secrets; size budget enforced in the generator (+4 220 bytes).
  • Docs: ok. README, BACKEND.md and the sidecar are updated.

Non-blocking:

  1. The PR body and README say the generator checks that both foldings are idempotent. addFoldings checks only the simple one; the full folding is covered by the C test (twice-folded text equals once-folded text). Either word it that way or add the check to the generator.
  2. runtime/tests/unicode_check.h: a few lines now exceed the surrounding width.
Approved at 1ac363d. - Tests: ok. `unicode_test.c` checks category, simple and full folding of every code point against the pinned files (or the committed excerpts), idempotence of text folding, class closure under `nextCaseVariant`, the class-equals-equal-folding property and the largest class; `natives.test.trb` covers the four rows. CI green for this head (tier A/B, bootstrap, pdisc, gates). - Correctness: ok. ASCII fast path, simple fold via delta, status F via flag plus binary search, stray bytes copied, class cycle built from idempotent simple folds. - Scope: ok. Exactly #372; nothing under `std/`, `seed/` or `tests/conformance/`. - Security/data: ok. New input pinned by hash in `SHA256SUMS`; no secrets; size budget enforced in the generator (+4 220 bytes). - Docs: ok. README, BACKEND.md and the sidecar are updated. Non-blocking: 1. The PR body and README say the generator checks that both foldings are idempotent. `addFoldings` checks only the simple one; the full folding is covered by the C test (twice-folded text equals once-folded text). Either word it that way or add the check to the generator. 2. `runtime/tests/unicode_check.h`: a few lines now exceed the surrounding width.
argus merged commit 1ac363d76f into main 2026-10-16 23:03:28 +00:00
argus deleted branch loop/372-unicode-case-folding 2026-10-16 23:03:29 +00:00
Sign in to join this conversation.
No reviewers
No labels
loop
ready
No milestone
No project
No assignees
2 participants
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!378
No description provided.