XPath compiler support for a bucket of functions

This includes the following functions:

* boolean()
* ceiling()
* floor()
* round()
* concat()
* contains()
* count()
This commit is contained in:
Yorick Peterse 2015-08-07 17:09:10 +02:00
parent e3b45fddfc
commit 2ff1f9ab4f
1 changed files with 144 additions and 20 deletions

View File

@ -649,31 +649,151 @@ module Oga
# @return [Oga::Ruby::Node] # @return [Oga::Ruby::Node]
# #
def on_call(ast, input, &block) def on_call(ast, input, &block)
name, args = *ast name, *args = *ast
handler = name.gsub('-', '_') handler = name.gsub('-', '_')
send(:"on_call_#{handler}", input, *args, &block) send(:"on_call_#{handler}", input, *args, &block)
end end
##
# Processes the `true()` function call.
#
# @return [Oga::Ruby::Node] # @return [Oga::Ruby::Node]
#
def on_call_true(*) def on_call_true(*)
self.true self.true
end end
##
# Processes the `false()` function call.
#
# @return [Oga::Ruby::Node] # @return [Oga::Ruby::Node]
#
def on_call_false(*) def on_call_false(*)
self.false self.false
end end
# @param [Oga::Ruby::Node] input
# @param [AST::Node] arg
# @return [Oga::Ruby::Node]
def on_call_boolean(input, arg)
arg_ast = try_match_first_node(arg, input)
call_arg = unique_literal(:call_arg)
conversion = literal(Conversion)
call_arg.assign(arg_ast)
.followed_by(conversion.to_boolean(call_arg))
end
# @param [Oga::Ruby::Node] input
# @param [AST::Node] arg
# @return [Oga::Ruby::Node]
def on_call_ceiling(input, arg)
arg_ast = try_match_first_node(arg, input)
call_arg = unique_literal(:call_arg)
conversion = literal(Conversion)
initial_assign = call_arg.assign(arg_ast)
float_assign = call_arg.assign(conversion.to_float(call_arg))
if_nan = call_arg.nan?
.if_true { call_arg }
.else { call_arg.ceil.to_f }
initial_assign.followed_by(float_assign)
.followed_by(if_nan)
end
# @param [Oga::Ruby::Node] input
# @param [AST::Node] arg
# @return [Oga::Ruby::Node]
def on_call_floor(input, arg)
arg_ast = try_match_first_node(arg, input)
call_arg = unique_literal(:call_arg)
conversion = literal(Conversion)
initial_assign = call_arg.assign(arg_ast)
float_assign = call_arg.assign(conversion.to_float(call_arg))
if_nan = call_arg.nan?
.if_true { call_arg }
.else { call_arg.floor.to_f }
initial_assign.followed_by(float_assign)
.followed_by(if_nan)
end
# @param [Oga::Ruby::Node] input
# @param [AST::Node] arg
# @return [Oga::Ruby::Node]
def on_call_round(input, arg)
arg_ast = try_match_first_node(arg, input)
call_arg = unique_literal(:call_arg)
conversion = literal(Conversion)
initial_assign = call_arg.assign(arg_ast)
float_assign = call_arg.assign(conversion.to_float(call_arg))
if_nan = call_arg.nan?
.if_true { call_arg }
.else { call_arg.round.to_f }
initial_assign.followed_by(float_assign)
.followed_by(if_nan)
end
# @param [Oga::Ruby::Node] input
# @param [Array<AST::Node>] args
# @return [Oga::Ruby::Node]
def on_call_concat(input, *args)
conversion = literal(Conversion)
assigns = []
conversions = []
args.each do |arg|
arg_var = unique_literal(:concat_arg)
arg_ast = try_match_first_node(arg, input)
assigns << arg_var.assign(arg_ast)
conversions << conversion.to_string(arg_var)
end
assigns.inject(:followed_by)
.followed_by(conversions.inject(:+))
end
# @param [Oga::Ruby::Node] input
# @param [AST::Node] haystack_ast
# @param [AST::Node] needle_ast
# @return [Oga::Ruby::Node]
def on_call_contains(input, haystack, needle)
haystack_lit = unique_literal(:haystack)
needle_lit = unique_literal(:needle)
conversion = literal(Conversion)
haystack_ast = try_match_first_node(haystack, input)
needle_ast = try_match_first_node(needle, input)
include_call = conversion.to_string(haystack_lit)
.include?(conversion.to_string(needle_lit))
haystack_lit.assign(haystack_ast)
.followed_by(needle_lit.assign(needle_ast))
.followed_by(include_call)
end
# @param [Oga::Ruby::Node] input
# @param [AST::Node] arg
# @return [Oga::Ruby::Node]
def on_call_count(input, arg)
count = unique_literal(:count)
assign = count.assign(literal('0.0'))
unless return_nodeset?(arg)
raise TypeError, 'count() can only operate on NodeSet instances'
end
increment = process(arg, input) do
count.assign(count + literal('1'))
end
assign.followed_by(increment)
.followed_by(count)
end
## ##
# Delegates type tests to specific handlers. # Delegates type tests to specific handlers.
# #
@ -818,6 +938,19 @@ module Oga
end end
end end
##
# Tries to match the first node in a set, otherwise processes it as usual.
#
# @see [#match_first_node]
#
def try_match_first_node(ast, input, optimize_first = true)
if return_nodeset?(ast) and optimize_first
match_first_node(ast, input)
else
process(ast, input)
end
end
## ##
# Generates the code for an operator. # Generates the code for an operator.
# #
@ -887,17 +1020,8 @@ module Oga
left_var = unique_literal(:op_left) left_var = unique_literal(:op_left)
right_var = unique_literal(:op_right) right_var = unique_literal(:op_right)
if return_nodeset?(left) and optimize_first left_ast = try_match_first_node(left, input, optimize_first)
left_ast = match_first_node(left, input) right_ast = try_match_first_node(right, input, optimize_first)
else
left_ast = process(left, input)
end
if return_nodeset?(right) and optimize_first
right_ast = match_first_node(right, input)
else
right_ast = process(right, input)
end
initial_assign = left_var.assign(left_ast.wrap) initial_assign = left_var.assign(left_ast.wrap)
.followed_by(right_var.assign(right_ast.wrap)) .followed_by(right_var.assign(right_ast.wrap))