Skip to content

Commit 3c95d7f

Browse files
committed
perf(expression): fast-path identifier characters and short-circuit keyword scans
Speed up expression parsing by skipping work that provably cannot match: - Add an identifier/number-character fast path to the expression loop. Such a character is never whitespace, never a terminator (no `shouldTerminate` implementation matches a word character), and is not one of the switch's cases, so it can short-circuit the termination checks and the switch dispatch entirely and just advance the position. - Bail out of the unary/binary operator keyword scans immediately when the surrounding character cannot start or end a keyword (every keyword is lowercase ASCII letters). No behavior change: the full test suite passes and parser output is identical.
1 parent 221d3b7 commit 3c95d7f

2 files changed

Lines changed: 30 additions & 0 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"htmljs-parser": patch
3+
---
4+
5+
Speed up expression parsing by skipping work that provably cannot match. An
6+
identifier/number character is never whitespace, never a terminator (no
7+
`shouldTerminate` implementation matches a word character), and is not handled
8+
by the expression switch, so it now takes a fast path that just advances the
9+
position. The unary/binary operator keyword scans also bail out immediately when
10+
the surrounding character cannot start or end a keyword. This improves
11+
steady-state parsing throughput with no behavior change.

src/states/EXPRESSION.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,16 @@ export const EXPRESSION: StateDefinition<ExpressionMeta> = {
120120
continue;
121121
}
122122

123+
// Fast path: an identifier/number character is never whitespace, never
124+
// a terminator (no `shouldTerminate` implementation matches a word
125+
// character), and is not handled by the switch below, so it just
126+
// advances. Short-circuiting here skips the termination checks and the
127+
// switch dispatch for the bulk of expression content.
128+
if (isWordCode(code)) {
129+
this.pos++;
130+
continue;
131+
}
132+
123133
// Termination checks (no groupStack)
124134
if (!expression.groupStack.length) {
125135
if (expression.terminatedByWhitespace && isWhitespaceCode(code)) {
@@ -461,6 +471,10 @@ function lookBehindForOperator(
461471
}
462472

463473
default: {
474+
// Every unary keyword ends in a lowercase letter; if the character
475+
// before `pos` is not one, no keyword can match.
476+
if (code < CODE.LOWER_A || code > CODE.LOWER_Z) return -1;
477+
464478
for (const keyword of expression.inType
465479
? tsUnaryKeywords
466480
: unaryKeywords) {
@@ -510,6 +524,11 @@ function lookAheadForOperator(
510524
}
511525

512526
default: {
527+
// Every binary keyword starts with a lowercase letter; if the character
528+
// at `pos` is not one, no keyword can match.
529+
const startCode = data.charCodeAt(pos);
530+
if (startCode < CODE.LOWER_A || startCode > CODE.LOWER_Z) return -1;
531+
513532
for (const keyword of binaryKeywords) {
514533
const keywordPos = lookAheadFor(data, pos, keyword);
515534
if (keywordPos === -1) continue;

0 commit comments

Comments
 (0)