Skip to content

Commit 05b121f

Browse files
authored
parsers performance improvements (#32)
1 parent b2f78bf commit 05b121f

File tree

7 files changed

+78
-92
lines changed

7 files changed

+78
-92
lines changed

src/parse-anplusb.ts

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ export class ANplusBParser {
3333
this.lexer.pos = start
3434
this.lexer.line = line
3535

36-
let a: string | null = null
3736
let b: string | null = null
3837
let a_start = start
3938
let a_end = start
@@ -56,11 +55,9 @@ export class ANplusBParser {
5655
const text = this.source.substring(this.lexer.token_start, this.lexer.token_end).toLowerCase()
5756

5857
if (text === 'odd' || text === 'even') {
59-
// Store the keyword as authored
60-
a = this.source.substring(this.lexer.token_start, this.lexer.token_end)
6158
a_start = this.lexer.token_start
6259
a_end = this.lexer.token_end
63-
return this.create_anplusb_node(node_start, a, null, a_start, a_end, 0, 0)
60+
return this.create_anplusb_node(node_start, a_start, a_end, 0, 0)
6461
}
6562

6663
// Check if it's 'n', '-n', or starts with 'n'
@@ -74,18 +71,15 @@ export class ANplusBParser {
7471
const third_char = this.source.charCodeAt(this.lexer.token_start + 2)
7572
if (third_char === CHAR_MINUS_HYPHEN /* - */) {
7673
// -n-5 pattern
77-
a = '-n'
7874
a_start = this.lexer.token_start
7975
a_end = this.lexer.token_start + 2
8076
b = this.source.substring(this.lexer.token_start + 2, this.lexer.token_end)
8177
b_start = this.lexer.token_start + 2
8278
b_end = this.lexer.token_end
83-
return this.create_anplusb_node(node_start, a, b, a_start, a_end, b_start, b_end)
79+
return this.create_anplusb_node(node_start, a_start, a_end, b_start, b_end)
8480
}
8581
}
8682

87-
// Store -n as authored
88-
a = '-n'
8983
a_start = this.lexer.token_start
9084
a_end = this.lexer.token_start + 2
9185

@@ -95,7 +89,7 @@ export class ANplusBParser {
9589
b_start = this.lexer.token_start
9690
b_end = this.lexer.token_end
9791
}
98-
return this.create_anplusb_node(node_start, a, b, a_start, a_end, b_start, b_end)
92+
return this.create_anplusb_node(node_start, a_start, a_end, b !== null ? b_start : 0, b !== null ? b_end : 0)
9993
}
10094

10195
// n, n+3, n-5
@@ -105,18 +99,16 @@ export class ANplusBParser {
10599
const second_char = this.source.charCodeAt(this.lexer.token_start + 1)
106100
if (second_char === CHAR_MINUS_HYPHEN /* - */) {
107101
// n-5 pattern
108-
a = 'n'
102+
// a = 'n'
109103
a_start = this.lexer.token_start
110104
a_end = this.lexer.token_start + 1
111105
b = this.source.substring(this.lexer.token_start + 1, this.lexer.token_end)
112106
b_start = this.lexer.token_start + 1
113107
b_end = this.lexer.token_end
114-
return this.create_anplusb_node(node_start, a, b, a_start, a_end, b_start, b_end)
108+
return this.create_anplusb_node(node_start, a_start, a_end, b_start, b_end)
115109
}
116110
}
117111

118-
// Store n as authored
119-
a = 'n'
120112
a_start = this.lexer.token_start
121113
a_end = this.lexer.token_start + 1
122114

@@ -126,7 +118,7 @@ export class ANplusBParser {
126118
b_start = this.lexer.token_start
127119
b_end = this.lexer.token_end
128120
}
129-
return this.create_anplusb_node(node_start, a, b, a_start, a_end, b_start, b_end)
121+
return this.create_anplusb_node(node_start, a_start, a_end, b !== null ? b_start : 0, b !== null ? b_end : 0)
130122
}
131123

132124
// Not a valid An+B pattern
@@ -144,8 +136,6 @@ export class ANplusBParser {
144136
const first_char = text.charCodeAt(0)
145137

146138
if (first_char === 0x6e /* n */) {
147-
// Store +n as authored (including the +)
148-
a = '+n'
149139
a_start = saved.pos - 1 // Position of the + delim
150140
a_end = this.lexer.token_start + 1
151141

@@ -157,7 +147,7 @@ export class ANplusBParser {
157147
b = this.source.substring(this.lexer.token_start + 1, this.lexer.token_end)
158148
b_start = this.lexer.token_start + 1
159149
b_end = this.lexer.token_end
160-
return this.create_anplusb_node(node_start, a, b, a_start, a_end, b_start, b_end)
150+
return this.create_anplusb_node(node_start, a_start, a_end, b_start, b_end)
161151
}
162152
}
163153

@@ -167,7 +157,7 @@ export class ANplusBParser {
167157
b_start = this.lexer.token_start
168158
b_end = this.lexer.token_end
169159
}
170-
return this.create_anplusb_node(node_start, a, b, a_start, a_end, b_start, b_end)
160+
return this.create_anplusb_node(node_start, a_start, a_end, b !== null ? b_start : 0, b !== null ? b_end : 0)
171161
}
172162
}
173163

@@ -180,8 +170,6 @@ export class ANplusBParser {
180170
const n_index = token_text.toLowerCase().indexOf('n')
181171

182172
if (n_index !== -1) {
183-
// Store 'a' coefficient including the 'n'
184-
a = token_text.substring(0, n_index + 1)
185173
a_start = this.lexer.token_start
186174
a_end = this.lexer.token_start + n_index + 1
187175

@@ -194,7 +182,7 @@ export class ANplusBParser {
194182
b = remainder
195183
b_start = this.lexer.token_start + n_index + 1
196184
b_end = this.lexer.token_end
197-
return this.create_anplusb_node(node_start, a, b, a_start, a_end, b_start, b_end)
185+
return this.create_anplusb_node(node_start, a_start, a_end, b_start, b_end)
198186
}
199187
}
200188

@@ -204,7 +192,7 @@ export class ANplusBParser {
204192
b_start = this.lexer.token_start
205193
b_end = this.lexer.token_end
206194
}
207-
return this.create_anplusb_node(node_start, a, b, a_start, a_end, b_start, b_end)
195+
return this.create_anplusb_node(node_start, a_start, a_end, b_start, b_end)
208196
}
209197
}
210198

@@ -214,7 +202,7 @@ export class ANplusBParser {
214202
b = num_text
215203
b_start = this.lexer.token_start
216204
b_end = this.lexer.token_end
217-
return this.create_anplusb_node(node_start, a, b, a_start, a_end, b_start, b_end)
205+
return this.create_anplusb_node(node_start, 0, 0, b_start, b_end)
218206
}
219207

220208
return null
@@ -278,8 +266,6 @@ export class ANplusBParser {
278266

279267
private create_anplusb_node(
280268
start: number,
281-
a: string | null,
282-
b: string | null,
283269
a_start: number,
284270
a_end: number,
285271
b_start: number,
@@ -291,14 +277,14 @@ export class ANplusBParser {
291277
this.arena.set_length(node, this.lexer.pos - start)
292278
this.arena.set_start_line(node, this.lexer.line)
293279

294-
// Store 'a' coefficient in content fields
295-
if (a !== null) {
280+
// Store 'a' coefficient in content fields if it exists (length > 0)
281+
if (a_end > a_start) {
296282
this.arena.set_content_start(node, a_start)
297283
this.arena.set_content_length(node, a_end - a_start)
298284
}
299285

300-
// Store 'b' coefficient in value fields
301-
if (b !== null) {
286+
// Store 'b' coefficient in value fields if it exists (length > 0)
287+
if (b_end > b_start) {
302288
this.arena.set_value_start(node, b_start)
303289
this.arena.set_value_length(node, b_end - b_start)
304290
}

src/parse-atrule-prelude.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,11 @@ export class AtRulePreludeParser {
8989

9090
// Skip comma separator
9191
this.skip_whitespace()
92-
if (this.peek_token_type() === TOKEN_COMMA) {
93-
this.next_token() // consume comma
92+
const saved = this.lexer.save_position()
93+
this.next_token()
94+
if (this.lexer.token_type !== TOKEN_COMMA) {
95+
// Not a comma, restore position
96+
this.lexer.restore_position(saved)
9497
}
9598
}
9699

@@ -108,14 +111,14 @@ export class AtRulePreludeParser {
108111
}
109112

110113
private is_and_or_not(str: string): boolean {
111-
if (str.length > 3 || str.length < 2) return false
114+
// All logical operators are 2-3 chars: "and" (3), "or" (2), "not" (3)
115+
// The str_equals calls will quickly reject strings of other lengths
112116
return str_equals('and', str) || str_equals('or', str) || str_equals('not', str)
113117
}
114118

115119
// Parse a single media query: screen and (min-width: 768px)
116120
private parse_single_media_query(): number | null {
117121
let query_start = this.lexer.pos
118-
let query_line = this.lexer.line
119122

120123
// Skip whitespace
121124
this.skip_whitespace()
@@ -191,7 +194,6 @@ export class AtRulePreludeParser {
191194
// Parse media feature: (min-width: 768px)
192195
private parse_media_feature(): number | null {
193196
let feature_start = this.lexer.token_start // '(' position
194-
let feature_line = this.lexer.token_line
195197

196198
// Find matching right paren
197199
let depth = 1
@@ -229,7 +231,6 @@ export class AtRulePreludeParser {
229231
private parse_container_query(): number[] {
230232
let nodes: number[] = []
231233
let query_start = this.lexer.pos
232-
let query_line = this.lexer.line
233234

234235
// Parse components (identifiers, operators, features)
235236
let components: number[] = []
@@ -292,7 +293,6 @@ export class AtRulePreludeParser {
292293
// Feature query: (property: value)
293294
if (token_type === TOKEN_LEFT_PAREN) {
294295
let feature_start = this.lexer.token_start
295-
let feature_line = this.lexer.token_line
296296

297297
// Find matching right paren
298298
let depth = 1
@@ -438,7 +438,6 @@ export class AtRulePreludeParser {
438438
// For url() function, we need to consume all tokens until the closing paren
439439
let url_start = this.lexer.token_start
440440
let url_end = this.lexer.token_end
441-
let url_line = this.lexer.token_line
442441

443442
if (this.lexer.token_type === TOKEN_FUNCTION) {
444443
// It's url( ... we need to find the matching )
@@ -481,7 +480,6 @@ export class AtRulePreludeParser {
481480
if (str_equals('layer', text)) {
482481
let layer_start = this.lexer.token_start
483482
let layer_end = this.lexer.token_end
484-
let layer_line = this.lexer.token_line
485483
let content_start = 0
486484
let content_length = 0
487485

@@ -539,7 +537,6 @@ export class AtRulePreludeParser {
539537
let text = this.source.substring(this.lexer.token_start, this.lexer.token_end - 1) // -1 to exclude '('
540538
if (str_equals('supports', text)) {
541539
let supports_start = this.lexer.token_start
542-
let supports_line = this.lexer.token_line
543540

544541
// Find matching closing parenthesis
545542
let paren_depth = 1

src/parse-selector.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ import {
6161
CHAR_SINGLE_QUOTE,
6262
CHAR_DOUBLE_QUOTE,
6363
CHAR_COLON,
64+
CHAR_CARRIAGE_RETURN,
65+
CHAR_FORM_FEED,
66+
CHAR_NEWLINE,
67+
CHAR_SPACE,
6468
} from './string-utils'
6569
import { ANplusBParser } from './parse-anplusb'
6670
import { CSSNode } from './css-node'
@@ -113,8 +117,10 @@ export class SelectorParser {
113117

114118
// Find the last component in the chain
115119
let last_component = complex_selector
116-
while (this.arena.get_next_sibling(last_component) !== 0) {
117-
last_component = this.arena.get_next_sibling(last_component)
120+
let next_sibling = this.arena.get_next_sibling(last_component)
121+
while (next_sibling !== 0) {
122+
last_component = next_sibling
123+
next_sibling = this.arena.get_next_sibling(last_component)
118124
}
119125

120126
// Set the complex selector chain as children
@@ -199,8 +205,6 @@ export class SelectorParser {
199205
}
200206

201207
while (this.lexer.pos < this.selector_end) {
202-
if (this.lexer.pos >= this.selector_end) break
203-
204208
// Parse compound selector first
205209
let compound = this.parse_compound_selector()
206210
if (compound !== null) {
@@ -347,22 +351,15 @@ export class SelectorParser {
347351

348352
// Parse the local part after | in a namespace selector (E or *)
349353
// Returns the node type (TYPE or UNIVERSAL) or null if invalid
350-
private parse_namespace_local_part(
351-
selector_start: number,
352-
namespace_start: number,
353-
namespace_length: number,
354-
): number | null {
354+
private parse_namespace_local_part(selector_start: number, namespace_start: number, namespace_length: number): number | null {
355355
const saved = this.lexer.save_position()
356356
this.lexer.next_token_fast(false)
357357

358358
let node_type: number
359359
if (this.lexer.token_type === TOKEN_IDENT) {
360360
// ns|type
361361
node_type = NODE_SELECTOR_TYPE
362-
} else if (
363-
this.lexer.token_type === TOKEN_DELIM &&
364-
this.source.charCodeAt(this.lexer.token_start) === CHAR_ASTERISK
365-
) {
362+
} else if (this.lexer.token_type === TOKEN_DELIM && this.source.charCodeAt(this.lexer.token_start) === CHAR_ASTERISK) {
366363
// ns|*
367364
node_type = NODE_SELECTOR_UNIVERSAL
368365
} else {
@@ -425,7 +422,8 @@ export class SelectorParser {
425422
// Skip whitespace and check for combinator
426423
while (this.lexer.pos < this.selector_end) {
427424
let ch = this.source.charCodeAt(this.lexer.pos)
428-
if (is_whitespace(ch)) {
425+
// no calling is_whitespace() because of function call overhead
426+
if (ch === CHAR_SPACE || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
429427
has_whitespace = true
430428
this.lexer.pos++
431429
} else {
@@ -452,7 +450,8 @@ export class SelectorParser {
452450
this.lexer.pos = whitespace_start
453451
while (this.lexer.pos < this.selector_end) {
454452
let ch = this.source.charCodeAt(this.lexer.pos)
455-
if (is_whitespace(ch)) {
453+
// no calling is_whitespace() because of function call overhead
454+
if (ch === CHAR_SPACE || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
456455
this.lexer.pos++
457456
} else {
458457
break
@@ -565,28 +564,29 @@ export class SelectorParser {
565564

566565
// Parse operator
567566
let ch1 = this.source.charCodeAt(pos)
567+
let ch2 = pos + 1 < end ? this.source.charCodeAt(pos + 1) : 0 // Cache second character to avoid repeated calls
568568

569569
if (ch1 === CHAR_EQUALS) {
570570
// =
571571
operator_end = pos + 1
572572
this.arena.set_attr_operator(node, ATTR_OPERATOR_EQUAL)
573-
} else if (ch1 === CHAR_TILDE && pos + 1 < end && this.source.charCodeAt(pos + 1) === CHAR_EQUALS) {
573+
} else if (ch1 === CHAR_TILDE && ch2 === CHAR_EQUALS) {
574574
// ~=
575575
operator_end = pos + 2
576576
this.arena.set_attr_operator(node, ATTR_OPERATOR_TILDE_EQUAL)
577-
} else if (ch1 === CHAR_PIPE && pos + 1 < end && this.source.charCodeAt(pos + 1) === CHAR_EQUALS) {
577+
} else if (ch1 === CHAR_PIPE && ch2 === CHAR_EQUALS) {
578578
// |=
579579
operator_end = pos + 2
580580
this.arena.set_attr_operator(node, ATTR_OPERATOR_PIPE_EQUAL)
581-
} else if (ch1 === CHAR_CARET && pos + 1 < end && this.source.charCodeAt(pos + 1) === CHAR_EQUALS) {
581+
} else if (ch1 === CHAR_CARET && ch2 === CHAR_EQUALS) {
582582
// ^=
583583
operator_end = pos + 2
584584
this.arena.set_attr_operator(node, ATTR_OPERATOR_CARET_EQUAL)
585-
} else if (ch1 === CHAR_DOLLAR && pos + 1 < end && this.source.charCodeAt(pos + 1) === CHAR_EQUALS) {
585+
} else if (ch1 === CHAR_DOLLAR && ch2 === CHAR_EQUALS) {
586586
// $=
587587
operator_end = pos + 2
588588
this.arena.set_attr_operator(node, ATTR_OPERATOR_DOLLAR_EQUAL)
589-
} else if (ch1 === CHAR_ASTERISK && pos + 1 < end && this.source.charCodeAt(pos + 1) === CHAR_EQUALS) {
589+
} else if (ch1 === CHAR_ASTERISK && ch2 === CHAR_EQUALS) {
590590
// *=
591591
operator_end = pos + 2
592592
this.arena.set_attr_operator(node, ATTR_OPERATOR_STAR_EQUAL)

0 commit comments

Comments
 (0)