195 lines
4.4 KiB
Ruby
195 lines
4.4 KiB
Ruby
module Oga
|
|
module Ruby
|
|
##
|
|
# Class representing a single node in a Ruby AST.
|
|
#
|
|
# The setup of this class is roughly based on the "ast" Gem. The "ast" Gem
|
|
# is not used for this class as it provides too many methods that might
|
|
# conflict with this class' {#method_missing}.
|
|
#
|
|
# ASTs can be built by creating a node and then chaining various method
|
|
# calls together. For example, the following could be used to build an "if"
|
|
# statement:
|
|
#
|
|
# number1 = Node.new(:lit, %w{10})
|
|
# number2 = Node.new(:lit, %w{20})
|
|
#
|
|
# (number2 > number1).if_true do
|
|
# Node.new(:lit, %w{30})
|
|
# end
|
|
#
|
|
# When serialized to Ruby this would roughly lead to the following code:
|
|
#
|
|
# if 20 > 10
|
|
# 30
|
|
# end
|
|
#
|
|
# @private
|
|
#
|
|
class Node < BasicObject
|
|
undef_method :!, :!=
|
|
|
|
# @return [Symbol]
|
|
attr_reader :type
|
|
|
|
# @param [Symbol] type
|
|
# @param [Array] children
|
|
def initialize(type, children = [])
|
|
@type = type.to_sym
|
|
@children = children
|
|
end
|
|
|
|
# @return [Array]
|
|
def to_a
|
|
@children
|
|
end
|
|
|
|
##
|
|
# Returns an assignment node.
|
|
#
|
|
# @param [Oga::Ruby::Node] other
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def assign(other)
|
|
Node.new(:assign, [self, other])
|
|
end
|
|
|
|
##
|
|
# Returns an equality expression node.
|
|
#
|
|
# @param [Oga::Ruby::Node] other
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def eq(other)
|
|
Node.new(:eq, [self, other])
|
|
end
|
|
|
|
##
|
|
# Returns a boolean "and" node.
|
|
#
|
|
# @param [Oga::Ruby::Node] other
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def and(other)
|
|
Node.new(:and, [self, other])
|
|
end
|
|
|
|
##
|
|
# Returns a boolean "or" node.
|
|
#
|
|
# @param [Oga::Ruby::Node] other
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def or(other)
|
|
Node.new(:or, [self, other])
|
|
end
|
|
|
|
##
|
|
# Returns a node that evaluates to its inverse.
|
|
#
|
|
# For example, a variable `foo` would be turned into `!foo`.
|
|
#
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def not
|
|
!self
|
|
end
|
|
|
|
##
|
|
# Returns a node for Ruby's "is_a?" method.
|
|
#
|
|
# @param [Class] klass
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def is_a?(klass)
|
|
Node.new(:send, [self, 'is_a?', Node.new(:lit, [klass.to_s])])
|
|
end
|
|
|
|
##
|
|
# Wraps the current node in a block.
|
|
#
|
|
# @param [Array] args Arguments (as Node instances) to pass to the block.
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def add_block(*args)
|
|
Node.new(:block, [self, args, yield])
|
|
end
|
|
|
|
##
|
|
# Wraps the current node in a `begin` node.
|
|
#
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def wrap
|
|
Node.new(:begin, [self])
|
|
end
|
|
|
|
##
|
|
# Wraps the current node in an if statement node.
|
|
#
|
|
# The body of this statement is set to the return value of the supplied
|
|
# block.
|
|
#
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def if_true
|
|
Node.new(:if, [self, yield])
|
|
end
|
|
|
|
##
|
|
# Wraps the current node in a `while` statement.
|
|
#
|
|
# The body of this statement is set to the return value of the supplied
|
|
# block.
|
|
#
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def while_true
|
|
Node.new(:while, [self, yield])
|
|
end
|
|
|
|
##
|
|
# Adds an "else" statement to the current node.
|
|
#
|
|
# This method assumes it's being called only on "if" nodes.
|
|
#
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def else
|
|
Node.new(:if, @children + [yield])
|
|
end
|
|
|
|
##
|
|
# Chains two nodes together.
|
|
#
|
|
# @param [Oga::Ruby::Node] other
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def followed_by(other = nil)
|
|
other = yield if ::Kernel.block_given?
|
|
|
|
Node.new(:followed_by, [self, other])
|
|
end
|
|
|
|
##
|
|
# Returns a node for a method call.
|
|
#
|
|
# @param [Symbol] name The name of the method to call.
|
|
#
|
|
# @param [Array] args Any arguments (as Node instances) to pass to the
|
|
# method.
|
|
#
|
|
# @return [Oga::Ruby::Node]
|
|
#
|
|
def method_missing(name, *args)
|
|
Node.new(:send, [self, name.to_s, *args])
|
|
end
|
|
|
|
# @return [String]
|
|
def inspect
|
|
"(#{type} #{@children.map(&:inspect).join(' ')})"
|
|
end
|
|
end # Node
|
|
end # Ruby
|
|
end # Oga
|