Class: AST::Node

Inherits:
Object
  • Object
show all
Includes:
BELParser::Parsers
Defined in:
lib/bel_parser/vendor/ast/node.rb,
lib/bel_parser/parsers/expression/statement_autocomplete.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

Instance Method Summary collapse

Methods included from BELParser::Parsers

#serialize

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

#childrenArray (readonly)

Returns the children of this node. The returned value is frozen.

Returns:

  • (Array)


46
47
48
# File 'lib/bel_parser/vendor/ast/node.rb', line 46

def children
  @children
end

#hashFixnum (readonly)

Returns the precomputed hash value for this node

Returns:

  • (Fixnum)


50
51
52
# File 'lib/bel_parser/vendor/ast/node.rb', line 50

def hash
  @hash
end

#typeSymbol (readonly)

Returns the type of this node.

Returns:

  • (Symbol)


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.

Returns:

  • (Boolean)


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.

Returns:



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.

Returns:



156
157
158
# File 'lib/bel_parser/vendor/ast/node.rb', line 156

def concat(array)
  updated(nil, @children + array.to_a)
end

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

Returns:

  • self



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

Parameters:

  • other (Object)

Returns:

  • (Boolean)


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()

Parameters:

  • indent (Integer) (defaults to: 0)

    Base indentation level.

Returns:

  • (String)


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_aArray

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)

Returns:

  • (Array)


181
182
183
# File 'lib/bel_parser/vendor/ast/node.rb', line 181

def to_a
  children
end

#to_astAST::Node

Returns self.

Returns:



239
240
241
# File 'lib/bel_parser/vendor/ast/node.rb', line 239

def to_ast
  self
end

#to_belObject



1016
1017
1018
# File 'lib/bel_parser/parsers/expression/statement_autocomplete.rb', line 1016

def to_bel
  serialize(self)
end

#to_sString

Converts ‘self` to a pretty-printed s-expression.

Parameters:

  • indent (Integer)

    Base indentation level.

Returns:

  • (String)


210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/bel_parser/vendor/ast/node.rb', line 210

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

#to_sexp(indent = 0) ⇒ String

Converts ‘self` to a pretty-printed s-expression.

Parameters:

  • indent (Integer) (defaults to: 0)

    Base indentation level.

Returns:

  • (String)


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.

Parameters:

  • type (Symbol, nil) (defaults to: nil)
  • children (Array, nil) (defaults to: nil)
  • properties (Hash, nil) (defaults to: nil)

Returns:



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