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/sax_parser'
|
||||||
require 'oga/html/entities'
|
require 'oga/html/entities'
|
||||||
|
|
||||||
|
require 'oga/ruby/node'
|
||||||
|
|
||||||
require 'oga/xpath/lexer'
|
require 'oga/xpath/lexer'
|
||||||
require 'oga/xpath/parser'
|
require 'oga/xpath/parser'
|
||||||
require 'oga/xpath/evaluator'
|
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