Class: RLTK::ASTNode

Inherits:
Object
  • Object
show all
Extended by:
Filigree::AbstractClass, Filigree::Destructurable
Includes:
Filigree::Visitable
Defined in:
lib/rltk/ast.rb

Overview

This class is a good start for all your abstract syntax tree node needs.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*objects, &block) ⇒ ASTNode

Instantiates a new ASTNode object. The arguments to this method are split into two lists: the set of values for this node and a list of its children. If the node has 2 values and 3 children you would pass the values in as the first two arguments (in the order they were declared) and then the children as the remaining arguments (in the order they were declared).

If a node has 2 values and 2 children and is passed only a single value the remaining values and children are assumed to be nil or empty arrays, depending on the declared type of the value or child.

If a block is passed to initialize the block will be executed in the conext of the new object.

Parameters:

  • objects (Array<Object>)

    Values and children of this node



445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/rltk/ast.rb', line 445

def initialize(*objects, &block)
	@notes  = Hash.new()
	@parent = nil

	pairs =
	case self.class.member_order
	when :values   then (self.class.inc_values + self.class.inc_children)
	when :children then (self.class.inc_children + self.class.inc_values)
	when :def      then self.class.def_order
	when Array     then self.class.member_order
	end.zip(objects).first(objects.length)

	pairs.each do |name, value|
		self.send("#{name}=", value)
	end

	self.class.array_members.each do |member|
		ivar_name = '@' + member.to_s
		self.instance_variable_set(ivar_name, []) if self.instance_variable_get(ivar_name).nil?
	end

	self.instance_exec(&block) if not block.nil?
end

Instance Attribute Details

#notesHash

Returns The notes hash for this node.

Returns:

  • (Hash)

    The notes hash for this node.



34
35
36
# File 'lib/rltk/ast.rb', line 34

def notes
  @notes
end

#parentASTNode

Returns Reference to the parent node.

Returns:

  • (ASTNode)

    Reference to the parent node.



31
32
33
# File 'lib/rltk/ast.rb', line 31

def parent
  @parent
end

Class Method Details

.array_membersArray<Symbol>

Returns List of members (children and values) that have array types.

Returns:

  • (Array<Symbol>)

    List of members (children and values) that have array types



43
44
45
# File 'lib/rltk/ast.rb', line 43

def array_members
	@array_members
end

.check_odr(name) ⇒ Object

Check to make sure a name isn’t re-defining a value or child.

Raises:

  • (ArgumentError)

    Raised if the name is already used for an existing value or child



50
51
52
53
54
55
56
57
58
# File 'lib/rltk/ast.rb', line 50

def check_odr(name)
	if @child_names.include? name
		raise ArgumentError, "Class #{self} or one of its superclasses already defines a child named #{name}"
	end

	if @value_names.include?(name)
		raise ArgumentError, "Class #{self} or one of its superclasses already defines a value named #{name}"
	end
end

.child(name, type, omit = false) ⇒ void

This method returns an undefined value.

Defined a child for this AST class and its subclasses. The name of the child will be used to define accessor methods that include type checking. The type of this child must be a subclass of the ASTNode class.

Parameters:

  • name (String, Symbol)

    Name of child node

  • type (Class)

    Type of child node. Must be a subclass of ASTNode.

  • omit (Boolean) (defaults to: false)

    Include the child in the constructor or not



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/rltk/ast.rb', line 106

def child(name, type, omit = false)
	check_odr(name)

	if type.is_a?(Array) and type.length == 1
		t = type.first

	elsif type.is_a?(Class)
		t = type

	else
		raise 'Child and Value types must be a class name or an array with a single class name element.'
	end

	# Check to make sure that type is a subclass of ASTNode.
	if not t.subclass_of?(ASTNode)
		raise "A child's type specification must be a subclass of ASTNode."
	end

	@child_names   << name
	@array_members << name if type.is_a?(Array)

	if not omit
		@def_order    << name
		@inc_children << name
	end

	define_accessor(name, type, true)
end

.child_namesArray<Symbol>

Returns Array of the names of this node class’s children.

Returns:

  • (Array<Symbol>)

    Array of the names of this node class’s children



136
137
138
# File 'lib/rltk/ast.rb', line 136

def child_names
	@child_names
end

.custom_order(*members) ⇒ void

This method returns an undefined value.

Define a custom ordering for the class to use when building the default constructor and destructurer.

Parameters:

  • members (Array<Symbol>)

    List of member names



195
196
197
# File 'lib/rltk/ast.rb', line 195

def custom_order(*members)
	@member_order = members
end

.def_orderArray<Symbol>

Returns Array of names of values/children in the order they were defined.

Returns:

  • (Array<Symbol>)

    Array of names of values/children in the order they were defined



141
142
143
# File 'lib/rltk/ast.rb', line 141

def def_order
	@def_order
end

.define_accessor(name, type, set_parent = false) ⇒ void

This method returns an undefined value.

This method defines a type checking accessor named name with type type.

Parameters:

  • name (String, Symbol)

    Name of accessor

  • type (Class)

    Class used for type checking

  • set_parent (Boolean) (defaults to: false)

    Set the parent variable or not



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/rltk/ast.rb', line 153

def define_accessor(name, type, set_parent = false)
	ivar_name = ('@' + name.to_s).to_sym

	define_method(name) do
		self.instance_variable_get(ivar_name)
	end

	if type.is_a?(Class)
		if set_parent
			define_method((name.to_s + '=').to_sym) do |value|
				self.instance_variable_set(ivar_name, check_type(value, type, nil, true))
				value.parent = self if value
			end

		else
			define_method((name.to_s + '=').to_sym) do |value|
				self.instance_variable_set(ivar_name, check_type(value, type, nil, true))
			end
		end

	else
		if set_parent
			define_method((name.to_s + '=').to_sym) do |value|
				self.instance_variable_set(ivar_name, check_array_type(value, type.first, nil, true))
				value.each { |c| c.parent = self }
			end

		else
			define_method((name.to_s + '=').to_sym) do |value|
				self.instance_variable_set(ivar_name, check_array_type(value, type.first, nil, true))
			end
		end
	end
end

.inc_childrenArray<Symbol>

Returns Array of the names of children that should be included in the constructor.

Returns:

  • (Array<Symbol>)

    Array of the names of children that should be included in the constructor



200
201
202
# File 'lib/rltk/ast.rb', line 200

def inc_children
	@inc_children
end

.inc_valuesArray<Symbol>

Returns Array of the names of values that should be included in the constructor.

Returns:

  • (Array<Symbol>)

    Array of the names of values that should be included in the constructor



205
206
207
# File 'lib/rltk/ast.rb', line 205

def inc_values
	@inc_values
end

.inherited(klass) ⇒ void

This method returns an undefined value.

Called when the Lexer class is sub-classed, it installes necessary instance class variables.

Parameters:

  • klass (Class)

    The class is inheriting from this class.



92
93
94
# File 'lib/rltk/ast.rb', line 92

def inherited(klass)
	klass.install_icvars
end

.install_icvarsvoid

This method returns an undefined value.

Installs instance class variables into a class.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/rltk/ast.rb', line 63

def install_icvars
	if self.superclass == ASTNode
		@child_names   = Array.new
		@value_names   = Array.new
		@array_members = Array.new

		@member_order = :values
		@def_order    = Array.new
		@inc_children = Array.new
		@inc_values   = Array.new
	else
		@child_names   = self.superclass.child_names.clone
		@value_names   = self.superclass.value_names.clone
		@array_members = self.superclass.array_members.clone

		@member_order = (v = self.superclass.member_order).is_a?(Symbol) ? v : v.clone
		@def_order    = self.superclass.def_order.clone
		@inc_children = self.superclass.inc_children.clone
		@inc_values   = self.superclass.inc_values.clone
	end
end

.member_order(val = nil) ⇒ :values, ... Also known as: order

A getter and setter for a class’s initialization order. If the order value is ‘:values` the constructor will expect all of the values and then the children. If it is `:children` then the constructor expects children and then values. If it is `:def` the constructor expects to values and children in the order that they were defined. If val is nil the current value will be returned.

The default ordering is ‘:values`, which matches the behavior of previous versions of RLTK.

Parameters:

  • val (:values, :children, :def) (defaults to: nil)

    The new initialization order

Returns:

  • (:values, :children, :def)

    The current initialization order



223
224
225
226
227
228
229
# File 'lib/rltk/ast.rb', line 223

def member_order(val = nil)
	if val
		@member_order = val
	else
		@member_order
	end
end

.value(name, type, omit = false) ⇒ void

This method returns an undefined value.

Defined a value for this AST class and its subclasses. The name of the value will be used to define accessor methods that include type checking.

Parameters:

  • name (String, Symbol)

    Name of value

  • type (Class)

    Type of value

  • omit (Boolean) (defaults to: false)

    Include the value in the constructor or not



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/rltk/ast.rb', line 241

def value(name, type, omit = false)
	check_odr(name)

	if not (type.is_a?(Class) or (type.is_a?(Array) and type.length == 1))
		raise 'Child and Value types must be a class name or an array with a single class name element.'
	end

	@value_names   << name
	@array_members << name if type.is_a?(Array)

	if not omit
		@def_order  << name
		@inc_values << name
	end

	define_accessor(name, type)
end

.value_namesArray<Symbol>

Returns Array of the names of this node class’s values.

Returns:

  • (Array<Symbol>)

    Array of the names of this node class’s values



260
261
262
# File 'lib/rltk/ast.rb', line 260

def value_names
	@value_names
end

Instance Method Details

#==(other) ⇒ Boolean

Used for AST comparison, this function will return true if the two nodes are of the same class and all of their values and children are equal.

Parameters:

  • other (ASTNode)

    The ASTNode to compare to

Returns:

  • (Boolean)


276
277
278
# File 'lib/rltk/ast.rb', line 276

def ==(other)
	self.class == other.class and self.values == other.values and self.children == other.children
end

#[](key) ⇒ Object

Returns Note with the name key.

Returns:

  • (Object)

    Note with the name key



281
282
283
# File 'lib/rltk/ast.rb', line 281

def [](key)
	@notes[key]
end

#[]=(key, value) ⇒ Object

Sets the note named key to value.



286
287
288
# File 'lib/rltk/ast.rb', line 286

def []=(key, value)
	@notes[key] = value
end

#children(as = Array) ⇒ Array<ASTNode>, Hash{Symbol => ASTNode}

Returns Array or Hash of this node’s children.

Parameters:

  • as (Class) (defaults to: Array)

    The type that should be returned by the method. Must be either Array or hash.

Returns:

  • (Array<ASTNode>, Hash{Symbol => ASTNode})

    Array or Hash of this node’s children.



303
304
305
306
307
308
309
310
311
312
313
# File 'lib/rltk/ast.rb', line 303

def children(as = Array)
	if as == Array
		self.class.child_names.map { |name| self.send(name) }

	elsif as == Hash
		self.class.child_names.inject(Hash.new) { |h, name| h[name] = self.send(name); h }

	else
		raise 'Children can only be returned as an Array or a Hash.'
	end
end

#children=(children) ⇒ void

This method returns an undefined value.

Assigns an array or hash of AST nodes as the children of this node. If a hash is provided as an argument the key is used as the name of the child a object should be assigned to.

Parameters:

  • children (Array<ASTNode>, Hash{Symbol => ASTNode})

    Children to be assigned to this node.



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/rltk/ast.rb', line 322

def children=(children)
	case children
	when Array
		if children.length != self.class.child_names.length
			raise 'Wrong number of children specified.'
		end

		self.class.child_names.each_with_index do |name, i|
			self.send((name.to_s + '=').to_sym, children[i])
		end

	when Hash
		children.each do |name, val|
			if self.class.child_names.include?(name)
				self.send((name.to_s + '=').to_sym, val)
			else
				raise "ASTNode subclass #{self.class.name} does not have a child named #{name}."
			end
		end
	end
end

#copyASTNode

Produce an exact copy of this tree.

Returns:

  • (ASTNode)

    A copy of the tree.



347
348
349
# File 'lib/rltk/ast.rb', line 347

def copy
	self.map { |c| c }
end

#delete_note(key, recursive = true) ⇒ Object

Removes the note key from this node. If the recursive argument is true it will also remove the note from the node’s children.

Parameters:

  • key (Object)

    The key of the note to remove

  • recursive (Boolean) (defaults to: true)

    Do a recursive removal or not



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/rltk/ast.rb', line 356

def delete_note(key, recursive = true)
	if recursive
		self.children.each do |child|
			next if not child

			if child.is_a?(Array)
				child.each { |c| c.delete_note(key, true) }
			else
				child.delete_note(key, true)
			end
		end
	end

	@notes.delete(key)
end

#destructure(arity) ⇒ Object

This method allows ASTNodes to be destructured for pattern matching.



291
292
293
294
295
296
297
298
# File 'lib/rltk/ast.rb', line 291

def destructure(arity)
	case self.class.member_order
	when :values   then (self.class.inc_values + self.class.inc_children)
	when :children then (self.class.inc_children + self.class.inc_values)
	when :def      then self.class.def_order
	when Array     then self.class.member_order
	end.map { |m| self.send m }
end

#dump(dest = nil, limit = -1)) ⇒ void, String

This method is a simple wrapper around Marshal.dump, and is used to serialize an AST. You can use Marshal.load to reconstruct a serialized AST.

Parameters:

  • dest (nil, IO, String) (defaults to: nil)

    Where the serialized version of the AST will end up. If nil, this method will return the AST as a string.

  • limit (Fixnum) (defaults to: -1))

    Recursion depth. If -1 is specified there is no limit on the recursion depth.

Returns:

  • (void, String)

    String if dest is nil, void otherwise.



381
382
383
384
385
386
387
388
# File 'lib/rltk/ast.rb', line 381

def dump(dest = nil, limit = -1)
	case dest
	when nil    then Marshal.dump(self, limit)
	when String then File.open(dest, 'w') { |f| Marshal.dump(self, f, limit) }
	when IO     then Marshal.dump(self, dest, limit)
	else	            raise TypeError, "AST#dump expects nil, a String, or an IO object for the dest parameter."
	end
end

#each(order = :pre, &block) ⇒ void

This method returns an undefined value.

An iterator over the node’s children. The AST may be traversed in the following orders:

  • Pre-order (:pre)

  • Post-order (:post)

  • Level-order (:level)

Parameters:

  • order (:pre, :post, :level) (defaults to: :pre)

    The order in which to iterate over the tree



400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# File 'lib/rltk/ast.rb', line 400

def each(order = :pre, &block)
	case order
	when :pre
		yield self

		self.children.flatten.compact.each { |c| c.each(:pre, &block) }

	when :post
		self.children.flatten.compact.each { |c| c.each(:post, &block) }

		yield self

	when :level
		level_queue = [self]

		while node = level_queue.shift
			yield node

			level_queue += node.children.flatten.compact
		end
	end
end

#has_note?(key) ⇒ Boolean Also known as: note?

Tests to see if a note named key is present at this node.

Returns:

  • (Boolean)


424
425
426
# File 'lib/rltk/ast.rb', line 424

def has_note?(key)
	@notes.has_key?(key)
end

#map(&block) ⇒ Object

Note:

This does not modify the current tree.

Create a new tree by using the provided Proc object to map the nodes of this tree to new nodes. This is always done in post-order, meaning that all children of a node are visited before the node itself.

Returns:

  • (Object)

    Result of calling the given block on the root node



477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
# File 'lib/rltk/ast.rb', line 477

def map(&block)
	new_values = self.values.map { |v| v.clone }

	new_children =
	self.children.map do |c0|
		case c0
		when Array    then c0.map { |c1| c1.map(&block) }
		when ASTNode  then c0.map(&block)
		when NilClass then nil
		end
	end

	new_node		= self.class.new(*new_values, *new_children)
	new_node.notes = self.notes

	block.call(new_node)
end

#map!(&block) ⇒ Object

Note:

The root node can not be replaced and as such the result of

Map the nodes in an AST to new nodes using the provided Proc object. This is always done in post-order, meaning that all children of a node are visited before the node itself.

calling the provided block on the root node is used as the return value.

Returns:

  • (Object)

    Result of calling the given block on the root node



504
505
506
507
508
509
510
511
512
513
514
515
# File 'lib/rltk/ast.rb', line 504

def map!(&block)
	self.children =
	self.children.map do |c0|
		case c0
		when Array    then c0.map { |c1| c1.map!(&block) }
		when ASTNode  then c0.map!(&block)
		when NilClass then nil
		end
	end

	block.call(self)
end

#rootASTNode

Returns Root of the abstract syntax tree.

Returns:

  • (ASTNode)

    Root of the abstract syntax tree.



527
528
529
# File 'lib/rltk/ast.rb', line 527

def root
	if @parent then @parent.root else self end
end

#values(as = Array) ⇒ Array<Object>, Hash{Symbol => Object}

Returns Array or Hash of this node’s values.

Parameters:

  • as (Class) (defaults to: Array)

    The type that should be returned by the method. Must be either Array or hash.

Returns:

  • (Array<Object>, Hash{Symbol => Object})

    Array or Hash of this node’s values.



534
535
536
537
538
539
540
541
542
543
544
# File 'lib/rltk/ast.rb', line 534

def values(as = Array)
	if as == Array
		self.class.value_names.map { |name| self.send(name) }

	elsif as == Hash
		self.class.value_names.inject(Hash.new) { |h, name| h[name] = self.send(name); h }

	else
		raise 'Values can only be returned as an Array or a Hash.'
	end
end

#values=(values) ⇒ Object

Assigns an array or hash of objects as the values of this node. If a hash is provided as an argument the key is used as the name of the value an object should be assigned to.

Parameters:

  • values (Array<Object>, Hash{Symbol => Object})

    The values to be assigned to this node.



551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
# File 'lib/rltk/ast.rb', line 551

def values=(values)
	case values
	when Array
		if values.length != self.class.value_names.length
			raise 'Wrong number of values specified.'
		end

		self.class.value_names.each_with_index do |name, i|
			self.send((name.to_s + '=').to_sym, values[i])
		end

	when Hash
		values.each do |name, val|
			if self.class.value_names.include?(name)
				self.send((name.to_s + '=').to_sym, val)
			else
				raise "ASTNode subclass #{self.class.name} does not have a value named #{name}."
			end
		end
	end
end