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

#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



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

#notesHash

Returns The notes hash for this node.

Returns:

  • (Hash)

    The notes hash for this node.



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

def notes
  @notes
end

#parentASTNode

Returns Reference to the parent node.

Returns:

  • (ASTNode)

    Reference to the parent node.



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

def parent
  @parent
end

Class Method Details

.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



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

.child(name, type) ⇒ 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.



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

.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



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

def child_names
	@child_names
end

.child_typesArray

Returns Array of types of this node class’s children.

Returns:

  • (Array)

    Array of types of this node class’s children



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

def child_types
	@child_types
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



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/rltk/ast.rb', line 131

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

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



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

def inherited(klass)
	klass.install_icvars
end

.install_icvarsvoid

This method returns an undefined value.

Installs instance class varialbes into a class.



55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/rltk/ast.rb', line 55

def install_icvars
	if self.superclass == ASTNode
		@child_names = Array.new
		@child_types = Array.new
		@value_names = Array.new
		@value_types = Array.new
	else
		@child_names = self.superclass.child_names.clone
		@child_types = self.superclass.child_types.clone
		@value_names = self.superclass.value_names.clone
		@value_types = self.superclass.value_types.clone
	end
end

.value(name, type) ⇒ 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



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

.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



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

def value_names
	@value_names
end

.value_typesArray<Symbol>

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

Returns:

  • (Array<Symbol>)

    Array of the types of this node class’s values



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

def value_types
	@value_types
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)


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

#[](key) ⇒ Object

Returns Note with the name key.

Returns:

  • (Object)

    Note with the name key



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

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

#[]=(key, value) ⇒ Object

Sets the note named key to value.



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

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

#call(arity) ⇒ Object

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

#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.



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

#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.



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

#copyASTNode

Produce an exact copy of this tree.

Returns:

  • (ASTNode)

    A copy of the tree.



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

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



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

#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.



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

#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



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

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

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

Returns:

  • (Boolean)


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

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



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

#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



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

#rootASTNode

Returns Root of the abstract syntax tree.

Returns:

  • (ASTNode)

    Root of the abstract syntax tree.



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

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.



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

#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.



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