XPath compiler support for predicates
This currently supports index predicates (e.g. "foo[10]") and predicates using paths (e.g. foo[bar/baz]). The usage of boolean operators and more complex expressions has not yet been tested as these are not yet supported in the first place.
This commit is contained in:
parent
7fdf8d7460
commit
9925b2a9c9
|
@ -177,8 +177,72 @@ module Oga
|
||||||
def on_predicate(ast, input, &block)
|
def on_predicate(ast, input, &block)
|
||||||
test, predicate = *ast
|
test, predicate = *ast
|
||||||
|
|
||||||
process(test, input) do |node|
|
if xpath_number?(predicate)
|
||||||
process(predicate, node).if_true(&block)
|
index_predicate(test, predicate, input, &block)
|
||||||
|
else
|
||||||
|
expression_predicate(test, predicate, input, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Processes an index predicate such as `foo[10]`.
|
||||||
|
#
|
||||||
|
# @param [AST::Node] test
|
||||||
|
# @param [AST::Node] predicate
|
||||||
|
# @param [Oga::Ruby::Node] input
|
||||||
|
# @return [Oga::Ruby::Node]
|
||||||
|
#
|
||||||
|
def index_predicate(test, predicate, input)
|
||||||
|
int1 = literal('1')
|
||||||
|
index = on_int(predicate)
|
||||||
|
index_var = literal('index')
|
||||||
|
|
||||||
|
inner = process(test, input) do |matched_test_node|
|
||||||
|
index_var.eq(index).if_true { yield matched_test_node }
|
||||||
|
.followed_by(index_var.assign(index_var + int1))
|
||||||
|
end
|
||||||
|
|
||||||
|
index_var.assign(int1).followed_by(inner)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Processes a predicate using an expression.
|
||||||
|
#
|
||||||
|
# This method generates Ruby code that roughly looks like the following:
|
||||||
|
#
|
||||||
|
# if catch :predicate_matched do
|
||||||
|
# node.children.each do |node|
|
||||||
|
#
|
||||||
|
# if some_condition_that_matches_a_predicate
|
||||||
|
# throw :predicate_matched, true
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# nil
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# matched.push(node)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# @param [AST::Node] test
|
||||||
|
# @param [AST::Node] predicate
|
||||||
|
# @param [Oga::Ruby::Node] input
|
||||||
|
# @return [Oga::Ruby::Node]
|
||||||
|
#
|
||||||
|
def expression_predicate(test, predicate, input)
|
||||||
|
catch_arg = symbol(:predicate_matched)
|
||||||
|
|
||||||
|
process(test, input) do |matched_test_node|
|
||||||
|
catch_block = send_message('catch', catch_arg).add_block do
|
||||||
|
inner = process(predicate, matched_test_node) do
|
||||||
|
send_message('throw', catch_arg, literal('true'))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ensure that the "catch" only returns a value when "throw" is
|
||||||
|
# actually invoked.
|
||||||
|
inner.followed_by(literal('nil'))
|
||||||
|
end
|
||||||
|
|
||||||
|
catch_block.if_true { yield matched_test_node }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -225,7 +289,7 @@ module Oga
|
||||||
# @param [Oga::Ruby::Node] input
|
# @param [Oga::Ruby::Node] input
|
||||||
# @return [Oga::Ruby::Node]
|
# @return [Oga::Ruby::Node]
|
||||||
#
|
#
|
||||||
def on_string(ast, input)
|
def on_string(ast, *)
|
||||||
string(ast.children[0])
|
string(ast.children[0])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -236,7 +300,18 @@ module Oga
|
||||||
# @param [Oga::Ruby::Node] input
|
# @param [Oga::Ruby::Node] input
|
||||||
# @return [Oga::Ruby::Node]
|
# @return [Oga::Ruby::Node]
|
||||||
#
|
#
|
||||||
def on_int(ast, input)
|
def on_int(ast, *)
|
||||||
|
literal(ast.children[0].to_i.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Processes a float.
|
||||||
|
#
|
||||||
|
# @param [AST::Node] ast
|
||||||
|
# @param [Oga::Ruby::Node] input
|
||||||
|
# @return [Oga::Ruby::Node]
|
||||||
|
#
|
||||||
|
def on_float(ast, *)
|
||||||
literal(ast.children[0].to_s)
|
literal(ast.children[0].to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -251,12 +326,8 @@ module Oga
|
||||||
vars = variables_literal
|
vars = variables_literal
|
||||||
name = ast.children[0]
|
name = ast.children[0]
|
||||||
|
|
||||||
raise_call = Ruby::Node.new(
|
variables_literal.and(variables_literal[string(name)])
|
||||||
:send,
|
.or(send_message('raise', string("Undefined XPath variable: #{name}")))
|
||||||
[nil, 'raise', string("Undefined XPath variable: #{name}")]
|
|
||||||
)
|
|
||||||
|
|
||||||
variables_literal.and(variables_literal[string(name)]).or(raise_call)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -277,6 +348,23 @@ module Oga
|
||||||
Ruby::Node.new(:string, [value.to_s])
|
Ruby::Node.new(:string, [value.to_s])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# @param [String] value
|
||||||
|
# @return [Oga::Ruby::Node]
|
||||||
|
#
|
||||||
|
def symbol(value)
|
||||||
|
Ruby::Node.new(:symbol, [value.to_sym])
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# @param [String] name
|
||||||
|
# @param [Array] args
|
||||||
|
# @return [Oga::Ruby::Node]
|
||||||
|
#
|
||||||
|
def send_message(name, *args)
|
||||||
|
Ruby::Node.new(:send, [nil, name, *args])
|
||||||
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
# @param [Oga::Ruby::Node] node
|
# @param [Oga::Ruby::Node] node
|
||||||
# @return [Oga::Ruby::Node]
|
# @return [Oga::Ruby::Node]
|
||||||
|
@ -299,6 +387,14 @@ module Oga
|
||||||
def variables_literal
|
def variables_literal
|
||||||
literal('variables')
|
literal('variables')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# @param [AST::Node] ast
|
||||||
|
# @return [TrueClass|FalseClass]
|
||||||
|
#
|
||||||
|
def xpath_number?(ast)
|
||||||
|
ast.type == :int || ast.type == :float
|
||||||
|
end
|
||||||
end # Compiler
|
end # Compiler
|
||||||
end # XPath
|
end # XPath
|
||||||
end # Oga
|
end # Oga
|
||||||
|
|
Loading…
Reference in New Issue