Class: TreeHaver::Base::Node Abstract
- Inherits:
-
Object
- Object
- TreeHaver::Base::Node
- Includes:
- Comparable, Enumerable
- Defined in:
- lib/tree_haver/base/node.rb
Overview
Subclasses must implement #type, #start_byte, #end_byte, and #children
Base class for all backend Node implementations
This class defines the API contract for Node objects across all backends. It provides shared implementation for common behaviors and documents required/optional methods that subclasses must implement.
Backend Architecture
TreeHaver supports two categories of backends:
Tree-sitter Backends (MRI, Rust, FFI, Java)
These backends use the native tree-sitter library (via different bindings).
They return raw ::TreeSitter::Node objects which are wrapped by
TreeHaver::Node (which inherits from this class).
- Backend Tree#root_node returns:
::TreeSitter::Node(raw) - TreeHaver::Tree#root_node wraps it in:
TreeHaver::Node - These backends do NOT define their own Tree/Node classes
Pure-Ruby/Plugin Backends (Citrus, Prism, Psych, Commonmarker, Markly)
These backends define their own complete implementations:
Backend::X::Node- wraps parser-specific node objectsBackend::X::Tree- wraps parser-specific tree objects
For consistency, these should also inherit from Base::Node and Base::Tree.
Direct Known Subclasses
TreeHaver::Backends::Citrus::Node, TreeHaver::Backends::Parslet::Node, TreeHaver::Backends::Prism::Node, TreeHaver::Backends::Psych::Node, TreeHaver::Backends::Tslp::Node
Instance Attribute Summary collapse
-
#inner_node ⇒ Object
readonly
The underlying backend-specific node object.
-
#lines ⇒ Array<String>
readonly
Source lines for byte offset calculations.
-
#source ⇒ String
readonly
The source text.
Instance Method Summary collapse
-
#<=>(other) ⇒ Integer?
Comparison based on byte range.
-
#==(other) ⇒ Boolean
Equality based on type and byte range.
-
#child(index) ⇒ Node?
Get a child node by index.
-
#child_by_field_name(_name) ⇒ Node?
Get a child by field name.
-
#child_count ⇒ Integer
Get the number of child nodes.
-
#children ⇒ Array<Node>
Get all children as an array.
-
#each {|Node| ... } ⇒ Object
Iterate over children.
-
#end_byte ⇒ Integer
Get byte offset where the node ends.
-
#end_line ⇒ Integer
Get 1-based end line.
-
#end_point ⇒ Hash{Symbol => Integer}
Get end position (row/col) - 0-based.
-
#first_child ⇒ Node?
Retrieve the first child.
-
#has_error? ⇒ Boolean
Check if this node represents a syntax error.
-
#initialize(node, source: nil, lines: nil) ⇒ Node
constructor
Create a new Node wrapper.
-
#inspect ⇒ String
Human-readable representation.
-
#last_child ⇒ Node?
Retrieve the last child.
-
#missing? ⇒ Boolean
Check if this node was inserted for error recovery.
-
#named? ⇒ Boolean
(also: #structural?)
Check if this node is named (structural).
-
#native_type ⇒ String
The parser's original node type before a backend applies any portable vocabulary aliases.
-
#next_sibling ⇒ Node?
Get the next sibling node.
-
#parent ⇒ Node?
Get the parent node.
-
#prev_sibling ⇒ Node?
Get the previous sibling node.
-
#source_position ⇒ Hash{Symbol => Integer}
Get unified source position hash.
-
#start_byte ⇒ Integer
Get byte offset where the node starts.
-
#start_line ⇒ Integer
Get 1-based start line.
-
#start_point ⇒ Hash{Symbol => Integer}
Get start position (row/col) - 0-based.
-
#text ⇒ String
Get the text content of this node.
-
#to_s ⇒ String
String conversion returns the text content.
-
#type ⇒ String
Get the node type as a string.
Constructor Details
#initialize(node, source: nil, lines: nil) ⇒ Node
Create a new Node wrapper
57 58 59 60 61 |
# File 'lib/tree_haver/base/node.rb', line 57 def initialize(node, source: nil, lines: nil) @inner_node = node @source = source @lines = lines || source&.lines || [] end |
Instance Attribute Details
#inner_node ⇒ Object (readonly)
The underlying backend-specific node object
42 43 44 |
# File 'lib/tree_haver/base/node.rb', line 42 def inner_node @inner_node end |
#lines ⇒ Array<String> (readonly)
Source lines for byte offset calculations
50 51 52 |
# File 'lib/tree_haver/base/node.rb', line 50 def lines @lines end |
#source ⇒ String (readonly)
The source text
46 47 48 |
# File 'lib/tree_haver/base/node.rb', line 46 def source @source end |
Instance Method Details
#<=>(other) ⇒ Integer?
Comparison based on byte range
212 213 214 215 216 217 218 219 |
# File 'lib/tree_haver/base/node.rb', line 212 def <=>(other) return unless other.respond_to?(:start_byte) && other.respond_to?(:end_byte) cmp = start_byte <=> other.start_byte return cmp unless cmp.zero? end_byte <=> other.end_byte end |
#==(other) ⇒ Boolean
Equality based on type and byte range
301 302 303 304 305 |
# File 'lib/tree_haver/base/node.rb', line 301 def ==(other) return false unless other.respond_to?(:type) && other.respond_to?(:start_byte) && other.respond_to?(:end_byte) type == other.type && start_byte == other.start_byte && end_byte == other.end_byte end |
#child(index) ⇒ Node?
Get a child node by index
Returns nil for negative indices or indices out of bounds. This matches tree-sitter behavior where negative indices are invalid.
112 113 114 115 116 117 |
# File 'lib/tree_haver/base/node.rb', line 112 def child(index) return if index.negative? return if index >= child_count children[index] end |
#child_by_field_name(_name) ⇒ Node?
Get a child by field name
191 192 193 |
# File 'lib/tree_haver/base/node.rb', line 191 def child_by_field_name(_name) nil end |
#child_count ⇒ Integer
Get the number of child nodes
101 102 103 |
# File 'lib/tree_haver/base/node.rb', line 101 def child_count children.size end |
#children ⇒ Array<Node>
Get all children as an array
93 94 95 |
# File 'lib/tree_haver/base/node.rb', line 93 def children raise NotImplementedError, "#{self.class}#children must be implemented" end |
#each {|Node| ... } ⇒ Object
Iterate over children
121 122 123 124 125 |
# File 'lib/tree_haver/base/node.rb', line 121 def each(&block) return to_enum(__method__) unless block children.each(&block) end |
#end_byte ⇒ Integer
Get byte offset where the node ends
87 88 89 |
# File 'lib/tree_haver/base/node.rb', line 87 def end_byte raise NotImplementedError, "#{self.class}#end_byte must be implemented" end |
#end_line ⇒ Integer
Get 1-based end line
235 236 237 238 239 240 241 242 243 |
# File 'lib/tree_haver/base/node.rb', line 235 def end_line ep = end_point row = if ep.is_a?(Hash) ep[:row] else (ep.respond_to?(:row) ? ep.row : 0) end row + 1 end |
#end_point ⇒ Hash{Symbol => Integer}
Get end position (row/col) - 0-based
203 204 205 |
# File 'lib/tree_haver/base/node.rb', line 203 def end_point { row: 0, column: 0 } end |
#first_child ⇒ Node?
Retrieve the first child
129 130 131 |
# File 'lib/tree_haver/base/node.rb', line 129 def first_child children.first end |
#has_error? ⇒ Boolean
Check if this node represents a syntax error
170 171 172 |
# File 'lib/tree_haver/base/node.rb', line 170 def has_error? false end |
#inspect ⇒ String
Human-readable representation
282 283 284 285 286 287 288 289 290 |
# File 'lib/tree_haver/base/node.rb', line 282 def inspect class_name = self.class.name || "#{self.class.superclass&.name}(anonymous)" node_type = begin type rescue NotImplementedError '(not implemented)' end "#<#{class_name} type=#{node_type}>" end |
#last_child ⇒ Node?
Retrieve the last child
135 136 137 |
# File 'lib/tree_haver/base/node.rb', line 135 def last_child children.last end |
#missing? ⇒ Boolean
Check if this node was inserted for error recovery
176 177 178 |
# File 'lib/tree_haver/base/node.rb', line 176 def missing? false end |
#named? ⇒ Boolean Also known as: structural?
Check if this node is named (structural)
161 162 163 |
# File 'lib/tree_haver/base/node.rb', line 161 def named? true end |
#native_type ⇒ String
The parser's original node type before a backend applies any portable vocabulary aliases. Backends without aliases use their public type.
75 76 77 |
# File 'lib/tree_haver/base/node.rb', line 75 def native_type type end |
#next_sibling ⇒ Node?
Get the next sibling node
149 150 151 |
# File 'lib/tree_haver/base/node.rb', line 149 def next_sibling nil end |
#parent ⇒ Node?
Get the parent node
143 144 145 |
# File 'lib/tree_haver/base/node.rb', line 143 def parent nil end |
#prev_sibling ⇒ Node?
Get the previous sibling node
155 156 157 |
# File 'lib/tree_haver/base/node.rb', line 155 def prev_sibling nil end |
#source_position ⇒ Hash{Symbol => Integer}
Get unified source position hash
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/tree_haver/base/node.rb', line 247 def source_position sp = start_point ep = end_point sp_row = if sp.is_a?(Hash) sp[:row] else (sp.respond_to?(:row) ? sp.row : 0) end sp_col = if sp.is_a?(Hash) sp[:column] else (sp.respond_to?(:column) ? sp.column : 0) end ep_row = if ep.is_a?(Hash) ep[:row] else (ep.respond_to?(:row) ? ep.row : 0) end ep_col = if ep.is_a?(Hash) ep[:column] else (ep.respond_to?(:column) ? ep.column : 0) end { start_line: sp_row + 1, end_line: ep_row + 1, start_column: sp_col, end_column: ep_col } end |
#start_byte ⇒ Integer
Get byte offset where the node starts
81 82 83 |
# File 'lib/tree_haver/base/node.rb', line 81 def start_byte raise NotImplementedError, "#{self.class}#start_byte must be implemented" end |
#start_line ⇒ Integer
Get 1-based start line
223 224 225 226 227 228 229 230 231 |
# File 'lib/tree_haver/base/node.rb', line 223 def start_line sp = start_point row = if sp.is_a?(Hash) sp[:row] else (sp.respond_to?(:row) ? sp.row : 0) end row + 1 end |
#start_point ⇒ Hash{Symbol => Integer}
Get start position (row/col) - 0-based
197 198 199 |
# File 'lib/tree_haver/base/node.rb', line 197 def start_point { row: 0, column: 0 } end |
#text ⇒ String
Get the text content of this node
182 183 184 185 186 |
# File 'lib/tree_haver/base/node.rb', line 182 def text return '' unless source source[start_byte...end_byte] || '' end |
#to_s ⇒ String
String conversion returns the text content
294 295 296 |
# File 'lib/tree_haver/base/node.rb', line 294 def to_s text end |
#type ⇒ String
Get the node type as a string
67 68 69 |
# File 'lib/tree_haver/base/node.rb', line 67 def type raise NotImplementedError, "#{self.class}#type must be implemented" end |