All ref functions now have data adjacency guarantees

Accessors will ignore adjacent data and mutators will echo them, by
reference, unchanged.
master
Mike Gerwitz 2014-12-04 15:28:57 -05:00
commit bb049984d5
3 changed files with 290 additions and 37 deletions

View File

@ -31,15 +31,25 @@
The descriptor @var{desc} has the following format: The descriptor @var{desc} has the following format:
@example @example
<f:ref arity="N" [...]> <f:ref arity="N" length="M" [...]>
<target /> <target />
</f:ref> </f:ref>
@end example @end example
where the @var{target} node shares the same QName as the function to where the @var{target} node shares the same QName as the function to
be applied, and @var{@arity} is its arity. The @var{f:ref} node may be applied, and @var{@arity} is its arity. @var{@length} caches the
be decorated with additional attributes depending on its context or number of items (in a sequence) that make up the reference; it can
be retrieved with @code{f:length}. The @var{f:ref} node may be
decorated with additional attributes depending on its context or
constructor. constructor.
Each of these functions respects data adjacent to the given
reference (which is at the head of the sequence). For example,
@code{f:set-args} will set the arguments of the dynamic function
reference represented by the head of the provided sequence, but will
leave all data following the reference untouched; this allows
references to be processed in a streaming manner without cutting the
sequence up before operating on the reference.
--> -->
<stylesheet version="2.0" <stylesheet version="2.0"
@ -66,7 +76,7 @@
<variable name="ns" <variable name="ns"
select="namespace-uri-from-QName( $name )" /> select="namespace-uri-from-QName( $name )" />
<f:ref arity="{$arity}"> <f:ref arity="{$arity}" length="1">
<element name="{$name}" <element name="{$name}"
namespace="{$ns}" /> namespace="{$ns}" />
</f:ref> </f:ref>
@ -160,7 +170,12 @@
<function name="f:args" as="item()*"> <function name="f:args" as="item()*">
<param name="fnref" as="item()+" /> <param name="fnref" as="item()+" />
<sequence select="remove( $fnref, 1 )" /> <variable name="desc" as="element( f:ref )"
select="$fnref[ 1 ]" />
<variable name="length" as="xs:double"
select="$desc/@length" />
<sequence select="subsequence( $fnref, 2, ( $length - 1 ) )" />
</function> </function>
@ -200,10 +215,17 @@
<attribute name="arity" <attribute name="arity"
select="$target-arity - count( $args )" /> select="$target-arity - count( $args )" />
<attribute name="length"
select="count( $args ) + 1" />
<sequence select="$desc/*" /> <sequence select="$desc/*" />
</f:ref> </f:ref>
<sequence select="$args" /> <!-- be sure to retain the adjacent data (which is offset by the
_original_ reference length) -->
<sequence select="$args,
subsequence( $fnref,
$desc/@length + 1 )" />
</function> </function>
@ -234,4 +256,28 @@
($args, f:args( $fnref )) )" /> ($args, f:args( $fnref )) )" />
</function> </function>
<!--
Retrieve length of @var{fnref} as a number of sequence items
While the reference is intended to be opaque, it is no secret that
the data are stored as a sequence. The length is therefore
important for stepping through that sequence of data, similar to how
data/struct length are required for pointer arithmetic when dealing
with memory in C.
So, this doesn't break encapsulation: it just tells us how big we
are, which is an external quality that can be easily discovered
without our help; we just cache it for both performance and
convenience. Aren't we nice?
-->
<function name="f:length" as="xs:double">
<param name="fnref" as="item()+" />
<variable name="desc" as="element( f:ref )"
select="$fnref[ 1 ]" />
<sequence select="number( $desc/@length )" />
</function>
</stylesheet> </stylesheet>

View File

@ -27,6 +27,11 @@
xmlns:foo="http://www.lovullo.com/_junk" xmlns:foo="http://www.lovullo.com/_junk"
stylesheet="partial-test.xsl"> stylesheet="partial-test.xsl">
<!-- TODO: this is common between a number of files; move me -->
<variable name="foo-uri"
select="namespace-uri-for-prefix(
'foo', root(.)/element() )" />
<variable name="fnref" <variable name="fnref"
select="foo:ternary()" /> select="foo:ternary()" />
@ -89,11 +94,10 @@
test="$x:result[ 1 ] test="$x:result[ 1 ]
and $x:result[ 1 ] = foo:ternary-applied" /> and $x:result[ 1 ] = foo:ternary-applied" />
<variable name="rargs" select="f:args( $x:result )" />
<expect label="applies target function with arguments, by reference" <expect label="applies target function with arguments, by reference"
test="$rargs[ 1 ] is $args/foo:a test="$x:result[ 2 ] is $args/foo:a
and $rargs[ 2 ] is $args/foo:b and $x:result[ 3 ] is $args/foo:b
and $rargs[ 3 ] is $args/foo:c" /> and $x:result[ 4 ] is $args/foo:c" />
</scenario> </scenario>
@ -144,9 +148,9 @@
arguments to a target"> arguments to a target">
<scenario label="unary"> <scenario label="unary">
<call function="f:partial"> <call function="f:partial">
<param name="fnref"> <param name="fnref"
<f:ref arity="1"><foo:fn1 /></f:ref> select="f:make-ref( QName( $foo-uri, 'foo:fn1' ),
</param> 1 )" />
<param name="args" select="$args/foo:a" /> <param name="args" select="$args/foo:a" />
</call> </call>
@ -160,9 +164,9 @@
<scenario label="binary"> <scenario label="binary">
<call function="f:partial"> <call function="f:partial">
<param name="fnref"> <param name="fnref"
<f:ref arity="2"><foo:fn2 /></f:ref> select="f:make-ref( QName( $foo-uri, 'foo:fn2' ),
</param> 2 )" />
<param name="args" select="$args/foo:a, <param name="args" select="$args/foo:a,
$args/foo:b" /> $args/foo:b" />
</call> </call>
@ -178,9 +182,9 @@
<scenario label="ternary"> <scenario label="ternary">
<call function="f:partial"> <call function="f:partial">
<param name="fnref"> <param name="fnref"
<f:ref arity="3"><foo:fn3 /></f:ref> select="f:make-ref( QName( $foo-uri, 'foo:fn3' ),
</param> 3 )" />
<param name="args" select="$args/foo:a, <param name="args" select="$args/foo:a,
$args/foo:b, $args/foo:b,
$args/foo:c" /> $args/foo:c" />
@ -198,9 +202,9 @@
<scenario label="4-ary"> <scenario label="4-ary">
<call function="f:partial"> <call function="f:partial">
<param name="fnref"> <param name="fnref"
<f:ref arity="4"><foo:fn4 /></f:ref> select="f:make-ref( QName( $foo-uri, 'foo:fn4' ),
</param> 4 )" />
<param name="args" select="$args/foo:a, <param name="args" select="$args/foo:a,
$args/foo:b, $args/foo:b,
$args/foo:c, $args/foo:c,
@ -220,9 +224,9 @@
<scenario label="5-ary"> <scenario label="5-ary">
<call function="f:partial"> <call function="f:partial">
<param name="fnref"> <param name="fnref"
<f:ref arity="5"><foo:fn5 /></f:ref> select="f:make-ref( QName( $foo-uri, 'foo:fn5' ),
</param> 5 )" />
<param name="args" select="$args/foo:a, <param name="args" select="$args/foo:a,
$args/foo:b, $args/foo:b,
$args/foo:c, $args/foo:c,
@ -244,9 +248,9 @@
<scenario label="6-ary"> <scenario label="6-ary">
<call function="f:partial"> <call function="f:partial">
<param name="fnref"> <param name="fnref"
<f:ref arity="6"><foo:fn6 /></f:ref> select="f:make-ref( QName( $foo-uri, 'foo:fn6' ),
</param> 6 )" />
<param name="args" select="$args/foo:a, <param name="args" select="$args/foo:a,
$args/foo:b, $args/foo:b,
$args/foo:c, $args/foo:c,
@ -270,9 +274,9 @@
<scenario label="7-ary"> <scenario label="7-ary">
<call function="f:partial"> <call function="f:partial">
<param name="fnref"> <param name="fnref"
<f:ref arity="7"><foo:fn7 /></f:ref> select="f:make-ref( QName( $foo-uri, 'foo:fn7' ),
</param> 7 )" />
<param name="args" select="$args/foo:a, <param name="args" select="$args/foo:a,
$args/foo:b, $args/foo:b,
$args/foo:c, $args/foo:c,
@ -298,9 +302,9 @@
<scenario label="8-ary"> <scenario label="8-ary">
<call function="f:partial"> <call function="f:partial">
<param name="fnref"> <param name="fnref"
<f:ref arity="8"><foo:fn8 /></f:ref> select="f:make-ref( QName( $foo-uri, 'foo:fn8' ),
</param> 8 )" />
<param name="args" select="$args/foo:a, <param name="args" select="$args/foo:a,
$args/foo:b, $args/foo:b,
$args/foo:c, $args/foo:c,

View File

@ -68,6 +68,13 @@
<expect label="produces reference to expected QName" <expect label="produces reference to expected QName"
test="f:QName( $x:result ) = $qname" /> test="f:QName( $x:result ) = $qname" />
<!-- this isn't breaking encapsulation: we know that a sequence
is returned and we must know how to delimit the references
within it -->
<expect label="produces length equal to length of returned
sequence"
test="f:length( $x:result ) = count( $x:result )" />
</scenario> </scenario>
@ -102,6 +109,19 @@
</scenario> </scenario>
<scenario label="given a valid dynamic function reference adjacent
to other data">
<call function="f:is-ref">
<param name="fnref"
select="f:make-ref( QName( $foo-uri, 'foo' ), 0 ),
'adjacent data'" />
</call>
<expect label="returns true()"
select="true()" />
</scenario>
<scenario label="given an invalid dynamic function reference"> <scenario label="given an invalid dynamic function reference">
<call function="f:is-ref"> <call function="f:is-ref">
<param name="fnref"> <param name="fnref">
@ -223,14 +243,30 @@
<expect label="returns empty sequence" <expect label="returns empty sequence"
select="()" /> select="()" />
</scenario> </scenario>
<scenario label="given a valid dynamic function reference with
adjacent data">
<call function="f:QName">
<param name="fnref"
select="f:make-ref( $foo-qname, 0 ),
'adjacent data'" />
</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> </scenario>
<scenario label="f:arity"> <scenario label="f:arity">
<scenario label="given a proper function reference"> <variable name="test-arity"
<variable name="test-arity" select="5" />
select="5" />
<scenario label="given a proper function reference">
<!-- test our format directly; do not rely on the <!-- test our format directly; do not rely on the
apply-gen code --> apply-gen code -->
<call function="f:arity"> <call function="f:arity">
@ -242,6 +278,20 @@
</scenario> </scenario>
<scenario label="given a proper function reference with adjacent
data">
<!-- test our format directly; do not rely on the
apply-gen code -->
<call function="f:arity">
<param name="fnref" select="$test-fn,
'adjacent data'" />
</call>
<expect label="provides function arity"
select="$test-arity" />
</scenario>
<scenario label="given a partially applied function reference"> <scenario label="given a partially applied function reference">
<call function="f:arity"> <call function="f:arity">
<param name="fnref" <param name="fnref"
@ -281,6 +331,10 @@
<expect label="retains target arity" <expect label="retains target arity"
test="f:arity( $x:result ) = f:arity( $test-fn )" /> test="f:arity( $x:result ) = f:arity( $test-fn )" />
<expect label="produces length equal to length of constructed
sequence"
test="f:length( $x:result ) = count( $x:result )" />
</scenario> </scenario>
@ -296,6 +350,10 @@
<!-- the function is now partially applied --> <!-- the function is now partially applied -->
<expect label="reduces arity by number of arguments" <expect label="reduces arity by number of arguments"
test="f:arity( $x:result ) = f:arity( $test-fn ) - 2" /> test="f:arity( $x:result ) = f:arity( $test-fn ) - 2" />
<expect label="produces length equal to length of constructed
sequence"
test="f:length( $x:result ) = count( $x:result )" />
</scenario> </scenario>
@ -313,6 +371,10 @@
<!-- the function is no longer partially applied --> <!-- the function is no longer partially applied -->
<expect label="restores target function arity" <expect label="restores target function arity"
test="f:arity( $x:result ) = f:arity( $test-fn )" /> test="f:arity( $x:result ) = f:arity( $test-fn )" />
<expect label="produces length equal to length of constructed
sequence"
test="f:length( $x:result ) = count( $x:result )" />
</scenario> </scenario>
@ -330,6 +392,10 @@
<!-- the function is no longer partially applied --> <!-- the function is no longer partially applied -->
<expect label="reduces arity by number of argments" <expect label="reduces arity by number of argments"
test="f:arity( $x:result ) = f:arity( $test-fn ) - 3" /> test="f:arity( $x:result ) = f:arity( $test-fn ) - 3" />
<expect label="produces length equal to length of constructed
sequence"
test="f:length( $x:result ) = count( $x:result )" />
</scenario> </scenario>
@ -347,6 +413,36 @@
<!-- the function is no longer partially applied --> <!-- the function is no longer partially applied -->
<expect label="increases arity by difference in argument count" <expect label="increases arity by difference in argument count"
test="f:arity( $x:result ) = f:arity( $test-fn ) - 1" /> test="f:arity( $x:result ) = f:arity( $test-fn ) - 1" />
<expect label="produces length equal to length of constructed
sequence"
test="f:length( $x:result ) = count( $x:result )" />
</scenario>
<scenario label="operating adjacent to other data">
<call function="f:set-args">
<param name="fnref" select="$test-fn,
$args/foo:a,
$args/foo:b" />
<param name="args" select="(1, 2)" />
</call>
<expect label="retains target QName"
test="f:QName( $x:result ) = f:QName( $test-fn )" />
<expect label="reduces arity by number of arguments"
test="f:arity( $x:result ) = f:arity( $test-fn ) - 2" />
<!-- notice that the length _does not_ reflect adjacent data -->
<expect label="produces length equal to length of constructed
sequence, sans adjacent data"
test="f:length( $x:result ) = count( $x:result ) - 2" />
<variable name="rlength" select="count( $x:result )" />
<expect label="retains adjacent data by reference"
test="$x:result[ $rlength - 1 ] is $args/foo:a
and $x:result[ $rlength ] is $args/foo:b" />
</scenario> </scenario>
</scenario> </scenario>
@ -396,6 +492,23 @@
<expect label="returns no other items" <expect label="returns no other items"
test="count( $x:result ) = 2" /> test="count( $x:result ) = 2" />
</scenario> </scenario>
<scenario label="operating on a reference preceding adjacent data">
<call function="f:args">
<param name="fnref"
select="f:set-args( $test-fn, ($args/foo:a,
$args/foo:b) ),
'adjacent data'" />
</call>
<expect label="returns sequence of arguments by reference"
test="$x:result[ 1 ] is $args/foo:a
and $x:result[ 2 ] is $args/foo:b" />
<expect label="returns no adjacent data"
test="count( $x:result ) = 2" />
</scenario>
</scenario> </scenario>
@ -419,6 +532,10 @@
<expect label="sets arguments by reference" <expect label="sets arguments by reference"
test="f:args( $x:result )[ 1 ] is $args/foo:a test="f:args( $x:result )[ 1 ] is $args/foo:a
and f:args( $x:result )[ 2 ] is $args/foo:b" /> and f:args( $x:result )[ 2 ] is $args/foo:b" />
<expect label="produces length equal to length of constructed
sequence"
test="f:length( $x:result ) = count( $x:result )" />
</scenario> </scenario>
@ -448,6 +565,10 @@
<expect label="pushes new arguments by reference" <expect label="pushes new arguments by reference"
test="f:args( $x:result )[ 3 ] is $args/foo:c test="f:args( $x:result )[ 3 ] is $args/foo:c
and f:args( $x:result )[ 4 ] is $args/foo:d" /> and f:args( $x:result )[ 4 ] is $args/foo:d" />
<expect label="produces length equal to length of constructed
sequence"
test="f:length( $x:result ) = count( $x:result )" />
</scenario> </scenario>
@ -470,6 +591,43 @@
<expect label="retains previous arguments by reference" <expect label="retains previous arguments by reference"
test="f:args( $x:result )[ 1 ] is $args/foo:a test="f:args( $x:result )[ 1 ] is $args/foo:a
and f:args( $x:result )[ 2 ] is $args/foo:b" /> and f:args( $x:result )[ 2 ] is $args/foo:b" />
<expect label="produces length equal to length of constructed
sequence"
test="f:length( $x:result ) = count( $x:result )" />
</scenario>
<scenario label="pushing arguments onto reference adjacent to
other data">
<call function="f:push-args">
<param name="fnref"
select="$test-fn,
$args/foo:a,
$args/foo:b" />
<param name="args"
select="$args/foo:c" />
</call>
<expect label="retains target QName"
test="f:QName( $x:result ) = f:QName( $test-fn )" />
<expect label="reduces arity by number of arguments, relative to
target function"
test="f:arity( $x:result ) = f:arity( $test-fn ) - 1" />
<expect label="sets arguments by reference"
test="f:args( $x:result )[ 1 ] is $args/foo:c" />
<variable name="rlength" select="count( $x:result )" />
<expect label="produces length equal to length of constructed
sequence, sans adjacent data"
test="f:length( $x:result ) = $rlength - 2" />
<expect label="retains adjacent data by reference"
test="$x:result[ $rlength - 1 ] is $args/foo:a
and $x:result[ $rlength ] is $args/foo:b" />
</scenario> </scenario>
</scenario> </scenario>
@ -494,6 +652,10 @@
<expect label="sets arguments by reference" <expect label="sets arguments by reference"
test="f:args( $x:result )[ 1 ] is $args/foo:a test="f:args( $x:result )[ 1 ] is $args/foo:a
and f:args( $x:result )[ 2 ] is $args/foo:b" /> and f:args( $x:result )[ 2 ] is $args/foo:b" />
<expect label="produces length equal to length of constructed
sequence"
test="f:length( $x:result ) = count( $x:result )" />
</scenario> </scenario>
@ -523,6 +685,10 @@
<expect label="unshifts new arguments by reference" <expect label="unshifts new arguments by reference"
test="f:args( $x:result )[ 1 ] is $args/foo:c test="f:args( $x:result )[ 1 ] is $args/foo:c
and f:args( $x:result )[ 2 ] is $args/foo:d" /> and f:args( $x:result )[ 2 ] is $args/foo:d" />
<expect label="produces length equal to length of constructed
sequence"
test="f:length( $x:result ) = count( $x:result )" />
</scenario> </scenario>
@ -545,6 +711,43 @@
<expect label="retains previous arguments by reference" <expect label="retains previous arguments by reference"
test="f:args( $x:result )[ 1 ] is $args/foo:a test="f:args( $x:result )[ 1 ] is $args/foo:a
and f:args( $x:result )[ 2 ] is $args/foo:b" /> and f:args( $x:result )[ 2 ] is $args/foo:b" />
<expect label="produces length equal to length of constructed
sequence"
test="f:length( $x:result ) = count( $x:result )" />
</scenario>
<scenario label="unshifting arguments onto reference adjacent to
other data">
<call function="f:unshift-args">
<param name="fnref"
select="$test-fn,
$args/foo:a,
$args/foo:b" />
<param name="args"
select="$args/foo:c" />
</call>
<expect label="retains target QName"
test="f:QName( $x:result ) = f:QName( $test-fn )" />
<expect label="reduces arity by number of arguments, relative to
target function"
test="f:arity( $x:result ) = f:arity( $test-fn ) - 1" />
<expect label="sets arguments by reference"
test="f:args( $x:result )[ 1 ] is $args/foo:c" />
<variable name="rlength" select="count( $x:result )" />
<expect label="produces length equal to length of constructed
sequence, sans adjacent data"
test="f:length( $x:result ) = $rlength - 2" />
<expect label="retains adjacent data by reference"
test="$x:result[ $rlength - 1 ] is $args/foo:a
and $x:result[ $rlength ] is $args/foo:b" />
</scenario> </scenario>
</scenario> </scenario>
</description> </description>