Added Oga::Ruby::Node
This class will be used for building Ruby ASTs that will be generated based on XPath expressions.
This commit is contained in:
parent
c25879f18e
commit
6673f176d8
|
@ -49,6 +49,8 @@ require 'oga/html/parser'
|
|||
require 'oga/html/sax_parser'
|
||||
require 'oga/html/entities'
|
||||
|
||||
require 'oga/ruby/node'
|
||||
|
||||
require 'oga/xpath/lexer'
|
||||
require 'oga/xpath/parser'
|
||||
require 'oga/xpath/evaluator'
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
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
|
||||
# @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 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 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
|
||||
|
||||
##
|
||||
# Chains two nodes together.
|
||||
#
|
||||
# @param [Oga::Ruby::Node] other
|
||||
# @return [Oga::Ruby::Node]
|
||||
#
|
||||
def followed_by(other)
|
||||
Node.new(:begin, [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
|
|
@ -0,0 +1,131 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Oga::Ruby::Node do
|
||||
describe '#type' do
|
||||
it 'returns the type of the node as a Symbol' do
|
||||
node = described_class.new('foo')
|
||||
|
||||
node.type.should == :foo
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_a' do
|
||||
it 'returns the children of the Node as an Array' do
|
||||
node = described_class.new(:foo, %w{10})
|
||||
|
||||
node.to_a.should == %w{10}
|
||||
end
|
||||
end
|
||||
|
||||
describe '#assign' do
|
||||
it 'returns an assignment Node' do
|
||||
left = described_class.new(:lit, %w{number})
|
||||
right = described_class.new(:lit, %w{10})
|
||||
node = left.assign(right)
|
||||
|
||||
node.type.should == :assign
|
||||
node.to_a.should == [left, right]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#eq' do
|
||||
it 'returns an equality Node' do
|
||||
left = described_class.new(:lit, %w{number})
|
||||
right = described_class.new(:lit, %w{10})
|
||||
node = left.eq(right)
|
||||
|
||||
node.type.should == :eq
|
||||
node.to_a.should == [left, right]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#and' do
|
||||
it 'returns a boolean and Node' do
|
||||
left = described_class.new(:lit, %w{number})
|
||||
right = described_class.new(:lit, %w{10})
|
||||
node = left.and(right)
|
||||
|
||||
node.type.should == :and
|
||||
node.to_a.should == [left, right]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#or' do
|
||||
it 'returns a boolean or Node' do
|
||||
left = described_class.new(:lit, %w{number})
|
||||
right = described_class.new(:lit, %w{10})
|
||||
node = left.or(right)
|
||||
|
||||
node.type.should == :or
|
||||
node.to_a.should == [left, right]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#is_a?' do
|
||||
it 'returns a is_a? call Node' do
|
||||
left = described_class.new(:lit, %w{number})
|
||||
node = left.is_a?(String)
|
||||
|
||||
node.type.should == :send
|
||||
|
||||
node.to_a[0].should == left
|
||||
node.to_a[1].should == 'is_a?'
|
||||
|
||||
node.to_a[2].type.should == :lit
|
||||
node.to_a[2].to_a.should == %w{String}
|
||||
end
|
||||
end
|
||||
|
||||
describe '#add_block' do
|
||||
it 'returns a block Node' do
|
||||
left = described_class.new(:lit, %w{number})
|
||||
arg = described_class.new(:lit, %w{10})
|
||||
body = described_class.new(:lit, %w{20})
|
||||
block = left.add_block(arg) { body }
|
||||
|
||||
block.type.should == :block
|
||||
block.to_a.should == [left, [arg], body]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#if_true' do
|
||||
it 'returns an if-statement Node' do
|
||||
condition = described_class.new(:lit, %w{number})
|
||||
body = described_class.new(:lit, %w{10})
|
||||
statement = condition.if_true { body }
|
||||
|
||||
statement.type.should == :if
|
||||
statement.to_a.should == [condition, body]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#followed_by' do
|
||||
it 'returns a Node chaining two nodes together' do
|
||||
node1 = described_class.new(:lit, %w{A})
|
||||
node2 = described_class.new(:lit, %w{B})
|
||||
joined = node1.followed_by(node2)
|
||||
|
||||
joined.type.should == :begin
|
||||
joined.to_a.should == [node1, node2]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#method_missing' do
|
||||
it 'returns a send Node' do
|
||||
receiver = described_class.new(:lit, %w{foo})
|
||||
arg = described_class.new(:lit, %w{10})
|
||||
call = receiver.foo(arg)
|
||||
|
||||
call.type.should == :send
|
||||
call.to_a.should == [receiver, 'foo', arg]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#inspect' do
|
||||
it 'returns a String' do
|
||||
node = described_class.new(:lit, %w{10})
|
||||
|
||||
node.inspect.should == '(lit "10")'
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue