Skip to content

Commit e4dc806

Browse files
committed
perf: add new arena node helpers
1 parent b4b4dd1 commit e4dc806

File tree

5 files changed

+101
-56
lines changed

5 files changed

+101
-56
lines changed

src/arena.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,4 +376,92 @@ export class CSSDataArena {
376376
has_flag(node_index: number, flag: number): boolean {
377377
return (this.get_flags(node_index) & flag) !== 0
378378
}
379+
380+
// --- Node Creation Helpers (Used by parsers) ---
381+
382+
/**
383+
* Create a node with standard properties and line/column tracking
384+
* Used by parsers that don't need separate content tracking
385+
*
386+
* @param type - Node type constant
387+
* @param start - Start offset in source
388+
* @param end - End offset in source
389+
* @param line - Start line number (from lexer)
390+
* @param column - Start column number (from lexer)
391+
* @returns The created node ID
392+
*/
393+
create_node_with_line_column(type: number, start: number, end: number, line: number, column: number): number {
394+
let node = this.create_node()
395+
this.set_type(node, type)
396+
this.set_start_offset(node, start)
397+
this.set_length(node, end - start)
398+
this.set_start_line(node, line)
399+
this.set_start_column(node, column)
400+
return node
401+
}
402+
403+
/**
404+
* Create a node with content tracking (content and value have same range)
405+
* Used by selectors and nodes where content range equals the full node range
406+
*
407+
* @param type - Node type constant
408+
* @param start - Start offset in source
409+
* @param end - End offset in source
410+
* @param line - Start line number (from lexer)
411+
* @param column - Start column number (from lexer)
412+
* @returns The created node ID
413+
*/
414+
create_node_with_content(type: number, start: number, end: number, line: number, column: number): number {
415+
let node = this.create_node_with_line_column(type, start, end, line, column)
416+
this.set_content_start(node, start)
417+
this.set_content_length(node, end - start)
418+
return node
419+
}
420+
421+
/**
422+
* Link an array of nodes as siblings and attach to parent
423+
* Sets both first_child and last_child on parent
424+
* Reduces code duplication in node tree construction
425+
*
426+
* @param parent - Parent node ID
427+
* @param nodes - Array of node IDs to link as siblings
428+
*/
429+
link_nodes_as_children(parent: number, nodes: number[]): void {
430+
if (nodes.length === 0) return
431+
432+
this.set_first_child(parent, nodes[0])
433+
if (nodes.length > 1) {
434+
this.set_last_child(parent, nodes[nodes.length - 1])
435+
for (let i = 0; i < nodes.length - 1; i++) {
436+
this.set_next_sibling(nodes[i], nodes[i + 1])
437+
}
438+
}
439+
}
440+
441+
/**
442+
* Set content range (start offset and length) on a node
443+
* Convenience method that avoids calculating length separately
444+
*
445+
* @param node - Node ID
446+
* @param start - Content start offset
447+
* @param end - Content end offset (exclusive)
448+
*/
449+
set_content_range(node: number, start: number, end: number): void {
450+
this.set_content_start(node, start)
451+
this.set_content_length(node, end - start)
452+
}
453+
454+
/**
455+
* Set value range (start offset and length) on a node
456+
* Convenience method that avoids calculating length separately
457+
*
458+
* @param node - Node ID
459+
* @param start - Value start offset
460+
* @param end - Value end offset (exclusive)
461+
*/
462+
set_value_range(node: number, start: number, end: number): void {
463+
this.set_value_start(node, start)
464+
this.set_value_length(node, end - start)
465+
}
466+
379467
}

src/parse-atrule-prelude.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,7 @@ export class AtRulePreludeParser {
101101
}
102102

103103
private create_node(type: number, start: number, end: number): number {
104-
let node = this.arena.create_node()
105-
this.arena.set_type(node, type)
106-
this.arena.set_start_offset(node, start)
107-
this.arena.set_length(node, end - start)
108-
this.arena.set_start_line(node, this.lexer.token_line)
109-
this.arena.set_start_column(node, this.lexer.token_column)
110-
return node
104+
return this.arena.create_node_with_line_column(type, start, end, this.lexer.token_line, this.lexer.token_column)
111105
}
112106

113107
private is_and_or_not(str: string): boolean {
@@ -220,8 +214,7 @@ export class AtRulePreludeParser {
220214
// Store feature content (without parentheses) in value fields, trimmed
221215
let trimmed = trim_boundaries(this.source, content_start, content_end)
222216
if (trimmed) {
223-
this.arena.set_value_start(feature, trimmed[0])
224-
this.arena.set_value_length(feature, trimmed[1] - trimmed[0])
217+
this.arena.set_value_range(feature, trimmed[0], trimmed[1])
225218
}
226219

227220
return feature
@@ -318,8 +311,7 @@ export class AtRulePreludeParser {
318311
// Store query content in value fields, trimmed
319312
let trimmed = trim_boundaries(this.source, content_start, content_end)
320313
if (trimmed) {
321-
this.arena.set_value_start(query, trimmed[0])
322-
this.arena.set_value_length(query, trimmed[1] - trimmed[0])
314+
this.arena.set_value_range(query, trimmed[0], trimmed[1])
323315
}
324316

325317
nodes.push(query)
@@ -511,8 +503,7 @@ export class AtRulePreludeParser {
511503
if (content_length > 0) {
512504
let trimmed = trim_boundaries(this.source, content_start, content_start + content_length)
513505
if (trimmed) {
514-
this.arena.set_content_start(layer_node, trimmed[0])
515-
this.arena.set_content_length(layer_node, trimmed[1] - trimmed[0])
506+
this.arena.set_content_range(layer_node, trimmed[0], trimmed[1])
516507
}
517508
}
518509

src/parse-selector.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,7 @@ export class SelectorParser {
155155
this.arena.set_start_column(list_node, list_column)
156156

157157
// Link selector wrapper nodes as children
158-
this.arena.set_first_child(list_node, selectors[0])
159-
this.arena.set_last_child(list_node, selectors[selectors.length - 1])
160-
161-
// Chain selector wrappers as siblings (simple since they're already wrapped)
162-
for (let i = 0; i < selectors.length - 1; i++) {
163-
this.arena.set_next_sibling(selectors[i], selectors[i + 1])
164-
}
158+
this.arena.link_nodes_as_children(list_node, selectors)
165159

166160
return list_node
167161
}
@@ -945,15 +939,7 @@ export class SelectorParser {
945939
}
946940

947941
private create_node(type: number, start: number, end: number): number {
948-
let node = this.arena.create_node()
949-
this.arena.set_type(node, type)
950-
this.arena.set_start_offset(node, start)
951-
this.arena.set_length(node, end - start)
952-
this.arena.set_start_line(node, this.lexer.line)
953-
this.arena.set_start_column(node, this.lexer.column)
954-
this.arena.set_content_start(node, start)
955-
this.arena.set_content_length(node, end - start)
956-
return node
942+
return this.arena.create_node_with_content(type, start, end, this.lexer.line, this.lexer.column)
957943
}
958944

959945
// Helper to skip whitespace

src/parse-value.ts

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,8 @@ export class ValueParser {
131131
}
132132

133133
private create_node(node_type: number, start: number, end: number): number {
134-
let node = this.arena.create_node()
135-
this.arena.set_type(node, node_type)
136-
this.arena.set_start_offset(node, start)
134+
let node = this.arena.create_node_with_line_column(node_type, start, end, this.lexer.line, this.lexer.column)
137135
let length = end - start
138-
this.arena.set_length(node, length)
139-
// Skip set_content_start since it would compute delta = start - start = 0 (already zero-initialized)
140136
this.arena.set_content_length(node, length)
141137
return node
142138
}
@@ -280,13 +276,7 @@ export class ValueParser {
280276

281277
// Link arguments as children
282278
if (args.length > 0) {
283-
this.arena.set_first_child(node, args[0])
284-
this.arena.set_last_child(node, args[args.length - 1])
285-
286-
// Chain arguments as siblings
287-
for (let i = 0; i < args.length - 1; i++) {
288-
this.arena.set_next_sibling(args[i], args[i + 1])
289-
}
279+
this.arena.link_nodes_as_children(node, args)
290280
}
291281

292282
return node
@@ -338,13 +328,7 @@ export class ValueParser {
338328

339329
// Link children as siblings
340330
if (children.length > 0) {
341-
this.arena.set_first_child(node, children[0])
342-
this.arena.set_last_child(node, children[children.length - 1])
343-
344-
// Chain children as siblings
345-
for (let i = 0; i < children.length - 1; i++) {
346-
this.arena.set_next_sibling(children[i], children[i + 1])
347-
}
331+
this.arena.link_nodes_as_children(node, children)
348332
}
349333

350334
return node

src/parse.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,7 @@ export class Parser {
302302
this.arena.set_start_offset(declaration, prop_start)
303303

304304
// Store property name position
305-
this.arena.set_content_start(declaration, prop_start)
306-
this.arena.set_content_length(declaration, prop_end - prop_start)
305+
this.arena.set_content_range(declaration, prop_start, prop_end)
307306

308307
// Check for vendor prefix and set flag if detected
309308
if (is_vendor_prefixed(this.source, prop_start, prop_end)) {
@@ -352,8 +351,7 @@ export class Parser {
352351
let trimmed = trim_boundaries(this.source, value_start, value_end)
353352
if (trimmed) {
354353
// Store raw value string offsets (for fast string access)
355-
this.arena.set_value_start(declaration, trimmed[0])
356-
this.arena.set_value_length(declaration, trimmed[1] - trimmed[0])
354+
this.arena.set_value_range(declaration, trimmed[0], trimmed[1])
357355

358356
// Parse value into structured nodes (only if enabled)
359357
if (this.parse_values_enabled && this.value_parser) {
@@ -414,8 +412,7 @@ export class Parser {
414412
this.arena.set_start_offset(at_rule, at_rule_start)
415413

416414
// Store at-rule name in contentStart/contentLength
417-
this.arena.set_content_start(at_rule, name_start)
418-
this.arena.set_content_length(at_rule, name_length)
415+
this.arena.set_content_range(at_rule, name_start, name_start + name_length)
419416

420417
// Track prelude start and end
421418
let prelude_start = this.lexer.token_start
@@ -432,8 +429,7 @@ export class Parser {
432429
// Store prelude position (trimmed)
433430
let trimmed = trim_boundaries(this.source, prelude_start, prelude_end)
434431
if (trimmed) {
435-
this.arena.set_value_start(at_rule, trimmed[0])
436-
this.arena.set_value_length(at_rule, trimmed[1] - trimmed[0])
432+
this.arena.set_value_range(at_rule, trimmed[0], trimmed[1])
437433

438434
// Parse prelude if enabled
439435
if (this.prelude_parser) {

0 commit comments

Comments
 (0)