Class: AST::Node
- Inherits:
-
Object
- Object
- AST::Node
- Defined in:
- lib/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.
Instance Attribute Summary collapse
-
#children ⇒ Array
readonly
Returns the children of this node.
-
#type ⇒ Symbol
readonly
Returns the type of this node.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
Compares
self
toother
, possibly converting withto_ast
. -
#append(element) ⇒ AST::Node
(also: #<<)
Appends
element
tochildren
and returns the resulting node. -
#assign_properties(properties) ⇒ nil
protected
By default, each entry in the
properties
hash is assigned to an instance variable in this instance of Node. -
#concat(array) ⇒ AST::Node
(also: #+)
Concatenates
array
withchildren
and returns the resulting node. -
#dup ⇒ Object
Nodes are already frozen, so there is no harm in returning the current node as opposed to initializing from scratch and freezing another one.
-
#fancy_type ⇒ String
protected
Returns
@type
with all underscores replaced by dashes. -
#initialize(type, children = [], properties = {}) ⇒ Node
constructor
Constructs a new instance of Node.
-
#to_a ⇒ Array
Returns #children.
-
#to_ast ⇒ AST::Node
Self.
-
#to_s ⇒ String
Converts
self
to a concise s-expression, omitting any children. -
#to_sexp(indent = 0) ⇒ String
(also: #inspect)
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.
57 58 59 60 61 62 63 |
# File 'lib/ast/node.rb', line 57 def initialize(type, children=[], properties={}) @type, @children = type.to_sym, children.to_a.freeze assign_properties(properties) freeze end |
Instance Attribute Details
#children ⇒ Array (readonly)
Returns the children of this node. The returned value is frozen.
46 47 48 |
# File 'lib/ast/node.rb', line 46 def children @children end |
#type ⇒ Symbol (readonly)
Returns the type of this node.
41 42 43 |
# File 'lib/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.
124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/ast/node.rb', line 124 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.
148 149 150 |
# File 'lib/ast/node.rb', line 148 def append(element) updated(nil, @children + [element]) end |
#assign_properties(properties) ⇒ nil (protected)
By default, each entry in the properties
hash is assigned to
an instance variable in this instance of Node. A subclass should define
attribute readers for such variables. The values passed in the hash
are not frozen or whitelisted; such behavior can also be implemented\
by subclassing Node and overriding this method.
72 73 74 75 76 77 78 |
# File 'lib/ast/node.rb', line 72 def assign_properties(properties) properties.each do |name, value| instance_variable_set :"@#{name}", value end nil end |
#concat(array) ⇒ AST::Node Also known as: +
Concatenates array
with children
and returns the resulting node.
139 140 141 |
# File 'lib/ast/node.rb', line 139 def concat(array) updated(nil, @children + array.to_a) end |
#dup ⇒ Object
Nodes are already frozen, so there is no harm in returning the current node as opposed to initializing from scratch and freezing another one.
89 90 91 |
# File 'lib/ast/node.rb', line 89 def dup self end |
#fancy_type ⇒ String (protected)
Returns @type
with all underscores replaced by dashes. This allows
to write symbol literals without quotes in Ruby sources and yet have
nicely looking s-expressions.
213 214 215 |
# File 'lib/ast/node.rb', line 213 def fancy_type @type.to_s.gsub('_', '-') 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)
171 172 173 |
# File 'lib/ast/node.rb', line 171 def to_a children end |
#to_ast ⇒ AST::Node
Returns self.
202 203 204 |
# File 'lib/ast/node.rb', line 202 def to_ast self end |
#to_s ⇒ String
Converts self
to a concise s-expression, omitting any children.
157 158 159 |
# File 'lib/ast/node.rb', line 157 def to_s "(#{fancy_type} ...)" end |
#to_sexp(indent = 0) ⇒ String Also known as: inspect
Converts self
to a pretty-printed s-expression.
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/ast/node.rb', line 179 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.
106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/ast/node.rb', line 106 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 original_dup.send :initialize, new_type, new_children, new_properties end end |