Class: Ibex::Runtime::CST::SyntaxNode
- Inherits:
-
Object
- Object
- Ibex::Runtime::CST::SyntaxNode
- Includes:
- Enumerable, Enumerable[element]
- Defined in:
- lib/ibex/runtime/cst/syntax_node.rb,
sig/ibex/runtime/cst/syntax_node.rbs
Overview
Lazy Red navigation facade for one Green node occurrence.
Instance Attribute Summary collapse
- #green ⇒ GreenNode readonly
- #index ⇒ Integer readonly
- #kinds ⇒ Kind readonly
- #offset ⇒ Integer readonly
- #parent ⇒ SyntaxNode? readonly
- #source_text ⇒ SourceText readonly
- #trivia_policy ⇒ Symbol readonly
Instance Method Summary collapse
- #==(other) ⇒ Boolean
- #ancestors ⇒ Enumerator[SyntaxNode, void]
- #annotate(annotation) ⇒ SyntaxNode
- #annotated(annotation) ⇒ Enumerator[SyntaxNode, void]
- #child_at(child_index) ⇒ element
- #child_covering_offset(source_offset) ⇒ element?
- #child_nodes ⇒ Array[SyntaxNode]
- #children ⇒ Array[element]
- #compatibility_eof ⇒ SyntaxToken?
- #contains_error? ⇒ Boolean
-
#covering(range) ⇒ element?
Return the smallest syntax element whose full span covers a range.
- #covers_offsets?(start_offset, end_offset) ⇒ Boolean
- #cursor ⇒ Cursor
- #deconstruct ⇒ Array[element]
- #deconstruct_keys(_keys) ⇒ Hash[Symbol, untyped]
- #descendants ⇒ Enumerator[element, void]
- #each {|arg0| ... } ⇒ Object
- #each_error {|arg0| ... } ⇒ Object
- #ensure_coordinates! ⇒ void
- #error? ⇒ Boolean
- #find(kind:) ⇒ Enumerator[element, void]
- #first_token ⇒ SyntaxToken?
- #full_span ⇒ Range[Integer]
- #full_text ⇒ String (also: #to_source)
- #incomplete_input? ⇒ Boolean
-
#initialize(green:, kinds:, parent: nil, index: 0, offset: 0, trivia_policy: :leading, source_text: nil) ⇒ SyntaxNode
constructor
A new instance of SyntaxNode.
- #insert_child(child_index, child) ⇒ SyntaxNode
- #kind ⇒ Integer
- #kind_name ⇒ String
- #last_token ⇒ SyntaxToken?
- #location ⇒ Ibex::Location
- #missing? ⇒ Boolean
- #next_sibling ⇒ element?
- #normalize_range(range) ⇒ [ Integer, Integer ]
- #offset_for(child_index) ⇒ Integer
- #prev_sibling ⇒ element?
-
#production_id ⇒ Integer
Green kinds replace occurrence-local production ids.
- #remove_child(child_index) ⇒ SyntaxNode
- #replace_with(replacement) ⇒ SyntaxNode
- #root ⇒ SyntaxNode
- #root_source ⇒ String
- #same_node?(other) ⇒ Boolean
- #span ⇒ Range[Integer]
-
#symbol ⇒ String
Compatibility name for the physical grammar symbol.
- #text ⇒ String
- #to_h ⇒ Hash[Symbol, untyped]
-
#token_at(source_offset) ⇒ SyntaxToken?
Find the full-span-owning token for a byte offset.
- #tokens ⇒ Array[SyntaxToken]
-
#trailing_trivia ⇒ Array[GreenTrivia]
Compatibility view of file-tail trivia.
- #walk {|arg0, arg1| ... } ⇒ self
- #with_child(child_index, child) ⇒ SyntaxNode
Constructor Details
#initialize(green:, kinds:, parent: nil, index: 0, offset: 0, trivia_policy: :leading, source_text: nil) ⇒ SyntaxNode
Returns a new instance of SyntaxNode.
27 28 29 30 31 32 33 34 35 36 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 27 def initialize(green:, kinds:, parent: nil, index: 0, offset: 0, trivia_policy: :leading, source_text: nil) @green = green @kinds = kinds @parent = parent @index = index @offset = offset @trivia_policy = trivia_policy @source_text = source_text || SourceText.new(root_source) @children = Array.new(@green.children.length) #: Array[element?] end |
Instance Attribute Details
#green ⇒ GreenNode (readonly)
17 18 19 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 17 def green @green end |
#index ⇒ Integer (readonly)
19 20 21 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 19 def index @index end |
#kinds ⇒ Kind (readonly)
21 22 23 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 21 def kinds @kinds end |
#offset ⇒ Integer (readonly)
20 21 22 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 20 def offset @offset end |
#parent ⇒ SyntaxNode? (readonly)
18 19 20 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 18 def parent @parent end |
#source_text ⇒ SourceText (readonly)
23 24 25 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 23 def source_text @source_text end |
#trivia_policy ⇒ Symbol (readonly)
22 23 24 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 22 def trivia_policy @trivia_policy end |
Instance Method Details
#==(other) ⇒ Boolean
352 353 354 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 352 def ==(other) other.is_a?(SyntaxNode) && @green == other.green end |
#ancestors ⇒ Enumerator[SyntaxNode, void]
125 126 127 128 129 130 131 132 133 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 125 def ancestors Enumerator.new do |yielder| ancestor = @parent while ancestor yielder << ancestor ancestor = ancestor.parent end end end |
#annotate(annotation) ⇒ SyntaxNode
248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 248 def annotate(annotation) raise TypeError, "annotation must be a SyntaxAnnotation" unless annotation.is_a?(SyntaxAnnotation) return root if @green.annotations.include?(annotation) replace_with( GreenNode.new( kind: @green.kind, children: @green.children, flags: @green.intrinsic_flags, annotations: @green.annotations + [annotation] ) ) end |
#annotated(annotation) ⇒ Enumerator[SyntaxNode, void]
261 262 263 264 265 266 267 268 269 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 261 def annotated(annotation) Enumerator.new do |yielder| visit = lambda do |node| yielder << node if node.green.annotations.include?(annotation) node.child_nodes.each { |child| visit.call(child) } end visit.call(self) end end |
#child_at(child_index) ⇒ element
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 70 def child_at(child_index) cached = @children.fetch(child_index) return cached if cached green_child = @green.children.fetch(child_index) child_offset = offset_for(child_index) value = if green_child.is_a?(GreenNode) self.class.new( green: green_child, kinds: @kinds, parent: self, index: child_index, offset: child_offset, trivia_policy: @trivia_policy, source_text: @source_text ) else SyntaxToken.new(green: green_child, parent: self, index: child_index, offset: child_offset) end @children[child_index] = value end |
#child_covering_offset(source_offset) ⇒ element?
378 379 380 381 382 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 378 def child_covering_offset(source_offset) children.find do |child| source_offset >= child.offset && source_offset < child.offset + child.green.full_width end end |
#child_nodes ⇒ Array[SyntaxNode]
88 89 90 91 92 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 88 def child_nodes nodes = [] #: Array[SyntaxNode] children.each { |child| nodes << child if child.is_a?(SyntaxNode) } nodes.freeze end |
#children ⇒ Array[element]
54 55 56 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 54 def children @green.children.each_index.map { |child_index| child_at(child_index) }.freeze end |
#compatibility_eof ⇒ SyntaxToken?
387 388 389 390 391 392 393 394 395 396 397 398 399 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 387 def compatibility_eof if kind_name == "source_file" token = last_token return token if token&.kind_name == "$eof" end container = @parent return unless container return unless container.kind_name == "source_file" return unless @index.zero? token = container.last_token token if token&.kind_name == "$eof" end |
#contains_error? ⇒ Boolean
198 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 198 def contains_error? = @green.flags.anybits?(Flags::CONTAINS_ERROR) |
#covering(range) ⇒ element?
Return the smallest syntax element whose full span covers a range.
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 289 def covering(range) ensure_coordinates! start_offset, end_offset = normalize_range(range) return unless covers_offsets?(start_offset, end_offset) node = self loop do child = node.children.find do |candidate| candidate_start = candidate.offset candidate_end = candidate.offset + candidate.green.full_width candidate_start <= start_offset && candidate_end >= end_offset end return node unless child return child if child.is_a?(SyntaxToken) node = child end end |
#covers_offsets?(start_offset, end_offset) ⇒ Boolean
426 427 428 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 426 def covers_offsets?(start_offset, end_offset) start_offset >= @offset && end_offset <= @offset + @green.full_width && end_offset >= start_offset end |
#cursor ⇒ Cursor
344 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 344 def cursor = Cursor.new(self) |
#deconstruct ⇒ Array[element]
357 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 357 def deconstruct = children |
#deconstruct_keys(_keys) ⇒ Hash[Symbol, untyped]
360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 360 def deconstruct_keys(_keys) values = { kind: :node, symbol: symbol, production_id: production_id, children: children, location: location, trailing_trivia: trailing_trivia } #: Hash[Symbol, untyped] @kinds.fields(kind).each do |name, slot| index = slot.is_a?(Hash) ? slot.fetch(:index) : slot values[name.to_sym] = child_at(index) end values.freeze end |
#descendants ⇒ Enumerator[element, void]
136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 136 def descendants Enumerator.new do |yielder| visit = lambda do |node| node.children.each do |child| yielder << child visit.call(child) if child.is_a?(SyntaxNode) end end visit.call(self) end end |
#each ⇒ Enumerator[element, self] #each ⇒ self
62 63 64 65 66 67 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 62 def each(&block) return enum_for(:each) unless block children.each(&block) self end |
#each_error ⇒ Enumerator[element, void] #each_error ⇒ self
318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 318 def each_error(&block) errors = Enumerator.new do |yielder| descendants.each do |element| yielder << element if element.error? || element.missing? end end #: Enumerator[element, void] return errors unless block errors.each(&block) self end |
#ensure_coordinates! ⇒ void
This method returns an undefined value.
413 414 415 416 417 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 413 def ensure_coordinates! return unless @trivia_policy == :drop raise TriviaDroppedError, "source coordinates are unavailable when CST trivia is dropped" end |
#error? ⇒ Boolean
192 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 192 def error? = @kinds.error?(kind) |
#find(kind:) ⇒ Enumerator[element, void]
309 310 311 312 313 314 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 309 def find(kind:) expected = kind.is_a?(Integer) ? kind : @kinds.fetch(kind) Enumerator.new do |yielder| descendants.each { |element| yielder << element if element.kind == expected } end end |
#first_token ⇒ SyntaxToken?
102 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 102 def first_token = tokens.first |
#full_span ⇒ Range[Integer]
155 156 157 158 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 155 def full_span ensure_coordinates! @offset...(@offset + @green.full_width) end |
#full_text ⇒ String Also known as: to_source
170 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 170 def full_text = @green.to_source |
#incomplete_input? ⇒ Boolean
201 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 201 def incomplete_input? = @green.flags.anybits?(Flags::INCOMPLETE_INPUT) |
#insert_child(child_index, child) ⇒ SyntaxNode
220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 220 def insert_child(child_index, child) raise IndexError, "child index #{child_index} is outside 0..#{@green.children.length}" unless child_index.between?(0, @green.children.length) children = @green.children.dup children.insert(child_index, Editing.green_element(child)) replace_with( GreenNode.new( kind: @green.kind, children: children, flags: @green.intrinsic_flags, annotations: @green.annotations ) ) end |
#kind ⇒ Integer
39 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 39 def kind = @green.kind |
#kind_name ⇒ String
42 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 42 def kind_name = @kinds.name(kind) |
#last_token ⇒ SyntaxToken?
105 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 105 def last_token = tokens.last |
#location ⇒ Ibex::Location
167 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 167 def location = @source_text.location(span) |
#missing? ⇒ Boolean
195 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 195 def missing? = false |
#next_sibling ⇒ element?
108 109 110 111 112 113 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 108 def next_sibling parent = @parent return unless parent parent.children[@index + 1] end |
#normalize_range(range) ⇒ [ Integer, Integer ]
420 421 422 423 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 420 def normalize_range(range) value = [range.begin, range.end + (range.exclude_end? ? 0 : 1)] #: [Integer, Integer] value.freeze end |
#offset_for(child_index) ⇒ Integer
408 409 410 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 408 def offset_for(child_index) @offset + @green.children.first(child_index).sum(&:full_width) end |
#prev_sibling ⇒ element?
116 117 118 119 120 121 122 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 116 def prev_sibling parent = @parent return unless parent return if @index.zero? parent.children[@index - 1] end |
#production_id ⇒ Integer
Green kinds replace occurrence-local production ids. Keep the legacy key and reader with the historical synthetic sentinel.
51 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 51 def production_id = -1 |
#remove_child(child_index) ⇒ SyntaxNode
235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 235 def remove_child(child_index) children = @green.children.dup children.fetch(child_index) children.delete_at(child_index) replace_with( GreenNode.new( kind: @green.kind, children: children, flags: @green.intrinsic_flags, annotations: @green.annotations ) ) end |
#replace_with(replacement) ⇒ SyntaxNode
204 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 204 def replace_with(replacement) = Editing.replace(self, replacement) |
#root ⇒ SyntaxNode
149 150 151 152 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 149 def root parent = @parent parent ? parent.root : self end |
#root_source ⇒ String
402 403 404 405 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 402 def root_source parent = @parent parent ? parent.source_text.text : @green.to_source end |
#same_node?(other) ⇒ Boolean
347 348 349 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 347 def same_node?(other) root.equal?(other.root) && @green.equal?(other.green) && @offset == other.offset end |
#span ⇒ Range[Integer]
161 162 163 164 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 161 def span ensure_coordinates! (@offset + @green.leading_width)...(@offset + @green.full_width - @green.trailing_width) end |
#symbol ⇒ String
Compatibility name for the physical grammar symbol.
46 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 46 def symbol = @kinds.name(@kinds.nonterminal_of(kind)) |
#text ⇒ String
174 175 176 177 178 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 174 def text start_offset = @green.leading_width width = @green.full_width - @green.leading_width - @green.trailing_width full_text.byteslice(start_offset, width) || "".b end |
#to_h ⇒ Hash[Symbol, untyped]
373 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 373 def to_h = deconstruct_keys(nil) |
#token_at(source_offset) ⇒ SyntaxToken?
Find the full-span-owning token for a byte offset.
273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 273 def token_at(source_offset) ensure_coordinates! return unless source_offset >= @offset && source_offset < @offset + @green.full_width node = self loop do child = node.child_covering_offset(source_offset) return unless child return child if child.is_a?(SyntaxToken) node = child end end |
#tokens ⇒ Array[SyntaxToken]
95 96 97 98 99 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 95 def tokens values = [] #: Array[SyntaxToken] descendants.each { |element| values << element if element.is_a?(SyntaxToken) } values.freeze end |
#trailing_trivia ⇒ Array[GreenTrivia]
Compatibility view of file-tail trivia. The Red/Green layout owns it on EOF rather than on the start node.
183 184 185 186 187 188 189 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 183 def trailing_trivia eof = compatibility_eof return (eof.green.leading + eof.green.trailing).freeze if eof token = last_token token ? token.green.trailing : [] end |
#walk {|arg0, arg1| ... } ⇒ self
331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 331 def walk(&block) raise ArgumentError, "walk requires a block" unless block visit = lambda do |element| block.call(:enter, element) element.children.each { |child| visit.call(child) } if element.is_a?(SyntaxNode) block.call(:leave, element) end visit.call(self) self end |
#with_child(child_index, child) ⇒ SyntaxNode
207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 207 def with_child(child_index, child) children = @green.children.dup children.fetch(child_index) children[child_index] = Editing.green_element(child) replace_with( GreenNode.new( kind: @green.kind, children: children, flags: @green.intrinsic_flags, annotations: @green.annotations ) ) end |