Class: C::Node

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/cast/node.rb,
lib/cast/to_s.rb,
lib/cast/parse.rb,
lib/cast/c_nodes.rb,
lib/cast/inspect.rb

Overview


                  Class implementations

Defined Under Namespace

Classes: BadParent, Field, NoParent, Pos

Constant Summary collapse

INSPECT_TAB =
'    '

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &blk) ⇒ Object



587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
# File 'lib/cast/node.rb', line 587

def method_missing(meth, *args, &blk)
  # respond to `Module?'
  methstr = meth.to_s
  if methstr =~ /^([A-Z].*)\?$/ && C.const_defined?($1)
    klass = C.const_get($1)
    if klass.is_a?(::Module)
      return self.is_a?(klass)
    end
  end

  begin
    super
  rescue NoMethodError => e
    e.set_backtrace(caller)
    raise e
  end
end

Instance Attribute Details

#parentObject

The Node's parent.



228
229
230
# File 'lib/cast/node.rb', line 228

def parent
  @parent
end

#posObject

The position in the source file the construct this node represents appears at.



235
236
237
# File 'lib/cast/node.rb', line 235

def pos
  @pos
end

#subclassesObject (readonly)

The direct subclasses of this class (an Array of Class).



398
399
400
# File 'lib/cast/node.rb', line 398

def subclasses
  @subclasses
end

Class Method Details

.abstractObject

Declare this class as abstract.



422
423
# File 'lib/cast/node.rb', line 422

def self.abstract
end

.add_field(newfield) ⇒ Object

Add the Field `newfield' to the list of fields for this class.



449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/cast/node.rb', line 449

def self.add_field(newfield)
  # add the newfield to @fields, and set the index
  fields = @fields
  newfield.index = fields.length
  fields << newfield
  # getter
  # define_method(newfield.reader) do
  #   instance_variable_get(newfield.var)
  # end
  eval "def #{newfield.reader}; #{newfield.var}; end"
  # setter
  if newfield.child?
    define_method(newfield.writer) do |val|
      old = send(newfield.reader)
      return if val.equal? old
      # detach the old Node
      old = self.send(newfield.reader)
      unless old.nil?
        old.instance_variable_set(:@parent, nil)
      end
      # copy val if needed
      val = val.clone if !val.nil? && val.attached?
      # set
      self.instance_variable_set(newfield.var, val)
      # attach the new Node
      unless val.nil?
        val.instance_variable_set(:@parent, self)
        val.instance_variable_set(:@parent_field, newfield)
      end
    end
  else
    define_method(newfield.writer) do |val|
      instance_variable_set(newfield.var, val)
    end
  end
end

.child(name, default = nil) ⇒ Object

Declare a child with the given name and default value. The default value is cloned when used (unless cloning is unnecessary).



573
574
575
576
577
# File 'lib/cast/node.rb', line 573

def self.child(name, default=nil)
  field = Field.new(name, default)
  field.child = true
  add_field field
end

.field(name, default = :'no default') ⇒ Object

Declare a field with the given name and default value.



548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'lib/cast/node.rb', line 548

def self.field(name, default=:'no default')
  if default == :'no default'
    if name.to_s[-1] == ??
      default = false
    else
      default = nil
    end
  end

  # if the field exists, just update its default, otherwise, add
  # a new field
  self.fields.each do |field|
    if field.reader == name
      field.default = default
      return
    end
  end
  add_field Field.new(name, default)
end

.fieldsObject



485
486
487
# File 'lib/cast/node.rb', line 485

def self.fields
  @fields
end

.inherited(klass) ⇒ Object

Callback defined in Class.



413
414
415
416
417
# File 'lib/cast/node.rb', line 413

def self.inherited(klass)
  @subclasses << klass
  klass.instance_variable_set(:@subclasses, [])
  klass.instance_variable_set(:@fields    , [])
end

.initializer(*syms) ⇒ Object

Define an initialize method. The initialize method sets the fields named in syms' from the arguments given to it. The initialize method also takes named parameters (i.e., an optional Hash as the last argument), which may be used to set fields not even named in syms'. The syms in the optional Hash are the values of the `init_key' members of the corresponding Field objects.

As an example for this Node class:

class X < Node
field :x
field :y
child :z
initializer :z, :y
end

...X.new can be called in any of these ways:

X.new                           # all fields set to default
X.new(1)                        # .z = 1
X.new(1, 2)                     # .z = 1, .y = 2
X.new(:x = 1, :y => 2, :z => 3) # .x = 1, .y = 2, .z = 3
X.new(1, :x => 2)               # .z = 1, .x = 2
X.new(1, :z => 2)               # undefined behaviour!
...etc.


517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
# File 'lib/cast/node.rb', line 517

def self.initializer(*syms)
  define_method(:initialize) do |*args|
    # pop off the opts hash
    opts = args.last.is_a?(::Hash) ? args.pop : {}

    # add positional args to opts
    args.each_with_index do |arg, i|
      opts[syms[i]] = arg
    end

    # set field values
    fields.each do |field|
      key = field.init_key
      if opts.key?(key)
        send(field.writer, opts[key])
      else
        send(field.writer, field.make_default)
      end
    end

    # pos, parent
    @pos    = nil
    @parent = nil
  end
end

.inspect1(x, prefix = '', indent = 0, is_child = true) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
# File 'lib/cast/inspect.rb', line 14

def Node.inspect1(x, prefix='', indent=0, is_child=true)
  case x
  when NodeList
    if x.empty?
      return "#{INSPECT_TAB*indent}#{prefix}[]\n"
    else
      str = "#{INSPECT_TAB*indent}#{prefix}\n"
      x.each do |el|
        str << inspect1(el, "- ", indent+1)
      end
      return str
    end
  when Node
    classname = x.class.name.gsub(/^C::/, '')
    str = "#{INSPECT_TAB*indent}#{prefix}#{classname}"

    fields = x.fields
    bools, others = fields.partition{|field| field.reader.to_s[-1] == ??}
    bools.delete_if{|field| !x.send(field.reader)}
    bools.map!{|field| field.init_key}

    unless bools == []
      str << " (#{bools.join(' ')})"
    end
    str << "\n"

    others.each do |field|
      val = x.send(field.reader)
      next if val == field.make_default ||
        # don't bother with non-child Nodes, since they may cause
        # loops in the tree
        (val.is_a?(Node) && !field.child?)
      str << inspect1(val, "#{field.reader}: ", indent+1, field.child?)
    end
    return str
  when Symbol
    return "#{INSPECT_TAB*indent}#{prefix}#{x}\n"
  else
    return "#{INSPECT_TAB*indent}#{prefix}#{x.inspect}\n"
  end
  return s.string
end

.new_at(pos, *args) ⇒ Object

Like self.new, but the first argument is taken as the position of the Node.



30
31
32
33
34
# File 'lib/cast/node.rb', line 30

def self.new_at(pos, *args)
  ret = new(*args)
  ret.pos = pos
  return ret
end

.subclasses_recursiveObject

Return all classes which have this class somewhere in its ancestry (an Array of Class).



404
405
406
407
408
# File 'lib/cast/node.rb', line 404

def self.subclasses_recursive
  ret = @subclasses.dup
  @subclasses.each{|c| ret.concat(c.subclasses_recursive)}
  return ret
end

Instance Method Details

#==(other) ⇒ Object

True iff both are of the same class, and all fields are #==.



39
40
41
42
43
44
45
46
47
# File 'lib/cast/node.rb', line 39

def ==(other)
  return false if !other.is_a? self.class

  fields.all? do |field|
    mine  = self .send(field.reader)
    yours = other.send(field.reader)
    mine == yours
  end
end

#=~(*args) ⇒ Object

Same as #match?.



31
32
33
# File 'lib/cast/parse.rb', line 31

def =~(*args)
  match?(*args)
end

#assert_invariants(testcase) ⇒ Object

Called by the test suite to ensure all invariants are true.



15
16
17
18
19
20
21
22
23
24
# File 'lib/cast/node.rb', line 15

def assert_invariants(testcase)
  fields.each do |field|
    if val = send(field.reader)
      assert_same(self, node.parent, "field.reader is #{field.reader}")
      if field.child?
        assert_same(field, val.instance_variable_get(:@parent_field), "field.reader is #{field.reader}")
      end
    end
  end
end

#attached?Boolean

Return true if this Node is attached (i.e., #parent is nonnil), false otherwise.

This is equal to !detached?

Returns:

  • (Boolean)


387
388
389
# File 'lib/cast/node.rb', line 387

def attached?
  !@parent.nil?
end

#cloneObject

As defined for ::Object, but children are recursively `#clone'd.



85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/cast/node.rb', line 85

def clone
  ret = super
  ret.instance_variable_set(:@parent, nil)
  fields.each do |field|
    next if !field.child?
    val = instance_variable_get(field.var)
    val = val.clone unless val.nil?
    ret.instance_variable_set(field.var, nil)
    ret.send(field.writer, val)
  end
  return ret
end

#depth_first {|:ascending, _self| ... } ⇒ Object

Perform a depth-first walk of the AST, yielding on recursively on each child node:

- (:descending, node) just prior to descending into `node'
- (:ascending, node) just after ascending from `node'

If the block throws :prune while descending, the children of the node that was passed to that block will not be visited.

Yields:

  • (:ascending, _self)

Yield Parameters:

  • _self (C::Node)

    the object that the method was called on



140
141
142
143
144
145
146
147
# File 'lib/cast/node.rb', line 140

def depth_first(&blk)
  catch :prune do
    yield :descending, self
    each{|n| n.depth_first(&blk)}
  end
  yield :ascending, self
  return self
end

#detachObject

Detach this Node from the tree and return it.

Raises NoParent if there's no parent.



292
293
294
295
296
# File 'lib/cast/node.rb', line 292

def detach
  @parent or raise NoParent
  @parent.remove_node(self)
  return self
end

#detached?Boolean

Return true if this Node is detached (i.e., #parent is nil), false otherwise.

This is equal to !attached?

Returns:

  • (Boolean)


377
378
379
# File 'lib/cast/node.rb', line 377

def detached?
  @parent.nil?
end

#dupObject

As defined for ::Object, but children are recursively `#dup'ed.



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/cast/node.rb', line 69

def dup
  ret = super
  ret.instance_variable_set(:@parent, nil)
  fields.each do |field|
    next if !field.child?
    val = instance_variable_get(field.var)
    val = val.dup unless val.nil?
    ret.instance_variable_set(field.var, nil)
    ret.send(field.writer, val)
  end
  return ret
end

#each(&blk) ⇒ Object

Yield each child in field order.



107
108
109
110
111
112
113
114
115
# File 'lib/cast/node.rb', line 107

def each(&blk)
  fields.each do |field|
    if field.child?
      val = self.send(field.reader)
      yield val unless val.nil?
    end
  end
  return self
end

#eql?(other) ⇒ Boolean

Same as #==.

Returns:

  • (Boolean)


52
53
54
# File 'lib/cast/node.rb', line 52

def eql?(other)
  return self == other
end

#fieldsObject

Return the list of fields this object has. Don't modify the returned array!



583
584
585
# File 'lib/cast/node.rb', line 583

def fields
  self.class.fields
end

#hashObject

#hash, as defined in Object.



59
60
61
62
63
64
# File 'lib/cast/node.rb', line 59

def hash
  fields.inject(0) do |hash, field|
    val = send(field.reader)
    hash ^= val.hash
  end
end

#insert_next(*newnodes) ⇒ Object

Insert `newnodes' after this node. Return this node.

Raises:

-- NoParent if there's no parent
-- BadParent if the parent is otherwise not a NodeList


364
365
366
367
368
369
# File 'lib/cast/node.rb', line 364

def insert_next(*newnodes)
  @parent or raise NoParent
  @parent.NodeList? or raise BadParent
  @parent.insert_after(self, *newnodes)
  return self
end

#insert_prev(*newnodes) ⇒ Object

Insert `newnodes' before this node. Return this node.

Raises:

-- NoParent if there's no parent
-- BadParent if the parent is otherwise not a NodeList


350
351
352
353
354
355
# File 'lib/cast/node.rb', line 350

def insert_prev(*newnodes)
  @parent or raise NoParent
  @parent.NodeList? or raise BadParent
  @parent.insert_before(self, *newnodes)
  return self
end

#inspectObject



10
11
12
# File 'lib/cast/inspect.rb', line 10

def inspect
  return Node.inspect1(self)
end

#list_nextObject

Return the sibling Node that comes after this in the parent NodeList.

Raises:

-- NoParent if there's no parent
-- BadParent if the parent is otherwise not a NodeList


256
257
258
259
260
# File 'lib/cast/node.rb', line 256

def list_next
  @parent or raise NoParent
  @parent.NodeList? or raise BadParent
  return @parent.node_after(self)
end

#list_prevObject

Return the sibling Node that comes before this in the parent NodeList.

Raises:

-- NoParent if there's no parent
-- BadParent if the parent is otherwise not a NodeList


281
282
283
284
285
# File 'lib/cast/node.rb', line 281

def list_prev
  @parent or raise NoParent
  @parent.NodeList? or raise BadParent
  return @parent.node_before(self)
end

#match?(str, parser = nil) ⇒ Boolean

Return true if str' is parsed to something ==' to this Node. str is first converted to a String using #to_s, then given to self.class.parse (along with the optional `parser').

Returns:

  • (Boolean)


24
25
26
27
# File 'lib/cast/parse.rb', line 24

def match?(str, parser=nil)
  node = self.class.parse(str.to_s, parser) rescue (return false)
  self == node
end

#nextObject

Return the sibling Node that comes after this in preorder sequence.

Raises NoParent if there's no parent.



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

def next
  @parent or raise NoParent
  return @parent.node_after(self)
end

#node_after(node) ⇒ Object

Return the Node that comes after the given Node in tree preorder.



615
616
617
618
619
620
621
622
623
624
625
626
627
# File 'lib/cast/node.rb', line 615

def node_after(node)
  node.parent.equal? self or
    raise ArgumentError, "node is not a child"
  fields = self.fields
  i = node.instance_variable_get(:@parent_field).index + 1
  (i...fields.length).each do |i|
    f = fields[i]
    if f.child? && (val = self.send(f.reader))
      return val
    end
  end
  return nil
end

#node_before(node) ⇒ Object

Return the Node that comes before the given Node in tree preorder.



633
634
635
636
637
638
639
640
641
642
643
644
645
# File 'lib/cast/node.rb', line 633

def node_before(node)
  node.parent.equal? self or
    raise ArgumentError, "node is not a child"
  fields = self.fields
  i = node.instance_variable_get(:@parent_field).index - 1
  i.downto(0) do |i|
    f = fields[i]
    if f.child? && (val = self.send(f.reader))
      return val
    end
  end
  return nil
end

#postorder {|_self| ... } ⇒ Object

Perform a postorder walk of the AST, yielding each node in turn. Return self.

Yields:

  • (_self)

Yield Parameters:

  • _self (C::Node)

    the object that the method was called on



202
203
204
205
206
# File 'lib/cast/node.rb', line 202

def postorder(&blk)
  each{|n| n.postorder(&blk)}
  yield self
  return self
end

#preorder(&blk) ⇒ Object

Perform a preorder walk of the AST, yielding each node in turn. Return self.

If the block throws :prune, the children of the node that was passed to that block will not be visited.



175
176
177
178
179
180
181
# File 'lib/cast/node.rb', line 175

def preorder(&blk)
  catch :prune do
    yield self
    each{|n| n.preorder(&blk)}
  end
  return self
end

#prevObject

Return the sibling Node that comes before this in preorder sequence.

Raises NoParent if there's no parent.



268
269
270
271
# File 'lib/cast/node.rb', line 268

def prev
  @parent or raise NoParent
  return @parent.node_before(self)
end

#remove_node(node) ⇒ Object

Remove the given Node.



650
651
652
653
654
655
656
657
658
# File 'lib/cast/node.rb', line 650

def remove_node(node)
  node.parent.equal? self or
    raise ArgumentError, "node is not a child"
  field = node.instance_variable_get(:@parent_field)
  node.instance_variable_set(:@parent, nil)
  node.instance_variable_set(:@parent_field, nil)
  self.instance_variable_set(field.var, nil)
  return self
end

#replace_node(node, newnode = nil) ⇒ Object

Replace node' with newnode'.



663
664
665
666
667
668
669
# File 'lib/cast/node.rb', line 663

def replace_node(node, newnode=nil)
  node.parent.equal? self or
    raise ArgumentError, "node is not a child"
  field = node.instance_variable_get(:@parent_field)
  self.send(field.writer, newnode)
  return self
end

#replace_with(*nodes) ⇒ Object

Replace this Node in the tree with the given node(s). Return this node.

Raises:

-- NoParent if there's no parent
-- BadParent if the parent is otherwise not a NodeList, and
 more than one node is given.


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

def replace_with(*nodes)
  @parent or raise NoParent
  @parent.replace_node(self, *nodes)
  return self
end

#reverse_depth_first {|:ascending, _self| ... } ⇒ Object

Perform a reverse depth-first walk of the AST, yielding on each node:

- (:descending, node) just prior to descending into `node'
- (:ascending, node) just after ascending from `node'

If the block throws :prune while descending, the children of the node that was passed to that block will not be visited.

Yields:

  • (:ascending, _self)

Yield Parameters:

  • _self (C::Node)

    the object that the method was called on



159
160
161
162
163
164
165
166
# File 'lib/cast/node.rb', line 159

def reverse_depth_first(&blk)
  catch :prune do
    yield :descending, self
    reverse_each{|n| n.reverse_depth_first(&blk)}
  end
  yield :ascending, self
  return self
end

#reverse_each(&blk) ⇒ Object

Yield each child in reverse field order.



120
121
122
123
124
125
126
127
128
# File 'lib/cast/node.rb', line 120

def reverse_each(&blk)
  fields.reverse_each do |field|
    if field.child?
      val = self.send(field.reader)
      yield val unless val.nil?
    end
  end
  return self
end

#reverse_postorder {|_self| ... } ⇒ Object

Perform a reverse postorder walk of the AST, yielding each node in turn. Return self.

Yields:

  • (_self)

Yield Parameters:

  • _self (C::Node)

    the object that the method was called on



212
213
214
215
216
# File 'lib/cast/node.rb', line 212

def reverse_postorder(&blk)
  reverse_each{|n| n.reverse_postorder(&blk)}
  yield self
  return self
end

#reverse_preorder(&blk) ⇒ Object

Perform a reverse preorder walk of the AST, yielding each node in turn. Return self.

If the block throws :prune, the children of the node that was passed to that block will not be visited.



190
191
192
193
194
195
196
# File 'lib/cast/node.rb', line 190

def reverse_preorder(&blk)
  catch :prune do
    yield self
    reverse_each{|n| n.reverse_preorder(&blk)}
  end
  return self
end

#swap_with(node) ⇒ Object

Swap this node with `node' in their trees. If either node is detached, the other will become detached as a result of calling this method.



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/cast/node.rb', line 318

def swap_with node
  return self if node.equal? self
  if self.attached?
    if node.attached?
      # both attached -- use placeholder
      placeholder = Default.new
      my_parent = @parent
      my_parent.replace_node(self, placeholder)
      node.parent.replace_node(node, self)
      my_parent.replace_node(placeholder, node)
    else
      # only `self' attached
      @parent.replace_node(self, node)
    end
  else
    if node.attached?
      # only `node' attached
      node.parent.replace_node(node, self)
    else
      # neither attached -- nothing to do
    end
  end
  return self
end