Class: RLTK::ASTNode
- Inherits:
-
Object
- Object
- RLTK::ASTNode
- 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
-
#notes ⇒ Hash
The notes hash for this node.
-
#parent ⇒ ASTNode
Reference to the parent node.
Class Method Summary collapse
-
.check_odr(name) ⇒ Object
Check to make sure a name isn’t re-defining a value or child.
-
.child(name, type) ⇒ void
Defined a child for this AST class and its subclasses.
-
.child_names ⇒ Array<Symbol>
Array of the names of this node class’s children.
-
.child_types ⇒ Array
Array of types of this node class’s children.
-
.define_accessor(name, type, set_parent = false) ⇒ void
This method defines a type checking accessor named name with type type.
-
.inherited(klass) ⇒ void
Called when the Lexer class is sub-classed, it installes necessary instance class variables.
-
.install_icvars ⇒ void
Installs instance class varialbes into a class.
-
.value(name, type) ⇒ void
Defined a value for this AST class and its subclasses.
-
.value_names ⇒ Array<Symbol>
Array of the names of this node class’s values.
-
.value_types ⇒ Array<Symbol>
Array of the types of this node class’s values.
Instance Method Summary collapse
-
#==(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.
-
#[](key) ⇒ Object
Note with the name key.
-
#[]=(key, value) ⇒ Object
Sets the note named key to value.
-
#call(arity) ⇒ Object
This method allows ASTNodes to be destructured for pattern matching.
-
#children(as = Array) ⇒ Array<ASTNode>, Hash{Symbol => ASTNode}
Array or Hash of this node’s children.
-
#children=(children) ⇒ void
Assigns an array or hash of AST nodes as the children of this node.
-
#copy ⇒ ASTNode
Produce an exact copy of this tree.
-
#delete_note(key, recursive = true) ⇒ Object
Removes the note key from this node.
-
#dump(dest = nil, limit = -1)) ⇒ void, String
This method is a simple wrapper around Marshal.dump, and is used to serialize an AST.
-
#each(order = :pre, &block) ⇒ void
An iterator over the node’s children.
-
#has_note?(key) ⇒ Boolean
(also: #note?)
Tests to see if a note named key is present at this node.
-
#initialize(*objects, &block) ⇒ ASTNode
constructor
Instantiates a new ASTNode object.
-
#map(&block) ⇒ Object
Create a new tree by using the provided Proc object to map the nodes of this tree to new nodes.
-
#map!(&block) ⇒ Object
Map the nodes in an AST to new nodes using the provided Proc object.
-
#root ⇒ ASTNode
Root of the abstract syntax tree.
-
#values(as = Array) ⇒ Array<Object>, Hash{Symbol => Object}
Array or Hash of this node’s values.
-
#values=(values) ⇒ Object
Assigns an array or hash of objects as the values of this node.
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.
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
#notes ⇒ Hash
Returns The notes hash for this node.
31 32 33 |
# File 'lib/rltk/ast.rb', line 31 def notes @notes end |
#parent ⇒ ASTNode
Returns 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.
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.
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_names ⇒ Array<Symbol>
Returns 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_types ⇒ Array
Returns 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.
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.
76 77 78 |
# File 'lib/rltk/ast.rb', line 76 def inherited(klass) klass.install_icvars end |
.install_icvars ⇒ void
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.
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_names ⇒ Array<Symbol>
Returns 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_types ⇒ Array<Symbol>
Returns 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.
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.
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.
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.
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 |
#copy ⇒ ASTNode
Produce an exact copy of this 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.
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.
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)
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.
362 363 364 |
# File 'lib/rltk/ast.rb', line 362 def has_note?(key) @notes.has_key?(key) end |
#map(&block) ⇒ Object
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 |
#map!(&block) ⇒ Object
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 |
#root ⇒ ASTNode
Returns 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.
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.
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 |