Class: RLTK::ASTNode

Inherits:
Object
  • Object
show all
Extended by:
Filigree::AbstractClass, Filigree::Destructurable
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

- (ASTNode) initialize(*objects, &block)

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.



383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'lib/rltk/ast.rb', line 383

def initialize(*objects, &block)
	@notes  = Hash.new()
	@parent = nil
	
	# Pad out the objects array with nil values and empty
	# arrays.
	all_types       = self.class.value_types + self.class.child_types
	remaining_types = all_types[objects.length..-1]
	
	objects += remaining_types.map { |type| type.is_a?(Array) ? [] : nil }
	
	pivot = self.class.value_names.length
	
	self.values   = objects[0...pivot]
	self.children = objects[pivot..-1]
	
	self.instance_exec(&block) if not block.nil?
end

Instance Attribute Details

- (Hash) notes



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

def notes
  @notes
end

- (ASTNode) parent



28
29
30
# File 'lib/rltk/ast.rb', line 28

def parent
  @parent
end

Class Method Details

+ (Object) check_odr(name)

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



42
43
44
45
46
47
48
49
50
# File 'lib/rltk/ast.rb', line 42

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

+ (void) child(name, type)

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.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/rltk/ast.rb', line 89

def child(name, type)
	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
	@child_types << type
	define_accessor(name, type, true)
end

+ (Array<Symbol>) child_names



114
115
116
# File 'lib/rltk/ast.rb', line 114

def child_names
	@child_names
end

+ (Array) child_types



119
120
121
# File 'lib/rltk/ast.rb', line 119

def child_types
	@child_types
end

+ (void) inherited(klass)

This method returns an undefined value.

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



76
77
78
# File 'lib/rltk/ast.rb', line 76

def inherited(klass)
	klass.install_icvars
end

+ (void) value(name, type)

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.



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/rltk/ast.rb', line 175

def value(name, type)
	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
	
	@value_names << name
	@value_types << type
	define_accessor(name, type)
end

+ (Array<Symbol>) value_names



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

def value_names
	@value_names
end

+ (Array<Symbol>) value_types



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

def value_types
	@value_types
end

Instance Method Details

- (Boolean) ==(other)

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.



215
216
217
# File 'lib/rltk/ast.rb', line 215

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

- (Object) [](key)



220
221
222
# File 'lib/rltk/ast.rb', line 220

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

- (Object) []=(key, value)

Sets the note named key to value.



225
226
227
# File 'lib/rltk/ast.rb', line 225

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

- (Object) call(arity)

This method allows ASTNodes to be destructured for pattern matching.



230
231
232
233
234
235
236
# File 'lib/rltk/ast.rb', line 230

def call(arity)
	if arity == self.values.length
		self.values
	else
		[*self.values, *self.children]
	end
end

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



241
242
243
244
245
246
247
248
249
250
251
# File 'lib/rltk/ast.rb', line 241

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

- (void) children=(children)

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.



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/rltk/ast.rb', line 260

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

- (ASTNode) copy

Produce an exact copy of this tree.



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

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

- (Object) delete_note(key, recursive = true)

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



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/rltk/ast.rb', line 294

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

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

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.



319
320
321
322
323
324
325
326
# File 'lib/rltk/ast.rb', line 319

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

- (void) each(order = :pre, &block)

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)



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/rltk/ast.rb', line 338

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

- (Boolean) has_note?(key) Also known as: note?

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



362
363
364
# File 'lib/rltk/ast.rb', line 362

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

- (Object) map(&block)

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.



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/rltk/ast.rb', line 410

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

- (Object) map!(&block)

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.



437
438
439
440
441
442
443
444
445
446
447
448
# File 'lib/rltk/ast.rb', line 437

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

- (ASTNode) root



460
461
462
# File 'lib/rltk/ast.rb', line 460

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

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



467
468
469
470
471
472
473
474
475
476
477
# File 'lib/rltk/ast.rb', line 467

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

- (Object) values=(values)

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.



484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/rltk/ast.rb', line 484

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