diff --git a/src/arena.ts b/src/arena.ts index 27a631f..b9dfa41 100644 --- a/src/arena.ts +++ b/src/arena.ts @@ -373,4 +373,92 @@ export class CSSDataArena { has_flag(node_index: number, flag: number): boolean { return (this.get_flags(node_index) & flag) !== 0 } + + // --- Node Creation Helpers (Used by parsers) --- + + /** + * Create a node with standard properties and line/column tracking + * Used by parsers that don't need separate content tracking + * + * @param type - Node type constant + * @param start - Start offset in source + * @param end - End offset in source + * @param line - Start line number (from lexer) + * @param column - Start column number (from lexer) + * @returns The created node ID + */ + create_node_with_line_column(type: number, start: number, end: number, line: number, column: number): number { + let node = this.create_node() + this.set_type(node, type) + this.set_start_offset(node, start) + this.set_length(node, end - start) + this.set_start_line(node, line) + this.set_start_column(node, column) + return node + } + + /** + * Create a node with content tracking (content and value have same range) + * Used by selectors and nodes where content range equals the full node range + * + * @param type - Node type constant + * @param start - Start offset in source + * @param end - End offset in source + * @param line - Start line number (from lexer) + * @param column - Start column number (from lexer) + * @returns The created node ID + */ + create_node_with_content(type: number, start: number, end: number, line: number, column: number): number { + let node = this.create_node_with_line_column(type, start, end, line, column) + this.set_content_start(node, start) + this.set_content_length(node, end - start) + return node + } + + /** + * Link an array of nodes as siblings and attach to parent + * Sets both first_child and last_child on parent + * Reduces code duplication in node tree construction + * + * @param parent - Parent node ID + * @param nodes - Array of node IDs to link as siblings + */ + link_nodes_as_children(parent: number, nodes: number[]): void { + if (nodes.length === 0) return + + this.set_first_child(parent, nodes[0]) + if (nodes.length > 1) { + this.set_last_child(parent, nodes[nodes.length - 1]) + for (let i = 0; i < nodes.length - 1; i++) { + this.set_next_sibling(nodes[i], nodes[i + 1]) + } + } + } + + /** + * Set content range (start offset and length) on a node + * Convenience method that avoids calculating length separately + * + * @param node - Node ID + * @param start - Content start offset + * @param end - Content end offset (exclusive) + */ + set_content_range(node: number, start: number, end: number): void { + this.set_content_start(node, start) + this.set_content_length(node, end - start) + } + + /** + * Set value range (start offset and length) on a node + * Convenience method that avoids calculating length separately + * + * @param node - Node ID + * @param start - Value start offset + * @param end - Value end offset (exclusive) + */ + set_value_range(node: number, start: number, end: number): void { + this.set_value_start(node, start) + this.set_value_length(node, end - start) + } + } diff --git a/src/parse-atrule-prelude.ts b/src/parse-atrule-prelude.ts index f98c089..d5e7f89 100644 --- a/src/parse-atrule-prelude.ts +++ b/src/parse-atrule-prelude.ts @@ -99,13 +99,7 @@ export class AtRulePreludeParser { } private create_node(type: number, start: number, end: number): number { - let node = this.arena.create_node() - this.arena.set_type(node, type) - this.arena.set_start_offset(node, start) - this.arena.set_length(node, end - start) - this.arena.set_start_line(node, this.lexer.token_line) - this.arena.set_start_column(node, this.lexer.token_column) - return node + return this.arena.create_node_with_line_column(type, start, end, this.lexer.token_line, this.lexer.token_column) } private is_and_or_not(str: string): boolean { @@ -218,8 +212,7 @@ export class AtRulePreludeParser { // Store feature content (without parentheses) in value fields, trimmed let trimmed = trim_boundaries(this.source, content_start, content_end) if (trimmed) { - this.arena.set_value_start(feature, trimmed[0]) - this.arena.set_value_length(feature, trimmed[1] - trimmed[0]) + this.arena.set_value_range(feature, trimmed[0], trimmed[1]) } return feature @@ -316,8 +309,7 @@ export class AtRulePreludeParser { // Store query content in value fields, trimmed let trimmed = trim_boundaries(this.source, content_start, content_end) if (trimmed) { - this.arena.set_value_start(query, trimmed[0]) - this.arena.set_value_length(query, trimmed[1] - trimmed[0]) + this.arena.set_value_range(query, trimmed[0], trimmed[1]) } nodes.push(query) @@ -509,8 +501,7 @@ export class AtRulePreludeParser { if (content_length > 0) { let trimmed = trim_boundaries(this.source, content_start, content_start + content_length) if (trimmed) { - this.arena.set_content_start(layer_node, trimmed[0]) - this.arena.set_content_length(layer_node, trimmed[1] - trimmed[0]) + this.arena.set_content_range(layer_node, trimmed[0], trimmed[1]) } } diff --git a/src/parse-selector.ts b/src/parse-selector.ts index 3091767..f5d6e4e 100644 --- a/src/parse-selector.ts +++ b/src/parse-selector.ts @@ -155,13 +155,7 @@ export class SelectorParser { this.arena.set_start_column(list_node, list_column) // Link selector wrapper nodes as children - this.arena.set_first_child(list_node, selectors[0]) - this.arena.set_last_child(list_node, selectors[selectors.length - 1]) - - // Chain selector wrappers as siblings (simple since they're already wrapped) - for (let i = 0; i < selectors.length - 1; i++) { - this.arena.set_next_sibling(selectors[i], selectors[i + 1]) - } + this.arena.link_nodes_as_children(list_node, selectors) return list_node } @@ -941,15 +935,7 @@ export class SelectorParser { } private create_node(type: number, start: number, end: number): number { - let node = this.arena.create_node() - this.arena.set_type(node, type) - this.arena.set_start_offset(node, start) - this.arena.set_length(node, end - start) - this.arena.set_start_line(node, this.lexer.line) - this.arena.set_start_column(node, this.lexer.column) - this.arena.set_content_start(node, start) - this.arena.set_content_length(node, end - start) - return node + return this.arena.create_node_with_content(type, start, end, this.lexer.line, this.lexer.column) } // Helper to skip whitespace diff --git a/src/parse-value.ts b/src/parse-value.ts index 508d10b..b21fa2e 100644 --- a/src/parse-value.ts +++ b/src/parse-value.ts @@ -121,12 +121,8 @@ export class ValueParser { } private create_node(node_type: number, start: number, end: number): number { - let node = this.arena.create_node() - this.arena.set_type(node, node_type) - this.arena.set_start_offset(node, start) + let node = this.arena.create_node_with_line_column(node_type, start, end, this.lexer.line, this.lexer.column) let length = end - start - this.arena.set_length(node, length) - // Skip set_content_start since it would compute delta = start - start = 0 (already zero-initialized) this.arena.set_content_length(node, length) return node } @@ -270,13 +266,7 @@ export class ValueParser { // Link arguments as children if (args.length > 0) { - this.arena.set_first_child(node, args[0]) - this.arena.set_last_child(node, args[args.length - 1]) - - // Chain arguments as siblings - for (let i = 0; i < args.length - 1; i++) { - this.arena.set_next_sibling(args[i], args[i + 1]) - } + this.arena.link_nodes_as_children(node, args) } return node @@ -328,13 +318,7 @@ export class ValueParser { // Link children as siblings if (children.length > 0) { - this.arena.set_first_child(node, children[0]) - this.arena.set_last_child(node, children[children.length - 1]) - - // Chain children as siblings - for (let i = 0; i < children.length - 1; i++) { - this.arena.set_next_sibling(children[i], children[i + 1]) - } + this.arena.link_nodes_as_children(node, children) } return node diff --git a/src/parse.ts b/src/parse.ts index f85fccd..ed08fd7 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -302,8 +302,7 @@ export class Parser { this.arena.set_start_offset(declaration, prop_start) // Store property name position - this.arena.set_content_start(declaration, prop_start) - this.arena.set_content_length(declaration, prop_end - prop_start) + this.arena.set_content_range(declaration, prop_start, prop_end) // Check for vendor prefix and set flag if detected if (is_vendor_prefixed(this.source, prop_start, prop_end)) { @@ -352,8 +351,7 @@ export class Parser { let trimmed = trim_boundaries(this.source, value_start, value_end) if (trimmed) { // Store raw value string offsets (for fast string access) - this.arena.set_value_start(declaration, trimmed[0]) - this.arena.set_value_length(declaration, trimmed[1] - trimmed[0]) + this.arena.set_value_range(declaration, trimmed[0], trimmed[1]) // Parse value into structured nodes (only if enabled) if (this.parse_values_enabled && this.value_parser) { @@ -414,8 +412,7 @@ export class Parser { this.arena.set_start_offset(at_rule, at_rule_start) // Store at-rule name in contentStart/contentLength - this.arena.set_content_start(at_rule, name_start) - this.arena.set_content_length(at_rule, name_length) + this.arena.set_content_range(at_rule, name_start, name_start + name_length) // Track prelude start and end let prelude_start = this.lexer.token_start @@ -432,8 +429,7 @@ export class Parser { // Store prelude position (trimmed) let trimmed = trim_boundaries(this.source, prelude_start, prelude_end) if (trimmed) { - this.arena.set_value_start(at_rule, trimmed[0]) - this.arena.set_value_length(at_rule, trimmed[1] - trimmed[0]) + this.arena.set_value_range(at_rule, trimmed[0], trimmed[1]) // Parse prelude if enabled if (this.prelude_parser) {