Error and hook for arity count in partial application

master
Mike Gerwitz 2014-11-25 15:02:36 -05:00
parent 04373b5edd
commit 0dc019c9ed
3 changed files with 155 additions and 30 deletions

View File

@ -28,6 +28,8 @@
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="http://www.lovullo.com/hoxsl/apply">
<import href="arity.xsl" />
<!--
Partially apply dynamic function reference FNREF
@ -43,7 +45,7 @@
until their parameters are exhausted. This can be used to implement
currying.
-->
<function name="f:partial">
<function name="f:partial" as="item()+">
<param name="fnref" as="item()+" />
<param name="args" as="item()*" />
@ -53,22 +55,45 @@
as="element(f:ref)"
select="$fnref[ 1 ]" />
<f:ref>
<sequence select="$ref/@*" />
<variable name="argout" as="item()*">
<!-- include any previously applied arguments (if we're partially
applying a partial application) -->
<sequence select="remove( $fnref, 1 )" />
<attribute name="partial"
select="count( $args )" />
<!-- nested sequences are implicitly flattened, so we're not
returning a sub-sequence here -->
<sequence select="$args" />
</variable>
<sequence select="$ref/*" />
</f:ref>
<variable name="argn" as="xs:decimal"
select="count( $argout )" />
<!-- include any previously applied arguments (if we're partially
applying a partial application) -->
<sequence select="remove( $fnref, 1 )" />
<variable name="arity" as="xs:decimal"
select="f:arity( $ref )" />
<!-- nested sequences are implicitly flattened, so we're not
returning a sub-sequence here -->
<sequence select="$args" />
<choose>
<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>
@ -94,4 +119,42 @@
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>
</stylesheet>

View File

@ -33,6 +33,21 @@
<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" />

View File

@ -27,19 +27,20 @@
xmlns:foo="http://www.lovullo.com/_junk"
stylesheet="partial-test.xsl">
<scenario label="f:partial">
<variable name="fnref"
select="foo:ternary()" />
<variable name="fnref"
select="foo:ternary()" />
<variable name="args">
<foo:parent>
<foo:a />
<foo:b />
<foo:c />
</foo:parent>
</variable>
<variable name="args">
<foo:parent>
<foo:a />
<foo:b />
<foo:c />
<foo:d />
</foo:parent>
</variable>
<scenario label="f:partial constructor">
<scenario label="given an empty argument list">
<call function="f:partial">
<param name="fnref" select="$fnref" />
@ -70,12 +71,8 @@
test="$x:result/@partial = 2" />
<expect label="returns each argument, ordered"
test="$x:result[ 2 ] = $args/foo:a
and exists(
$x:result[ 2 ]/parent::foo:parent )
and $x:result[ 3 ] = $args/foo:b
and exists(
$x:result[ 3 ]/parent::foo:parent )" />
test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] is $args/foo:b" />
</scenario>
@ -118,7 +115,7 @@
</scenario>
<scenario label="f:is-partial">
<scenario label="f:is-partial predicate">
<scenario label="given a non-none">
<call function="f:is-partial">
<param name="fnref"
@ -220,4 +217,54 @@
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>