`apply-gen' now generates functions for partial applications

This is intended as a convenient shorthand:

```xml
  <!-- these two are identical -->
  <sequence select="f:apply( my:func(), $x, $y )" />
  <sequence select="my:func( $x, $y )" />
```
master
Mike Gerwitz 2014-12-02 16:04:34 -05:00
parent faadaa3edd
commit bb9107c8c1
3 changed files with 315 additions and 24 deletions

View File

@ -32,6 +32,7 @@
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://www.lovullo.com/hoxsl/apply"
xmlns:_f="http://www.lovullo.com/hoxsl/apply/_priv"
xmlns:fgen="http://www.lovullo.com/hoxsl/apply/gen"
xmlns:out="http://www.lovullo.com/hoxsl/apply/gen/_out"
exclude-result-prefixes="#default fgen">
@ -147,10 +148,15 @@
<!--
Generate nullary function for delayed application
Generate dynamic application functions for the application of @var{fnref}.
This will output a node that can be later applied to a template with
mode `f:apply' to invoke the associated application template.
The most important function is the nullary, which returns a dynamic
function reference that can be later used to apply the function.
Functions are also generated for partial applications: given a target
function @var{f} of arity @var{α}, functions of arity @math{1<x<α} are
generated that partially apply @var{f} with @var{x} arguments; this is
short-hand for invoking @code{f:apply} or @code{f:partial} directly.
-->
<function name="fgen:create-func">
<param name="fnref" as="element(xsl:function)" />
@ -161,15 +167,42 @@
<variable name="local-name"
select="substring-after( $fnref/@name, ':' )" />
<variable name="arity"
select="count( $fnref/xsl:param )" />
<variable name="params"
select="$fnref/xsl:param" />
<!-- the nullary simply returns a dynamic function reference -->
<out:function name="{$name-resolv}" as="element()">
<namespace name="{$ns-prefix}"
select="$ns" />
<variable name="arity"
select="count( $fnref/xsl:param )" />
<sequence select="f:make-ref( $name-resolv, $arity )" />
</out:function>
<!-- all others perform partial applications -->
<for-each select="1 to ( $arity - 1 )">
<variable name="partial-arity"
select="." />
<variable name="subparams"
select="subsequence( $params, 1, $partial-arity )" />
<out:function name="{$name-resolv}" as="item()+">
<namespace name="{$ns-prefix}"
select="$ns" />
<variable name="argstr"
select="_f:argstr-gen( $subparams )" />
<sequence select="_f:param-gen( $subparams )" />
<out:sequence select="f:partial(
{$name-resolv}(),
({$argstr}) )" />
</out:function>
</for-each>
</function>
@ -187,7 +220,7 @@
<param name="ns-prefix" as="xs:string" />
<param name="ns" as="xs:anyURI" />
<variable name="params"
<variable name="params" as="element()+"
select="$defn/xsl:param" />
<out:template mode="f:apply"
@ -196,27 +229,58 @@
<namespace name="{$ns-prefix}"
select="$ns" />
<for-each select="$params">
<xsl:variable name="i"
select="position()" />
<sequence select="_f:param-gen( $params )" />
<out:param name="arg{$i}">
<copy-of select="@as" />
</out:param>
</for-each>
<variable name="argstr">
<for-each select="$params">
<if test="position() gt 1">
<text>, </text>
</if>
<text>$arg</text>
<value-of select="position()" />
</for-each>
</variable>
<variable name="argstr"
select="_f:argstr-gen( $params )" />
<out:sequence select="{$name-resolv}({$argstr})" />
</out:template>
</function>
<!--
Given a sequence of XSLT function parameters @var{params}, generate
enumerated argument @code{<param>}s.
This is to be used in conjunction with @code{_f:argstr-gen}.
-->
<function name="_f:param-gen" as="element()+">
<param name="params" as="element()+" />
<for-each select="$params">
<xsl:variable name="i"
select="position()" />
<out:param name="arg{$i}">
<copy-of select="@as" />
</out:param>
</for-each>
</function>
<!--
Given a sequence of XSLT function parameters @var{params}, generate
a string that, when used in an XPath, represents a list of arguments of a
function application.
This is to be used in conjunction with @code{_f:param-gen}.
-->
<function name="_f:argstr-gen" as="xs:string">
<param name="params" as="element()+" />
<xsl:variable name="argstr">
<for-each select="$params">
<if test="position() gt 1">
<text>, </text>
</if>
<text>$arg</text>
<value-of select="position()" />
</for-each>
</xsl:variable>
<!-- force to string -->
<value-of select="string( $argstr )" />
</function>
</stylesheet>

View File

@ -41,4 +41,19 @@
<sequence select="$x - $y" />
</function>
<!-- large number of arguments to test partial application -->
<function name="foo:eight" as="item()+">
<param name="arg1" />
<param name="arg2" />
<param name="arg3" />
<param name="arg4" />
<param name="arg5" />
<param name="arg6" />
<param name="arg7" />
<param name="arg8" />
<sequence select="$arg1, $arg2, $arg3, $arg4,
$arg5, $arg6, $arg7, $arg8" />
</function>
</stylesheet>

View File

@ -26,6 +26,20 @@
xmlns:foo="http://www.lovullo.com/_junk"
stylesheet="apply-gen-test.xsl">
<variable name="args">
<foo:args>
<foo:arg1 />
<foo:arg2 />
<foo:arg3 />
<foo:arg4 />
<foo:arg5 />
<foo:arg6 />
<foo:arg7 />
<foo:arg8 />
</foo:args>
</variable>
<!-- basic case -->
<scenario label="given a unary function">
<context>
@ -218,5 +232,203 @@
select="2" />
</scenario>
</scenario>
<!-- more duplication...we support up to eight arguments, so we'll
have to test the generation of 1..7 (nullary is already
tested and 8 results in a full application) -->
<scenario label="with partial functions of 8-ary target, given">
<variable name="qname"
select="QName( 'http://www.lovullo.com/_junk',
'foo:eight' )" />
<scenario label="one argument">
<call function="foo:eight">
<param name="arg1" select="$args/foo:arg1" />
</call>
<expect label="returns partially applied function"
test="f:is-partial( $x:result )" />
<expect label="references the target function"
test="f:QName( $x:result ) = $qname" />
<expect label="partially applies one argument"
test="f:arity( $x:result ) = 7" />
<variable name="result-args" as="item()+"
select="f:args( $x:result )" />
<expect label="argument is partially applied by reference"
test="$result-args[ 1 ] is $args/foo:arg1" />
</scenario>
<scenario label="two arguments">
<call function="foo:eight">
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
</call>
<expect label="returns partially applied function"
test="f:is-partial( $x:result )" />
<expect label="references the target function"
test="f:QName( $x:result ) = $qname" />
<expect label="partially applies one argument"
test="f:arity( $x:result ) = 6" />
<variable name="result-args" as="item()+"
select="f:args( $x:result )" />
<expect label="argument is partially applied by reference"
test="$result-args[ 1 ] is $args/foo:arg1
and $result-args[ 2 ] is $args/foo:arg2" />
</scenario>
<scenario label="three arguments">
<call function="foo:eight">
<param name="arg1" select="$args/foo:arg1" />
<param name="arg2" select="$args/foo:arg2" />
<param name="arg3" select="$args/foo:arg3" />
</call>
<expect label="returns partially applied function"
test="f:is-partial( $x:result )" />
<expect label="references the target function"
test="f:QName( $x:result ) = $qname" />
<expect label="partially applies one argument"
test="f:arity( $x:result ) = 5" />
<variable name="result-args" as="item()+"
select="f:args( $x:result )" />
<expect label="argument is partially applied by reference"
test="$result-args[ 1 ] is $args/foo:arg1
and $result-args[ 2 ] is $args/foo:arg2
and $result-args[ 3 ] is $args/foo:arg3" />
</scenario>
<scenario label="four arguments">
<call function="foo:eight">
<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="returns partially applied function"
test="f:is-partial( $x:result )" />
<expect label="references the target function"
test="f:QName( $x:result ) = $qname" />
<expect label="partially applies one argument"
test="f:arity( $x:result ) = 4" />
<variable name="result-args" as="item()+"
select="f:args( $x:result )" />
<expect label="argument is partially applied by reference"
test="$result-args[ 1 ] is $args/foo:arg1
and $result-args[ 2 ] is $args/foo:arg2
and $result-args[ 3 ] is $args/foo:arg3
and $result-args[ 4 ] is $args/foo:arg4" />
</scenario>
<scenario label="five arguments">
<call function="foo:eight">
<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="returns partially applied function"
test="f:is-partial( $x:result )" />
<expect label="references the target function"
test="f:QName( $x:result ) = $qname" />
<expect label="partially applies one argument"
test="f:arity( $x:result ) = 3" />
<variable name="result-args" as="item()+"
select="f:args( $x:result )" />
<expect label="argument is partially applied by reference"
test="$result-args[ 1 ] is $args/foo:arg1
and $result-args[ 2 ] is $args/foo:arg2
and $result-args[ 3 ] is $args/foo:arg3
and $result-args[ 4 ] is $args/foo:arg4
and $result-args[ 5 ] is $args/foo:arg5" />
</scenario>
<scenario label="six arguments">
<call function="foo:eight">
<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="returns partially applied function"
test="f:is-partial( $x:result )" />
<expect label="references the target function"
test="f:QName( $x:result ) = $qname" />
<expect label="partially applies one argument"
test="f:arity( $x:result ) = 2" />
<variable name="result-args" as="item()+"
select="f:args( $x:result )" />
<expect label="argument is partially applied by reference"
test="$result-args[ 1 ] is $args/foo:arg1
and $result-args[ 2 ] is $args/foo:arg2
and $result-args[ 3 ] is $args/foo:arg3
and $result-args[ 4 ] is $args/foo:arg4
and $result-args[ 5 ] is $args/foo:arg5
and $result-args[ 6 ] is $args/foo:arg6" />
</scenario>
<scenario label="seven arguments">
<call function="foo:eight">
<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="returns partially applied function"
test="f:is-partial( $x:result )" />
<expect label="references the target function"
test="f:QName( $x:result ) = $qname" />
<expect label="partially applies one argument"
test="f:arity( $x:result ) = 1" />
<variable name="result-args" as="item()+"
select="f:args( $x:result )" />
<expect label="argument is partially applied by reference"
test="$result-args[ 1 ] is $args/foo:arg1
and $result-args[ 2 ] is $args/foo:arg2
and $result-args[ 3 ] is $args/foo:arg3
and $result-args[ 4 ] is $args/foo:arg4
and $result-args[ 5 ] is $args/foo:arg5
and $result-args[ 6 ] is $args/foo:arg6
and $result-args[ 7 ] is $args/foo:arg7" />
</scenario>
</scenario>
</scenario>
</description>