class AST::Node

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.

Attributes

children[R]

Returns the children of this node. The returned value is frozen. The to_a alias is useful for decomposing nodes concisely. For example:

node = s(:gasgn, :$foo, s(:integer, 1))
var_name, value = *node
p var_name # => :$foo
p value    # => (integer 1)

@return [Array]

hash[R]

Returns the precomputed hash value for this node @return [Fixnum]

to_a[R]

Returns the children of this node. The returned value is frozen. The to_a alias is useful for decomposing nodes concisely. For example:

node = s(:gasgn, :$foo, s(:integer, 1))
var_name, value = *node
p var_name # => :$foo
p value    # => (integer 1)

@return [Array]

type[R]

Returns the type of this node. @return [Symbol]

Public Class Methods

new(type, children=[], properties={}) click to toggle source

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

# File lib/ast/node.rb, line 72
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

Public Instance Methods

+(array)
Alias for: concat
<<(element)
Alias for: append
==(other) click to toggle source

Compares `self` to `other`, possibly converting with `to_ast`. Only `type` and `children` are compared; metadata is deliberately ignored.

@return [Boolean]

# File lib/ast/node.rb, line 153
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) click to toggle source

Appends `element` to `children` and returns the resulting node.

@return [AST::Node]

# File lib/ast/node.rb, line 177
def append(element)
  updated(nil, @children + [element])
end
Also aliased as: <<
clone()
Alias for: dup
concat(array) click to toggle source

Concatenates `array` with `children` and returns the resulting node.

@return [AST::Node]

# File lib/ast/node.rb, line 168
def concat(array)
  updated(nil, @children + array.to_a)
end
Also aliased as: +
deconstruct() click to toggle source

Enables matching for Node, where type is the first element and the children are remaining items.

@return [Array]

# File lib/ast/node.rb, line 253
def deconstruct
  [type, *children]
end
dup() click to toggle source

Nodes are already frozen, so there is no harm in returning the current node as opposed to initializing from scratch and freezing another one.

@return self

# File lib/ast/node.rb, line 115
def dup
  self
end
Also aliased as: original_dup, clone
eql?(other) click to toggle source

Test if other object is equal to @param [Object] other @return [Boolean]

# File lib/ast/node.rb, line 85
def eql?(other)
  self.class.eql?(other.class)   &&
  @type.eql?(other.type)         &&
  @children.eql?(other.children)
end
inspect(indent=0) click to toggle source

Converts `self` to a s-expression ruby string. The code return will recreate the node, using the sexp module s()

@param [Integer] indent Base indentation level. @return [String]

# File lib/ast/node.rb, line 211
def inspect(indent=0)
  indented = "  " * indent
  sexp = "#{indented}s(:#{@type}"

  children.each do |child|
    if child.is_a?(Node)
      sexp += ",\n#{child.inspect(indent + 1)}"
    else
      sexp += ", #{child.inspect}"
    end
  end

  sexp += ")"

  sexp
end
original_dup()
Alias for: dup
to_ast() click to toggle source

@return [AST::Node] self

# File lib/ast/node.rb, line 229
def to_ast
  self
end
to_s(indent=0)
Alias for: to_sexp
to_sexp(indent=0) click to toggle source

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

@param [Integer] indent Base indentation level. @return [String]

# File lib/ast/node.rb, line 187
def to_sexp(indent=0)
  indented = "  " * indent
  sexp = "#{indented}(#{fancy_type}"

  children.each do |child|
    if child.is_a?(Node)
      sexp += "\n#{child.to_sexp(indent + 1)}"
    else
      sexp += " #{child.inspect}"
    end
  end

  sexp += ")"

  sexp
end
Also aliased as: to_s
to_sexp_array() click to toggle source

Converts `self` to an Array where the first element is the type as a Symbol, and subsequent elements are the same representation of its children.

@return [Array<Symbol, […Array]>]

# File lib/ast/node.rb, line 237
def to_sexp_array
  children_sexp_arrs = children.map do |child|
    if child.is_a?(Node)
      child.to_sexp_array
    else
      child
    end
  end

  [type, *children_sexp_arrs]
end
updated(type=nil, children=nil, properties=nil) click to toggle source

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.

@param [Symbol, nil] type @param [Array, nil] children @param [Hash, nil] properties @return [AST::Node]

# File lib/ast/node.rb, line 133
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
    copy = original_dup
    copy.send :initialize, new_type, new_children, new_properties
    copy
  end
end

Protected Instance Methods

assign_properties(properties) click to toggle source

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.

@return [nil]

# File lib/ast/node.rb, line 98
def assign_properties(properties)
  properties.each do |name, value|
    instance_variable_set :"@#{name}", value
  end

  nil
end
fancy_type() click to toggle source

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.

@return [String]

# File lib/ast/node.rb, line 264
def fancy_type
  @type.to_s.gsub('_', '-')
end