Initial support for slot data (no accessors or mutators)

* src/record.xsl: added R:init-slots and friends

* test/record.xspec: add slot data tests
Mike Gerwitz 2016-03-28 00:04:13 -04:00
parent a921741f09
commit d38c6b3a8f
No known key found for this signature in database
GPG Key ID: F22BB8158EE30EAB
2 changed files with 299 additions and 0 deletions

View File

@ -81,6 +81,7 @@
* Design Considerations: Record Design Considerations.
* Header: Record Header.
* Slot Naming: Record Slot Naming.
* Slot Data: Record Slot Data.
* Polymorphism: Record Polymorphism.
@end menu
@ -444,6 +445,182 @@
@node Record Slot Data
@section Record Slot Data
Slot data directly follows the @ref{Record Header} in a@tie{}sequence.
By default,
all records are initialized to a@tie{}special value @ref{R:empty-slot}
which indicates that the@tie{}slot contains no@tie{}value.@c
@footnote{It is otherwise not possible to indicate the@tie{}absence of
since the@tie{}empty sequence within a@tie{}sequence is
eliminated@mdash{}that is, @t{(1, (), 2) eq (1, 2)}.}
Special element indicating an empty slot.
This value will be automatically converted into the@tie{}empty sequence by
accessor functions.
<variable name="R:empty-slot" as="element( R:empty-slot )">
<R:empty-slot />
Slots cannot correspond 1-to-1 with sequence items,
because they might themselves contain records.
Let the @dfn{slot span}@tie{}@math{ϱ} of some slot@tie{}@math{s} be the
number of sequence items that constitute its value,
and let @math{#S} be the number of items in some set@tie{}@math{S};
@item @math{ϱ(s) = #T + ∑ᵢϱ(tᵢ) + 2, t∈T}, where @math{T} is the@tie{}set
of slots in@tie{}@math{s}, if @math{s}@tie{}is a record; and
@item @math{ϱ(s) = 1} otherwise.
@end itemize
@anchor{record span}
The first equation is the@tie{}@dfn{record span}.
It accounts for (@pxref{Record Slot Offsets}):
@item A @ref{Record Header} (one item);
@item The record span (one item);
@item @math{#T} items to hold the offset for each slot
(@pxref{Record Slot Offsets}); and
@item @math{∑ᵢϱ(tᵢ)} items to hold all slot contents.
@end enumerate
So, if a record in@tie{}@math{s} contains no nested records,
its span will always be @math{2#T + 2}.
All of this allows for dynamically-typed, dynamically-sized records.
* Slot Offsets: Record Slot Offsets.
@end menu
@node Record Slot Offsets
@subsection Record Slot Offsets
Recursively calculating the slot span of a record would be an incredibly
costly operation for deeply nested records,
so we reduce this lookup to @math{O(1)} constant time by recomputing it
each time a record slot is set.
To eliminate an @math{O(n)} linear scan of the slots for record span
each slot has its offset cached; this also allows us to look up a slot
in constant time.
These offsets are stored directly after the @ref{Record Header},
before the slot values;
this allows quick lookup and modification@c
@mdash{}modifying the record header involves reconstructing an element,
which involves many more operations than swapping out items in a
For records,
it is also important that we know the entire @ref{record span} so that
the end of the entire record in a sequence can be immediately known
without consulting the final slot span,
which involves looking up the last slot offset and then determining its
This offset precedes the individual slot offsets.
@anchor{record sequence}
The resulting construction is called a @dfn{record sequence}:
@float Figure, fig:record-construction
(<header> as R:Record,
<record span> as xs:integer,
<slot span>... as xs:integer*,
<slot>... as item()*)
@end verbatim
@caption{Items of a record sequence}
@end float
Produce a record sequence of @var{Record} initialized with
@math{N}@tie{}empty slots,
where @math{N}@tie{}is the@tie{}slot count of@tie{}@var{Record}.
Slots are initialized to @ref{R:empty-slot}.
The resulting sequence contains @math{2N + 2}@tie{}items,
with the first item being @var{Record}.
<function name="R:init-slots" as="item()+">
<param name="Record" as="element( R:Record )" />
<variable name="n" as="xs:integer"
select="R:slot-count( $Record )" />
<variable name="offsets" as="xs:integer*"
select="for $i in 1 to $n
return $i" />
<variable name="slots" as="element( R:empty-slot )*"
select="for $i in 1 to $n
return $R:empty-slot" />
<sequence select="$Record, (2*$n + 2), $offsets, $slots" />
Determine whether the given sequence @var{seq} is a
valid@tie{}@ref{record sequence}.
This ensures that the a proper @ref{Record Header} is present and that
there are enough items in the sequence to satisfy all data requirements
for that record (@pxref{record span}).
It does not, however, guarantee that the items in the sequence do actually
belong to that record.
<function name="R:is-record-seq" as="xs:boolean">
<param name="seq" as="item()*" />
<variable name="header" as="item()?"
select="$seq[ 1 ]" />
<variable name="slen" as="xs:integer"
select="max( ( 2, R:record-span( $seq ) ) )" />
<sequence select="$header
and R:is-record( $header )
and count( $seq ) ge $slen" />
Determine the @ref{record span} of the record sequence @var{Rseq}.
This function checks to ensure that the first item in the sequence is a
@ref{Record Header},
but it @emph{does not} ensure that the provided sequence is a valid
@ref{record sequence};
to do so, use @ref{R:is-record-seq#1}.
<function name="R:record-span" as="xs:integer?">
<param name="Rseq" as="item()+" />
<variable name="header" as="item()?"
select="$Rseq[ 1 ]" />
<variable name="span" as="item()?"
select="$Rseq[ 2 ]" />
<sequence select="if ( R:is-record( $header )
and $span instance of xs:integer ) then
else ()" />
@node Record Polymorphism
@section Record Polymorphism

View File

@ -281,6 +281,128 @@
<scenario label="Record data function">
<!-- yes, this tests implementation details rather than relying on
abstractions provided by the API; this is intentional to ensure
that records are operating according to their specification -->
<scenario label="R:init-slots">
<variable name="anon-record"
select="R:make-record-header( 3 )" />
<call function="R:init-slots">
<param name="Record" select="$anon-record" />
<expect label="produces a sequence of length 2N+2"
test="count( $x:result ) eq 8" />
<expect label="first item is record header"
test="$x:result[ 1 ] = $anon-record" />
<expect label="second item is record span"
test="$x:result[ 2 ] = 8" />
<expect label="R:record-span agrees with span item"
test="R:record-span( $x:result ) = $x:result[ 2 ]" />
<expect label="slot offsets are initialized to slot positions"
test="$x:result[ 3 ] eq 1
and $x:result[ 4 ] eq 2
and $x:result[ 5 ] eq 3" />
<expect label="slots are initialized with empty values"
test="every $slot in subsequence( $x:result, 6 ) satisfies
$slot eq $R:empty-slot" />
<scenario label="R:is-record-seq">
<scenario label="given an empty sequence">
<call function="R:is-record-seq">
<param name="seq"
select="()" />
<expect label="yields false()"
test="$x:result eq false()" />
<scenario label="given a non-record header">
<call function="R:is-record-seq">
<param name="seq"
select="n:element( $test-qname )" />
<expect label="yields false()"
test="$x:result eq false()" />
<scenario label="given a record header only">
<call function="R:is-record-seq">
<param name="seq"
select="R:make-record-header( 0 )" />
<expect label="yields false()"
test="$x:result eq false()" />
<scenario label="given a record with sequence lt span">
<call function="R:is-record-seq">
<param name="seq"
R:make-record-header( 2 ) ),
1, 4 )" />
<expect label="yields false()"
test="$x:result eq false()" />
<scenario label="given a full record sequence">
<call function="R:is-record-seq">
<param name="seq"
R:make-record-header( 2 ) )" />
<expect label="yields true()"
test="$x:result eq true()" />
<!-- see above scenario for record test -->
<scenario label="R:record-span">
<scenario label="given a non-record">
<call function="R:record-span">
<param name="Rseq"
select="n:element( $test-qname )" />
<expect label="returns the empty sequence"
test="empty( $x:result )" />
<scenario label="given a record with an invalid span">
<call function="R:record-span">
<param name="Rseq"
select="R:make-record-header( 0 ), 'invalid'" />
<expect label="returns the empty sequence"
test="empty( $x:result )" />
<scenario label="R:is-record">
<scenario label="given a record">
<call function="R:is-record">