105. Character Class Tables in Generated Go Parsers
- Status: Implemented
- Date: 2026-10-22
Problem and Mechanism
Each character class of a generated typed parser spelled out, in place, the read
of the next character, the range test, the expectation record on failure and the
construction of the terminal. A case-insensitive keyword such as [uU] [nN] [iI] [oO] [nN] repeated that for every letter. These lines were the largest repeated
pattern in the large generated parsers.
A parser generated without work controls and without Documents now declares one
immutable Class per distinct class (a 128-bit ASCII membership set, the pairs of
wider ranges, and the negation flag) and uses it in two places:
- a run of consecutive classes whose value is not used (the letters of a keyword)
is one call of
ClassesSkipover a table of steps; - a single class of more than four ranges is tested through
ClassFast, which the compiler inlines (an ASCII bitset test on the code point or byte at the position), followed byClassSkipfor every other character, which also records the expectation of the failing class at the failing position, as the inline form did. Values, errors and expected sets are unchanged.
A single class of up to four ranges, the first-character test of a choice and the repetition of a class keep their inline forms; a run of classes uses one table call whatever the size of its classes. Measured on cue with 30 interleaved rounds, a table lookup in each of them cost between 0.5% and 1.5% (a call per scan alone cost 4%), and it saved almost no bytes in the repetition and the first-character test. Controlled parsers and Documents keep the inline forms.
Results
| Parser | Before | Large classes and keywords | Tables for every class |
|---|---|---|---|
| duckdb | 5.98 MB | 5.12 MB | 4.84 MB |
| typescript | 2.86 MB | 2.56 MB | 2.53 MB |
| python | 1.74 MB | 1.57 MB | 1.55 MB |
| golang | 1.52 MB | 1.43 MB | 1.41 MB |
| cue | 1.15 MB | 1.07 MB | 0.98 MB |
| cel | 0.30 MB | 0.28 MB | 0.27 MB |
Sizes are the bytes of parser.go with -typed-only. The adopted column is the
middle one. The last column (also tables for single classes of up to four ranges)
was +2% to +3% in the geometric mean of the language-module benchmarks and is not
adopted.
Language-module benchmarks of the adopted form (six alternating rounds,
-benchtime=200ms, against the commit before): cue +1.2%, python +1.1%, duckdb
-0.7%, with the unchanged yaml parser at +0.8% as the noise reference. The
./bench generated parsers keep controls or are not specialized and show no
change.
Correctness and Tradeoffs
The module tests of cel, cue, duckdb, golang and python compare each generated
parser with the interpreter on error text and spans. The engine tests, the root
suite and parsers/test.sh pass.
Decision and Limits
Adopted for Go parsers without work controls and Documents. The helpers are generated-code support, not a stable API.