Class: Ibex::Runtime::CST::SyntaxNode

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initialize(green:, kinds:, parent: nil, index: 0, offset: 0, trivia_policy: :leading, source_text: nil) ⇒ SyntaxNode

Returns a new instance of SyntaxNode.

RBS:

  • (green: GreenNode, kinds: Kind, ?parent: SyntaxNode?, ?index: Integer, ?offset: Integer, ?trivia_policy: Symbol, ?source_text: SourceText?) -> void

Parameters:

  • green: (GreenNode)
  • kinds: (Kind)
  • parent: (SyntaxNode, nil) (defaults to: nil)
  • index: (Integer) (defaults to: 0)
  • offset: (Integer) (defaults to: 0)
  • trivia_policy: (Symbol) (defaults to: :leading)
  • source_text: (SourceText, nil) (defaults to: nil)


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

#greenGreenNode (readonly)

Signature:

  • GreenNode

Returns:



17
18
19
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 17

def green
  @green
end

#indexInteger (readonly)

Signature:

  • Integer

Returns:

  • (Integer)


19
20
21
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 19

def index
  @index
end

#kindsKind (readonly)

Signature:

  • Kind

Returns:



21
22
23
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 21

def kinds
  @kinds
end

#offsetInteger (readonly)

Signature:

  • Integer

Returns:

  • (Integer)


20
21
22
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 20

def offset
  @offset
end

#parentSyntaxNode? (readonly)

Signature:

  • SyntaxNode?

Returns:



18
19
20
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 18

def parent
  @parent
end

#source_textSourceText (readonly)

Signature:

  • SourceText

Returns:



23
24
25
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 23

def source_text
  @source_text
end

#trivia_policySymbol (readonly)

Signature:

  • Symbol

Returns:

  • (Symbol)


22
23
24
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 22

def trivia_policy
  @trivia_policy
end

Instance Method Details

#==(other) ⇒ Boolean

RBS:

  • (untyped other) -> bool

Parameters:

  • other (Object)

Returns:

  • (Boolean)


352
353
354
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 352

def ==(other)
  other.is_a?(SyntaxNode) && @green == other.green
end

#ancestorsEnumerator[SyntaxNode, void]

RBS:

  • () -> Enumerator[SyntaxNode, void]

Returns:



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

RBS:

  • (SyntaxAnnotation annotation) -> SyntaxNode

Parameters:

Returns:



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]

RBS:

  • (SyntaxAnnotation annotation) -> Enumerator[SyntaxNode, void]

Parameters:

Returns:



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

RBS:

  • (Integer child_index) -> element

Parameters:

  • child_index (Integer)

Returns:

  • (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?

RBS:

  • (Integer source_offset) -> element?

Parameters:

  • source_offset (Integer)

Returns:

  • (element, nil)


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_nodesArray[SyntaxNode]

RBS:

  • () -> Array[SyntaxNode]

Returns:



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

#childrenArray[element]

RBS:

  • () -> Array[element]

Returns:

  • (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_eofSyntaxToken?

RBS:

  • () -> SyntaxToken?

Returns:



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

RBS:

  • () -> bool

Returns:

  • (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.

RBS:

  • (Range[Integer] range) -> element?

Parameters:

  • range (Range[Integer])

Returns:

  • (element, nil)


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

RBS:

  • (Integer start_offset, Integer end_offset) -> bool

Parameters:

  • start_offset (Integer)
  • end_offset (Integer)

Returns:

  • (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

#cursorCursor

RBS:

  • () -> Cursor

Returns:



344
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 344

def cursor = Cursor.new(self)

#deconstructArray[element]

RBS:

  • () -> Array[element]

Returns:

  • (Array[element])


357
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 357

def deconstruct = children

#deconstruct_keys(_keys) ⇒ Hash[Symbol, untyped]

RBS:

  • (Array[Symbol]?) -> Hash[Symbol, untyped]

Parameters:

  • (Array[Symbol], nil)

Returns:

  • (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

#descendantsEnumerator[element, void]

RBS:

  • () -> Enumerator[element, void]

Returns:

  • (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

#eachEnumerator[element, self] #eachself

Overloads:

  • #eachEnumerator[element, self]

    Returns:

    • (Enumerator[element, self])
  • #eachself

    Returns:

    • (self)

RBS:

  • def each: () -> Enumerator[element, self]
            | () { (element) -> void } -> self

Yields:

Yield Parameters:

  • arg0 (element)

Yield Returns:

  • (void)


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_errorEnumerator[element, void] #each_errorself

Overloads:

  • #each_errorEnumerator[element, void]

    Returns:

    • (Enumerator[element, void])
  • #each_errorself

    Returns:

    • (self)

RBS:

  • () -> Enumerator[element, void]

  • () { (element) -> void } -> self

Yields:

Yield Parameters:

  • arg0 (element)

Yield Returns:

  • (void)


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.

RBS:

  • () -> void



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

RBS:

  • () -> bool

Returns:

  • (Boolean)


192
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 192

def error? = @kinds.error?(kind)

#find(kind:) ⇒ Enumerator[element, void]

RBS:

  • (kind: Integer | String | Symbol) -> Enumerator[element, void]

Parameters:

  • kind: (Integer, String, Symbol)

Returns:

  • (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_tokenSyntaxToken?

RBS:

  • () -> SyntaxToken?

Returns:



102
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 102

def first_token = tokens.first

#full_spanRange[Integer]

RBS:

  • () -> Range[Integer]

Returns:

  • (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_textString Also known as: to_source

RBS:

  • () -> String

Returns:

  • (String)


170
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 170

def full_text = @green.to_source

#incomplete_input?Boolean

RBS:

  • () -> bool

Returns:

  • (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

RBS:

  • (Integer child_index, GreenNode | GreenToken | SyntaxNode | SyntaxToken child) -> SyntaxNode

Parameters:

Returns:



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

#kindInteger

RBS:

  • () -> Integer

Returns:

  • (Integer)


39
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 39

def kind = @green.kind

#kind_nameString

RBS:

  • () -> String

Returns:

  • (String)


42
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 42

def kind_name = @kinds.name(kind)

#last_tokenSyntaxToken?

RBS:

  • () -> SyntaxToken?

Returns:



105
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 105

def last_token = tokens.last

#locationIbex::Location

RBS:

  • () -> Ibex::Location

Returns:

  • (Ibex::Location)


167
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 167

def location = @source_text.location(span)

#missing?Boolean

RBS:

  • () -> bool

Returns:

  • (Boolean)


195
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 195

def missing? = false

#next_siblingelement?

RBS:

  • () -> element?

Returns:

  • (element, nil)


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 ]

RBS:

  • (Range[Integer] range) -> [Integer, Integer]

Parameters:

  • range (Range[Integer])

Returns:

  • ([ 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

RBS:

  • (Integer child_index) -> Integer

Parameters:

  • child_index (Integer)

Returns:

  • (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_siblingelement?

RBS:

  • () -> element?

Returns:

  • (element, nil)


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_idInteger

Green kinds replace occurrence-local production ids. Keep the legacy key and reader with the historical synthetic sentinel.

RBS:

  • () -> Integer

Returns:

  • (Integer)


51
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 51

def production_id = -1

#remove_child(child_index) ⇒ SyntaxNode

RBS:

  • (Integer child_index) -> SyntaxNode

Parameters:

  • child_index (Integer)

Returns:



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

RBS:

  • (GreenNode | GreenToken | SyntaxNode | SyntaxToken replacement) -> SyntaxNode

Parameters:

Returns:



204
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 204

def replace_with(replacement) = Editing.replace(self, replacement)

#rootSyntaxNode

RBS:

  • () -> SyntaxNode

Returns:



149
150
151
152
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 149

def root
  parent = @parent
  parent ? parent.root : self
end

#root_sourceString

RBS:

  • () -> String

Returns:

  • (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

RBS:

  • (SyntaxNode other) -> bool

Parameters:

Returns:

  • (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

#spanRange[Integer]

RBS:

  • () -> Range[Integer]

Returns:

  • (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

#symbolString

Compatibility name for the physical grammar symbol.

RBS:

  • () -> String

Returns:

  • (String)


46
# File 'lib/ibex/runtime/cst/syntax_node.rb', line 46

def symbol = @kinds.name(@kinds.nonterminal_of(kind))

#textString

RBS:

  • () -> String

Returns:

  • (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_hHash[Symbol, untyped]

RBS:

  • () -> Hash[Symbol, untyped]

Returns:

  • (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.

RBS:

  • (Integer source_offset) -> SyntaxToken?

Parameters:

  • source_offset (Integer)

Returns:



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

#tokensArray[SyntaxToken]

RBS:

  • () -> Array[SyntaxToken]

Returns:



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_triviaArray[GreenTrivia]

Compatibility view of file-tail trivia. The Red/Green layout owns it on EOF rather than on the start node.

RBS:

  • () -> Array[GreenTrivia]

Returns:



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

RBS:

  • () { (Symbol, element) -> void } -> self

Yields:

Yield Parameters:

  • arg0 (Symbol)
  • arg1 (element)

Yield Returns:

  • (void)

Returns:

  • (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

RBS:

  • (Integer child_index, GreenNode | GreenToken | SyntaxNode | SyntaxToken child) -> SyntaxNode

Parameters:

Returns:



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