Skip to content

Commit 31cb9d0

Browse files
committed
perf: add new arena node helpers
1 parent 7cdaf1c commit 31cb9d0

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

src/parse-atrule-prelude.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,7 @@ export class AtRulePreludeParser {
9999
}
100100

101101
private create_node(type: number, start: number, end: number): number {
102-
let node = this.arena.create_node()
103-
this.arena.set_type(node, type)
104-
this.arena.set_start_offset(node, start)
105-
this.arena.set_length(node, end - start)
106-
this.arena.set_start_line(node, this.lexer.token_line)
107-
this.arena.set_start_column(node, this.lexer.token_column)
108-
return node
102+
return this.arena.create_node_with_line_column(type, start, end, this.lexer.token_line, this.lexer.token_column)
109103
}
110104

111105
private is_and_or_not(str: string): boolean {
@@ -218,8 +212,7 @@ export class AtRulePreludeParser {
218212
// Store feature content (without parentheses) in value fields, trimmed
219213
let trimmed = trim_boundaries(this.source, content_start, content_end)
220214
if (trimmed) {
221-
this.arena.set_value_start(feature, trimmed[0])
222-
this.arena.set_value_length(feature, trimmed[1] - trimmed[0])
215+
this.arena.set_value_range(feature, trimmed[0], trimmed[1])
223216
}
224217

225218
return feature
@@ -316,8 +309,7 @@ export class AtRulePreludeParser {
316309
// Store query content in value fields, trimmed
317310
let trimmed = trim_boundaries(this.source, content_start, content_end)
318311
if (trimmed) {
319-
this.arena.set_value_start(query, trimmed[0])
320-
this.arena.set_value_length(query, trimmed[1] - trimmed[0])
312+
this.arena.set_value_range(query, trimmed[0], trimmed[1])
321313
}
322314

323315
nodes.push(query)
@@ -509,8 +501,7 @@ export class AtRulePreludeParser {
509501
if (content_length > 0) {
510502
let trimmed = trim_boundaries(this.source, content_start, content_start + content_length)
511503
if (trimmed) {
512-
this.arena.set_content_start(layer_node, trimmed[0])
513-
this.arena.set_content_length(layer_node, trimmed[1] - trimmed[0])
504+
this.arena.set_content_range(layer_node, trimmed[0], trimmed[1])
514505
}
515506
}
516507

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
}
@@ -941,15 +935,7 @@ export class SelectorParser {
941935
}
942936

943937
private create_node(type: number, start: number, end: number): number {
944-
let node = this.arena.create_node()
945-
this.arena.set_type(node, type)
946-
this.arena.set_start_offset(node, start)
947-
this.arena.set_length(node, end - start)
948-
this.arena.set_start_line(node, this.lexer.line)
949-
this.arena.set_start_column(node, this.lexer.column)
950-
this.arena.set_content_start(node, start)
951-
this.arena.set_content_length(node, end - start)
952-
return node
938+
return this.arena.create_node_with_content(type, start, end, this.lexer.line, this.lexer.column)
953939
}
954940

955941
// Helper to skip whitespace

src/parse-value.ts

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

123123
private create_node(node_type: number, start: number, end: number): number {
124-
let node = this.arena.create_node()
125-
this.arena.set_type(node, node_type)
126-
this.arena.set_start_offset(node, start)
124+
let node = this.arena.create_node_with_line_column(node_type, start, end, this.lexer.line, this.lexer.column)
127125
let length = end - start
128-
this.arena.set_length(node, length)
129-
// Skip set_content_start since it would compute delta = start - start = 0 (already zero-initialized)
130126
this.arena.set_content_length(node, length)
131127
return node
132128
}
@@ -270,13 +266,7 @@ export class ValueParser {
270266

271267
// Link arguments as children
272268
if (args.length > 0) {
273-
this.arena.set_first_child(node, args[0])
274-
this.arena.set_last_child(node, args[args.length - 1])
275-
276-
// Chain arguments as siblings
277-
for (let i = 0; i < args.length - 1; i++) {
278-
this.arena.set_next_sibling(args[i], args[i + 1])
279-
}
269+
this.arena.link_nodes_as_children(node, args)
280270
}
281271

282272
return node
@@ -328,13 +318,7 @@ export class ValueParser {
328318

329319
// Link children as siblings
330320
if (children.length > 0) {
331-
this.arena.set_first_child(node, children[0])
332-
this.arena.set_last_child(node, children[children.length - 1])
333-
334-
// Chain children as siblings
335-
for (let i = 0; i < children.length - 1; i++) {
336-
this.arena.set_next_sibling(children[i], children[i + 1])
337-
}
321+
this.arena.link_nodes_as_children(node, children)
338322
}
339323

340324
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)