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 objects -
Backend::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
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).
-
#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
204 205 206 207 208 209 210 211 |
# File 'lib/tree_haver/base/node.rb', line 204 def <=>(other) return unless other.respond_to?(:start_byte) && other.respond_to?(:end_byte) cmp = start_byte <=> other.start_byte return cmp unless cmp == 0 end_byte <=> other.end_byte end |
#==(other) ⇒ Boolean
Equality based on type and byte range
293 294 295 296 297 |
# File 'lib/tree_haver/base/node.rb', line 293 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.
104 105 106 107 108 109 |
# File 'lib/tree_haver/base/node.rb', line 104 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
183 184 185 |
# File 'lib/tree_haver/base/node.rb', line 183 def child_by_field_name(_name) nil end |
#child_count ⇒ Integer
Get the number of child nodes
93 94 95 |
# File 'lib/tree_haver/base/node.rb', line 93 def child_count children.size end |
#children ⇒ Array<Node>
Get all children as an array
85 86 87 |
# File 'lib/tree_haver/base/node.rb', line 85 def children raise NotImplementedError, "#{self.class}#children must be implemented" end |
#each {|Node| ... } ⇒ Object
Iterate over children
113 114 115 116 117 |
# File 'lib/tree_haver/base/node.rb', line 113 def each(&block) return to_enum(__method__) unless block children.each(&block) end |
#end_byte ⇒ Integer
Get byte offset where the node ends
79 80 81 |
# File 'lib/tree_haver/base/node.rb', line 79 def end_byte raise NotImplementedError, "#{self.class}#end_byte must be implemented" end |
#end_line ⇒ Integer
Get 1-based end line
227 228 229 230 231 232 233 234 235 |
# File 'lib/tree_haver/base/node.rb', line 227 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
195 196 197 |
# File 'lib/tree_haver/base/node.rb', line 195 def end_point {row: 0, column: 0} end |
#first_child ⇒ Node?
Retrieve the first child
121 122 123 |
# File 'lib/tree_haver/base/node.rb', line 121 def first_child children.first end |
#has_error? ⇒ Boolean
Check if this node represents a syntax error
162 163 164 |
# File 'lib/tree_haver/base/node.rb', line 162 def has_error? false end |
#inspect ⇒ String
Human-readable representation
274 275 276 277 278 279 280 281 282 |
# File 'lib/tree_haver/base/node.rb', line 274 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
127 128 129 |
# File 'lib/tree_haver/base/node.rb', line 127 def last_child children.last end |
#missing? ⇒ Boolean
Check if this node was inserted for error recovery
168 169 170 |
# File 'lib/tree_haver/base/node.rb', line 168 def missing? false end |
#named? ⇒ Boolean Also known as: structural?
Check if this node is named (structural)
153 154 155 |
# File 'lib/tree_haver/base/node.rb', line 153 def named? true end |
#next_sibling ⇒ Node?
Get the next sibling node
141 142 143 |
# File 'lib/tree_haver/base/node.rb', line 141 def next_sibling nil end |
#parent ⇒ Node?
Get the parent node
135 136 137 |
# File 'lib/tree_haver/base/node.rb', line 135 def parent nil end |
#prev_sibling ⇒ Node?
Get the previous sibling node
147 148 149 |
# File 'lib/tree_haver/base/node.rb', line 147 def prev_sibling nil end |
#source_position ⇒ Hash{Symbol => Integer}
Get unified source position hash
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/tree_haver/base/node.rb', line 239 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
73 74 75 |
# File 'lib/tree_haver/base/node.rb', line 73 def start_byte raise NotImplementedError, "#{self.class}#start_byte must be implemented" end |
#start_line ⇒ Integer
Get 1-based start line
215 216 217 218 219 220 221 222 223 |
# File 'lib/tree_haver/base/node.rb', line 215 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
189 190 191 |
# File 'lib/tree_haver/base/node.rb', line 189 def start_point {row: 0, column: 0} end |
#text ⇒ String
Get the text content of this node
174 175 176 177 178 |
# File 'lib/tree_haver/base/node.rb', line 174 def text return "" unless source source[start_byte...end_byte] || "" end |
#to_s ⇒ String
String conversion returns the text content
286 287 288 |
# File 'lib/tree_haver/base/node.rb', line 286 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 |