Class: Gullah::Node

Inherits:
Object
  • Object
show all
Defined in:
lib/gullah/node.rb

Overview

a node in an AST

Direct Known Subclasses

Boundary, Trash

Defined Under Namespace

Classes: Ancestors, Descendants

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parse, s, e, rule) ⇒ Node

:nodoc:



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/gullah/node.rb', line 29

def initialize(parse, s, e, rule) # :nodoc:
  @rule = rule
  @leaf = rule.is_a?(Leaf) || trash?
  @text = parse.text
  @attributes = {}
  @failed_test = false
  if @leaf
    @start = s
    @end = e
  else
    @children = parse.roots[s...e]
    @children.each { |n| adopt n }
  end
  unless trash?
    rule.tests.each do |t|
      result, *extra = Array(t.call(self))
      case result
      when :ignore
        # no-op test
      when :pass
        (attributes[:satisfied] ||= []) << [t.name, *extra]
      when :fail
        @failed_test = true
        (attributes[:failures] ||= []) << [t.name, *extra]
        break
      else
        raise Error, "          test \#{t.name} returned an unexpected value:\n            \#{result.inspect}\n          expected values: \#{%i[ignore pass fail].inspect}\n        MSG\n      end\n    end\n  end\n  unless failed?\n    # if any test failed, this node will not be the child of another node\n    rule.ancestor_tests.each do |t|\n      # use position rather than node itself for the sake of clonability\n      (attributes[:pending] ||= []) << [t, position]\n    end\n  end\nend\n"

Instance Attribute Details

#attributesObject (readonly) Also known as: atts

A hash of attributes, including indicators of tests that passed or failed. The atts alias of attributes exists for when a more telegraphic coding style is useful.



15
16
17
# File 'lib/gullah/node.rb', line 15

def attributes
  @attributes
end

#childrenObject (readonly)

The children of this node, if any, as an array.



19
20
21
# File 'lib/gullah/node.rb', line 19

def children
  @children
end

#parentObject (readonly)

The parent node of this node, if any.



8
9
10
# File 'lib/gullah/node.rb', line 8

def parent
  @parent
end

#ruleObject (readonly)

:nodoc:



10
11
12
# File 'lib/gullah/node.rb', line 10

def rule
  @rule
end

#summaryObject (readonly)

A concise stringification of the structure of this node’s subtree.



23
24
25
# File 'lib/gullah/node.rb', line 23

def summary
  @summary
end

Instance Method Details

#_ancestors(skip) ⇒ Object

:nodoc:



456
457
458
# File 'lib/gullah/node.rb', line 456

def _ancestors(skip) # :nodoc:
  Ancestors.new(self, skip)
end

#_attributes=(attributes) ⇒ Object

:nodoc:



440
441
442
# File 'lib/gullah/node.rb', line 440

def _attributes=(attributes) # :nodoc:
  @attributes = attributes
end

#_children=(children) ⇒ Object

:nodoc:



448
449
450
# File 'lib/gullah/node.rb', line 448

def _children=(children) # :nodoc:
  @children = children
end

#_descendants(skip) ⇒ Object

:nodoc:



452
453
454
# File 'lib/gullah/node.rb', line 452

def _descendants(skip) # :nodoc:
  Descendants.new(self, skip)
end

#_failed_test=(bool) ⇒ Object

:nodoc:



460
461
462
# File 'lib/gullah/node.rb', line 460

def _failed_test=(bool) # :nodoc:
  @failed_test = bool
end

#_loop_check?(seen = nil) ⇒ Boolean

used during parsing make sure we don’t have any repeated symbols in a unary branch

Returns:

  • (Boolean)


426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'lib/gullah/node.rb', line 426

def _loop_check?(seen = nil) # :nodoc:
  return true if seen == name

  return false if !@leaf && children.length > 1

  if seen.nil?
    # this is the beginning of the check
    # the only name we need look for is this rule's name, since
    # all those below it must have passed the check
    seen = name
  end
  @leaf ? false : children.first._loop_check?(seen)
end

#_parent=(other) ⇒ Object

:nodoc:



444
445
446
# File 'lib/gullah/node.rb', line 444

def _parent=(other) # :nodoc:
  @parent = other
end

#_summary=(str) ⇒ Object

:stopdoc:



420
421
422
# File 'lib/gullah/node.rb', line 420

def _summary=(str) # :nodoc:
  @summary = str
end

#ancestorsObject

Returns an Enumerable enumerating the nodes immediately above this node in the tree: its parent, its parent’s parent, etc.



251
252
253
# File 'lib/gullah/node.rb', line 251

def ancestors
  _ancestors self
end

#boundary?Boolean

Is this node one that cannot be the child of another node?

Returns:

  • (Boolean)


86
87
88
# File 'lib/gullah/node.rb', line 86

def boundary?
  false
end

#cloneObject

:nodoc:



340
341
342
343
344
345
346
347
348
349
350
# File 'lib/gullah/node.rb', line 340

def clone # :nodoc:
  super.tap do |c|
    c._attributes = deep_clone(attributes)
    unless c.leaf?
      c._children = deep_clone(children)
      c.children.each do |child|
        child._parent = c
      end
    end
  end
end

#contains?(offset) ⇒ Boolean

Does this node contain the given text offset?

Returns:

  • (Boolean)


205
206
207
# File 'lib/gullah/node.rb', line 205

def contains?(offset)
  start <= offset && offset < self.end
end

#dbg(so: false) ⇒ Object

Produces a simplified representation of the node to facilitate debugging. The so named parameter, if true, will cause the representation to drop ignored nodes. The name “so” stands for “significant only”.

> pp root.dbg

{:name=>:S,
 :pos=>{:start=>0, :end=>11, :depth=>0},
 :children=>
  [{:name=>:NP,
    :pos=>{:start=>0, :end=>7, :depth=>1},
    :children=>
     [{:name=>:D, :pos=>{:start=>0, :end=>3, :depth=>2}, :text=>"the"},
      {:name=>:_ws,
       :pos=>{:start=>3, :end=>4, :depth=>2},
       :ignorable=>true,
       :text=>" "},
      {:name=>:N, :pos=>{:start=>4, :end=>7, :depth=>2}, :text=>"cat"}]},
   {:name=>:_ws,
    :pos=>{:start=>7, :end=>8, :depth=>1},
    :ignorable=>true,
    :text=>" "},
   {:name=>:VP,
    :pos=>{:start=>8, :end=>11, :depth=>1},
    :children=>
     [{:name=>:V, :pos=>{:start=>8, :end=>11, :depth=>2}, :text=>"sat"}]}]}

> pp root.dbg so: true

{:name=>:S,
 :pos=>{:start=>0, :end=>11, :depth=>0},
 :children=>
  [{:name=>:NP,
    :pos=>{:start=>0, :end=>7, :depth=>1},
    :children=>
     [{:name=>:D, :pos=>{:start=>0, :end=>3, :depth=>2}, :text=>"the"},
      {:name=>:_ws, :pos=>{:start=>3, :end=>4, :depth=>2}, :text=>" "},
      {:name=>:N, :pos=>{:start=>4, :end=>7, :depth=>2}, :text=>"cat"}]},
   {:name=>:_ws, :pos=>{:start=>7, :end=>8, :depth=>1}, :text=>" "},
   {:name=>:VP,
    :pos=>{:start=>8, :end=>11, :depth=>1},
    :children=>
     [{:name=>:V, :pos=>{:start=>8, :end=>11, :depth=>2}, :text=>"sat"}]}]}


395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/gullah/node.rb', line 395

def dbg(so: false)
  {
    name: name,
    pos: {
      start: start,
      end: self.end,
      depth: depth
    }
  }.tap do |simpleton|
    simpleton[:failed] = true if @failed_test
    simpleton[:attributes] = deep_clone attributes if attributes.any?
    if leaf?
      simpleton[:trash] = true if trash?
      simpleton[:ignorable] = true unless so || significant?
      simpleton[:text] = text
    else
      simpleton[:children] = children.map { |c| c.dbg so: so }
    end
  end
end

#depthObject

Distance of the node from the root node of the parse tree. During parsing, while nodes are being added, this distance may change, unlike the height.

The root node has a depth of 0. It’s children have a depth of 1. Their children have a depth of 2. And so forth.



184
185
186
# File 'lib/gullah/node.rb', line 184

def depth
  parent ? 1 + parent.depth : 0
end

#descendantsObject

Returns an Enumerable over the descendants of this node: its children, its children’s children, etc. This enumeration is depth-first.



258
259
260
# File 'lib/gullah/node.rb', line 258

def descendants
  _descendants self
end

#endObject

The node’s end text offset. For a non-terminal node, this will be the same as the end of the last leaf node of its subtree.



173
174
175
# File 'lib/gullah/node.rb', line 173

def end
  @end ||= @children[-1].end
end

#error?Boolean

Does this node have some failed test?

Returns:

  • (Boolean)


109
110
111
# File 'lib/gullah/node.rb', line 109

def error?
  @failed_test
end

#failed?Boolean

Does this node have some failed test or does it represent characters no leaf rule mached?

Returns:

  • (Boolean)


98
99
100
# File 'lib/gullah/node.rb', line 98

def failed?
  trash? || error?
end

#find(pos) ⇒ Object

Finds the node at the given position within this node’s subtree.



211
212
213
214
215
216
217
218
219
220
# File 'lib/gullah/node.rb', line 211

def find(pos)
  offset = pos.first
  return nil unless contains?(offset)

  return self if pos == position

  if (child = children&.find { |c| c.contains? offset })
    child.find(pos)
  end
end

#first_child?Boolean

Is this node the first of its parent’s children?

Returns:

  • (Boolean)


301
302
303
# File 'lib/gullah/node.rb', line 301

def first_child?
  sibling_index.zero?
end

#full_textObject

A reference to the full text the node’s text is embedded in.



147
148
149
# File 'lib/gullah/node.rb', line 147

def full_text
  @text
end

#heightObject

The distance of a node from the first leaf node in its subtree. If the node is the immediate parent of this leaf, its distance will be one. Leaves have a height of zero.



192
193
194
# File 'lib/gullah/node.rb', line 192

def height
  @height ||= @leaf ? 0 : 1 + children[0].height
end

#ignorable?Boolean

Was this node created by an ignore rule?

Returns:

  • (Boolean)


122
123
124
# File 'lib/gullah/node.rb', line 122

def ignorable?
  @leaf && rule.ignorable
end

#last_child?Boolean

Is this node the last of its parent’s children?

Returns:

  • (Boolean)


295
296
297
# File 'lib/gullah/node.rb', line 295

def last_child?
  parent && sibling_index == parent.children.length - 1
end

#laterObject

The collection of nodes in the subtree containing this node whose start offset is at or after its end offset.



336
337
338
# File 'lib/gullah/node.rb', line 336

def later
  root.descendants.select { |n| n.start >= self.end }
end

#later_siblingObject

The immediately following sibling to this node.



315
316
317
# File 'lib/gullah/node.rb', line 315

def later_sibling
  parent && parent.children[sibling_index + 1]
end

#later_siblingsObject

Returns the children of this node’s parent that follow it.



289
290
291
# File 'lib/gullah/node.rb', line 289

def later_siblings
  parent && siblings[(sibling_index + 1)..]
end

#leaf?Boolean

Is this a leaf node?

Returns:

  • (Boolean)


92
93
94
# File 'lib/gullah/node.rb', line 92

def leaf?
  @leaf
end

#leavesObject

The leaves of this node’s subtree. If the node is a leaf, this returns a single-member array containing the node itself.



322
323
324
# File 'lib/gullah/node.rb', line 322

def leaves
  @leaf ? [self] : descendants.select(&:leaf?)
end

#nameObject

The name of the rule that created this node.



74
75
76
# File 'lib/gullah/node.rb', line 74

def name
  rule.name
end

#nonterminal?Boolean

Is this a node that has other nodes as children?

Returns:

  • (Boolean)


134
135
136
# File 'lib/gullah/node.rb', line 134

def nonterminal?
  !@leaf
end

#pending_tests?Boolean

Does this node’s subtree contain unsatisfied syntactic requirements? These are tests that depend on nodes not in the node’s own subtree.

Returns:

  • (Boolean)


116
117
118
# File 'lib/gullah/node.rb', line 116

def pending_tests?
  !!attributes[:pending]
end

#positionObject

A pair consisting of the nodes start and height. This will be a unique identifier for the node in its parse and is constant at all stages of parsing.



199
200
201
# File 'lib/gullah/node.rb', line 199

def position
  @position ||= [start, height]
end

#priorObject

The collection of nodes in the subtree containing this node that do not contain the node and whose start offset precedes its start offset.



329
330
331
# File 'lib/gullah/node.rb', line 329

def prior
  root.descendants.reject { |n| n.contains? start }.select { |n| n.start < start }
end

#prior_siblingObject

The immediately prior sibling to this node.



307
308
309
310
311
# File 'lib/gullah/node.rb', line 307

def prior_sibling
  if parent
    first_child? ? nil : parent.children[sibling_index - 1]
  end
end

#prior_siblingsObject

Returns the children of this node’s parent that precede it.



283
284
285
# File 'lib/gullah/node.rb', line 283

def prior_siblings
  parent && siblings[0...sibling_index]
end

#rootObject

The root of this node’s current parse tree.

Note, if you use this in a node test the root will always be the same as the node itself because these tests are run when the node is being added to the tree. If you use it in structure tests, it will be some ancestor of the node but not necessarily the final root. The current root is always the first argument to structure tests. Using this argument is more efficient than using the root method. Really, the root method is only useful in completed parses.



238
239
240
# File 'lib/gullah/node.rb', line 238

def root
  parent ? parent.root : self
end

#root?Boolean

Does this node have any parent? If not, it is a root.

Returns:

  • (Boolean)


244
245
246
# File 'lib/gullah/node.rb', line 244

def root?
  parent.nil?
end

#sibling_indexObject

The index of this node among its parent’s children.



277
278
279
# File 'lib/gullah/node.rb', line 277

def sibling_index
  @sibling_index ||= parent.children.index self if parent
end

#siblingsObject

Returns the children of this node’s parent’s children minus this node itself.



271
272
273
# File 'lib/gullah/node.rb', line 271

def siblings
  parent&.children&.reject { |n| n == self }
end

#significant?Boolean

Was this node created by something other than an ignore rule?

Returns:

  • (Boolean)


128
129
130
# File 'lib/gullah/node.rb', line 128

def significant?
  !ignorable?
end

#sizeObject

The number of nodes in this node’s subtree. Leaves always have a size of 1.



224
225
226
# File 'lib/gullah/node.rb', line 224

def size
  @size ||= @leaf ? 1 : @children.map(&:size).sum + 1
end

#startObject

The node’s start text offset. For a non-terminal node, this will be the same as the start of the first leaf node of its subtree.



166
167
168
# File 'lib/gullah/node.rb', line 166

def start
  @start ||= @children[0].start
end

#subtreeObject

Returns an Enumerable over this node and its descendants. The node itself is the first node returned.



265
266
267
# File 'lib/gullah/node.rb', line 265

def subtree
  _descendants nil
end

#textObject

The portion of the original text covered by this node. This is in effect the text of the leaves of its subtree.



141
142
143
# File 'lib/gullah/node.rb', line 141

def text
  @text[start...self.end]
end

#text_afterObject

The text following this node’s text. Useful for lookaround tests and preconditions.



159
160
161
# File 'lib/gullah/node.rb', line 159

def text_after
  @text[self.end..]
end

#text_beforeObject

The text preceding this node’s text. Useful for lookaround tests and preconditions.



153
154
155
# File 'lib/gullah/node.rb', line 153

def text_before
  @text[0...start]
end

#trash?Boolean

Does this node represent a character sequence no leaf rule matched?

Returns:

  • (Boolean)


80
81
82
# File 'lib/gullah/node.rb', line 80

def trash?
  false
end

#traversible?Boolean

is this node some sort of boundary to further matching

Returns:

  • (Boolean)


103
104
105
# File 'lib/gullah/node.rb', line 103

def traversible? # :nodoc:
  !(boundary? || trash? || error?)
end