Class: AST::Node
- Inherits:
-
Object
- Object
- AST::Node
- Defined in:
- lib/bel_parser/vendor/ast/node.rb
Overview
Node is an immutable class, instances of which represent abstract syntax tree nodes. It combines semantic information (i.e. anything that affects the algorithmic properties of a program) with meta-information (line numbers or compiler intermediates).
Notes on inheritance
The distinction between semantics and metadata is important. Complete semantic information should be contained within just the #type and #children of a Node instance; in other words, if an AST was to be stripped of all meta-information, it should remain a valid AST which could be successfully processed to yield a result with the same algorithmic properties.
Thus, Node should never be inherited in order to define methods which affect or return semantic information, such as getters for ‘class_name`, `superclass` and `body` in the case of a hypothetical `ClassNode`. The correct solution is to use a generic Node with a #type of `:class` and three children. See also Processor for tips on working with such ASTs.
On the other hand, Node can and should be inherited to define application-specific metadata (see also #initialize) or customize the printing format. It is expected that an application would have one or two such classes and use them across the entire codebase.
The rationale for this pattern is extensibility and maintainability. Unlike static ones, dynamic languages do not require the presence of a predefined, rigid structure, nor does it improve dispatch efficiency, and while such a structure can certainly be defined, it does not add any value but incurs a maintaining cost. For example, extending the AST even with a transformation-local temporary node type requires making globally visible changes to the codebase.
Direct Known Subclasses
BELParser::Language::Semantics::SemanticASTNode, BELParser::Parsers::AST::Node
Instance Attribute Summary collapse
-
#children ⇒ Array
readonly
Returns the children of this node.
-
#hash ⇒ Fixnum
readonly
Returns the precomputed hash value for this node.
-
#type ⇒ Symbol
readonly
Returns the type of this node.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
Compares ‘self` to `other`, possibly converting with `to_ast`.
-
#append(element) ⇒ AST::Node
(also: #<<)
Appends ‘element` to `children` and returns the resulting node.
-
#concat(array) ⇒ AST::Node
(also: #+)
Concatenates ‘array` with `children` and returns the resulting node.
-
#dup ⇒ Object
(also: #clone)
Nodes are already frozen, so there is no harm in returning the current node as opposed to initializing from scratch and freezing another one.
-
#eql?(other) ⇒ Boolean
Test if other object is equal to.
-
#initialize(type, children = [], properties = {}) ⇒ Node
constructor
Constructs a new instance of Node.
-
#inspect(indent = 0) ⇒ String
Converts ‘self` to a s-expression ruby string.
-
#to_a ⇒ Array
Returns #children.
-
#to_ast ⇒ AST::Node
Self.
-
#to_sexp(indent = 0) ⇒ String
(also: #to_s)
Converts ‘self` to a pretty-printed s-expression.
-
#updated(type = nil, children = nil, properties = nil) ⇒ AST::Node
Returns a new instance of Node where non-nil arguments replace the corresponding fields of ‘self`.
Constructor Details
#initialize(type, children = [], properties = {}) ⇒ Node
Constructs a new instance of Node.
The arguments ‘type` and `children` are converted with `to_sym` and `to_a` respectively. Additionally, the result of converting `children` is frozen. While mutating the arguments is generally considered harmful, the most common case is to pass an array literal to the constructor. If your code does not expect the argument to be frozen, use `#dup`.
The ‘properties` hash is passed to #assign_properties.
61 62 63 64 65 66 67 68 69 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 61 def initialize(type, children=[], properties={}) @type, @children = type.to_sym, children.to_a.freeze assign_properties(properties) @hash = [@type, @children, self.class].hash freeze end |
Instance Attribute Details
#children ⇒ Array (readonly)
Returns the children of this node. The returned value is frozen.
46 47 48 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 46 def children @children end |
#hash ⇒ Fixnum (readonly)
Returns the precomputed hash value for this node
50 51 52 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 50 def hash @hash end |
#type ⇒ Symbol (readonly)
Returns the type of this node.
41 42 43 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 41 def type @type end |
Instance Method Details
#==(other) ⇒ Boolean
Compares ‘self` to `other`, possibly converting with `to_ast`. Only `type` and `children` are compared; metadata is deliberately ignored.
141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 141 def ==(other) if equal?(other) true elsif other.respond_to? :to_ast other = other.to_ast other.type == self.type && other.children == self.children else false end end |
#append(element) ⇒ AST::Node Also known as: <<
Appends ‘element` to `children` and returns the resulting node.
165 166 167 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 165 def append(element) updated(nil, @children + [element]) end |
#concat(array) ⇒ AST::Node Also known as: +
Concatenates ‘array` with `children` and returns the resulting node.
156 157 158 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 156 def concat(array) updated(nil, @children + array.to_a) end |
#dup ⇒ Object Also known as: clone
Nodes are already frozen, so there is no harm in returning the current node as opposed to initializing from scratch and freezing another one.
104 105 106 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 104 def dup self end |
#eql?(other) ⇒ Boolean
Test if other object is equal to
74 75 76 77 78 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 74 def eql?(other) self.class.eql?(other.class) && @type.eql?(other.type) && @children.eql?(other.children) end |
#inspect(indent = 0) ⇒ String
Converts ‘self` to a s-expression ruby string. The code return will recreate the node, using the sexp module s()
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 217 def inspect(indent=0) indented = " " * indent sexp = "#{indented}s(:#{@type}" first_node_child = children.index do |child| child.is_a?(Node) || child.is_a?(Array) end || children.count children.each_with_index do |child, idx| if child.is_a?(Node) && idx >= first_node_child sexp << ",\n#{child.inspect(indent + 1)}" else sexp << ", #{child.inspect}" end end sexp << ")" sexp end |
#to_a ⇒ Array
Returns #children. This is very useful in order to decompose nodes concisely. For example:
node = s(:gasgn, :$foo, s(:integer, 1))
s
var_name, value = *node
p var_name # => :$foo
p value # => (integer 1)
181 182 183 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 181 def to_a children end |
#to_ast ⇒ AST::Node
Returns self.
239 240 241 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 239 def to_ast self end |
#to_sexp(indent = 0) ⇒ String Also known as: to_s
Converts ‘self` to a pretty-printed s-expression.
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 189 def to_sexp(indent=0) indented = " " * indent sexp = "#{indented}(#{fancy_type}" first_node_child = children.index do |child| child.is_a?(Node) || child.is_a?(Array) end || children.count children.each_with_index do |child, idx| if child.is_a?(Node) && idx >= first_node_child sexp << "\n#{child.to_sexp(indent + 1)}" else sexp << " #{child.inspect}" end end sexp << ")" sexp end |
#updated(type = nil, children = nil, properties = nil) ⇒ AST::Node
Returns a new instance of Node where non-nil arguments replace the corresponding fields of ‘self`.
For example, ‘Node.new(:foo, [ 1, 2 ]).updated(:bar)` would yield `(bar 1 2)`, and `Node.new(:foo, [ 1, 2 ]).updated(nil, [])` would yield `(foo)`.
If the resulting node would be identical to ‘self`, does nothing.
122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/bel_parser/vendor/ast/node.rb', line 122 def updated(type=nil, children=nil, properties=nil) new_type = type || @type new_children = children || @children new_properties = properties || {} if @type == new_type && @children == new_children && properties.nil? self else # Maybe change call? original_dup.send :initialize, new_type, new_children, new_properties end end |