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

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/ibex/runtime/cst/syntax_node.rb

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.



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

#greenObject (readonly)

: GreenNode



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

def green
  @green
end

#indexObject (readonly)

: Integer



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

def index
  @index
end

#kindsObject (readonly)

: Kind



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

def kinds
  @kinds
end

#offsetObject (readonly)

: Integer



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

def offset
  @offset
end

#parentObject (readonly)

: SyntaxNode?



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

def parent
  @parent
end

#source_textObject (readonly)

: SourceText



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

def source_text
  @source_text
end

#trivia_policyObject (readonly)

: Symbol



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

def trivia_policy
  @trivia_policy
end

Instance Method Details

#==(other) ⇒ Object



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

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

#ancestorsObject



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) ⇒ Object

Raises:

  • (TypeError)


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) ⇒ Object



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) ⇒ Object



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) ⇒ Object (protected)



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_nodesObject



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

#childrenObject



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

#contains_error?Boolean

Returns:

  • (Boolean)


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

def contains_error? = @green.flags.anybits?(Flags::CONTAINS_ERROR)

#covering(range) ⇒ Object

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

#cursorObject



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

def cursor = Cursor.new(self)

#deconstructObject



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

def deconstruct = children

#deconstruct_keys(_keys) ⇒ Object



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

#descendantsObject



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(&block) ⇒ Object

@rbs! def each: () -> Enumerator[element, self] | () { (element) -> void } -> 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(&block) ⇒ Object



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

#error?Boolean

Returns:

  • (Boolean)


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

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

#find(kind:) ⇒ Object



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_tokenObject



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

def first_token = tokens.first

#full_spanObject



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_textObject 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

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) ⇒ Object

Raises:

  • (IndexError)


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

#kindObject



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

def kind = @green.kind

#kind_nameObject



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

def kind_name = @kinds.name(kind)

#last_tokenObject



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

def last_token = tokens.last

#locationObject



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

def location = @source_text.location(span)

#missing?Boolean

Returns:

  • (Boolean)


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

def missing? = false

#next_siblingObject



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

#prev_siblingObject



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_idObject

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) ⇒ Object



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) ⇒ Object



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

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

#rootObject



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

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

#same_node?(other) ⇒ Boolean

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

#spanObject



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

#symbolObject

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))

#textObject



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_hObject



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

def to_h = deconstruct_keys(nil)

#token_at(source_offset) ⇒ Object

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

#tokensObject



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_triviaObject

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(&block) ⇒ Object

Raises:

  • (ArgumentError)


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) ⇒ Object



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