Partial dynamic function applications

This introduces support for partial applications on dynamic functions.
The test cases are comprehensive and the implementation should be
quite stable.

Even though the base higher-order function implementation was based on
the work of Dimitre Novatchev[0], I have chosen a different route for
partial application:  Novatchev's method relies on storing partially
applied arguments as nodes, together with their types.  The types are
detected the best they can be, with accuracy varying wildly depending
on whether a schema-aware processor is present.

But regardless, that method would not retain node context.

Sequences, however, do.  By using sequences, we allow the processor to
retain all type and context information perfectly without any
additional work that is inaccurate, adds complexity, and likely to
cause subtle problems.  It will also support any types that we are
unaware of.  As Novatchev mentions:

  The argument type-detection that f:curry() uses has been implemented
  only for the builtin XML Schema datatypes. It will not recognize the
  user-defined type of an argument, which can be created by an user of
  a Schema-Aware (SA) XSLT 2.0 processor.[0]

This is not a problem with hoxsl.

[0]: http://conferences.idealliance.org/extreme/html/2006/Novatchev01/EML2006Novatchev01.html
master
Mike Gerwitz 2014-11-27 00:34:52 -05:00
commit 7fd1618390
13 changed files with 1532 additions and 223 deletions

View File

@ -19,12 +19,13 @@
path_src := src
path_test := test
test_in_apply_gen := $(path_test)/transform/apply-gen-test-in.xsl.apply
test_apply := $(path_test)/apply/partial-test.xsl.apply \
$(path_test)/transform/apply-gen-test-in.xsl.apply
.PHONY: check test
test: check
check: $(test_in_apply_gen)
check: $(test_apply)
$(path_test)/runner
%.apply: %

View File

@ -15,7 +15,8 @@ functions and XSLT templates that take XSLT as input and produce XSLT
as output.
The system is fully tested---see the test cases for additional
documentation as this project gets under way.
documentation as this project gets under way. This README will serve
as an informal manual until official documentation is produced.
## Higher-Order Functions
@ -25,8 +26,8 @@ proprietary versions of their software. Others still may wish to
continue using XSLT 2.0.
There are various approaches/kluges for this problem in earlier
versions of XSLT; this implementation is motivated by Dimitre
Novatchev's work on [higher-order functions in FXSL][nova-ho].
versions of XSLT; the basis of this implementation is motivated by
Dimitre Novatchev's work on [higher-order functions in FXSL][nova-ho].
For example, consider an implementation of a filter function that
accepts a node set and a predicate:
@ -66,6 +67,33 @@ work is needed. This can be included as part of a build process, and
the output included within a distribution.
### Partial Applications
Dynamic function applications using `f:apply` are partially applied if
the number of arguments provided is less than the arity of the target
function.
```xml
<!-- produces a new dynamic function of arity 5 - 3 = 2 -->
<variable name="partial"
select="f:apply( my:arity5(), 1, 2, 3 )" />
<!-- consequently, currying is supported -->
<variable name="result"
select="f:apply( f:apply( $partial, 4 ), 5 )" />
<!-- equiv = true() -->
<variable name="equiv"
select="$result
= f:apply( my:arity5(), 1, 2, 3, 4, 5 )
= my:arity5( 1, 2, 3, 4, 5 )" />
```
The implementation of partial function applications avoids the
complexity and inaccuracies of [Novatchev's approach][nova-ho] by
using only sequences, allowing arguments to retain their type and
context.
## License
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml version="1.0" encoding="utf-8"?>
<!--
Dynamic function application
@ -30,86 +30,73 @@
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="http://www.lovullo.com/hoxsl/apply">
<import href="apply/partial.xsl" />
<function name="f:apply">
<param name="fnref" as="element(f:ref)" />
<param name="fnref" as="item()+" />
<apply-templates select="$fnref" mode="f:apply" />
<sequence select="f:partial( $fnref, () )" />
</function>
<function name="f:apply">
<param name="fnref" as="element(f:ref)" />
<param name="fnref" as="item()+" />
<param name="arg1" />
<apply-templates select="$fnref" mode="f:apply">
<with-param name="arg1" select="$arg1" />
</apply-templates>
<sequence select="f:partial( $fnref, $arg1 )" />
</function>
<function name="f:apply">
<param name="fnref" as="element(f:ref)" />
<param name="fnref" as="item()+" />
<param name="arg1" />
<param name="arg2" />
<apply-templates select="$fnref" mode="f:apply">
<with-param name="arg1" select="$arg1" />
<with-param name="arg2" select="$arg2" />
</apply-templates>
<sequence select="f:partial( $fnref,
($arg1, $arg2) )" />
</function>
<function name="f:apply">
<param name="fnref" as="element(f:ref)" />
<param name="fnref" as="item()+" />
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
<apply-templates select="$fnref" mode="f:apply">
<with-param name="arg1" select="$arg1" />
<with-param name="arg2" select="$arg2" />
<with-param name="arg3" select="$arg3" />
</apply-templates>
<sequence select="f:partial( $fnref,
($arg1, $arg2, $arg3) )" />
</function>
<function name="f:apply">
<param name="fnref" as="element(f:ref)" />
<param name="fnref" as="item()+" />
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
<param name="arg4" />
<apply-templates select="$fnref" mode="f:apply">
<with-param name="arg1" select="$arg1" />
<with-param name="arg2" select="$arg2" />
<with-param name="arg3" select="$arg3" />
<with-param name="arg4" select="$arg4" />
</apply-templates>
<sequence select="f:partial( $fnref,
($arg1, $arg2, $arg3, $arg4) )" />
</function>
<function name="f:apply">
<param name="fnref" as="element(f:ref)" />
<param name="fnref" as="item()+" />
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
<param name="arg4" />
<param name="arg5" />
<apply-templates select="$fnref" mode="f:apply">
<with-param name="arg1" select="$arg1" />
<with-param name="arg2" select="$arg2" />
<with-param name="arg3" select="$arg3" />
<with-param name="arg4" select="$arg4" />
<with-param name="arg5" select="$arg5" />
</apply-templates>
<sequence select="f:partial( $fnref,
($arg1, $arg2, $arg3, $arg4,
$arg5) )" />
</function>
<function name="f:apply">
<param name="fnref" as="element(f:ref)" />
<param name="fnref" as="item()+" />
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
@ -117,19 +104,14 @@
<param name="arg5" />
<param name="arg6" />
<apply-templates select="$fnref" mode="f:apply">
<with-param name="arg1" select="$arg1" />
<with-param name="arg2" select="$arg2" />
<with-param name="arg3" select="$arg3" />
<with-param name="arg4" select="$arg4" />
<with-param name="arg5" select="$arg5" />
<with-param name="arg6" select="$arg6" />
</apply-templates>
<sequence select="f:partial( $fnref,
($arg1, $arg2, $arg3, $arg4,
$arg5, $arg6) )" />
</function>
<function name="f:apply">
<param name="fnref" as="element(f:ref)" />
<param name="fnref" as="item()+" />
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
@ -138,20 +120,14 @@
<param name="arg6" />
<param name="arg7" />
<apply-templates select="$fnref" mode="f:apply">
<with-param name="arg1" select="$arg1" />
<with-param name="arg2" select="$arg2" />
<with-param name="arg3" select="$arg3" />
<with-param name="arg4" select="$arg4" />
<with-param name="arg5" select="$arg5" />
<with-param name="arg6" select="$arg6" />
<with-param name="arg7" select="$arg7" />
</apply-templates>
<sequence select="f:partial( $fnref,
($arg1, $arg2, $arg3, $arg4,
$arg5, $arg6, $arg7) )" />
</function>
<function name="f:apply">
<param name="fnref" as="element(f:ref)" />
<param name="fnref" as="item()+" />
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
@ -161,16 +137,9 @@
<param name="arg7" />
<param name="arg8" />
<apply-templates select="$fnref" mode="f:apply">
<with-param name="arg1" select="$arg1" />
<with-param name="arg2" select="$arg2" />
<with-param name="arg3" select="$arg3" />
<with-param name="arg4" select="$arg4" />
<with-param name="arg5" select="$arg5" />
<with-param name="arg6" select="$arg6" />
<with-param name="arg7" select="$arg7" />
<with-param name="arg8" select="$arg8" />
</apply-templates>
<sequence select="f:partial( $fnref,
($arg1, $arg2, $arg3, $arg4,
$arg5, $arg6, $arg7, $arg8) )" />
</function>
@ -195,18 +164,76 @@
<!--
Attempt to retrieve arity of delayed function
Create a reference to dynamic function @var{name} with arity
@var{arity}
The input must be a function reference. If the arity cannot be
determined, -1 is returned.
This function @emph{does not} verify that the function @var{name}
exists, nor does it verify that the provided @var{arity} is valid
for it. Further, the returned function reference will work
@emph{only with dynamic functions}—that is, an application template
is needed. See @file{transform/apply-gen.xsl} for more information
and examples.
-->
<function name="f:arity" as="xs:decimal">
<param name="fnref" as="element(f:ref)" />
<function name="f:make-ref" as="element( f:ref )">
<param name="name" as="xs:QName" />
<param name="arity" as="xs:integer" />
<sequence select="if ( $fnref/@arity ) then
$fnref/@arity
else
-1" />
<variable name="ns"
select="namespace-uri-from-QName( $name )" />
<f:ref arity="{$arity}">
<element name="{$name}"
namespace="{$ns}" />
</f:ref>
</function>
<!--
Determines whether @var{fnref} represents a valid dynamic function
reference
This can be used to determine if @var{fnref} is valid as input to
other functions, some of which may produce an error if called with
an invalid dynamic function reference.
@i{Implementation details:} To be valid, @var{fnref} must:
@enumerate
@item Be an element of type @code{f:ref};
@item Have a numeric @code{@arity}; and
@item Have a child target function node.
@enumerate
-->
<function name="f:is-ref" as="xs:boolean">
<param name="fnref" as="item()*" />
<variable name="ref" select="$fnref[ 1 ]" />
<!-- for @arity check: note that NaN != NaN -->
<sequence select="$ref instance of element( f:ref )
and number( $ref/@arity ) = number( $ref/@arity )
and exists( $ref/*[ 1 ] )" />
</function>
<!--
Retrieve the QName of the target dynamic function
Usually, this would match precisely the QName of the target
function.
@i{Implementation details:} This actually represents the QName of
the @emph{application template}, which could differ from the target
function name. One reason this may be the case is to provide a
function alias.
-->
<function name="f:QName" as="xs:QName?">
<param name="fnref" as="element( f:ref )" />
<variable name="target" as="element()?"
select="$fnref/element()[ 1 ]" />
<sequence select="node-name( $target )" />
</function>
</stylesheet>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Arity calculations on dynamic and partially applied functions
Copyright (C) 2014 LoVullo Associates, Inc.
This file is part of hoxsl.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<stylesheet version="2.0"
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="http://www.lovullo.com/hoxsl/apply">
<!--
Attempt to retrieve arity of dynamic function
The input must be a function reference. Partially applied function
references will have an arity equivalent to the remaining parameters
in the original function.
If the arity cannot be determined, -1 is returned.
-->
<function name="f:arity" as="xs:decimal">
<param name="fnref" as="element(f:ref)" />
<sequence select="if ( $fnref/@arity ) then
$fnref/@arity
else
-1" />
</function>
</stylesheet>

View File

@ -0,0 +1,202 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Partial function application
Copyright (C) 2014 LoVullo Associates, Inc.
This file is part of hoxsl.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Partial applications may only be used with dynamic functions
(functions compatible with `f:apply').
-->
<stylesheet version="2.0"
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="http://www.lovullo.com/hoxsl/apply"
xmlns:_f="http://www.lovullo.com/hoxsl/apply/_priv">
<import href="arity.xsl" />
<!--
Partially apply dynamic function reference @var{fnref}
When provided to @code{f:apply}, @var{fnref} will be applied with
@var{args} as its beginning argments, followed by any arguments to
`f:apply'.
Note that you usually do not have to invoke this function directly:
the dynamic function calls will handle currying/partial application
for you, which has a much more inviting syntax.
Partially applied functions may continue to be partially applied
until their parameters are exhausted. This can be used to implement
currying.
When destructuring the result of this function, note that the
returned function reference may not match @var{fnref} by reference,
as it may have been modified.
-->
<function name="f:partial" as="item()+">
<param name="fnref" as="item()+" />
<param name="args" as="item()*" />
<!-- perform type check here, not above, since we can be passed a
sequence (e.g.a partially applied function) -->
<variable name="ref"
as="element(f:ref)"
select="$fnref[ 1 ]" />
<variable name="argout" as="item()*">
<!-- include any previously applied arguments (if we're partially
applying a partial application) -->
<sequence select="remove( $fnref, 1 )" />
<!-- nested sequences are implicitly flattened, so we're not
returning a sub-sequence here -->
<sequence select="$args" />
</variable>
<variable name="argn" as="xs:decimal"
select="count( $argout )" />
<variable name="arity" as="xs:decimal"
select="f:arity( $ref )" />
<choose>
<when test="$argn eq $arity">
<sequence select="_f:apply-partial( $ref, $argout )" />
</when>
<when test="$argn gt $arity">
<apply-templates mode="f:partial-arity-error-hook"
select="$ref">
<with-param name="args" select="$argout" />
<with-param name="arity" select="$arity" />
<with-param name="argn" select="$argn" />
</apply-templates>
</when>
<otherwise>
<f:ref>
<sequence select="$ref/@*" />
<attribute name="partial"
select="count( $args )" />
<sequence select="$ref/*" />
</f:ref>
<sequence select="$argout" />
</otherwise>
</choose>
</function>
<!--
Determines whether FNREF is a partial application
You really should not rely on this for your code—it is intended for
internal use. Instead, treat partial applications opaquely as their
own functions.
-->
<function name="f:is-partial" as="xs:boolean">
<param name="fnref" as="item()+" />
<!-- if we're passed a sequence, then the function portion of the
reference is first; all others are arguments -->
<variable name="fn"
select="$fnref[ 1 ]" />
<!-- we never want to fail, so we perform our type check here rather
than using param/@as -->
<sequence select="$fn instance of element(f:ref)
and exists( $fn/@partial )
and number( $fn/@partial ) gt 0" />
</function>
<!--
Hook invoked when the number of arguments of a partial application
exceeds the parameter count of the target function
The `target' function is the root of the partial application—given
@t{Fx @arrow{} F'y @arrow{} F''z}, @t{F} is the target.
Implementations may override this hook to display their own errors,
or even handle the error and continue by returning a proper partial
application. For such implementation details, see
@file{apply/partial.xsl}.
-->
<template mode="f:partial-arity-error-hook"
match="f:ref"
priority="1">
<param name="args" as="item()*" />
<param name="arity" as="xs:decimal" />
<variable name="ref" as="element(f:ref)"
select="." />
<variable name="argn" as="xs:decimal"
select="count( $args )" />
<variable name="fn"
select="$ref/*[1]" />
<variable name="fname"
select="concat( '{', namespace-uri( $fn ), '}',
$fn/local-name() )" />
<sequence
select="error(
QName( namespace-uri-for-prefix( 'f', $ref ),
'err:PARTIAL_PARAM_OVERFLOW' ),
concat( 'Attempted partial application of ',
$fname, '#', $arity, ' with ',
$argn, ' arguments' ) )" />
</template>
<!--
Apply a partial dynamic function application
This function is called automatically by @code{f:partial} when
partial application would otherwise result in the returning of a
nullary function. @emph{It performs no validations} to ensure the
integrity of the data.}
Just as @code{f:apply}, please note that @emph{up to eight arguments
are supported}. This should be enough.
-->
<function name="_f:apply-partial">
<param name="fnref" as="element(f:ref)" />
<param name="args" as="item()*" />
<variable name="fn" as="element()"
select="$fnref[ 1 ]" />
<!-- just as `f:apply', we support up to 8 arguments -->
<apply-templates select="$fn" mode="f:apply">
<with-param name="arg1" select="$args[ 1 ]" />
<with-param name="arg2" select="$args[ 2 ]" />
<with-param name="arg3" select="$args[ 3 ]" />
<with-param name="arg4" select="$args[ 4 ]" />
<with-param name="arg5" select="$args[ 5 ]" />
<with-param name="arg6" select="$args[ 6 ]" />
<with-param name="arg7" select="$args[ 7 ]" />
<with-param name="arg8" select="$args[ 8 ]" />
</apply-templates>
</function>
</stylesheet>

View File

@ -18,6 +18,13 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
TODOs:
- Properly handle arity overloading.
- Generate functions for partial applications, so long as the target
function does not have its arity overloaded (since that would
cause currying and partial application of N>1 arguments to
potentially yield different results).
-->
<stylesheet version="2.0"

View File

@ -28,59 +28,86 @@
<import href="../src/apply.xsl" />
<template mode="f:apply" match="f:ref[ foo:fn0 ]">
<!-- return something to show that we were called properly -->
<sequence select="0" />
<!-- catch errors (otherwise, we'd terminate) -->
<template mode="f:apply"
match="*"
priority="2">
<foo:unknown-dyn-fun />
</template>
<template mode="f:apply" match="f:ref[ foo:fn1 ]">
<template mode="f:apply"
match="f:ref[ foo:fn0 ]"
priority="5">
<!-- return something to show that we were called properly -->
<foo:applied n="0" />
</template>
<template mode="f:apply"
match="f:ref[ foo:fn1 ]"
priority="5">
<param name="arg1" />
<sequence select="string($arg1)" />
<foo:applied n="1" />
<sequence select="$arg1" />
</template>
<template mode="f:apply" match="f:ref[ foo:fn2 ]">
<template mode="f:apply"
match="f:ref[ foo:fn2 ]"
priority="5">
<param name="arg1" />
<param name="arg2" />
<sequence select="concat($arg1, $arg2)" />
<foo:applied n="2" />
<sequence select="$arg1, $arg2" />
</template>
<template mode="f:apply" match="f:ref[ foo:fn3 ]">
<template mode="f:apply"
match="f:ref[ foo:fn3 ]"
priority="5">
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
<sequence select="concat($arg1, $arg2, $arg3)" />
<foo:applied n="3" />
<sequence select="$arg1, $arg2, $arg3" />
</template>
<template mode="f:apply" match="f:ref[ foo:fn4 ]">
<template mode="f:apply"
match="f:ref[ foo:fn4 ]"
priority="5">
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
<param name="arg4" />
<sequence select="concat($arg1, $arg2, $arg3, $arg4)" />
<foo:applied n="4" />
<sequence select="$arg1, $arg2, $arg3, $arg4" />
</template>
<template mode="f:apply" match="f:ref[ foo:fn5 ]">
<template mode="f:apply"
match="f:ref[ foo:fn5 ]"
priority="5">
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
<param name="arg4" />
<param name="arg5" />
<sequence select="concat($arg1, $arg2, $arg3, $arg4,
$arg5)" />
<foo:applied n="5" />
<sequence select="$arg1, $arg2, $arg3, $arg4,
$arg5" />
</template>
<template mode="f:apply" match="f:ref[ foo:fn6 ]">
<template mode="f:apply"
match="f:ref[ foo:fn6 ]"
priority="5">
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
@ -88,12 +115,15 @@
<param name="arg5" />
<param name="arg6" />
<sequence select="concat($arg1, $arg2, $arg3, $arg4,
$arg5, $arg6)" />
<foo:applied n="6" />
<sequence select="$arg1, $arg2, $arg3, $arg4,
$arg5, $arg6" />
</template>
<template mode="f:apply" match="f:ref[ foo:fn7 ]">
<template mode="f:apply"
match="f:ref[ foo:fn7 ]"
priority="5">
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
@ -102,12 +132,15 @@
<param name="arg6" />
<param name="arg7" />
<sequence select="concat($arg1, $arg2, $arg3, $arg4,
$arg5, $arg6, $arg7)" />
<foo:applied n="7" />
<sequence select="$arg1, $arg2, $arg3, $arg4,
$arg5, $arg6, $arg7" />
</template>
<template mode="f:apply" match="f:ref[ foo:fn8 ]">
<template mode="f:apply"
match="f:ref[ foo:fn8 ]"
priority="5">
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
@ -117,7 +150,24 @@
<param name="arg7" />
<param name="arg8" />
<sequence select="concat($arg1, $arg2, $arg3, $arg4,
$arg5, $arg6, $arg7, $arg8)" />
<foo:applied n="8" />
<sequence select="$arg1, $arg2, $arg3, $arg4,
$arg5, $arg6, $arg7, $arg8" />
</template>
<function name="foo:arg8-check">
<param name="args" as="item()+" />
<param name="result" as="item()+" />
<sequence select="$result[ 1 ] instance of element( foo:applied )
and $result[ 2 ] is $args[1]
and $result[ 3 ] is $args[2]
and $result[ 4 ] is $args[3]
and $result[ 5 ] is $args[4]
and $result[ 6 ] is $args[5]
and $result[ 7 ] is $args[6]
and $result[ 8 ] is $args[7]
and $result[ 9 ] is $args[8]" />
</function>
</stylesheet>

View File

@ -26,209 +26,554 @@
xmlns:foo="http://www.lovullo.com/_junk"
stylesheet="apply-test.xsl">
<variable name="foo-uri"
select="namespace-uri-for-prefix( 'foo', $args )" />
<variable name="args">
<foo:args>
<foo:arg1 />
<foo:arg2 />
<foo:arg3 />
<foo:arg4 />
<foo:arg5 />
<foo:arg6 />
<foo:arg7 />
<foo:arg8 />
</foo:args>
</variable>
<scenario label="f:apply">
<scenario label="applied to a function reference element">
<variable name="arg"
select="'foo'" />
<call function="f:apply">
<param name="fnref">
<f:ref>
<foo:fn1 />
</f:ref>
</param>
<param name="arg1" select="$arg" />
</call>
<!-- our test appliaction template simply echoes back its only
argument -->
<expect label="applies application template with mode f:apply"
test="$x:result = ($arg)" />
</scenario>
<scenario label="applying">
<scenario label="applied to complete argument list of">
<scenario label="nullary function">
<call function="f:apply">
<param name="fnref">
<f:ref><foo:fn0 /></f:ref>
</param>
<param name="fnref"
select="f:make-ref( QName( $foo-uri, 'fn0' ), 0 )" />
</call>
<expect label="passes each argument"
test="$x:result = 0" />
<!-- technically it applies a template -->
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 0 ]" />
</scenario>
<scenario label="unary function">
<call function="f:apply">
<param name="fnref">
<f:ref><foo:fn1 /></f:ref>
</param>
<param name="arg1" select="1" />
<param name="fnref"
select="f:make-ref( QName( $foo-uri, 'fn1' ), 1 )" />
<param name="arg1" select="$args/foo:arg1" />
</call>
<expect label="passes each argument"
test="$x:result = '1'" />
test="$x:result[ 1 ] = foo:applied[ @n = 1 ]
and $x:result[ 2 ] is $args/foo:arg1" />
</scenario>
<scenario label="binary function">
<call function="f:apply">
<param name="fnref">
<f:ref><foo:fn2 /></f:ref>
</param>
<param name="arg1" select="1" />
<param name="arg2" select="2" />
<param name="fnref"
select="f:make-ref( QName( $foo-uri, 'fn2' ), 2 )" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
</call>
<expect label="passes each argument"
test="$x:result = '12'" />
test="$x:result[ 1 ] = foo:applied[ @n = 2 ]
and $x:result[ 2 ] is $args/foo:arg1
and $x:result[ 3 ] is $args/foo:arg2" />
</scenario>
<scenario label="ternary function">
<call function="f:apply">
<param name="fnref">
<f:ref><foo:fn3 /></f:ref>
</param>
<param name="arg1" select="1" />
<param name="arg2" select="2" />
<param name="arg3" select="3" />
<param name="fnref"
select="f:make-ref( QName( $foo-uri, 'fn3' ), 3 )" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
<param name="arg3" select="$args/foo:arg3" />
</call>
<expect label="passes each argument"
test="$x:result = '123'" />
test="$x:result[ 1 ] = foo:applied[ @n = 3 ]
and $x:result[ 2 ] is $args/foo:arg1
and $x:result[ 3 ] is $args/foo:arg2
and $x:result[ 4 ] is $args/foo:arg3" />
</scenario>
<scenario label="4-ary function">
<call function="f:apply">
<param name="fnref">
<f:ref><foo:fn4 /></f:ref>
</param>
<param name="arg1" select="1" />
<param name="arg2" select="2" />
<param name="arg3" select="3" />
<param name="arg4" select="4" />
<param name="fnref"
select="f:make-ref( QName( $foo-uri, 'fn4' ), 4 )" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
<param name="arg3" select="$args/foo:arg3" />
<param name="arg4" select="$args/foo:arg4" />
</call>
<expect label="passes each argument"
test="$x:result = '1234'" />
test="$x:result[ 1 ] = foo:applied[ @n = 4 ]
and $x:result[ 2 ] is $args/foo:arg1
and $x:result[ 3 ] is $args/foo:arg2
and $x:result[ 4 ] is $args/foo:arg3
and $x:result[ 5 ] is $args/foo:arg4" />
</scenario>
<scenario label="5-ary function">
<call function="f:apply">
<param name="fnref">
<f:ref><foo:fn5 /></f:ref>
</param>
<param name="arg1" select="1" />
<param name="arg2" select="2" />
<param name="arg3" select="3" />
<param name="arg4" select="4" />
<param name="arg5" select="5" />
<param name="fnref"
select="f:make-ref( QName( $foo-uri, 'fn5' ), 5 )" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
<param name="arg3" select="$args/foo:arg3" />
<param name="arg4" select="$args/foo:arg4" />
<param name="arg5" select="$args/foo:arg5" />
</call>
<expect label="passes each argument"
test="$x:result = '12345'" />
test="$x:result[ 1 ] = foo:applied[ @n = 5 ]
and $x:result[ 2 ] is $args/foo:arg1
and $x:result[ 3 ] is $args/foo:arg2
and $x:result[ 4 ] is $args/foo:arg3
and $x:result[ 5 ] is $args/foo:arg4
and $x:result[ 6 ] is $args/foo:arg5" />
</scenario>
<scenario label="6-ary function">
<call function="f:apply">
<param name="fnref">
<f:ref><foo:fn6 /></f:ref>
</param>
<param name="arg1" select="1" />
<param name="arg2" select="2" />
<param name="arg3" select="3" />
<param name="arg4" select="4" />
<param name="arg5" select="5" />
<param name="arg6" select="6" />
<param name="fnref"
select="f:make-ref( QName( $foo-uri, 'fn6' ), 6 )" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
<param name="arg3" select="$args/foo:arg3" />
<param name="arg4" select="$args/foo:arg4" />
<param name="arg5" select="$args/foo:arg5" />
<param name="arg6" select="$args/foo:arg6" />
</call>
<expect label="passes each argument"
test="$x:result = '123456'" />
test="$x:result[ 1 ] = foo:applied[ @n = 6 ]
and $x:result[ 2 ] is $args/foo:arg1
and $x:result[ 3 ] is $args/foo:arg2
and $x:result[ 4 ] is $args/foo:arg3
and $x:result[ 5 ] is $args/foo:arg4
and $x:result[ 6 ] is $args/foo:arg5
and $x:result[ 7 ] is $args/foo:arg6" />
</scenario>
<scenario label="7-ary function">
<call function="f:apply">
<param name="fnref">
<f:ref><foo:fn7 /></f:ref>
</param>
<param name="arg1" select="1" />
<param name="arg2" select="2" />
<param name="arg3" select="3" />
<param name="arg4" select="4" />
<param name="arg5" select="5" />
<param name="arg6" select="6" />
<param name="arg7" select="7" />
<param name="fnref"
select="f:make-ref( QName( $foo-uri, 'fn7' ), 7 )" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
<param name="arg3" select="$args/foo:arg3" />
<param name="arg4" select="$args/foo:arg4" />
<param name="arg5" select="$args/foo:arg5" />
<param name="arg6" select="$args/foo:arg6" />
<param name="arg7" select="$args/foo:arg7" />
</call>
<expect label="passes each argument"
test="$x:result = '1234567'" />
test="$x:result[ 1 ] = foo:applied[ @n = 7 ]
and $x:result[ 2 ] is $args/foo:arg1
and $x:result[ 3 ] is $args/foo:arg2
and $x:result[ 4 ] is $args/foo:arg3
and $x:result[ 5 ] is $args/foo:arg4
and $x:result[ 6 ] is $args/foo:arg5
and $x:result[ 7 ] is $args/foo:arg6
and $x:result[ 8 ] is $args/foo:arg7" />
</scenario>
<scenario label="8-ary function">
<call function="f:apply">
<param name="fnref">
<f:ref><foo:fn8 /></f:ref>
</param>
<param name="arg1" select="1" />
<param name="arg2" select="2" />
<param name="arg3" select="3" />
<param name="arg4" select="4" />
<param name="arg5" select="5" />
<param name="arg6" select="6" />
<param name="arg7" select="7" />
<param name="arg8" select="8" />
<param name="fnref"
select="f:make-ref( QName( $foo-uri, 'fn8' ), 8 )" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
<param name="arg3" select="$args/foo:arg3" />
<param name="arg4" select="$args/foo:arg4" />
<param name="arg5" select="$args/foo:arg5" />
<param name="arg6" select="$args/foo:arg6" />
<param name="arg7" select="$args/foo:arg7" />
<param name="arg8" select="$args/foo:arg8" />
</call>
<expect label="passes each argument"
test="$x:result = '12345678'" />
test="$x:result[ 1 ] = foo:applied[ @n = 8 ]
and $x:result[ 2 ] is $args/foo:arg1
and $x:result[ 3 ] is $args/foo:arg2
and $x:result[ 4 ] is $args/foo:arg3
and $x:result[ 5 ] is $args/foo:arg4
and $x:result[ 6 ] is $args/foo:arg5
and $x:result[ 7 ] is $args/foo:arg6
and $x:result[ 8 ] is $args/foo:arg7
and $x:result[ 9 ] is $args/foo:arg8" />
</scenario>
</scenario>
<scenario label="applied to partial argument list">
<variable name="fn8"
select="f:make-ref( QName( $foo-uri, 'fn8' ),
8 )" />
<!-- while this looks comprehensive, this really only tests
f:apply#2 -->
<scenario label="supports currying">
<call function="f:apply">
<!-- @, @ makin' you dizzy? -->
<param name="fnref"
select="f:apply(
f:apply(
f:apply(
f:apply(
f:apply(
f:apply(
f:apply(
$fn8,
$args/foo:arg1 ),
$args/foo:arg2 ),
$args/foo:arg3 ),
$args/foo:arg4 ),
$args/foo:arg5 ),
$args/foo:arg6 ),
$args/foo:arg7 )" />
<!-- the final argument that will apply the unary
partially applied function to the target 8-ary -->
<param name="arg1"
select="$args/foo:arg8" />
</call>
<expect label="applies target function and binds each
argument in proper order"
test="foo:arg8-check( $args/*, $x:result )" />
</scenario>
<scenario label="with 1 argument">
<call function="f:apply">
<param name="fnref" select="$fn8" />
<param name="arg1" select="$args/foo:arg1" />
</call>
<expect label="produces a partial application"
test="f:is-partial( $x:result )" />
<expect label="can complete application with arguments in
proper order"
test="foo:arg8-check(
$args/*,
f:apply( $x:result, $args/foo:arg2,
$args/foo:arg3,
$args/foo:arg4,
$args/foo:arg5,
$args/foo:arg6,
$args/foo:arg7,
$args/foo:arg8 ) )" />
</scenario>
<scenario label="with 2 arguments">
<call function="f:apply">
<param name="fnref" select="$fn8" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
</call>
<expect label="produces a partial application"
test="f:is-partial( $x:result )" />
<expect label="can complete application with arguments in
proper order"
test="foo:arg8-check(
$args/*,
f:apply( $x:result, $args/foo:arg3,
$args/foo:arg4,
$args/foo:arg5,
$args/foo:arg6,
$args/foo:arg7,
$args/foo:arg8 ) )" />
</scenario>
<scenario label="with 3 arguments">
<call function="f:apply">
<param name="fnref" select="$fn8" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
<param name="arg3" select="$args/foo:arg3" />
</call>
<expect label="produces a partial application"
test="f:is-partial( $x:result )" />
<expect label="can complete application with arguments in
proper order"
test="foo:arg8-check(
$args/*,
f:apply( $x:result, $args/foo:arg4,
$args/foo:arg5,
$args/foo:arg6,
$args/foo:arg7,
$args/foo:arg8 ) )" />
</scenario>
<scenario label="with 4 arguments">
<call function="f:apply">
<param name="fnref" select="$fn8" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
<param name="arg3" select="$args/foo:arg3" />
<param name="arg4" select="$args/foo:arg4" />
</call>
<expect label="produces a partial application"
test="f:is-partial( $x:result )" />
<expect label="can complete application with arguments in
proper order"
test="foo:arg8-check(
$args/*,
f:apply( $x:result, $args/foo:arg5,
$args/foo:arg6,
$args/foo:arg7,
$args/foo:arg8 ) )" />
</scenario>
<scenario label="with 5 arguments">
<call function="f:apply">
<param name="fnref" select="$fn8" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
<param name="arg3" select="$args/foo:arg3" />
<param name="arg4" select="$args/foo:arg4" />
<param name="arg5" select="$args/foo:arg5" />
</call>
<expect label="produces a partial application"
test="f:is-partial( $x:result )" />
<expect label="can complete application with arguments in
proper order"
test="foo:arg8-check(
$args/*,
f:apply( $x:result, $args/foo:arg6,
$args/foo:arg7,
$args/foo:arg8 ) )" />
</scenario>
<scenario label="with 6 arguments">
<call function="f:apply">
<param name="fnref" select="$fn8" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
<param name="arg3" select="$args/foo:arg3" />
<param name="arg4" select="$args/foo:arg4" />
<param name="arg5" select="$args/foo:arg5" />
<param name="arg6" select="$args/foo:arg6" />
</call>
<expect label="produces a partial application"
test="f:is-partial( $x:result )" />
<expect label="can complete application with arguments in
proper order"
test="foo:arg8-check(
$args/*,
f:apply( $x:result, $args/foo:arg7,
$args/foo:arg8 ) )" />
</scenario>
<scenario label="with 7 arguments">
<call function="f:apply">
<param name="fnref" select="$fn8" />
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
<param name="arg3" select="$args/foo:arg3" />
<param name="arg4" select="$args/foo:arg4" />
<param name="arg5" select="$args/foo:arg5" />
<param name="arg6" select="$args/foo:arg6" />
<param name="arg7" select="$args/foo:arg7" />
</call>
<expect label="produces a partial application"
test="f:is-partial( $x:result )" />
<expect label="can complete application with arguments in
proper order"
test="foo:arg8-check(
$args/*,
f:apply( $x:result, $args/foo:arg8 ) )" />
</scenario>
</scenario>
</scenario>
<scenario label="f:arity">
<scenario label="given a proper function reference">
<variable name="test-arity"
select="5" />
<scenario label="f:make-ref constructor">
<scenario label="when called with a function QName and arity">
<variable name="qname"
select="QName( $foo-uri, 'reftest' )" />
<variable name="arity"
select="2" />
<!-- test our format directly; do not rely on the
apply-gen code -->
<variable name="fnref">
<f:ref arity="{$test-arity}">
<foo:bar />
</f:ref>
</variable>
<call function="f:arity">
<param name="fnref"
select="$fnref" />
<call function="f:make-ref">
<param name="name" select="$qname" />
<param name="arity" select="$arity" />
</call>
<expect label="provides function arity"
test="$x:result = $test-arity" />
<expect label="produces a dynamic function reference"
test="f:is-ref( $x:result )" />
<expect label="produces provided reference arity"
test="f:arity( $x:result ) = $arity" />
<expect label="produces reference to expected QName"
test="f:QName( $x:result ) = $qname" />
</scenario>
<scenario label="given a function reference with no defined arity">
<variable name="fnref">
<f:ref><foo:bar /></f:ref>
</variable>
<call function="f:arity">
<scenario label="produces a valid dynamic function reference">
<call function="f:apply">
<param name="fnref"
select="$fnref" />
select="f:make-ref( QName( $foo-uri, 'fn1' ),
1 )" />
<param name="arg1"
select="$args/foo:arg1" />
</call>
<expect label="returns -1"
test="$x:result = -1" />
<expect label="that can be applied using f:apply"
test="$x:result[ 1 ] = foo:applied[ @n = 1 ]
and $x:result[ 2 ] is $args/foo:arg1" />
</scenario>
</scenario>
<scenario label="f:is-ref predicate">
<scenario label="given a valid dynamic function reference">
<call function="f:is-ref">
<param name="fnref"
select="f:make-ref( QName( $foo-uri, 'foo' ), 0 )" />
</call>
<expect label="returns true()"
select="true()" />
</scenario>
<scenario label="given an invalid dynamic function reference">
<call function="f:is-ref">
<param name="fnref">
<foo:bar />
</param>
</call>
<expect label="returns false()"
select="false()" />
</scenario>
<scenario label="given a dynamic function reference without arity">
<call function="f:is-ref">
<param name="fnref">
<f:ref>
<foo:bar />
</f:ref>
</param>
</call>
<expect label="returns false()"
select="false()" />
</scenario>
<scenario label="given a dynamic function reference without
integral arity">
<call function="f:is-ref">
<param name="fnref">
<f:ref arity="moo">
<foo:bar />
</f:ref>
</param>
</call>
<expect label="returns false()"
select="false()" />
</scenario>
<scenario label="given a dynamic function reference without target
node">
<call function="f:is-ref">
<param name="fnref">
<f:ref arity="0" />
</param>
</call>
<expect label="returns false()"
select="false()" />
</scenario>
<scenario label="given an empty sequence">
<call function="f:is-ref">
<param name="fnref" select="()" />
</call>
<expect label="returns false()"
select="false()" />
</scenario>
<scenario label="given a non-element">
<call function="f:is-ref">
<param name="fnref" select="5" />
</call>
<expect label="returns false()"
select="false()" />
</scenario>
</scenario>
<scenario label="f:QName accessor">
<scenario label="given a valid dynamic function reference">
<variable name="foo-qname"
select="QName( $foo-uri, 'foo' )" />
<call function="f:QName">
<param name="fnref"
select="f:make-ref( $foo-qname, 0 )" />
</call>
<expect label="returns QName"
test="$x:result instance of xs:QName" />
<expect label="returns QName of target dynamic function"
select="$foo-qname" />
</scenario>
<scenario label="given a dynamic function reference with no target
node">
<call function="f:QName">
<param name="fnref">
<f:ref arity="5" />
</param>
</call>
<expect label="returns empty sequence"
select="()" />
</scenario>
</scenario>
</description>

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Tests arity calculations
Copyright (C) 2014 LoVullo Associates, Inc.
This file is part of hoxsl.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<description xmlns="http://www.jenitennison.com/xslt/xspec"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://www.jenitennison.com/xslt/xspec"
xmlns:f="http://www.lovullo.com/hoxsl/apply"
xmlns:foo="http://www.lovullo.com/_junk"
stylesheet="../../src/apply/arity.xsl">
<scenario label="f:arity">
<scenario label="given a proper function reference">
<variable name="test-arity"
select="5" />
<!-- test our format directly; do not rely on the
apply-gen code -->
<variable name="fnref">
<f:ref arity="{$test-arity}">
<foo:bar />
</f:ref>
</variable>
<call function="f:arity">
<param name="fnref"
select="$fnref" />
</call>
<expect label="provides function arity"
test="$x:result = $test-arity" />
</scenario>
<scenario label="given a function reference with no defined arity">
<variable name="fnref">
<f:ref><foo:bar /></f:ref>
</variable>
<call function="f:arity">
<param name="fnref"
select="$fnref" />
</call>
<expect label="returns -1"
test="$x:result = -1" />
</scenario>
</scenario>
</description>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Tests partial function application
Copyright (C) 2014 LoVullo Associates, Inc.
This file is part of hoxsl.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<stylesheet version="2.0"
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="http://www.lovullo.com/hoxsl/apply"
xmlns:foo="http://www.lovullo.com/_junk">
<!-- SUT -->
<import href="../../src/apply/partial.xsl" />
<!-- numerous templates for arity tests -->
<import href="../apply-test.xsl" />
<!-- generated -->
<import href="partial-test.xsl.apply" />
<!-- the default implementation is to raise an error, which can't be
tested without XSLT 3.0 support -->
<template mode="f:partial-arity-error-hook"
match="f:ref"
priority="5">
<param name="args" as="item()*" />
<param name="arity" as="xs:decimal" />
<foo:partial-error arity="{$arity}" />
<sequence select="." />
<sequence select="$args" />
</template>
<function name="foo:ternary">
<param name="x" />
<param name="y" />
<param name="z" />
<foo:ternary-applied />
<sequence select="$x, $y, $z" />
</function>
</stylesheet>

View File

@ -0,0 +1,471 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Tests partial function application
Copyright (C) 2014 LoVullo Associates, Inc.
This file is part of hoxsl.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<description xmlns="http://www.jenitennison.com/xslt/xspec"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://www.jenitennison.com/xslt/xspec"
xmlns:f="http://www.lovullo.com/hoxsl/apply"
xmlns:foo="http://www.lovullo.com/_junk"
stylesheet="partial-test.xsl">
<variable name="fnref"
select="foo:ternary()" />
<variable name="args">
<foo:parent>
<foo:a />
<foo:b />
<foo:c />
<foo:d />
<foo:e />
<foo:f />
<foo:g />
<foo:h />
</foo:parent>
</variable>
<scenario label="f:partial constructor">
<scenario label="given an empty argument list">
<call function="f:partial">
<param name="fnref" select="$fnref" />
<param name="args" select="()" />
</call>
<expect label="returns FNREF"
test="$x:result[ 1 ] = $fnref" />
<expect label="indicates that 0 arguments were partially applied"
test="$x:result/@partial = 0" />
<expect label="returns nothing else"
test="count( $x:result ) = 1" />
</scenario>
<scenario label="given arguments fewer than function param count">
<call function="f:partial">
<param name="fnref" select="$fnref" />
<param name="args" select="$args/foo:a, $args/foo:b" />
</call>
<expect label="returns FNREF as first item in sequence"
test="$x:result[ 1 ] = $fnref" />
<expect label="indicates that N arguments were partially applied"
test="$x:result/@partial = 2" />
<expect label="returns each argument, ordered"
test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] is $args/foo:b" />
</scenario>
<!-- our implementation can be thought of like currying -->
<scenario label="given arguments for all function params">
<call function="f:partial">
<param name="fnref" select="$fnref" />
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ]
and $x:result[ 1 ] = foo:ternary-applied" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] is $args/foo:b
and $x:result[ 4 ] is $args/foo:c" />
</scenario>
<!-- partial applications should behave as their own functions -->
<scenario label="partially applying partial application, with
fewer arguments than target function arity">
<call function="f:partial">
<param name="fnref" select="f:partial( $fnref, $args/foo:a )" />
<param name="args" select="$args/foo:b" />
</call>
<expect label="returns FNREF as first item in sequence"
test="$x:result[ 1 ] = $fnref" />
<expect label="returns arguments from both partial applications"
test="count( $x:result ) = 3" />
<expect label="arguments are ordered with previous applications
first in sequence"
test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] is $args/foo:b" />
</scenario>
<scenario label="partially applying partial application, with all
arguments">
<call function="f:partial">
<param name="fnref" select="f:partial( $fnref, ($args/foo:a,
$args/foo:b) )" />
<param name="args" select="$args/foo:c" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ]
and $x:result[ 1 ] = foo:ternary-applied" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] is $args/foo:b
and $x:result[ 4 ] is $args/foo:c" />
</scenario>
<!-- let the repitition begin (don't we wish we had macros?
...foreshadowing?) -->
<scenario label="completing partial application passes all
arguments to a target">
<scenario label="unary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="1"><foo:fn1 /></f:ref>
</param>
<param name="args" select="$args/foo:a" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 1 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] is $args/foo:a" />
</scenario>
<scenario label="binary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="2"><foo:fn2 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 2 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] is $args/foo:b" />
</scenario>
<scenario label="ternary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="3"><foo:fn3 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 3 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] is $args/foo:b
and $x:result[ 4 ] is $args/foo:c" />
</scenario>
<scenario label="4-ary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="4"><foo:fn4 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c,
$args/foo:d" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 4 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] = $args/foo:a
and $x:result[ 3 ] = $args/foo:b
and $x:result[ 4 ] = $args/foo:c
and $x:result[ 5 ] = $args/foo:d" />
</scenario>
<scenario label="5-ary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="5"><foo:fn5 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c,
$args/foo:d,
$args/foo:e" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 5 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] = $args/foo:a
and $x:result[ 3 ] = $args/foo:b
and $x:result[ 4 ] = $args/foo:c
and $x:result[ 5 ] = $args/foo:d
and $x:result[ 6 ] = $args/foo:e" />
</scenario>
<scenario label="6-ary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="6"><foo:fn6 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c,
$args/foo:d,
$args/foo:e,
$args/foo:f" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 6 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] = $args/foo:a
and $x:result[ 3 ] = $args/foo:b
and $x:result[ 4 ] = $args/foo:c
and $x:result[ 5 ] = $args/foo:d
and $x:result[ 6 ] = $args/foo:e
and $x:result[ 7 ] = $args/foo:f" />
</scenario>
<scenario label="7-ary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="7"><foo:fn7 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c,
$args/foo:d,
$args/foo:e,
$args/foo:f,
$args/foo:g" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 7 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] = $args/foo:a
and $x:result[ 3 ] = $args/foo:b
and $x:result[ 4 ] = $args/foo:c
and $x:result[ 5 ] = $args/foo:d
and $x:result[ 6 ] = $args/foo:e
and $x:result[ 7 ] = $args/foo:f
and $x:result[ 8 ] = $args/foo:g" />
</scenario>
<scenario label="8-ary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="8"><foo:fn8 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c,
$args/foo:d,
$args/foo:e,
$args/foo:f,
$args/foo:g,
$args/foo:h" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 8 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] = $args/foo:a
and $x:result[ 3 ] = $args/foo:b
and $x:result[ 4 ] = $args/foo:c
and $x:result[ 5 ] = $args/foo:d
and $x:result[ 6 ] = $args/foo:e
and $x:result[ 7 ] = $args/foo:f
and $x:result[ 8 ] = $args/foo:g
and $x:result[ 9 ] = $args/foo:h" />
</scenario>
</scenario>
</scenario>
<scenario label="f:is-partial predicate">
<scenario label="given a non-none">
<call function="f:is-partial">
<param name="fnref"
select="5" />
</call>
<expect label="returns false()"
test="$x:result = false()" />
</scenario>
<scenario label="given a non-fref node">
<call function="f:is-partial">
<param name="fnref">
<foo:moo />
</param>
</call>
<expect label="returns false()"
test="$x:result = false()" />
</scenario>
<!-- this leaks implementation details :x -->
<scenario label="given a non-fref node with spoofed attribute">
<call function="f:is-partial">
<param name="fnref">
<foo:moo partial="5" f:partial="5" />
</param>
</call>
<expect label="returns false()"
test="$x:result = false()" />
</scenario>
<scenario label="given a valid fref, not partially applied">
<call function="f:is-partial">
<param name="fnref"
select="foo:ternary()" />
</call>
<expect label="returns false()"
test="$x:result = false()" />
</scenario>
<!-- if partially applied with no arguments, then it may as well
not be; let's not alter the logic of callers just because it
was called with no effect -->
<scenario label="given a valid fref, partially applied with zero
arguments">
<call function="f:is-partial">
<param name="fnref"
select="f:partial( foo:ternary(), () )" />
</call>
<expect label="returns false()"
test="$x:result = false()" />
</scenario>
<scenario label="given a valid fref, partially applied with N>0
arguments, including argument sequence">
<call function="f:is-partial">
<param name="fnref"
select="f:partial( foo:ternary(), (1, 2) )" />
</call>
<expect label="returns true()"
test="$x:result = true()" />
</scenario>
<!-- caller may extract the function ref from the sequence and
assert on that -->
<scenario label="given a valid fref, partially applied with N>0
arguments, without argument sequence">
<call function="f:is-partial">
<param name="fnref"
select="f:partial( foo:ternary(), (1, 2) )[ 1 ]" />
</call>
<expect label="returns true()"
test="$x:result = true()" />
</scenario>
</scenario>
<scenario label="f:partial-arity-error-hook mode">
<scenario label="given more arguments than target function
parameters">
<!-- see partial-test.xsl for the result sequence order -->
<call function="f:partial">
<param name="fnref" select="$fnref" />
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c,
$args/foo:d" />
</call>
<expect label="produces an error"
test="$x:result[ 1 ] instance of
element( foo:partial-error )" />
<expect label="provides source dynamic function reference"
test="$x:result[ 2 ] is $fnref" />
<expect label="provides arity of target (root of all partial
applications) function"
test="$x:result[ 1 ]/@arity = 3" />
<expect label="provides each argument of partial application
attempt, including excess"
test="$x:result[ 3 ] is $args/foo:a
and $x:result[ 4 ] is $args/foo:b
and $x:result[ 5 ] is $args/foo:c
and $x:result[ 6 ] is $args/foo:d" />
</scenario>
<scenario label="triggered when sum of partial applications yields
more arguments than target parameters">
<call function="f:partial">
<param name="fnref"
select="f:partial( $fnref, ($args/foo:a,
$args/foo:b) )" />
<param name="args" select="$args/foo:c,
$args/foo:d" />
</call>
<expect label="produces error with all arguments"
test="exists( foo:partial-error )
and $x:result[ 3 ] is $args/foo:a
and $x:result[ 6 ] is $args/foo:d" />
</scenario>
</scenario>
</description>

View File

@ -73,7 +73,7 @@
<xsl:function name="foo:bar">
<xsl:param name="a" />
<xsl:param name="b" as="xs:decimal" />
<xsl:param name="c" />"
<xsl:param name="c" />
</xsl:function>
</xsl:stylesheet>
</context>
@ -187,7 +187,7 @@
<scenario label="f:arity">
<call function="f:arity">
<param name="fnref" select="foo:sub-two()" />
</call>"
</call>
<expect label="is able to determine arity"
test="$x:result = 2" />

@ -1 +1 @@
Subproject commit 3dc0ab2e1b78dae5d31c228c22a7dd8868d5678f
Subproject commit 20c6b8a32f37a6511698fe89b78b82fd1aa64bd2