From 60e4bffd74dcfa850327341c4538ae016274ed8b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 09:39:10 +0000 Subject: [PATCH 1/4] Reorder tokenizer dispatch chain by measured token frequency Profiled parsing bootstrap.css/tailwind.css: next_token_fast (21.3%) and consume_ident_or_function (20.1%) dominate runtime. Token-frequency analysis on the same files showed identifiers are the single most common non-whitespace token (~15-21%), yet the dispatch chain checked them 11th - behind CDO/CDC (``, which never occurred in either file - a legacy token from `` compatibility) and the at-keyword/hash checks, both individually rarer than identifiers. Reordered the chain to check identifiers right after the comment/whitespace fast paths, and moved CDO to the very end, right before the delimiter fallback. Consolidated the three separate `ch === CHAR_HYPHEN` checks (CDC, hyphen-led identifier, hyphen-led signed number) into one block sharing a single lookahead read, instead of three independent re-reads - while preserving the exact original precedence (CDC must still win for "-->", since a bare "--" is valid identifier-start for custom properties like `--custom-color`). Two other approaches were measured and rejected before landing on this: - Sticky-regex-based identifier scanning was 35-40% *slower* than the existing manual char-loop, even accounting for real identifiers averaging ~11-12 chars (longer than assumed) - V8's JIT-compiled loop with a lookup table already beats regex .exec() overhead here. - Pre-scanning the source once for "" and gating the CDO check behind a boolean flag measured slightly *slower* than the bare comparison - branch prediction already makes an always-false check near-free, so the extra flag read is pure overhead. Net effect verified via 6 repeated full end-to-end parse benchmarks (3 before, 3 after) on both files together: ~1.5-4% faster depending on file, with bootstrap.css seeing the larger gain (tailwind.css's much higher rate of backslash-escaped identifiers, from Tailwind's `\:` variant syntax, doesn't benefit from the identifier-check reorder since escaped idents take a separate path). Full test suite (1386 tests, including dedicated CDO/CDC and custom-property tokenizer tests) passes unchanged. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017XjfDK7or6VrnJ41vBhxrK --- src/tokenize.ts | 122 ++++++++++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 51 deletions(-) diff --git a/src/tokenize.ts b/src/tokenize.ts index 5b24579..2967572 100644 --- a/src/tokenize.ts +++ b/src/tokenize.ts @@ -248,67 +248,47 @@ export class Lexer { continue } - // Strings: " or ' - if (ch === CHAR_DOUBLE_QUOTE || ch === CHAR_SINGLE_QUOTE) { - return this.consume_string(ch, start_line, start_column) - } - - // Numbers: digit or . followed by digit - if (ch < 128 && (char_types[ch] & CHAR_DIGIT) !== 0) { - return this.consume_number(start_line, start_column) + // Identifier or function — checked early: across real-world CSS this is the single + // most common non-whitespace token (~15-20%+ of all tokens), well ahead of numbers, + // hashes, at-keywords, strings, and the (essentially extinct in modern CSS) CDO/CDC + // tokens that used to precede it here. Measured via profiling + token frequency + // analysis on bootstrap.css/tailwind.css — see PR discussion for numbers. + if (is_ident_start(ch)) { + return this.consume_ident_or_function(start_line, start_column) } - if (ch === CHAR_DOT) { + // Hyphen-led tokens: --custom-property/-webkit-prefix (identifier), -5px (signed + // number), or the legacy --> CDC token. Consolidated into one block (single charCodeAt + // for the lookahead char) instead of three separate `ch === CHAR_HYPHEN` checks. + // NOTE: CDC must be checked first — "-->" would otherwise be misread as identifier "--" + // followed by a stray ">" delimiter, since -- is itself valid identifier-start (custom + // properties). + if (ch === CHAR_HYPHEN) { let next = this.pos + 1 < source_length ? source.charCodeAt(this.pos + 1) : 0 - if (next < 128 && (char_types[next] & CHAR_DIGIT) !== 0) { - return this.consume_number(start_line, start_column) - } - } - - // CDO: - if (ch === CHAR_HYPHEN && this.pos + 2 < source_length) { if ( - source.charCodeAt(this.pos + 1) === CHAR_HYPHEN && + next === CHAR_HYPHEN && + this.pos + 2 < source_length && source.charCodeAt(this.pos + 2) === CHAR_GREATER_THAN ) { - // --> contains no newlines + // CDC: --> (contains no newlines) this.pos += 3 return this.make_token(TOKEN_CDC, start, this.pos, start_line, start_column) } - } - - // At-keyword: @media, @keyframes, etc - if (ch === CHAR_AT_SIGN) { - return this.consume_at_keyword(start_line, start_column) - } - - // Hash: #id or #fff - if (ch === CHAR_HASH) { - return this.consume_hash(start_line, start_column) - } - // Identifier or function - if (is_ident_start(ch)) { - return this.consume_ident_or_function(start_line, start_column) - } - if (ch === CHAR_HYPHEN) { - let next = this.pos + 1 < source_length ? source.charCodeAt(this.pos + 1) : 0 if (is_ident_start(next) || next === CHAR_HYPHEN) { return this.consume_ident_or_function(start_line, start_column) } + + if (next < 128 && (char_types[next] & CHAR_DIGIT) !== 0) { + return this.consume_number(start_line, start_column) + } + if (next === CHAR_DOT) { + let next2 = this.pos + 2 < source_length ? source.charCodeAt(this.pos + 2) : 0 + if (next2 < 128 && (char_types[next2] & CHAR_DIGIT) !== 0) { + return this.consume_number(start_line, start_column) + } + } } // Backslash: escape sequence starting an identifier @@ -319,11 +299,22 @@ export class Lexer { } } - // Hyphen/Plus: could be signed number like -5 or +5 - if (ch === CHAR_HYPHEN || ch === CHAR_PLUS) { + // Numbers: digit or . followed by digit + if (ch < 128 && (char_types[ch] & CHAR_DIGIT) !== 0) { + return this.consume_number(start_line, start_column) + } + + if (ch === CHAR_DOT) { + let next = this.pos + 1 < source_length ? source.charCodeAt(this.pos + 1) : 0 + if (next < 128 && (char_types[next] & CHAR_DIGIT) !== 0) { + return this.consume_number(start_line, start_column) + } + } + + // Plus: could be a signed number like +5 (the hyphen-led case is handled above) + if (ch === CHAR_PLUS) { let next = this.pos + 1 < source_length ? source.charCodeAt(this.pos + 1) : 0 - let is_next_digit = next < 128 && (char_types[next] & CHAR_DIGIT) !== 0 - if (is_next_digit) { + if (next < 128 && (char_types[next] & CHAR_DIGIT) !== 0) { return this.consume_number(start_line, start_column) } if (next === CHAR_DOT) { @@ -334,6 +325,35 @@ export class Lexer { } } + // Strings: " or ' + if (ch === CHAR_DOUBLE_QUOTE || ch === CHAR_SINGLE_QUOTE) { + return this.consume_string(ch, start_line, start_column) + } + + // At-keyword: @media, @keyframes, etc + if (ch === CHAR_AT_SIGN) { + return this.consume_at_keyword(start_line, start_column) + } + + // Hash: #id or #fff + if (ch === CHAR_HASH) { + return this.consume_hash(start_line, start_column) + } + + // CDO: CDC token. Consolidated into one block (single charCodeAt - // for the lookahead char) instead of three separate `ch === CHAR_HYPHEN` checks. - // NOTE: CDC must be checked first — "-->" would otherwise be misread as identifier "--" - // followed by a stray ">" delimiter, since -- is itself valid identifier-start (custom - // properties). + // Hyphen-led tokens: --custom-property (identifier), -5px (signed number), or the + // legacy --> CDC token — consolidated into one block sharing a single lookahead read. + // CDC must be checked first: "-->" would otherwise read as identifier "--" plus ">". if (ch === CHAR_HYPHEN) { let next = this.pos + 1 < source_length ? source.charCodeAt(this.pos + 1) : 0 @@ -346,8 +336,7 @@ export class Lexer { return this.consume_hash(start_line, start_column) } - // CDO: