Liberate current implementation of "Calc DSL"

(Copyright headers will be added in the next commit; these are the
original files, unaltered in any way.)

The internal project name at LoVullo is simply "Calc DSL".  This
liberates the entire thing.  If anything was missed, I'll be added
later.

To continue building at LoVullo with this move, symlinks are used for
the transition; this is the exact code that is used in production.

There is a lot here---over 25,000 lines.  Much of it is in disarray from
the environment surrounding its development, but it does work well for
what it was intended to do.

(LoVullo folks: fork point is 65723a0 in calcdsl.git.)
master
Mike Gerwitz 2016-08-24 09:43:05 -04:00
parent 6c0aa54bd1
commit ff01f39c1e
72 changed files with 25223 additions and 2 deletions

View File

@ -22,7 +22,9 @@ path_src = src
path_test = test
# all source files will be run through hoxsl; see `applies' target
apply_src := $(shell find "$(path_src)" "$(path_test)" -name '*.xsl')
apply_src := $(shell find "$(path_src)" "$(path_test)" \
-name '*.xsl' \
-a \! -path "$(path_src)"/current/\* )
apply_dest := $(apply_src:%.xsl=%.xsl.apply)
# needed by test runner

View File

@ -37,6 +37,13 @@ TAME's core library, and [hoxsl](https://github.com/lovullo/hoxsl) was
developed as a supporting library.
## "Current"
The current state of the project as used in production is found in
`src/current/`. The environment surrounding the development of this
project resulted in a bit of a mess, which is being refactored into
`src/` as it is touched. Documentation is virtually non-existent.
## 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

@ -21,7 +21,9 @@
path_src := ../src
path_tools := ../tools
stylesheets := $(shell find "$(path_src)" -name '*.xsl')
stylesheets := $(shell find "$(path_src)" \
-name '*.xsl' \
-a \! -path "$(path_src)"/current/\* )
stexi := $(stylesheets:.xsl=.texi)
info_TEXINFOS = tame.texi

1
src/current/.gitignore vendored 100644
View File

@ -0,0 +1 @@
!Makefile

View File

@ -0,0 +1,8 @@
.PHONY: dslc clean
dslc:
$(MAKE) -C src/ dslc
clean:
$(MAKE) -C src/ clean

View File

@ -0,0 +1,383 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Generates PHP code that works with the LoVullo ConceptOne import system
This map expects that the data are available in the bucket provided by the
quote and therefore validates against a provided Program UI source file. Data
external to the bucket may be provided if it is indicated as such.
Each map source file is independent; variables and values do not bleed into
one-another, unless explicitly passed.
-->
<xsl:stylesheet version="2.0"
xmlns:c1="http://www.epic-premier.com/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lvm="http://www.lovullo.com/rater/map/c1"
xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp">
<xsl:output
indent="yes"
omit-xml-declaration="yes"
/>
<!-- newline -->
<xsl:variable name="lvmp:nl" select="'&#10;'" />
<xsl:include href="c1map/c1nodes.xsl" />
<xsl:include href="c1map/valparse.xsl" />
<xsl:include href="c1map/render.xsl" />
<!--
Represents the root of the source document that processing was initiated upon
-->
<xsl:variable name="orig-root" select="/" />
<!--
The root node
-->
<xsl:template match="lvm:c1-map" priority="5">
<!-- populated by includes, if any -->
<xsl:param name="args" />
<xsl:message select="$args" />
<!-- get the name from the first C1 node -->
<xsl:variable name="name" select="
if ( @id ) then
@id
else
concat( translate( c1:*[1]/name(), '_', '' ), 'Composer' )
" />
<!-- preprocessed result -->
<xsl:variable name="pp-result">
<lvmp:root program="{@program}" name="{$name}">
<!-- introduce outer scope for variables -->
<lvmp:scope id="">
<xsl:apply-templates />
</lvmp:scope>
</lvmp:root>
</xsl:variable>
<!-- final processing -->
<xsl:variable name="result">
<xsl:apply-templates select="$pp-result/lvmp:root" mode="lvmp:render" />
</xsl:variable>
<!-- remove escapes -->
<xsl:value-of disable-output-escaping="yes" select="$result" />
</xsl:template>
<!--
Include another source map file relative to the path of the original source
file
The attributes @name and @for-each are special and cannot be used as
arguments to the template. The special @for-each attribute will be copied
into each of the children of the root node in the template as @lvm:for-each.
For example, if the template consists of
<c1-map>
<Product>
</Product>
</c1-map>
then <lvm:include for-each="foo" /> would produce
<c1-map>
<Product lvm:for-each="foo">
</Product>
</c1-map>
-->
<xsl:template match="lvm:include" priority="5">
<xsl:message>[c1map] +<xsl:value-of select="@name" /></xsl:message>
<xsl:variable name="src" select="
document( concat( @name, '.xml' ), $orig-root )/lvm:c1-map
" />
<xsl:if test="not( $src )">
<xsl:message terminate="yes">fatal: c1-map node not found</xsl:message>
</xsl:if>
<!-- process the body of the c1-map; we don't want to process the root node,
as that would start processing from scratch, prematurely rendering the result -->
<lvmp:scope id="/{@name}">
<!-- arguments are included as attributes -->
<xsl:variable name="args" select="
@*[ not( local-name()='name' or local-name()='for-each' ) ]
" />
<xsl:variable name="for-each" select="@for-each" />
<!-- augment the XML with our own mappings -->
<xsl:variable name="augmented">
<lvm:c1-map>
<xsl:copy-of select="$src/@*" />
<xsl:for-each select="$src/lvm:param">
<xsl:call-template name="lvmp:param-to-map">
<xsl:with-param name="args" select="$args" />
<xsl:with-param name="param" select="." />
<xsl:with-param name="context" select="/lvm:c1-map" />
</xsl:call-template>
</xsl:for-each>
<xsl:choose>
<!-- if @for-each was provided, then apply to all first-level
children of the included template -->
<xsl:when test="$for-each">
<!-- we will need to expose the mapping -->
<!-- TODO: need to validate that it actually exists -->
<lvm:external name="{$for-each}" />
<xsl:for-each select="$src/*">
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:attribute name="lvm:for-each" select="$for-each" />
<xsl:copy-of select="*|text()" />
</xsl:copy>
</xsl:for-each>
</xsl:when>
<!-- no @for-each; just do a quick copy of all the nodes -->
<xsl:otherwise>
<xsl:copy-of select="$src/*" />
</xsl:otherwise>
</xsl:choose>
</lvm:c1-map>
</xsl:variable>
<xsl:apply-templates select="$augmented/lvm:c1-map/*" />
</lvmp:scope>
<xsl:message>[c1map] -<xsl:value-of select="@name" /></xsl:message>
</xsl:template>
<!--
Processes a template param into mappings
This will generate the mappings necessary to process the template as though
it was hard-coded with the imported mappings.
The {} brace syntax denotes a variable, but mixing values and inline
variables are not supported.
-->
<xsl:template name="lvmp:param-to-map">
<xsl:param name="args" />
<xsl:param name="param" />
<xsl:param name="context" />
<xsl:variable name="name" select="$param/@name" />
<xsl:variable name="arg" select="$args[ local-name()=$name ]" />
<xsl:variable name="argvar" select="substring-after( $arg, '{' )" />
<xsl:if test="$argvar and not( $argvar='' )">
<xsl:variable name="varname" select="substring-before( $argvar, '}' )" />
<lvmp:translate name="{$name}" to="{$varname}" />
<xsl:variable name="predot" select="substring-before( $varname, '.' )" />
<xsl:choose>
<!-- no dot; output the entire thing -->
<xsl:when test="$predot = ''">
<lvm:external name="{$varname}" />
</xsl:when>
<!-- multi-level var -->
<xsl:otherwise>
<lvm:external name="{$predot}" dict="true" lvmp:no-validate="true" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
<!-- TODO: no need to do this if the above conditional matches -->
<lvm:map to="{$name}" lvmp:allow-default="true">
<xsl:copy-of select="$param/@dict" />
<xsl:copy-of select="$param/@default" />
<xsl:choose>
<xsl:when test="$arg">
<!-- determines if we have a variable -->
<xsl:choose>
<xsl:when test="$argvar and not( $argvar='' )">
<xsl:attribute name="from"
select="substring-before( $argvar, '}' )" />
</xsl:when>
<!-- static value -->
<xsl:otherwise>
<xsl:attribute name="value" select="$arg" />
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<!-- required param -->
<xsl:when test="$param/@required">
<xsl:message terminate="yes">
<xsl:text>error: missing required template argument `</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:when>
<!-- otherwise, we have no value -->
<xsl:otherwise>
<xsl:attribute name="value" select="''" />
</xsl:otherwise>
</xsl:choose>
</lvm:map>
</xsl:template>
<!--
Common actions performed by nearly every mapping
-->
<xsl:template name="lvmp:map-common">
<!-- may or may not be set -->
<xsl:copy-of select="@dict" />
<xsl:copy-of select="@link" />
<xsl:copy-of select="@transform" />
<xsl:apply-templates select="@default" />
</xsl:template>
<xsl:template match="lvm:param" priority="4">
<!-- processed above on import; no longer needed -->
</xsl:template>
<xsl:template match="lvm:map[@from]" priority="4">
<lvmp:var name="{@to}" from="{@from}" src="map">
<xsl:call-template name="lvmp:map-common" />
</lvmp:var>
</xsl:template>
<xsl:template match="lvm:map[lvm:from]" priority="4">
<lvmp:var name="{@to}" from="{lvm:from/@name}">
<xsl:call-template name="lvmp:map-common" />
</lvmp:var>
</xsl:template>
<xsl:template match="lvm:map[@value]" priority="4">
<!-- it does not make sense to have a string value be a dictionary -->
<xsl:if test="@dict">
<xsl:message terminate="yes">
<xsl:text>error: cannot have @dict on static mapping `</xsl:text>
<xsl:value-of select="@to" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:if>
<!-- nor does a default make sense -->
<xsl:if test="@default and not( @lvmp:allow-default='true' )">
<xsl:message terminate="yes">
<xsl:text>error: cannot have @default on static mapping `</xsl:text>
<xsl:value-of select="@to" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:if>
<lvmp:var name="{@to}" value="{@value}">
<!-- we may use defaults internally -->
<xsl:call-template name="lvmp:map-common" />
</lvmp:var>
</xsl:template>
<xsl:template match="lvm:pass" priority="4">
<lvmp:var name="{@name}" from="{@name}" src="map">
<xsl:call-template name="lvmp:map-common" />
</lvmp:var>
</xsl:template>
<xsl:template match="lvm:external" priority="4">
<lvmp:var name="{@name}" from="{@name}" src="external">
<xsl:call-template name="lvmp:map-common" />
</lvmp:var>
</xsl:template>
<xsl:template match="lvm:*/@default">
<lvmp:default>
<xsl:apply-templates select="." mode="lvm:valparse" />
</lvmp:default>
</xsl:template>
<xsl:template match="lvmp:translate" priority="4">
<!-- added by pre-processor during include; ignore -->
</xsl:template>
<!--
Override default behavior of c1 nodes when iteration is requested
-->
<xsl:template match="c1:*[ @lvm:for-each ]"
mode="lvmp:c1-node-result" priority="5">
<lvmp:for-each name="{@lvm:for-each}">
<!-- proceed with processing as normal -->
<xsl:apply-templates select="@*|*" />
</lvmp:for-each>
</xsl:template>
<xsl:template match="lvm:if" priority="4">
<lvmp:condition>
<lvmp:when>
<xsl:call-template name="lvmp:gen-val">
<xsl:with-param name="name" select="@name" />
</xsl:call-template>
</lvmp:when>
<xsl:apply-templates />
</lvmp:condition>
</xsl:template>
<!--
Unhandled node character data
Note that, if a node contains newlines, then there will be text preceding and
following its children. For example:
<foo>
<bar>
</foo>
In the above, the `foo' node has the text "\n ", followed by the node `bar',
followed by the text "\n" (assuming that `foo' starts in column 1).
-->
<xsl:template match="text()" priority="1">
<!-- do not output whitespace from source files -->
</xsl:template>
<!--
Bail out on unhandled nodes.
-->
<xsl:template match="*" priority="1">
<xsl:message>
<xsl:text>[c1map] fatal: unexpected node </xsl:text>
<xsl:apply-templates select="." mode="lvmp:node-out" />
<xsl:text>:</xsl:text>
</xsl:message>
<xsl:message terminate="yes" select="." />
</xsl:template>
<xsl:template match="*" mode="lvmp:node-out">
<xsl:variable name="parent" select="parent::*" />
<xsl:if test="$parent">
<xsl:apply-templates select="$parent" mode="lvmp:node-out" />
</xsl:if>
<xsl:text>/</xsl:text>
<xsl:value-of select="name()" />
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Describes how ConceptOne nodes are handled in the output.
Only nodes in the C1 XML namespace will be included in the output; all other
nodes will be in error, except for nodes as part of the c1 map namespace,
which are processed and will not be included in the output.
The output is an array format used to generate the final XML at runtime; this
format was not developed in conjunction with this project and is separate, so
be sure that this compiler is updated if the format changes.
-->
<xsl:stylesheet version="2.0"
xmlns:c1="http://www.epic-premier.com/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lvm="http://www.lovullo.com/rater/map/c1"
xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp">
<!--
Nodes with attributes or children are recursively processed and have the
form:
'>Name' => array( <recurse> )
-->
<xsl:template match="c1:*[*|@*]" priority="5">
<!-- make the output a little bit sane -->
<xsl:value-of select="$lvmp:nl" />
<!-- defer node rendering; allows us to easily determine if there are
siblings of the same name within a node boundary -->
<lvmp:node name="{name()}" />
<xsl:text> => </xsl:text>
<lvmp:node-boundary>
<xsl:apply-templates select="." mode="lvmp:c1-node-result" />
</lvmp:node-boundary>
</xsl:template>
<!--
The default behavior of c1 nodes is to simply output the nodes as-is, with
variable substitutions.
-->
<xsl:template match="c1:*" mode="lvmp:c1-node-result" priority="1">
<xsl:text>array( </xsl:text>
<xsl:apply-templates select="@*|*" />
<xsl:text>) </xsl:text>
</xsl:template>
<!--
Text-only nodes are of the form:
'>Name' => 'value'
-->
<xsl:template match="c1:*[text()]" priority="4">
<!-- defer node rendering; allows us to easily determine if there are
siblings of the same name within a node boundary -->
<lvmp:node name="{name()}" />
<xsl:text> => </xsl:text>
<xsl:text></xsl:text>
<!-- TODO: escape single quotes -->
<xsl:apply-templates select="text()" mode="lvm:valparse" />
<xsl:text>, </xsl:text>
</xsl:template>
<!--
Attributes are of the format:
'[Name]' => 'value'
-->
<xsl:template match="c1:*/@*" priority="5">
<xsl:text>'[</xsl:text>
<xsl:value-of select="name()" />
<xsl:text>]' => </xsl:text>
<xsl:apply-templates select="." mode="lvm:valparse" />
<xsl:text>, </xsl:text>
</xsl:template>
<!-- alternative attribute format for special situations -->
<xsl:template match="lvm:attribute" priority="5">
<xsl:text>'[</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>]' => </xsl:text>
<xsl:apply-templates select="@value" mode="lvm:valparse" />
<xsl:text>, </xsl:text>
</xsl:template>
<xsl:template match="c1:*/@lvm:*" priority="6">
<!-- discard all system attributes -->
<!-- TODO: error once everything is properly implemented -->
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,339 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Renders the final PHP code
-->
<xsl:stylesheet version="2.0"
xmlns:c1="http://www.epic-premier.com/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lvm="http://www.lovullo.com/rater/map/c1"
xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp">
<xsl:import href="transform.xsl" />
<xsl:template match="lvmp:root" mode="lvmp:render" priority="5">
<xsl:text>&lt;?php </xsl:text>
<xsl:value-of select="$lvmp:nl" />
<!-- TODO: add program id to namespace -->
<xsl:text>namespace lovullo\c1\interfaces\c1\contract\</xsl:text>
<xsl:value-of select="@program" />
<xsl:text>;</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<xsl:text>class </xsl:text>
<xsl:value-of select="@name" />
<xsl:text> {</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<xsl:text>public function compose( $contract ) {</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<xsl:text> return array(</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<!-- render the preprocessed content -->
<xsl:apply-templates mode="lvmp:render" />
<xsl:value-of select="$lvmp:nl" />
<xsl:text> );</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<xsl:text> }</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<xsl:text>}</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<xsl:text>?&gt;</xsl:text>
</xsl:template>
<xsl:template match="text()" mode="lvmp:render" priority="5">
<xsl:value-of select="." />
</xsl:template>
<xsl:template name="lvmp:value" match="lvmp:value" mode="lvmp:render" priority="5">
<xsl:param name="name" select="@ref" />
<xsl:param name="scope" select="ancestor::lvmp:scope[1]" />
<xsl:param name="var" select="$scope/lvmp:var[ @name=$name ][1]" />
<xsl:param name="from" select="$var/@from" />
<xsl:param name="value" select="$var/@value" />
<xsl:param name="default" select="$var/lvmp:default" />
<!-- provide error if the variable could not be found in the current scope -->
<xsl:call-template name="lvmp:check-var">
<xsl:with-param name="name" select="$name" />
<xsl:with-param name="scope" select="$scope" />
</xsl:call-template>
<xsl:choose>
<!-- mapping was provided -->
<xsl:when test="$from and not( $from='' )">
<xsl:apply-templates select="$var" mode="lvmp:transform">
<xsl:with-param name="value">
<xsl:apply-templates select="." mode="lvmp:render-value">
<xsl:with-param name="var" select="$var" />
<xsl:with-param name="from" select="$from" />
<xsl:with-param name="scope" select="$scope" />
<xsl:with-param name="default" select="$default" />
</xsl:apply-templates>
</xsl:with-param>
</xsl:apply-templates>
</xsl:when>
<!-- static string mapping -->
<xsl:when test="$value and not( $value='' )">
<!-- TODO: escape single quote -->
<xsl:text>'</xsl:text>
<xsl:value-of select="$value" />
<xsl:text>'</xsl:text>
</xsl:when>
<!-- if no @from was provided, then it forces the use of the default
value -->
<xsl:when test="$default">
<xsl:text>$contract-&gt;checkDefaultValue( '</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>', </xsl:text>
<xsl:apply-templates select="$default" mode="lvmp:render" />
<xsl:text> )</xsl:text>
</xsl:when>
<!-- if no default is available, then this node shall never be set -->
<xsl:otherwise>
<xsl:text>null</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template mode="lvmp:render" priority="7"
match="lvmp:value[ lvmp:value ]">
<xsl:param name="name" select="@ref" />
<xsl:param name="scope" select="ancestor::lvmp:scope[1]" />
<xsl:param name="var" select="$scope/lvmp:var[ @name=$name ][1]" />
<xsl:param name="from" select="$var/@from" />
<xsl:param name="value" select="$var/@value" />
<xsl:param name="default" select="$var/lvmp:default" />
<xsl:text>$contract-&gt;getValueByContext( </xsl:text>
<!-- recursive process contexts -->
<xsl:apply-templates select="lvmp:value" mode="lvmp:render">
<xsl:with-param name="scope" select="$scope" />
<xsl:with-param name="default" select="$default" />
</xsl:apply-templates>
<xsl:text>, '</xsl:text>
<!-- TODO: validate existence of key in dict -->
<xsl:value-of select="$name" />
<xsl:text>' </xsl:text>
<xsl:text> )</xsl:text>
</xsl:template>
<xsl:template match="lvmp:value" mode="lvmp:render-value" priority="1">
<xsl:param name="var" />
<xsl:param name="scope" />
<xsl:param name="default" />
<xsl:param name="from" />
<!-- optional -->
<xsl:param name="value-node" select="." />
<xsl:param name="context" />
<!-- the name of the index we reference for our value defaults to our own
name, but can be overridden in the mapping -->
<xsl:param name="index-name">
<xsl:choose>
<!-- link provided; override -->
<xsl:when test="$var/@link">
<!-- parse the link and replace it with the var it maps to -->
<xsl:call-template name="lvmp:var-from">
<xsl:with-param name="name" select="$var/@link" />
<xsl:with-param name="scope" select="$scope" />
</xsl:call-template>
</xsl:when>
<!-- default to our own index -->
<xsl:otherwise>
<xsl:value-of select="$from" />
</xsl:otherwise>
</xsl:choose>
</xsl:param>
<xsl:text>$contract-&gt;getValue( '</xsl:text>
<xsl:value-of select="$from" />
<xsl:text>', $contract->getValueIndex( '</xsl:text>
<xsl:value-of select="$index-name" />
<xsl:text>' )</xsl:text>
<xsl:if test="$default">
<xsl:text>, </xsl:text>
<xsl:apply-templates select="$default" mode="lvmp:render" />
</xsl:if>
<xsl:text> )</xsl:text>
</xsl:template>
<!--
Generates an error in the event that the given var name cannot be found in
the given scope.
-->
<xsl:template name="lvmp:check-var">
<xsl:param name="name" />
<xsl:param name="scope" select="ancestor::lvmp:scope[1]" />
<!-- look up variable in parent scope -->
<xsl:variable name="var" select="$scope/lvmp:var[ @name=$name ][1]" />
<!-- provide error if the variable could not be found in the current scope -->
<xsl:if test="not( $var )">
<xsl:message terminate="yes">
<xsl:text>error: variable not within scope: </xsl:text>
<xsl:value-of select="$scope/@id" />
<xsl:text>/</xsl:text>
<xsl:value-of select="$name" />
</xsl:message>
</xsl:if>
</xsl:template>
<!--
Validates and then returns the source mapping of the given variable within
the given (or current) scope
-->
<xsl:template name="lvmp:var-from">
<xsl:param name="name" />
<xsl:param name="scope" select="ancestor::lvmp:scope[1]" />
<!-- undefined/scoping check -->
<xsl:call-template name="lvmp:check-var">
<xsl:with-param name="name" select="$name" />
<xsl:with-param name="scope" select="$scope" />
</xsl:call-template>
<xsl:value-of select="$scope/lvmp:var[ @name=$name ][1]/@from" />
</xsl:template>
<xsl:template match="lvmp:translate" mode="lvmp:render" priority="5">
<!-- no longer needed -->
</xsl:template>
<xsl:template match="lvmp:var" mode="lvmp:render" priority="5">
<!-- no longer needed -->
</xsl:template>
<xsl:template match="lvmp:var/lvmp:default" mode="lvmp:render" priority="5">
<!-- render contents -->
<xsl:apply-templates mode="lvmp:render" />
</xsl:template>
<!--
Scope boundary; no longer needed in output
-->
<xsl:template match="lvmp:scope" mode="lvmp:render" priority="2">
<xsl:apply-templates mode="lvmp:render" />
</xsl:template>
<!--
Conditional boundary; not needed in output
-->
<xsl:template match="lvmp:condition" mode="lvmp:render" priority="2">
<xsl:apply-templates mode="lvmp:render" />
</xsl:template>
<xsl:template match="lvmp:condition/lvmp:when" mode="lvmp:render" priority="2">
<!-- will be processed as part of sibling output -->
</xsl:template>
<!--
Iteration boundary
-->
<xsl:template match="lvmp:for-each" mode="lvmp:render" priority="5">
<xsl:variable name="from">
<xsl:call-template name="lvmp:var-from">
<xsl:with-param name="name" select="@name" />
</xsl:call-template>
</xsl:variable>
<xsl:text>$contract->iterateValues( '</xsl:text>
<xsl:value-of select="$from" />
<xsl:text>', </xsl:text>
<xsl:text>function( $contract ) {</xsl:text>
<xsl:text> return array(</xsl:text>
<xsl:apply-templates mode="lvmp:render" />
<xsl:text>);</xsl:text>
<xsl:text>}</xsl:text>
<xsl:text>)</xsl:text>
</xsl:template>
<!--
The C1 import system recognizes the following formats:
'>Node'
'N>Node'
where N is any number; the latter is used for creating unique indexes for
multiple siblings of the same name.
-->
<xsl:template match="lvmp:node" mode="lvmp:render" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:text>'</xsl:text>
<!-- generate a unique key to avoid conflicts -->
<xsl:value-of select="generate-id(.)" />
<xsl:text>&gt;</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>'</xsl:text>
</xsl:template>
<!--
Node boundary; not needed in output
The boundary dictates the context of a conditional. This template has a lower
priority and will be the default if there is no parent conditional.
-->
<xsl:template match="lvmp:node-boundary" mode="lvmp:render" priority="2">
<xsl:apply-templates mode="lvmp:render" />
<!-- unconditional delimiter; nothing funky going on -->
<xsl:text>, </xsl:text>
</xsl:template>
<xsl:template mode="lvmp:render" priority="5" match="
lvmp:node-boundary[
parent::lvmp:condition
or parent::lvmp:scope/parent::lvmp:condition
]
">
<!-- select the nearest condition -->
<xsl:variable name="cond" select="ancestor::lvmp:condition[1]" />
<xsl:text>( ( </xsl:text>
<xsl:text>$contract->isTruthy( </xsl:text>
<xsl:apply-templates select="$cond/lvmp:when/lvmp:*" mode="lvmp:render" />
<xsl:text>)</xsl:text>
<xsl:text> ) ? </xsl:text>
<xsl:apply-templates mode="lvmp:render">
<xsl:with-param name="no-trailing-sep" select="true()" />
</xsl:apply-templates>
<xsl:text> : null ), </xsl:text>
</xsl:template>
<xsl:template match="*" mode="lvmp:render" priority="1">
<xsl:message terminate="yes">
<xsl:text>[c1map] fatal: unexpected node during render: </xsl:text>
<xsl:apply-templates select="." mode="lvmp:node-out" />
</xsl:message>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Transforms values
-->
<xsl:stylesheet version="2.0"
xmlns:c1="http://www.epic-premier.com/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lvm="http://www.lovullo.com/rater/map/c1"
xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp">
<xsl:template match="lvmp:var[ @transform='proper-case' ]" priority="5"
mode="lvmp:transform">
<xsl:param name="value" />
<xsl:text>ucwords(strtolower(</xsl:text>
<xsl:copy-of select="$value" />
<xsl:text>))</xsl:text>
</xsl:template>
<xsl:template match="lvmp:var[ not( @transform ) or @transform='' ]"
mode="lvmp:transform" priority="3">
<xsl:param name="value" />
<!-- no transformation; do nothing -->
<xsl:copy-of select="$value" />
</xsl:template>
<xsl:template match="lvmp:var" mode="lvmp:transform" priority="2">
<xsl:message terminate="yes">
<xsl:text>error: unknown transformation `</xsl:text>
<xsl:value-of select="@transform" />
<xsl:text>' for `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:template>
<xsl:template match="lvmp:*" mode="lvmp:transform" priority="1">
<xsl:message terminate="yes">
<xsl:text>internal error: unexpected node for transformation: </xsl:text>
<xsl:copy-of select="." />
</xsl:message>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Inline value parser
Will parse all attributes and text of the form "a{b}c", where `b' is some
variable.
-->
<xsl:stylesheet version="2.0"
xmlns:c1="http://www.epic-premier.com/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lvm="http://www.lovullo.com/rater/map/c1"
xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp">
<xsl:template match="@*|text()" mode="lvm:valparse">
<xsl:call-template name="lvm:valparse">
<xsl:with-param name="str" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="lvm:valparse">
<xsl:param name="str" />
<xsl:variable name="pre" select="substring-before( $str, '{' )" />
<xsl:variable name="post" select="substring-after( $str, '}' )" />
<!-- get stuff between the two -->
<xsl:variable name="postpre" select="substring-after( $str, '{' )" />
<xsl:variable name="val" select="substring-before( $postpre, '}' )" />
<xsl:if test="$pre">
<xsl:text>'</xsl:text>
<!-- TODO: escape -->
<xsl:value-of select="$pre" />
<xsl:text>' . </xsl:text>
</xsl:if>
<xsl:choose>
<!-- variable reference -->
<xsl:when test="$val">
<xsl:call-template name="lvmp:gen-val">
<xsl:with-param name="name" select="$val" />
</xsl:call-template>
</xsl:when>
<!-- static value; no variable -->
<xsl:otherwise>
<xsl:text>'</xsl:text>
<xsl:value-of select="$str" />
<xsl:text>'</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="$post">
<xsl:text> . '</xsl:text>
<!-- TODO: escape -->
<xsl:value-of select="$post" />
<xsl:text>'</xsl:text>
</xsl:if>
</xsl:template>
<!--
Generate a variable reference for later rendering
If a variable of the form x.y is found, it is recursively processed with `y'
as the variable and `x' as the context: a dictionary lookup.
-->
<xsl:template name="lvmp:gen-val">
<xsl:param name="name" />
<xsl:variable name="trans" select="
ancestor::lvm:c1-map/lvmp:translate[ @name=$name ]
" />
<xsl:choose>
<!-- name translation requested -->
<xsl:when test="$trans">
<xsl:call-template name="lvmp:do-gen-val">
<xsl:with-param name="name" select="$trans/@to" />
</xsl:call-template>
</xsl:when>
<!-- no translation; use name as-is -->
<xsl:otherwise>
<xsl:call-template name="lvmp:do-gen-val">
<xsl:with-param name="name" select="$name" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="lvmp:do-gen-val">
<xsl:param name="name" />
<!-- we may have a variable of the form `x.y', in which case we should
process `y' withint he context of `x' -->
<xsl:variable name="rightmost" select="
substring-after( $name, '.' )
" />
<xsl:choose>
<!-- no more key references -->
<xsl:when test="$rightmost = ''">
<lvmp:value ref="{$name}" />
</xsl:when>
<!-- recursively process key reference -->
<xsl:otherwise>
<!-- determine the key context, which is the entire base sans the
rightmost variable name -->
<xsl:variable name="context" select="
substring( $name, 1, (
string-length( $name ) - string-length( $rightmost ) - 1
) )
" />
<lvmp:value ref="{$rightmost}" index-key="{$context}">
<xsl:call-template name="lvmp:gen-val">
<xsl:with-param name="name" select="$context" />
</xsl:call-template>
</lvmp:value>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,883 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lovullo.com/calc"
xmlns="http://www.lovullo.com/calc"
elementFormDefault="qualified">
<!--basicTypes-->
<xs:simpleType name="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Generic name reference restriction, intended to be generic enough to
work with most systems (supporting both C-style and Lisp-style
identifiers).
The systems that implement this schema should perform their own, more
strict, type checks.
</xs:documentation>
</xs:annotation>
<!-- we need to allow '@' since that's used in macros -->
<xs:restriction base="xs:string">
<xs:pattern value="[a-zA-Z_@{-][a-zA-Z0-9_@{}-]*" />
<xs:maxLength value="50" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="constValueType">
<xs:annotation>
<xs:documentation xml:lang="en">
Valid value for constants.
</xs:documentation>
</xs:annotation>
<!-- we need to allow '@' since that's used in macros -->
<xs:restriction base="xs:string">
<xs:pattern value="-?[0-9]+(.[0-9]+)?[mek]?|\{?@[a-z][a-zA-Z0-9_]*@\}?" />
<xs:maxLength value="50" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="descType">
<xs:annotation>
<xs:documentation xml:lang="en">
Documentation for a specific element.
The documentation must not be sparse; please provide something
descriptive that will be useful to someone completely new to the code.
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:minLength value="2" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="indexNameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Single-character index variable
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:NCName">
<xs:pattern value="[a-z]" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="symbolType">
<xs:annotation>
<xs:documentation xml:lang="en">
Symbol used to represent an entity when rendered.
The string should consist of TeX/LaTeX commands and should produce a
single symbol.
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string" />
</xs:simpleType>
<!--/basicTypes-->
<!--calculations-->
<!--operators-->
<xs:element name="operator" type="operatorBaseType" abstract="true">
<xs:annotation>
<xs:documentation xml:lang="en">
Abstract operator type used to classify elements as operators.
Operators are portions of the calculation that perform a portion of
the calculation. For example, a summation would classify as an
operation (operator), but value-of would not (as it is only
representing a value, but not performing a calculation).
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:complexType name="operatorBaseType" abstract="true">
<xs:annotation>
<xs:documentation xml:lang="en">
Elements/attributes common to all operator types (see the operator
element for a description on what qualifies as an operator).
All operators may include child calculations. Furthermore, they may
be labeled and may have an identifier associated with their result.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="when" type="whenType" minOccurs="0" maxOccurs="unbounded" />
<xs:group ref="calculation" minOccurs="0" maxOccurs="unbounded" />
<xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded" processContents="lax" />
</xs:sequence>
<xs:attribute name="yields" type="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional identifier with which the result of the calculation may be
referenced.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="label" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional description of a calculation. @label is used in place of
@desc to ensure that it is not confused with the otherwise required
@desc attribute.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="desc" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
@desc should be used to describe a value that is generated by a
calculation and should not otherwise be used (e.g. with
lv:sum/@generate)
This is different from @label, which provides a high-level
description of what the equation is doing. @desc describes what the
generated value is.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="index" type="indexNameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Allows for the definition of an index variable which will be
defined for all children of the operation.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:element name="sum" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Represents a summation (addition) of a set of values.
If @of is used, the equation defined by child elements will be
iterated using an index on the set provided by @of. If no equation is
given, all elements in the set identified by @of will be summed.
If @of is omitted, the result of each child element will be summed.
This operator should also be used for subtraction by summing negative
values.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:attribute name="of" type="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Iterate over all values of this set. Must be a set.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="generates" type="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional name of a set to generate from this expressions.
Generators are compatible only with @of and a sub-expression.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="sym" type="symbolType">
<xs:annotation>
<xs:documentation xml:lang="en">
Symbol to use when typesetting the generator
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="precision" type="constValueType">
<xs:annotation>
<xs:documentation xml:lang="en">
Yield precision
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="product" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Represents the product (multiplication) of a set of values.
If @of is used, the equation defined by child elements will be
iterated using an index on the set provided by @of. If no equation is
given, all elements in the set identified by @of will be multiplied.
If @of is omitted, the result of each child element will be
multiplied.
While this operator may also be used for division by multiplying by
the inverse of a number (n^-1), a limited quotient operator is
provided for clarity and convenience.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:attribute name="of" type="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Iterate over all values of this set. Must be a set.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="generates" type="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional name of a set to generate from this expressions.
Generators are compatible only with @of and a sub-expression.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="sym" type="symbolType">
<xs:annotation>
<xs:documentation xml:lang="en">
Symbol to use when typesetting the generator
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="dot" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
Dot product of any number of vectors
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="precision" type="constValueType">
<xs:annotation>
<xs:documentation xml:lang="en">
Yield precision
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="quotient" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Represents the quotient (division) of two values.
This operator is to be thought of as a fraction: the numerator is
identified by the first child element and the denominator the second.
While the summation operator may also be used for division by
multiplying by the inverse of a number (n^-1), this limited operator
is provided for clarity and convenience.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType" />
</xs:complexContent>
</xs:complexType>
</xs:element>
<!-- named after Scheme's expt; same arg order -->
<xs:element name="expt" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Exponent; the first child node is the base, the second is the order.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType" />
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="cons" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Construct a list using the given element and an existing list
This is analogous to lisp's cons; the first argument is the car and
the second is the cdr.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:sequence>
<!-- both car and cdr required -->
<xs:group ref="calculation" minOccurs="2" maxOccurs="2" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="car" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Retrieve the first element of a list
Analogous to lisp's car.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:sequence>
<!-- we accept only a set -->
<xs:group ref="calculation" minOccurs="1" maxOccurs="1" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="cdr" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Return the list without its first element
If the list contains only one element, then an empty list will be
returned. Analogous to lisp's cdr.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:sequence>
<!-- we accept only a set -->
<xs:group ref="calculation" minOccurs="1" maxOccurs="1" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="length-of" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Retrieves the length of a vector
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:sequence>
<!-- we accept only a set -->
<xs:group ref="calculation" minOccurs="1" maxOccurs="1" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<!--apply-->
<xs:element name="apply" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Denotes a function application.
The result of the function identified by @name will be used in
place of the call. The application may optionally accept an
argument list (if the function accepts arguments).
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:sequence>
<xs:element name="arg" type="applyArgumentType" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="name" type="nameType" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Name of the function to apply
</xs:documentation>
</xs:annotation>
</xs:attribute>
<!-- argument shorthand -->
<xs:anyAttribute namespace="##any" processContents="lax" />
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:complexType name="applyArgumentType">
<xs:annotation>
<xs:documentation xml:lang="en">
Represents an argument to be applied to a function.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:group ref="calculation" minOccurs="1" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="name" type="nameType" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Name of the parameter to apply to the function
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:element name="recurse" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Recursively applies the current function.
All arguments are copied to the argument list of the function application unless overridden.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:sequence>
<xs:element name="arg" type="applyArgumentType" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<!-- argument shorthand -->
<xs:anyAttribute namespace="##any" processContents="lax" />
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<!--/apply-->
<xs:element name="ceil" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Converts the given value to an integer, rounding up if necessary (e.g. 0.1 => 1)
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:attribute name="precision" type="constValueType">
<xs:annotation>
<xs:documentation xml:lang="en">
Value precision to consider for rounding.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="floor" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Converts the given value to an integer, rounding down if necessary (e.g. 0.9 => 0)
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:attribute name="precision" type="constValueType">
<xs:annotation>
<xs:documentation xml:lang="en">
Value precision to consider for rounding.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="set" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Creates a set out of its siblings
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType" />
</xs:complexContent>
</xs:complexType>
</xs:element>
<!--
Debug
-->
<xs:element name="debug-to-console" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Outputs the result of the contained expression to the console and
returns its value.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType" />
</xs:complexContent>
</xs:complexType>
</xs:element>
<!--/operators-->
<!--when-->
<xs:complexType name="whenType">
<xs:annotation>
<xs:documentation xml:lang="en">
Defines a condition under which the expression will yield one if
true, otherwise will be <em>strongly</em> zero (zero even if
undefined)
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:group ref="conditions" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="name" type="nameType" use="required" />
<xs:attribute name="index" type="nameType" />
<xs:attribute name="label" type="xs:string" />
</xs:complexType>
<xs:group name="conditions">
<xs:choice>
<xs:element name="eq" type="conditionType" />
<xs:element name="ne" type="conditionType" />
<xs:element name="lt" type="conditionType" />
<xs:element name="gt" type="conditionType" />
<xs:element name="lte" type="conditionType" />
<xs:element name="gte" type="conditionType" />
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
</xs:choice>
</xs:group>
<xs:complexType name="conditionType">
<xs:sequence>
<xs:group ref="calculation" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<!--/when-->
<xs:complexType name="indexType">
<xs:annotation>
<xs:documentation xml:lang="en">
More flexible alternative to the @index attribute on elements; supports
calculations for determining the index
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:group ref="calculation" minOccurs="1" maxOccurs="1" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="valueType">
<xs:annotation>
<xs:documentation xml:lang="en">
Returns the value associated with the given identifier.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="when" type="whenType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="index" type="indexType" minOccurs="0" maxOccurs="2" />
</xs:sequence>
<xs:attribute name="name" type="nameType" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
An identifier for which the value should be retrieved
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="index" type="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Allows for the definition of an index variable which will be
defined for all children of the operation.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="label" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional description of a calculation. @label is used in place of
@desc to ensure that it is not confused with the otherwise required
@desc attribute.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="constType">
<xs:annotation>
<xs:documentation xml:lang="en">
Returns @value. This element simply enforces documentation and prevents
the use of magic values.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="when" type="whenType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="index" type="indexType" minOccurs="0" maxOccurs="2" />
</xs:sequence>
<xs:attribute name="value" type="constValueType" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Value of constant (must be within the domain of its @type)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="type" type="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Type (domain) of the constant (inferred when not provided)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="desc" type="descType" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Description of the value explaining its intent
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="label" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional description of a calculation. @label is used in place of
@desc to ensure that it is not confused with the otherwise required
@desc attribute.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="casesType">
<xs:annotation>
<xs:documentation xml:lang="en">
Represents a list of cases for a calculation.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="case" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="when" type="whenType" minOccurs="0" maxOccurs="unbounded" />
<xs:group ref="calculation" minOccurs="1" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="label" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional description of the case
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="otherwise" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:group ref="calculation" />
</xs:sequence>
<xs:attribute name="label" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional description of the case
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="label" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional description of the case set
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="letType">
<xs:annotation>
<xs:documentation xml:lang="en">
Declares variables visible within the let block.
This exists as a means of assignment for re-using values.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="values" minOccurs="1" maxOccurs="1">
<xs:complexType>
<xs:annotation>
<xs:documentation xml:lang="en">
Declares a list of values to define for this block
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded" processContents="lax" />
<xs:element name="value" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:annotation>
<xs:documentation xml:lang="en">
Declares a value that will remain in scope for the let block
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:group ref="calculation" minOccurs="1" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="name" type="nameType" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Variable name
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="type" type="nameType" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Variable datatype
</xs:documentation>
</xs:annotation>
</xs:attribute>
<!-- TODO: enum -->
<xs:attribute name="set" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Vector/matrix; if omitted, implicitly scalar.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="desc" type="xs:string" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Describe the purpose of the value
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded" processContents="lax" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:group ref="calculation" minOccurs="1" maxOccurs="1" />
</xs:sequence>
</xs:complexType>
<xs:group name="calculation">
<xs:annotation>
<xs:documentation xml:lang="en">
Represents every element that can be used to perform a calculation.
Implementors may use this group to accept any type of calculation.
</xs:documentation>
</xs:annotation>
<xs:choice>
<xs:element ref="operator" />
<xs:element name="value-of" type="valueType" />
<xs:element name="const" type="constType" />
<xs:element name="cases" type="casesType" />
<xs:element name="let" type="letType" />
<xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded" processContents="lax" />
</xs:choice>
</xs:group>
<xs:group name="calculationRoot">
<xs:annotation>
<xs:documentation xml:lang="en">
Root calculation node; this should be the entry point for any type of
calculation. This helps to ensure that certain nodes are only used at
the beginning of a calculation (such as cases).
</xs:documentation>
</xs:annotation>
<xs:choice>
<xs:group ref="calculation" />
</xs:choice>
</xs:group>
<!--/calculations-->
</xs:schema>

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Writes preprocessor output to disk to eliminate the overhead of reprocessing
for each task that requires such output.
Also performs validation.
N.B.: Requires XSLT >=2.0
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:lvm="http://www.lovullo.com/rater/map"
xmlns:lvmc="http://www.lovullo.com/rater/map/compiler"
xmlns:c="http://www.lovullo.com/calc"
xmlns:w="http://www.lovullo.com/rater/worksheet"
xmlns:util="http://www.lovullo.com/util"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:compiler="http://www.lovullo.com/rater/compiler"
xmlns:calc-compiler="http://www.lovullo.com/calc/compiler"
xmlns:ext="http://www.lovullo.com/ext">
<xsl:output
indent="yes"
omit-xml-declaration="yes"
/>
<!-- processing utilities -->
<xsl:include href="include/util.xsl" />
<!-- compiler -> JS -->
<xsl:include href="include/preprocess.xsl" />
<xsl:include href="compiler/validate.xsl" />
<xsl:include href="compiler/js.xsl" />
<xsl:include href="compiler/map.xsl" />
<!-- contains get-symbol-map -->
<xsl:include href="include/display.xsl" />
<!-- allows disabling of time-consuming validation -->
<xsl:param name="preproc-cache-validate" select="'true'" />
<!--
Simply copy the preprocessor output; the caller is responsible for outputting
this to a document.
-->
<xsl:template match="/lv:rater|/lv:package" priority="5">
<!-- XXX: Duplicate code; see summary -->
<xsl:variable name="processed">
<xsl:apply-templates select="." mode="preproc:compile" />
</xsl:variable>
<!-- fail on preprocessor errors -->
<xsl:call-template name="preproc:err-chk">
<xsl:with-param name="processed" select="$processed" />
</xsl:call-template>
<!-- validation must have passed; output the nodes -->
<xsl:copy-of select="$processed" />
</xsl:template>
<xsl:template name="preproc:err-chk">
<xsl:param name="processed" />
<xsl:for-each select="$processed//preproc:error">
<xsl:message terminate="yes">
<xsl:text>!!! preprocessor error: </xsl:text>
<xsl:value-of select="." />
</xsl:message>
</xsl:for-each>
</xsl:template>
<xsl:template match="*" mode="preproc:handle-errors" priority="1">
<!-- do nothing -->
</xsl:template>
<xsl:template match="lvm:program-map|lvm:return-map" priority="5">
<xsl:apply-templates select="." mode="lvmc:compile" />
</xsl:template>
<xsl:template match="w:worksheet" priority="5">
<xsl:apply-templates select="." mode="w:compile" />
</xsl:template>
<!-- any unhandled nodes should be an error -->
<xsl:template match="*" priority="1">
<xsl:message terminate="yes">
<xsl:text>fatal: source file is invalid: unexpected node </xsl:text>
<xsl:value-of select="name()" />
</xsl:message>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Compiles rater XML into JavaScript
This stylesheet should be included by whatever is doing the processing and is
responsible for outputting the generated code in whatever manner is
appropriate (inline JS, a file, etc).
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<xsl:template match="lv:package" mode="preproc:compile-fragments">
<xsl:copy>
<xsl:sequence select="@*, *" />
<!-- compile each fragment in the symbol table -->
<preproc:fragments>
<xsl:for-each select="preproc:symtable/preproc:sym">
<xsl:variable name="result">
<xsl:apply-templates select="." mode="preproc:compile-fragments" />
</xsl:variable>
<xsl:if test="$result != ''">
<preproc:fragment id="{@name}">
<xsl:value-of select="$result" />
</preproc:fragment>
</xsl:if>
</xsl:for-each>
</preproc:fragments>
</xsl:copy>
</xsl:template>
<xsl:template match="preproc:sym[ @src ]" mode="preproc:compile-fragments" priority="9">
<!-- do not compile external symbols -->
</xsl:template>
<xsl:template match="preproc:sym" mode="preproc:compile-fragments" priority="1">
<xsl:message terminate="yes">
<xsl:text>[jsc] fatal: unknown symbol type for `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>': </xsl:text>
<xsl:value-of select="@type" />
</xsl:message>
</xsl:template>
<xsl:template match="preproc:sym[ @type='rate' ]" mode="preproc:compile-fragments" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<!-- could be one of two places -->
<xsl:apply-templates mode="compile" select="
$pkg/lv:rate[ @yields=$name ]
, $pkg/lv:rate-group/lv:rate[ @yields=$name ]
" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='gen' ]" mode="preproc:compile-fragments" priority="5">
<!-- compiled by above -->
</xsl:template>
<xsl:template match="preproc:sym[ @type='class' ]" mode="preproc:compile-fragments" priority="5">
<!-- name is prefixed with :class: -->
<xsl:variable name="as" select="substring-after( @name, ':class:' )" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<xsl:apply-templates select="$pkg/lv:classify[ @as=$as ]" mode="compile" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='cgen' ]" mode="preproc:compile-fragments" priority="5">
<!-- compiled by above -->
</xsl:template>
<xsl:template match="preproc:sym[ @type='func' ]" mode="preproc:compile-fragments" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<xsl:apply-templates select="$pkg/lv:function[ @name=$name ]" mode="compile" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='param' ]" mode="preproc:compile-fragments" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<xsl:apply-templates select="$pkg/lv:param[ @name=$name ]" mode="compile" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='type' ]" mode="preproc:compile-fragments" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<!-- a typedef can stand on its own or exist within another typedef -->
<xsl:apply-templates mode="compile" select="
$pkg/lv:typedef[ @name=$name ]
, $pkg//lv:typedef//lv:typedef[ @name=$name ]
" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='const' ]" mode="preproc:compile-fragments" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<xsl:apply-templates select="$pkg/lv:const[ @name=$name ]" mode="compile" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='tpl' ]" mode="preproc:compile-fragments" priority="5">
<!-- templates are for the preprocessor only -->
</xsl:template>
<xsl:template match="preproc:sym[ @type='lparam' ]" mode="preproc:compile-fragments" priority="5">
<!-- they're local and therefore compiled as part of the containing block -->
</xsl:template>
<xsl:template match="preproc:sym[ @type='meta' ]"
mode="preproc:compile-fragments" priority="5">
<xsl:variable name="name" select="substring-after( @name, ':meta:' )" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<xsl:variable name="node" as="element( lv:prop )"
select="$pkg/lv:meta/lv:prop[ @name=$name ]" />
<xsl:apply-templates mode="compile"
select="$node" />
</xsl:template>
</xsl:stylesheet>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Compiles rater XML into JavaScript
This stylesheet should be included by whatever is doing the processing and is
responsible for outputting the generated code in whatever manner is
appropriate (inline JS, a file, etc).
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:log="http://www.lovullo.com/logger">
<xsl:template name="log:info">
<xsl:param name="name" />
<xsl:param name="msg" />
<xsl:message>
<xsl:if test="$name">
<xsl:text>[</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>] </xsl:text>
</xsl:if>
<xsl:value-of select="$msg" />
</xsl:message>
</xsl:template>
<xsl:template name="log:debug">
<xsl:param name="name" />
<xsl:param name="msg" />
<xsl:message>
<xsl:if test="$name">
<xsl:text>[</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>] </xsl:text>
</xsl:if>
<xsl:value-of select="$msg" />
</xsl:message>
</xsl:template>
<xsl:template name="log:warn">
<xsl:param name="name" />
<xsl:param name="msg" />
<xsl:message>
<xsl:if test="$name">
<xsl:text>[</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>] warning: </xsl:text>
</xsl:if>
<xsl:value-of select="$msg" />
</xsl:message>
</xsl:template>
<xsl:template name="log:error">
<xsl:param name="name" />
<xsl:param name="msg" />
<xsl:param name="terminate" select="'yes'" />
<xsl:message terminate="{$terminate}">
<xsl:if test="$msg">
<xsl:if test="$name">
<xsl:text>[</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>] error: </xsl:text>
</xsl:if>
<xsl:value-of select="$msg" />
</xsl:if>
</xsl:message>
</xsl:template>
<xsl:template name="log:internal-error">
<xsl:param name="name" />
<xsl:param name="msg" />
<xsl:param name="terminate" select="'yes'" />
<xsl:message terminate="{$terminate}">
<xsl:if test="$name">
<xsl:text>[</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>] internal error: </xsl:text>
</xsl:if>
<xsl:value-of select="$msg" />
</xsl:message>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,997 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Compiles map fragments to produce a map from source data to a destination.
The source fields will be validated at compile-time to ensure that they exist;
destination fields should be checked by the compiler and/or linker. The linker
is responsible for assembling the fragments into a working map function.
When linking, the special head and tail fragments of the topmost map should be
used (that is, if A includes B and C, use A).
XXX: This is tightly coupled with the Program UI; refactor to support any type
of source.
-->
<stylesheet version="2.0"
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:lvm="http://www.lovullo.com/rater/map"
xmlns:lvmc="http://www.lovullo.com/rater/map/compiler"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:lvp="http://www.lovullo.com">
<param name="map-noterminate" select="'no'" />
<!--
Turn on/off unused param checks
This is useful for, say, the global classifier, where a param may end up not
being used if it's used in external classifications.
-->
<param name="unused-param-check" select="'true'" />
<!--
Generate a function that maps a set of inputs to a set of outputs
-->
<template match="lvm:program-map" mode="lvmc:compile" priority="8">
<param name="rater" />
<variable name="program-ui" select="
document( concat( @src, '.xml' ), . )/lvp:program
" />
<variable name="map" select="." />
<variable name="vresult">
<choose>
<when test="$program-ui">
<apply-templates select="." mode="lvmc:validate-ui">
<with-param name="ui" select="$program-ui" />
</apply-templates>
</when>
<otherwise>
<message terminate="yes">
<text>fatal: program UI source XML not found</text>
</message>
</otherwise>
</choose>
</variable>
<if test="
$vresult/lvmc:terminate
and $map-noterminate = 'no'
">
<message terminate="yes">!!! Terminating due to errors.</message>
</if>
<!-- we need to use an lv-namespaced node so that we are recognized
consistently with the rest of the system -->
<variable name="pkg">
<lv:package name="{$__srcpkg}" lvmc:type="map">
<!-- initial symbol table; full table will be generated below -->
<call-template name="lvmc:stub-symtable">
<with-param name="type-prefix" select="'map'" />
</call-template>
<!-- copy all source nodes -->
<copy-of select="*" />
<preproc:fragments>
<!-- special fragment to be output as the head -->
<preproc:fragment id=":map:___head">
<!-- use a callback just in case we need to make portions of this async in the
future -->
<text>function( input, callback ) {</text>
<text>var output = {};</text>
</preproc:fragment>
<!-- compile mapped -->
<apply-templates select="./lvm:*" mode="lvmc:compile">
<with-param name="rater" select="$rater" />
<with-param name="type" select="'map'" />
</apply-templates>
<!-- special fragment to be output as the foot -->
<preproc:fragment id=":map:___tail">
<text>callback(output);</text>
<text>};</text>
</preproc:fragment>
</preproc:fragments>
</lv:package>
</variable>
<!-- output the result after symbol processing -->
<call-template name="preproc:gen-deps">
<with-param name="pkg" as="element( lv:package )">
<apply-templates select="$pkg" mode="preproc:sym-discover">
<with-param name="orig-root" select="." />
</apply-templates>
</with-param>
</call-template>
</template>
<!--
Generate a function that maps a set of rater outputs
-->
<template match="lvm:return-map" mode="lvmc:compile" priority="8">
<param name="rater" />
<variable name="pkg">
<lv:package name="{$__srcpkg}" lvmc:type="retmap">
<!-- initial symbol table; full table will be generated below -->
<call-template name="lvmc:stub-symtable">
<with-param name="type-prefix" select="'retmap'" />
</call-template>
<!-- copy source data -->
<copy-of select="*" />
<preproc:fragments>
<!-- special fragment to be output as the head -->
<preproc:fragment id=":retmap:___head">
<!-- use a callback just in case we need to make portions of this async in the
future -->
<text>function ( input, callback ) {</text>
<text>var output = {};</text>
</preproc:fragment>
<apply-templates select="./lvm:*" mode="lvmc:compile">
<with-param name="rater" select="$rater" />
<with-param name="type" select="'retmap'" />
</apply-templates>
<!-- special fragment to be output as the foot -->
<preproc:fragment id=":retmap:___tail">
<text>callback(output);</text>
<text>}</text>
</preproc:fragment>
</preproc:fragments>
</lv:package>
</variable>
<!-- output the result after symbol processing -->
<call-template name="preproc:gen-deps">
<with-param name="pkg" as="element( lv:package )">
<apply-templates select="$pkg" mode="preproc:sym-discover">
<with-param name="orig-root" select="." />
</apply-templates>
</with-param>
</call-template>
</template>
<template name="lvmc:stub-symtable">
<param name="type-prefix" select="'map'" />
<preproc:symtable>
<!-- purposely non-polluting. @ignore-dup is intended to be
temporary until static generation of these names is resolved;
this will not cause problems, since the code is always the
same (future bug pending!) -->
<preproc:sym name=":{$type-prefix}:___head"
type="{$type-prefix}:head"
ignore-dup="true" />
<preproc:sym name=":{$type-prefix}:___tail"
type="{$type-prefix}:tail"
ignore-dup="true" />
</preproc:symtable>
</template>
<template name="lvmc:mapsym">
<param name="name" />
<param name="from" />
<param name="type-prefix" select="/lv:package/@lvmc:type" />
<!-- allow mappings to be overridden after import, which allows defaults
to be set and then overridden -->
<preproc:sym name=":{$type-prefix}:{$name}" virtual="true"
type="{$type-prefix}" pollute="true">
<!-- for consistency and cleanliness, only copy over if set -->
<if test="@override='true'">
<copy-of select="@override" />
</if>
<copy-of select="@affects-eligibility" />
<!-- only copy from data if present -->
<if test="$from">
<copy-of select="$from" />
</if>
</preproc:sym>
</template>
<!--
Directly map an input to the output
-->
<template match="lvm:pass" mode="lvmc:compile" priority="5">
<param name="rater" />
<param name="type" />
<preproc:fragment id=":{$type}:{@name}">
<text>output['</text>
<value-of select="@name" />
<text>']=</text>
<call-template name="lvmc:gen-input-default">
<with-param name="rater" select="$rater" />
<with-param name="to" select="@name" />
<with-param name="from" select="@name" />
</call-template>
<text>;</text>
<!-- newline to make output reading and debugging easier -->
<text>&#10;</text>
</preproc:fragment>
</template>
<template match="lvm:pass" mode="preproc:symtable" priority="5">
<call-template name="lvmc:mapsym">
<with-param name="name" select="@name" />
<with-param name="from">
<preproc:from name="{@name}" />
</with-param>
</call-template>
</template>
<template match="lvm:pass" mode="preproc:depgen" priority="5">
<preproc:sym-ref name="{@name}" lax="true" />
</template>
<!--
Maps an input to an output of a different name
-->
<template match="lvm:map[ @from ]" mode="lvmc:compile" priority="5">
<param name="rater" />
<param name="type" />
<!-- if src and dest are identical, then it may as well be lvm:pass -->
<if test="@to = @from">
<message>
<text>[map] notice: `</text>
<value-of select="@to" />
<!-- TODO: get namespace prefix from name() -->
<text>' has a destination of the same name; use lvm:pass instead</text>
</message>
</if>
<preproc:fragment id=":{$type}:{@to}">
<text>output['</text>
<value-of select="@to" />
<text>']=</text>
<call-template name="lvmc:gen-input-default">
<with-param name="rater" select="$rater" />
<with-param name="to" select="@to" />
<with-param name="from" select="@from" />
</call-template>
<text>;</text>
<!-- newline to make output reading and debugging easier -->
<text>&#10;</text>
</preproc:fragment>
</template>
<template name="lvmc:sym-from" match="lvm:map[ @from ]" mode="preproc:symtable" priority="5">
<call-template name="lvmc:mapsym">
<with-param name="name" select="@to" />
<with-param name="from">
<preproc:from name="{@from}" />
</with-param>
</call-template>
</template>
<template match="lvm:map[ @from
and root(.)/@lvmc:type = 'map' ]"
mode="preproc:depgen" priority="5">
<!-- to the DSL -->
<preproc:sym-ref name="{@to}" lax="true" />
</template>
<template match="lvm:map[ @from
and root(.)/@lvmc:type = 'retmap' ]"
mode="preproc:depgen" priority="5">
<!-- from the DSL -->
<preproc:sym-ref name="{@from}" lax="true" />
</template>
<template match="lvm:map[ @from ]" mode="preproc:depgen" priority="4">
<message terminate="yes"
select="'internal error: unhandled lvm:map: ', ." />
</template>
<template match="/*[ @lvmc:type='retmap' ]//lvm:map[ @from ]" mode="preproc:depgen" priority="6">
<preproc:sym-ref name="{@from}" lax="true" />
</template>
<!--
Triggers dependency generation on the source document, which contains far more
information than our symbol table
-->
<template match="preproc:sym[ @type='map' ]" mode="preproc:depgen" priority="6">
<variable name="name" select="substring-after( @name, ':map:' )" />
<variable name="pkg" as="element( lv:package )"
select="root(.)" />
<apply-templates mode="preproc:depgen"
select="$pkg/lvm:*[ @name=$name or @to=$name ]" />
</template>
<!-- FIXME: this is a cluster -->
<template match="preproc:sym[ @type='retmap' ]" mode="preproc:depgen" priority="6">
<variable name="from" as="element( preproc:from )*"
select="preproc:from" />
<if test="$from">
<variable name="src-name" as="xs:string"
select="substring-after( @name, ':retmap:' )" />
<variable name="name" as="xs:string+"
select="$from/@name" />
<variable name="pkg" as="element( lv:package )"
select="root(.)" />
<variable name="src-node" as="element()+"
select="$pkg/lvm:*[ @name = $src-name
or @to = $src-name ]" />
<if test="count( $src-node ) gt count( $from )">
<message terminate="yes"
select="'error: duplicate source identifier: ',
$src-name" />
</if>
<apply-templates mode="preproc:depgen"
select="$src-node" />
</if>
</template>
<!--
These guys have no dependencies; handle them to prevent depgen errors
-->
<template match="preproc:sym[ @type='map:head' or @type='map:tail' ]" mode="preproc:depgen" priority="2">
<!-- do nothing -->
</template>
<template match="preproc:sym[ @type='retmap:head' or @type='retmap:tail' ]" mode="preproc:depgen" priority="2">
<!-- do nothing -->
</template>
<!--
Generate a direct input mapping or, if a default exists for the field, use the
default if the input is an empty string
XXX: This is broken; $rater is not provided at the entry point, and
if it were, this needs to reference its symbol table.
-->
<template name="lvmc:gen-input-default">
<param name="rater" />
<param name="to" />
<!-- use one or the other; latter takes precedence -->
<param name="from" />
<param name="from-str" />
<variable name="default">
<if test="$rater">
<value-of select="concat( '''',
lvmc:escape-string(
$rater/lv:param[ @name=$to ]/@default ),
'''' )" />
</if>
</variable>
<variable name="from-var">
<choose>
<when test="$from-str">
<value-of select="$from-str" />
</when>
<otherwise>
<text>input['</text>
<value-of select="$from" />
<text>']</text>
</otherwise>
</choose>
</variable>
<choose>
<when test="$default and not( $default = '' )">
<text>set_defaults(</text>
<value-of select="$from-var" />
<text>,'</text>
<value-of select="$default" />
<text>')</text>
</when>
<otherwise>
<value-of select="$from-var" />
</otherwise>
</choose>
</template>
<!--
Maps a static value to the output
-->
<template match="lvm:map[ @value ]" mode="lvmc:compile" priority="5">
<param name="type" />
<preproc:fragment id=":{$type}:{@to}">
<text>output['</text>
<value-of select="@to" />
<text>']='</text>
<value-of select="normalize-space( @value )" />
<text>';</text>
<!-- newline to make output reading and debugging easier -->
<text>&#10;</text>
</preproc:fragment>
</template>
<template match="lvm:map[ @value ]" mode="preproc:symtable" priority="5">
<call-template name="lvmc:mapsym">
<with-param name="name" select="@to" />
</call-template>
</template>
<template match="lvm:map[*]" mode="lvmc:compile" priority="5">
<param name="rater" />
<param name="type" />
<preproc:fragment id=":{$type}:{@to}">
<text>output['</text>
<value-of select="@to" />
<text>']=</text>
<apply-templates select="./lvm:*" mode="lvmc:compile">
<with-param name="rater" select="$rater" />
</apply-templates>
<text>;</text>
<!-- newline to make output reading and debugging easier -->
<text>&#10;</text>
</preproc:fragment>
</template>
<template match="lvm:map[ * ]" mode="preproc:symtable" priority="5">
<param name="to" select="@to" />
<call-template name="lvmc:mapsym">
<with-param name="name" select="$to" />
<with-param name="from">
<for-each select=".//lvm:from">
<preproc:from name="{@name}" />
</for-each>
</with-param>
</call-template>
</template>
<template match="/*[ @lvmc:type='retmap' ]/lvm:map[ * ]" mode="preproc:symtable" priority="6">
<variable name="to" select="@to" />
<call-template name="lvmc:mapsym">
<with-param name="name" select="$to" />
<with-param name="from">
<for-each select=".//lvm:from">
<preproc:from name="{@name}" />
</for-each>
</with-param>
</call-template>
</template>
<template match="lvm:map[ * ]" mode="preproc:depgen" priority="5">
<preproc:sym-ref name="{@to}" lax="true" />
</template>
<template match="lvm:map[ *
and root(.)/@lvmc:type = 'retmap' ]"
mode="preproc:depgen" priority="6">
<for-each select=".//lvm:from">
<preproc:sym-ref name="{@name}" lax="true" />
</for-each>
</template>
<template match="lvm:const" mode="lvmc:compile" priority="5">
<text>'</text>
<value-of select="@value" />
<text>'</text>
</template>
<template match="lvm:map//lvm:set[@each]" mode="lvmc:compile" priority="5">
<text>(function(){</text>
<text>var ret=[];</text>
<text>var len=input['</text>
<value-of select="@each" />
<text>'].length;</text>
<text>for(var _i=0;_i&lt;len;_i++){</text>
<text>var </text>
<value-of select="@index" />
<text>=_i;</text>
<text>ret[_i]=</text>
<apply-templates select="./lvm:*" mode="lvmc:compile" />
<text>;</text>
<text>}</text>
<text>return ret;</text>
<text>})()</text>
</template>
<template match="lvm:map//lvm:set[@ignore-empty='true']" mode="lvmc:compile" priority="3">
<text>(function(){</text>
<text>var ret=[]; var tmp;</text>
<for-each select="./lvm:*">
<text>tmp=</text>
<apply-templates select="." mode="lvmc:compile" />
<text>;</text>
<text>if(tmp&amp;&amp;tmp!=='0')ret.push(tmp);</text>
</for-each>
<text>return ret;</text>
<text>})()</text>
</template>
<template match="lvm:map//lvm:set" mode="lvmc:compile" priority="2">
<text>[</text>
<for-each select="./lvm:*">
<if test="position() > 1">
<text>,</text>
</if>
<apply-templates select="." mode="lvmc:compile" />
</for-each>
<text>]</text>
</template>
<template match="lvm:map//lvm:static" mode="lvmc:compile" priority="5">
<text>'</text>
<value-of select="@value" />
<text>'</text>
</template>
<template match="lvm:map//lvm:from[*]" mode="lvmc:compile" priority="5">
<param name="rater" />
<variable name="to" select="ancestor::lvm:map/@to" />
<variable name="nested" as="xs:boolean"
select="exists( ancestor::lvm:from )" />
<!-- oval = orig val -->
<text>(function(oval){</text>
<text>var val = ( (oval||'').length ) ? oval : [oval]; </text>
<text>var ret = []; </text>
<if test="not( $nested )">
<text>var curindex;</text>
</if>
<text>for ( var i = 0, l = val.length; i&lt;l; i++ ){</text>
<if test="not( $nested )">
<text>curindex = i;</text>
</if>
<!-- note that we're casting the value to a string; this is important,
since case comparisons are strict (===) -->
<text>switch(''+val[i]){</text>
<apply-templates mode="lvmc:compile" />
<if test="not( lvm:default )">
<text>default: ret.push(</text>
<choose>
<!-- give precedence to explicit default -->
<when test="@default">
<sequence select="concat( '''',
lvmc:escape-string( @default ),
'''' )" />
</when>
<!-- otherwise, generate one -->
<otherwise>
<call-template name="lvmc:gen-input-default">
<with-param name="rater" select="$rater" />
<with-param name="to" select="$to" />
<with-param name="from-str">
<text>''+val[i]</text>
</with-param>
</call-template>
</otherwise>
</choose>
<text>);</text>
</if>
<text>}</text>
<text>}</text>
<choose>
<when test="@scalar='true'">
<text>return ret[0]; </text>
</when>
<otherwise>
<text>return ret; </text>
</otherwise>
</choose>
<text>})(input['</text>
<value-of select="@name" />
<text>']</text>
<if test="$nested">
<text>[curindex]</text>
</if>
<text>)</text>
</template>
<template match="lvm:map//lvm:from" mode="lvmc:compile" priority="2">
<variable name="nested" as="xs:boolean"
select="exists( ancestor::lvm:from )" />
<text>input['</text>
<value-of select="@name" />
<text>']</text>
<choose>
<when test="@index">
<text>[</text>
<value-of select="@index" />
<text>]</text>
</when>
<when test="$nested">
<text>[curindex]</text>
</when>
</choose>
</template>
<template match="lvm:from/lvm:default"
mode="lvmc:compile" priority="5">
<sequence select="concat(
'default:ret.push(',
string-join(
lvmc:concat-compile( element(), () ),
'' ),
');' )" />
</template>
<template match="lvm:map//lvm:value"
mode="lvmc:compile" priority="5">
<sequence select="concat( '''', text(), '''' )" />
</template>
<template match="lvm:map//lvm:from/lvm:translate" mode="lvmc:compile" priority="5">
<text>case '</text>
<value-of select="@key" />
<text>':</text>
<apply-templates select="." mode="lvmc:compile-translate" />
<text> break;</text>
</template>
<template match="lvm:translate[ element() ]"
mode="lvmc:compile-translate" priority="5">
<sequence select="concat(
'ret.push(',
string-join(
lvmc:concat-compile( element(), @empty ),
'' ),
');' )" />
</template>
<function name="lvmc:concat-compile" as="xs:string+">
<param name="children" as="element()+" />
<param name="default" as="xs:string?" />
<text>(function(){</text>
<!-- end result should compile into a (dynamic) string -->
<text>var result=</text>
<for-each select="$children">
<if test="position() > 1">
<text> + </text>
</if>
<apply-templates mode="lvmc:compile"
select="." />
</for-each>
<text>;</text>
<text>return (result === "") ? '</text>
<sequence select="lvmc:escape-string( $default )" />
<text>' : result;</text>
<text>})()</text>
</function>
<function name="lvmc:escape-string" as="xs:string">
<param name="str" as="xs:string?" />
<sequence select="replace( $str, '''', '\\''' )" />
</function>
<template match="lvm:translate"
mode="lvmc:compile-translate" priority="1">
<text>ret.push('</text>
<value-of select="normalize-space( @value )" />
<text>');</text>
</template>
<template match="text()|comment()" mode="lvmc:compile" priority="1">
<!-- strip all text and comments -->
</template>
<template match="*" mode="lvmc:compile" priority="1">
<message terminate="yes">
<text>fatal: invalid map: unexpected node </text>
<apply-templates select="." mode="lvmc:pathout" />
</message>
</template>
<template match="lvm:import|lvm:class" mode="lvmc:compile" priority="2">
<!-- ignore -->
</template>
<!-- import symbols -->
<template match="lvm:import" mode="preproc:symtable" priority="5">
<!-- original root passed to sym-discover -->
<param name="orig-root" />
<!-- perform symbol import -->
<call-template name="preproc:symimport">
<with-param name="orig-root" select="$orig-root" />
<with-param name="package" select="@path" />
<with-param name="export" select="'true'" />
</call-template>
</template>
<template match="*" mode="lvmc:pathout">
<if test="parent::*">
<apply-templates select="parent::*" mode="lvmc:pathout" />
</if>
<text>/</text>
<value-of select="name()" />
</template>
<!--
Outputs a simple pass-through map that may be used if no map is present
This simply calls the callback with the given input after creating a new
object with it as the prototype, ensuring that altered data does not impact
the original data.
-->
<template name="lvmc:dummy-map">
<param name="name" select="'map'" />
<text>function </text>
<value-of select="$name" />
<text>( input, callback ) { </text>
<!-- protect input against potential mutilation from classifier -->
<text>var prot = function() {}; </text>
<text>prot.prototype = input; </text>
<text>callback( new prot() );</text>
<text> }</text>
</template>
<!--
Validates map between program and the rater, checking for errors that would
cause significant problems.
-->
<template match="lvm:program-map" mode="lvmc:validate-rater">
<param name="rater" />
<variable name="map" select="." />
<!--
Get a list of all fields that have not been mapped
-->
<variable name="nomap" select="
$rater/lv:param[
not(
@name=$map//lvm:pass/@name
or @name=$map//lvm:map/@to
)
]
" />
<!-- required and unmapped -->
<variable name="req-nomap" select="
$nomap[ not( @default ) or @default='' ]
" />
<!-- warning on non-mapped, but not required -->
<for-each select="$nomap[ @default ]">
<message>
<text>! [map warning] unmapped optional field: </text>
<value-of select="@name" />
</message>
</for-each>
<!-- error on required non-mapped -->
<for-each select="$req-nomap">
<message>
<text>!!! [map error] unmapped required field: </text>
<value-of select="@name" />
</message>
</for-each>
<if test="$unused-param-check = 'true'">
<variable name="unknown" select="
//lvm:pass[
not( @name=$rater/lv:param/@name )
]
|
//lvm:map[
not( @to=$rater/lv:param/@name )
]
|
//lvm:class[
not( @name=$rater/lv:classify/@as )
]
" />
<!-- error on unknown -->
<for-each select="$unknown">
<message>
<text>!!! [map error] unknown/unused destination identifier: </text>
<value-of select="@name|@to" />
</message>
</for-each>
<if test="count( $unknown )">
<lvmc:terminate />
</if>
</if>
<!-- fail. -->
<if test="count( $req-nomap )">
<lvmc:terminate />
</if>
</template>
<template match="lvm:program-map" mode="lvmc:validate-ui">
<param name="ui" />
<!-- get a list of unknown source mappings -->
<!-- TODO: this is a mess -->
<variable name="unknown-pre" select="
.//lvm:pass[
not( @name=($ui//lvp:question/@id|$ui//lvp:external/@id) )
and not( @name=$ui//lvp:calc/@id )
]
|
.//lvm:map[
@from
and not( @from=($ui//lvp:question/@id|$ui//lvp:external/@id) )
and not( @from=$ui//lvp:calc/@id )
]
|
.//lvm:from[
not( @name=($ui//lvp:question/@id|$ui//lvp:external/@id) )
and not( @name=$ui//lvp:calc/@id )
]
|
.//lvm:set[
@each
and not( @each=($ui//lvp:question/@id|$ui//lvp:external/@id) )
and not( @each=$ui//lvp:calc/@id )
]
" />
<variable name="unknown"
select="$unknown-pre[ not( @novalidate='true' ) ]" />
<!-- error on unknown -->
<for-each select="$unknown">
<message>
<text>!!! [map error] unknown source field: </text>
<value-of select="@name|@from" />
</message>
</for-each>
<if test="count( $unknown )">
<lvmc:terminate />
</if>
</template>
<!--
Outputs source and dest mappings in a common, easily-referenced format useful
for parsing
-->
<template match="lvm:program-map" mode="lvmc:source-dest-map" priority="5">
<lvmc:map>
<apply-templates select="./lvm:*" mode="lvmc:source-dest-map" />
</lvmc:map>
</template>
<template match="lvm:pass" mode="lvmc:source-dest-map" priority="5">
<lvmc:map from="{@name}" to="{@name}" elig="{@affects-eligibility}" />
</template>
<template match="lvm:map[ @from ]" mode="lvmc:source-dest-map" priority="5">
<lvmc:map from="{@from}" to="{@to}" elig="{@affects-eligibility}" />
</template>
<template match="lvm:map/lvm:from" mode="lvmc:source-dest-map" priority="5">
<lvmc:map from="{@name}" to="{ancestor::lvm:map/@to}"
elig="{@affects-eligibility}" />
</template>
<template match="lvm:map//lvm:set/lvm:from" mode="lvmc:source-dest-map" priority="4">
<!-- not included; not a one-to-one mapping -->
</template>
<template match="lvm:map[*]" mode="lvmc:source-dest-map" priority="5">
<apply-templates select=".//lvm:*" mode="lvmc:source-dest-map" />
</template>
<template match="lvm:map//lvm:set" mode="lvmc:source-dest-map" priority="2">
<!-- do nothing -->
</template>
<template match="lvm:map//lvm:static" mode="lvmc:source-dest-map" priority="2">
<!-- do nothing -->
</template>
<template match="lvm:map//lvm:value" mode="lvmc:source-dest-map" priority="2">
<!-- do nothing -->
</template>
<template match="lvm:map//lvm:translate" mode="lvmc:source-dest-map" priority="2">
<!-- do nothing -->
</template>
<template match="lvm:map[ @value ]" mode="lvmc:source-dest-map" priority="2">
<!-- no source -->
</template>
<template match="lvm:const" mode="lvmc:source-dest-map" priority="2">
<!-- no source -->
</template>
<template match="lvm:class" mode="lvmc:source-dest-map" priority="2">
<!-- not applicable -->
</template>
<template match="*" mode="lvmc:source-dest-map" priority="1">
<message terminate="yes">
<text>Unknown node: </text>
<value-of select="name()" />
</message>
</template>
</stylesheet>

View File

@ -0,0 +1,771 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Validates a document for correctness in a manner that is beyond XSD.
Schematron is not used for this due to certain complexieis. Furthermore, we
already have the data in a package structure that is easy to use and query
against.
Validations that can be expressed in the XSD will not be included here, unless
there is significant overlap that would make the XSD representation
pointlessly incomplete.
FIXME: Needs aggresive refactoring after introduction of symbol table, for
both performance and maintinance.
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:ext="http://www.lovullo.com/ext"
xmlns:c="http://www.lovullo.com/calc"
xmlns:lvv="http://www.lovullo.com/rater/validate"
xmlns:sym="http://www.lovullo.com/rater/symbol-map"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<xsl:import href="validate/domain.xsl" />
<xsl:include href="validate/param.xsl" />
<xsl:param name="prohibit-validation" select="'false'" />
<!-- FOR PERFORMANCE: constants may be used for large tables of data -->
<xsl:template match="lv:const" mode="lvv:validate" priority="9">
<!-- nothing to be done; constants are merely data declarations -->
</xsl:template>
<!--
Perform validations on a given rater/package
Validations will be returned as an RTF (result tree fragment), which can be
converted to a NodeSet using exsl:node-set(). All errors are returned in the
following format:
<lvv:error desc="Description of error type>Error details</lvv:error>
@return error RTF
-->
<xsl:template match="lv:package" mode="lvv:validate" priority="9">
<xsl:param name="symbol-map" />
<xsl:choose>
<xsl:when test="$prohibit-validation = 'true'">
<xsl:message>
<xsl:text>[validate] prohibited; skipping </xsl:text>
<xsl:value-of select="local-name()" />
<xsl:text> </xsl:text>
<xsl:value-of select="@name" />
<xsl:text>...</xsl:text>
</xsl:message>
</xsl:when>
<xsl:otherwise>
<xsl:message>
<xsl:text>[validate] validating </xsl:text>
<xsl:value-of select="local-name()" />
<xsl:text> </xsl:text>
<xsl:value-of select="@name" />
<xsl:text>...</xsl:text>
</xsl:message>
<!-- validate -->
<xsl:apply-templates mode="lvv:validate" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="preproc:*" mode="lvv:validate" priority="9">
<!-- well that would just be silly -->
</xsl:template>
<xsl:template match="lv:package[ not( @program='true' ) ]/lv:yield" mode="lvv:validate" priority="5">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'lv:yield cannot appear within a non-program package'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template match="*" mode="lvv:validate" priority="1">
<xsl:apply-templates mode="lvv:validate" />
</xsl:template>
<xsl:template match="lv:template" mode="lvv:validate" priority="9">
<!-- do not validate templates; we'll only validate expansions -->
</xsl:template>
<xsl:template name="lvv:symbol-chk">
<xsl:param name="root" />
<xsl:param name="symbol-map" />
<!-- build a symbol list of all used and default symbols -->
<xsl:variable name="symlist">
<!-- @sym attributes -->
<xsl:for-each select="$root//lv:*[@sym]">
<lvv:sym value="{@sym}" />
</xsl:for-each>
<!-- defaults from mapping document -->
<xsl:for-each select="$symbol-map/sym:symbol[ not( ./* ) ]">
<lvv:sym value="{.}" />
</xsl:for-each>
</xsl:variable>
<!-- error for each restricted node -->
<xsl:for-each select="
$root//lv:*[
@sym = $symbol-map/sym:reserved/sym:reserve/@sym
]
">
<xsl:variable name="symbol" select="@sym" />
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Symbol is reserved and cannot be used'" />
<xsl:with-param name="refnode" select="$root//lv:*[@sym=$symbol]" />
<xsl:with-param name="content" select="$symbol" />
</xsl:call-template>
</xsl:for-each>
<!-- error for each duplicate node -->
<xsl:for-each select="
$symlist/*[
@value = following-sibling::*/@value
]
">
<xsl:variable name="symbol" select="@value" />
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Symbol is not unique'" />
<xsl:with-param name="refnode" select="$root//lv:*[@sym=$symbol]" />
<xsl:with-param name="content" select="$symbol" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template match="c:apply[@name]" mode="lvv:validate" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:variable name="self" select="." />
<xsl:variable name="fsym" select="
root(.)/preproc:symtable/preproc:sym[
@type='func'
and @name=$name
]
" />
<!-- ensure that a function is being applied -->
<xsl:if test="not( $fsym )">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Applying non-function'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content" select="@name" />
</xsl:call-template>
</xsl:if>
<!-- check that all required arguments are provided -->
<xsl:for-each select="
$fsym/preproc:sym-ref[
concat( ':', $name, ':', @name ) = ancestor::preproc:symtable/preproc:sym[
@type='lparam'
and not( @default )
]
]
">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Missing required argument'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:value-of select="@name" />
<xsl:text> for application of </xsl:text>
<xsl:value-of select="$name" />
<xsl:text>()</xsl:text>
</xsl:with-param>
</xsl:call-template>
</xsl:for-each>
<xsl:apply-templates mode="lvv:validate" />
</xsl:template>
<xsl:template match="c:*[ @index ]/c:index" mode="lvv:validate" priority="9">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Ambiguous index specification'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content" select="../@name" />
</xsl:call-template>
<xsl:apply-templates mode="lvv:validate" />
</xsl:template>
<!--
Validate that match @on's exist
-->
<xsl:template match="lv:classify[ @as ]//lv:match" mode="lvv:validate" priority="9">
<xsl:if test="not( @on=root(.)/preproc:symtable/preproc:sym[ @type ]/@name )">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Unknown match @on'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:text>`</xsl:text>
<xsl:value-of select="@on" />
<xsl:text>' is unknown for classification </xsl:text>
<xsl:variable name="class"
select="ancestor::lv:classify" />
<xsl:value-of select="if ( $class/@preproc:generated-from ) then
$class/@preproc:generated-from
else
$class/@as" />
</xsl:with-param>
</xsl:call-template>
</xsl:if>
<xsl:apply-templates select="." mode="lvv:validate-match" />
</xsl:template>
<!--
Validate that non-numeric value matches actually exist and are constants
-->
<xsl:template match="lv:match[@value]" mode="lvv:validate-match" priority="5">
<xsl:if test="
not( number( @value ) = @value )
and not(
@value=root(.)/preproc:symtable/preproc:sym[
@type='const'
]/@name
)
">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Unknown match value'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:text>`</xsl:text>
<xsl:value-of select="@value" />
<xsl:text>' is unknown for classification </xsl:text>
<xsl:variable name="class"
select="ancestor::lv:classify" />
<xsl:value-of select="if ( $class/@preproc:generated-from ) then
$class/@preproc:generated-from
else
$class/@as" />
</xsl:with-param>
</xsl:call-template>
</xsl:if>
<xsl:apply-templates mode="lvv:validate-match" />
</xsl:template>
<xsl:template match="lv:match" mode="lvv:validate-match" priority="2">
<xsl:apply-templates mode="lvv:validate-match" />
</xsl:template>
<!--
Classification match assumptions must operate only on other classifiers and
must assume values that the referenced classifier actually matches on
-->
<xsl:template match="lv:match/lv:assuming" mode="lvv:validate-match" priority="5">
<xsl:variable name="on" select="../@on" />
<xsl:variable name="ref" select="root(.)//lv:classify[ @yields=$on ]" />
<!-- assumptions must only operate on variables mentioned in the referenced
classification -->
<xsl:for-each select="
.//lv:that[
not( @name=$ref//lv:match/@on )
]
">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Invalid classification assumption'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:value-of select="@name" />
<xsl:text> is not used to classify </xsl:text>
<xsl:value-of select="$on" />
</xsl:with-param>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template match="c:*" mode="lvv:validate-match" priority="2">
<xsl:apply-templates select="." mode="lvv:validate" />
</xsl:template>
<xsl:template match="*" mode="lvv:validate-match" priority="1">
<!-- do nothing -->
</xsl:template>
<xsl:template match="c:value" mode="lvv:validate" priority="5">
<!-- do nothing; just prevent the below validation from occurring -->
<xsl:apply-templates mode="lvv:validate" />
</xsl:template>
<xsl:template match="c:let" mode="lvv:validate" priority="5">
<!-- do not validate this node itself -->
<xsl:apply-templates mode="lvv:validate" />
</xsl:template>
<xsl:template match="c:*[@name or @of]" mode="lvv:validate" priority="2">
<xsl:variable name="name">
<xsl:choose>
<xsl:when test="@of">
<xsl:value-of select="@of" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@name" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- XXX: have to maintain this list! -->
<xsl:variable name="nodes" select="
root(.)//lv:*[
@name=$name
or @yields=$name
or @as=$name
]
, root(.)//c:*[
@generates=$name
]
, root(.)//c:values/c:value[ @name=$name ]
" />
<!-- locate function params/let vars -->
<xsl:variable name="fname" select="
ancestor::lv:function[
lv:param[
@name=$name
]
]/@name
|ancestor::c:apply[
c:arg[
@name=$name
]
]/@name
|ancestor::c:let[
c:values/c:value[
@name=$name
]
]/@name
" />
<!-- if this name references a function parameter, then it takes
precedence (note that this consequently means that it masks any other
names that may be globally defined) -->
<xsl:variable name="sym" select="
if ( $fname ) then
root(.)/preproc:symtable/preproc:sym[
@name=concat( ':', $fname, ':', $name )
]
else
root(.)/preproc:symtable/preproc:sym[
@name=$name
]
" />
<xsl:variable name="type" select="$sym/@dtype" />
<!-- all calculations must make use of numeric types -->
<xsl:if test="
not(
( $type = 'integer' )
or ( $type = 'float' )
or ( $type = 'boolean' )
or ( ancestor::c:*[ @of and @index=$name ] )
)
">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Non-numeric type in calculation'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:value-of select="$name" />
<xsl:text> is </xsl:text>
<xsl:choose>
<xsl:when test="not( $type ) or $type = ''">
<xsl:text>undefined</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>of type '</xsl:text>
<xsl:value-of select="$type" />
<xsl:text>'</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
<xsl:variable name="is-set" select="$sym/@dim" />
<xsl:choose>
<!-- furthermore, if @of is provided, then it must be a set -->
<xsl:when test="@of">
<xsl:if test="$is-set = 0">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'@of must reference a set'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content" select="$name" />
</xsl:call-template>
</xsl:if>
</xsl:when>
<!-- otherwise, an index is required to reference an item in the set unless
the value is being passed as an argument of the same set type, or is
the return value of a function (we assume it to be a return value if it
is in a tail position) -->
<!-- TODO: re-add argument param check for sets -->
<!-- TODO: c:values/c:value/@set check; should also only be allowed in tail -->
<xsl:otherwise>
<xsl:choose>
<xsl:when test="
( ( not( @index ) or ( @index = '' ) ) and not( ./c:index ) )
and not( ancestor::lv:match )
and (
$is-set != '0'
and not(
ancestor::c:arg
or (
ancestor::lv:function
and not( ./* )
)
or parent::c:length-of
or ancestor::c:value[ @set ]
)
)
and not( parent::c:length-of )
">
<xsl:choose>
<xsl:when test="$sym/@dim = '?'">
<xsl:message>
<xsl:text>internal warning: unresolved param </xsl:text>
<xsl:text>dimension: `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc">
<xsl:text>Unexpected vector/matrix reference</xsl:text>
</xsl:with-param>
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:value-of select="@name" />
<xsl:text> (did you forget @index?)</xsl:text>
</xsl:with-param>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<!-- matrices require two indexes, unless being used as an argument to a
function or as part of a dot product (it is also acceptable as the
return value of a function, which must be in a tail position) -->
<xsl:when test="
( number( $is-set ) gt 1 )
and not( ./c:index[ $is-set ] )
and not( ancestor::c:arg )
and not( ancestor::c:let )
and not( ancestor::c:product[ @dot ] )
and not( ancestor::c:cons )
and not( ancestor::c:cons )
and not(
ancestor::lv:function
and not( ./* )
)
">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Invalid matrix specification'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:value-of select="@name" />
<xsl:text> requires </xsl:text>
<xsl:value-of select="$is-set" />
<xsl:text> indexes (use c:index) unless being</xsl:text>
<xsl:text> passed as a function argument</xsl:text>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<!-- ensure that we do not have too many indexes -->
<xsl:when test="( number( $is-set ) gt 0 ) and ./c:index[ number( $is-set ) + 1 ]">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Invalid vector/matrix specification'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:value-of select="@name" />
<xsl:text> may only have </xsl:text>
<xsl:value-of select="$is-set" />
<xsl:text> index(s)</xsl:text>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<!-- if we have an index, but we're not dealing with a set, then that is
also an issue -->
<xsl:when test="@index and ( $is-set = '' )">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Using index with non-set'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content" select="@name" />
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
<!-- index references should be defined -->
<xsl:if test="
@index and not( local-name() = 'sum' or local-name() = 'product' )
">
<xsl:variable name="index" select="@index" />
<!-- search for index definition -->
<!-- XXX: This also requires knowledge of match and require-param -->
<xsl:if test="
not(
ancestor::c:*[ @index = $index ]
or ( root(.)//lv:*[ @name = $index ]
and (
local-name() != 'match'
and local-name() != 'require-param'
)
)
)
">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Undefined index'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content" select="@index" />
</xsl:call-template>
</xsl:if>
</xsl:if>
<!-- recursively validate any nested calculations -->
<xsl:apply-templates mode="lvv:validate" />
</xsl:template>
<xsl:template match="c:apply/c:arg[@name]" mode="lvv:validate" priority="5">
<!-- merely validate its existence -->
<xsl:variable name="fname" select="parent::c:apply/@name" />
<xsl:if test="not(
concat( ':', $fname, ':', @name ) = root(.)/preproc:symtable/preproc:sym[
@type='lparam'
and @parent=$fname
]/@name
)">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Unknown argument'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:text>Argument `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>' is unknown for function `</xsl:text>
<xsl:value-of select="$fname" />
<xsl:text>'</xsl:text>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
<!-- recursively validate any nested calculations -->
<xsl:apply-templates mode="lvv:validate" />
</xsl:template>
<xsl:template match="c:product[@dot]" mode="lvv:validate" priority="5">
<!-- TODO -->
</xsl:template>
<xsl:template mode="lvv:validate" priority="2" match="
lv:union/lv:typedef[
./lv:*[1]/@type != preceding-sibling::lv:typedef[1]/lv:*[1]/@type
]
">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Union type mismatch'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:text>Expected type '</xsl:text>
<xsl:value-of select="preceding-sibling::lv:typedef[1]/lv:*[1]/@type" />
<xsl:text>' for </xsl:text>
<xsl:value-of select="@name" />
<xsl:text>, but found '</xsl:text>
<xsl:value-of select="./lv:*[1]/@type" />
<xsl:text>'</xsl:text>
</xsl:with-param>
</xsl:call-template>
<xsl:apply-templates mode="lvv:validate" />
</xsl:template>
<!--
Checks for use of undefined classifications
-->
<xsl:template mode="lvv:validate" priority="2"
match="lv:rate/lv:class[
not( concat( ':class:', @ref ) = root(.)/preproc:symtable/preproc:sym[ @type='class' ]/@name )
]">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Unknown classification'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:text>unknown classification '</xsl:text>
<xsl:value-of select="@ref" />
<xsl:text>' referenced by </xsl:text>
<xsl:value-of select="ancestor::lv:rate/@yields" />
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<!--
All rate blocks must have non-empty yields
This is an awkward error, because it's not possible to identify the
rate block by name...there is none.
-->
<xsl:template mode="lvv:validate" priority="9"
match="lv:rate[ not( @yields ) or @yields = '' ]">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Unidentifiable rate block'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:text>missing or empty @yields; see document dump</xsl:text>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<!--
Throws an error if a generator is requested using unsupported data
Specifically, a generator is intended to generate a set from an expression
while looping over another set. If we're not looping, then we're not
generating a set. Furthermore, if a child expression was not provided, then
the set produced would be equivalent to @of, which is useless.
-->
<xsl:template mode="lvv:validate"
match="c:*[ @generates and not( @of and ./c:* ) ]" priority="9">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Invalid generator'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:text>Cannot create generator '</xsl:text>
<xsl:value-of select="@generates" />
<xsl:text>'; generating expressions must contain both @of </xsl:text>
<xsl:text>and a sub-expression.</xsl:text>
</xsl:with-param>
</xsl:call-template>
<xsl:apply-templates mode="lvv:validate" />
</xsl:template>
<!--
Since @generates creates a new variable that can be referenced, it needs
documentation! Refuse to compile if documentation is not provided. Yeah, we're
assholes.
-->
<xsl:template mode="lvv:validate"
match="c:*[ @generates and not( @desc ) ]" priority="9">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'No generator description'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:text>@desc required when creating generator </xsl:text>
<xsl:value-of select="@generates" />
</xsl:with-param>
</xsl:call-template>
<xsl:apply-templates mode="lvv:validate" />
</xsl:template>
<xsl:template match="ext:*" mode="lvv:get-path">
<!-- omit from path output -->
</xsl:template>
<xsl:template match="*" mode="lvv:get-path">
<xsl:apply-templates select="parent::*" mode="lvv:get-path" />
<xsl:text>/</xsl:text>
<xsl:value-of select="name()" />
<!-- certain nodes may support path descriptions to aid in determining which
node is being referenced -->
<xsl:variable name="desc">
<xsl:apply-templates select="." mode="lvv:get-path-desc" />
</xsl:variable>
<xsl:if test="$desc != ''">
<xsl:text>[</xsl:text>
<xsl:value-of select="$desc" />
<xsl:text>]</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="lv:rate[ @yields ]" mode="lvv:get-path-desc">
<xsl:text>@yields=</xsl:text>
<xsl:value-of select="@yields" />
</xsl:template>
<xsl:template match="c:*[ @name ]" mode="lvv:get-path-desc" priority="5">
<xsl:text>@name=</xsl:text>
<xsl:value-of select="@name" />
</xsl:template>
<xsl:template match="c:*[ @label ]" mode="lvv:get-path-desc" priority="1">
<xsl:text>@label=</xsl:text>
<xsl:value-of select="@label" />
</xsl:template>
<xsl:template match="*" mode="lvv:get-path-desc">
<!-- no desc by default -->
</xsl:template>
<xsl:template name="lvv:error">
<xsl:param name="desc" />
<xsl:param name="refnode" />
<xsl:param name="content" />
<xsl:variable name="path">
<xsl:if test="$refnode">
<xsl:apply-templates select="$refnode" mode="lvv:get-path" />
</xsl:if>
</xsl:variable>
<lvv:error desc="{$desc}" path="{$path}">
<xsl:value-of select="$content" />
</lvv:error>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,193 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Domain validations
TODO: For core domains, validate src package path as well. (Right now,
param types are polluting, and so this is not a problem.)
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:lvv="http://www.lovullo.com/rater/validate"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<!--
Assert that VALUE falls within the provided domain
SYM-DOMAIN should be a symbol resolving to the domain definition.
-->
<xsl:template name="lvv:domain-check">
<xsl:param name="value" />
<xsl:param name="sym-domain" />
<xsl:if test="not( $sym-domain )">
<xsl:message terminate="yes">
<xsl:text>internal error: no domain symbol provided; </xsl:text>
<xsl:text>caller: </xsl:text>
<xsl:copy-of select="." />
</xsl:message>
</xsl:if>
<!-- generate node to simplify xpath expressions -->
<xsl:variable name="sym-validate">
<lvv:chk value="{$value}">
<xsl:copy-of select="$sym-domain" />
</lvv:chk>
</xsl:variable>
<xsl:apply-templates mode="lvv:domain-check"
select="$sym-validate/lvv:chk">
<xsl:with-param name="self-pkg" select="
$sym-domain/ancestor::lv:package" />
</xsl:apply-templates>
</xsl:template>
<!--
Core type checks
- Integers must simply match when rounded;
- Floats must be any type of number; and
- Booleans may be only 1 or 0.
-->
<xsl:template match="
lvv:chk[
preproc:sym/@type = 'type'
and (
(
preproc:sym/@name = 'integer'
and not(
@value = floor( @value )
)
)
or (
preproc:sym/@name = 'float'
and not(
@value = number( @value )
)
)
or (
preproc:sym/@name = 'boolean'
and not(
number( @value ) = 0
or number( @value ) = 1
)
)
)
]"
mode="lvv:domain-check" priority="5">
<xsl:call-template name="lvv:domain-fail" />
</xsl:template>
<!--
Domain assertions on user-defined types
-->
<xsl:template match="
lvv:chk[
preproc:sym/@type='type'
and not (
preproc:sym/@name = 'integer'
or preproc:sym/@name = 'float'
or preproc:sym/@name = 'boolean'
)
]
"
mode="lvv:domain-check" priority="5">
<xsl:param name="self-pkg" />
<xsl:variable name="chkval" select="@value" />
<xsl:variable name="domain">
<xsl:call-template name="lvv:get-domain-by-sym">
<xsl:with-param name="sym" select="preproc:sym" />
<xsl:with-param name="self-pkg" select="$self-pkg" />
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$domain/lv:domain/lv:element[ @value = $chkval ]">
<lvv:ok type="domain-check" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="lvv:domain-fail" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
No validation failure
-->
<xsl:template match="lvv:chk"
mode="lvv:domain-check" priority="2">
<lvv:ok type="domain-check" />
</xsl:template>
<!--
We passed ourselves something unexpected
-->
<xsl:template match="*"
mode="lvv:domain-chk" priority="1">
<xsl:message terminate="yes">
<xsl:text>internal error: unexpected node for lvv:domain-chk: </xsl:text>
<xsl:copy-of select="." />
</xsl:message>
</xsl:template>
<!--
Mark validation as a failure, outputting the assertion
TODO: Once domains are used as the primary source instead of typedefs,
check to ensure that the symbol is an actual domain symbol.
-->
<xsl:template name="lvv:domain-fail">
<lvv:fail type="domain-check">
<xsl:copy-of select="." />
</lvv:fail>
</xsl:template>
<xsl:template name="lvv:get-domain-by-sym">
<xsl:param name="sym" />
<xsl:param name="self-pkg" select="ancestor::lv:package" />
<!-- package containing symbol -->
<xsl:variable name="pkg" select="
if ( $sym/@src and not( $sym/@src='' ) ) then
document( concat( $sym/@src, '.xmlo' ), $__entry-root )
/lv:package
else
$self-pkg
" />
<!-- attempt to locate domain of the given name -->
<xsl:variable name="domain" select="
$pkg/lv:domain[ @name = $sym/@name ]" />
<xsl:if test="not( $domain )">
<xsl:message terminate="yes">
<xsl:text>error: no domain found for </xsl:text>
<xsl:value-of select="$sym/@src" />
<xsl:text>/</xsl:text>
<xsl:value-of select="$sym/@name" />
</xsl:message>
</xsl:if>
<xsl:copy-of select="$domain" />
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Parameter validations
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:lvv="http://www.lovullo.com/rater/validate"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<!--
Param type must be known
TODO: Doesn't the symbol table lookup process handle this?
-->
<xsl:template match="
lv:param[
not(
@type=root(.)/preproc:symtable/preproc:sym[
@type
]/@name
)
]"
mode="lvv:validate" priority="5">
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'Unknown param type'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:text>'</xsl:text>
<xsl:value-of select="@type" />
<xsl:text>' is undefined for param </xsl:text>
<xsl:value-of select="@name" />
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<!--
Default must be within the domain of the param
Note that this template priority is less than the template that checks to
ensure that the param type exists in the first place.
-->
<xsl:template match="lv:param[ @default ]"
mode="lvv:validate" priority="4">
<xsl:variable name="type" select="@type" />
<!-- default must be within its domain -->
<xsl:variable name="result">
<xsl:call-template name="lvv:domain-check">
<xsl:with-param name="value" select="@default" />
<xsl:with-param name="sym-domain" select="
root(.)/preproc:symtable/preproc:sym[
@name = $type
]" />
</xsl:call-template>
</xsl:variable>
<xsl:if test="not( $result/lvv:ok )">
<xsl:variable name="fail" select="$result/lvv:fail/lvv:chk" />
<!-- if we didn't succeed, but we didn't fail, then we did something we
weren't supposed to -->
<xsl:if test="not( $fail )">
<xsl:message terminate="yes">
<xsl:text>internal error: in limbo processing param `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>' @default</xsl:text>
</xsl:message>
</xsl:if>
<xsl:call-template name="lvv:error">
<xsl:with-param name="desc" select="'param @default domain violation'" />
<xsl:with-param name="refnode" select="." />
<xsl:with-param name="content">
<xsl:text>param `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>' @default of `</xsl:text>
<xsl:value-of select="$fail/@value" />
<xsl:text>' is not within its domain of </xsl:text>
<xsl:value-of select="$fail/preproc:sym/@src" />
<xsl:text>/</xsl:text>
<xsl:value-of select="$fail/preproc:sym/@name" />
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>
<!--
Fallback for no validation issues
-->
<xsl:template match="lv:param" mode="lvv:validate" priority="2">
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,543 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Compiles rater XML into JavaScript
This stylesheet should be included by whatever is doing the processing and is
responsible for outputting the generated code in whatever manner is
appropriate (inline JS, a file, etc).
-->
<stylesheet version="2.0"
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:w="http://www.lovullo.com/rater/worksheet"
xmlns:_w="http://www.lovullo.com/rater/worksheet/_priv"
xmlns:c="http://www.lovullo.com/calc"
xmlns:lvmc="http://www.lovullo.com/rater/map/compiler"
xmlns:wc="http://www.lovullo.com/rater/worksheet/compiler"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:util="http://www.lovullo.com/util">
<variable name="wc:lc" select="'abcdefghijklmnopqrstuvwxyz'" />
<variable name="wc:uc" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<!-- lexemes to be converted to a human-readable format -->
<!-- TODO: move this into an external file so it may be easily configurable -->
<variable name="wc:hlex">
<!-- prefix to suffix -->
<wc:lex prefix="prem" to="Premium" />
<wc:lex prefix="rate" to="Rate" />
<wc:lex prefix="credit" to="Credit" />
<wc:lex prefix="surcharge" to="Surcharge" />
<wc:lex str="gl" to="GL" />
<wc:lex str="prop" to="Property" />
<wc:lex str="equip" to="Equipment" />
<wc:lex str="adjust" to="Adjustment" />
<wc:lex str="adj" to="Adjustment" />
<wc:lex str="ded" to="Deductible" />
<wc:lex str="dw" to="Dwelling" />
<wc:lex str="fam" to="Family" />
<wc:lex str="tiv" to="TIV" />
<!-- *Each is used for generators -->
<wc:lex str="each" to="" />
</variable>
<!-- we expect that the worksheet will have been preprocessed into the rater
document -->
<template match="w:worksheet" mode="w:compile" priority="10">
<param name="corder" />
<variable name="displays" as="element( w:display )*"
select="w:display" />
<variable name="package" as="element( lv:package )"
select="_w:load-package( @package, . )" />
<variable name="syms" as="element( preproc:sym )*"
select="_w:filter-needed-symbols(
_w:load-symbols( $package ),
$displays )" />
<lv:package name="{@name}"
lvmc:type="worksheet">
<!-- we provide one special symbol -->
<preproc:symtable>
<preproc:sym name="___worksheet"
type="worksheet" />
</preproc:symtable>
<!-- TODO -->
<preproc:sym-deps>
<preproc:sym-dep name="___worksheet" />
</preproc:sym-deps>
<copy-of select="node()" />
<preproc:fragments>
<preproc:fragment id="___worksheet">
<text>rater.worksheet = </text>
<call-template name="util:json">
<with-param name="obj">
<for-each select="$displays">
<sequence select="_w:compile-display( ., $syms )" />
</for-each>
<variable name="yield" as="element( lv:rate )?"
select="$package/lv:rate[ @yields = '___yield' ]" />
<!-- always include yield -->
<if test="$yield">
<util:value>
<call-template name="util:json">
<with-param name="id" select="'yield'" />
<with-param name="array">
<util:value>
<call-template name="util:json">
<with-param name="value" select="'Yields'" />
</call-template>
</util:value>
<util:value>
<apply-templates mode="wc:compile"
select="$yield/c:*" />
</util:value>
</with-param>
</call-template>
</util:value>
</if>
</with-param>
</call-template>
<text>;</text>
</preproc:fragment>
</preproc:fragments>
</lv:package>
</template>
<function name="_w:compile-display" as="element( util:value )">
<param name="display" as="element( w:display )" />
<param name="syms" as="element( preproc:sym )*" />
<variable name="name" as="xs:string"
select="$display/@name" />
<variable name="sym" as="element( preproc:sym )?"
select="$syms[ @name = $name ]" />
<!-- terminate on unknown references -->
<if test="empty( $sym )">
<message terminate="yes"
select="'Reference to unknown symbol:', $name" />
</if>
<util:value>
<call-template name="util:json">
<with-param name="id" select="$name" />
<with-param name="array">
<util:value>
<call-template name="util:json">
<with-param name="value">
<call-template name="wc:var-to-hstr">
<with-param name="var" select="$name" />
</call-template>
</with-param>
</call-template>
</util:value>
<util:value>
<choose>
<when test="$display/@collapse = 'true'">
<sequence select="''" />
</when>
<otherwise>
<variable name="rate-block" as="element( lv:rate )"
select="_w:get-src-node( $sym )" />
<apply-templates mode="wc:compile"
select="$rate-block/c:*">
<with-param name="display" select="$display"
tunnel="yes" />
</apply-templates>
</otherwise>
</choose>
</util:value>
<util:value>
<call-template name="util:json">
<with-param name="value">
<value-of select="$display/@always" />
</with-param>
</call-template>
</util:value>
</with-param>
</call-template>
</util:value>
</function>
<function name="_w:load-package" as="element( lv:package )">
<param name="path" as="xs:string" />
<param name="context" as="node()" />
<!-- TODO: function to provide xmlo extension -->
<variable name="package-uri" as="xs:anyURI"
select="resolve-uri(
concat( $path, '.xmlo' ),
base-uri( $context ) )" />
<if test="not( doc-available( $package-uri ) )">
<message terminate="yes"
select="concat( 'fatal: package ',
$path,
' not found' )" />
</if>
<sequence select="doc( $package-uri )/lv:package" />
</function>
<!-- TODO: some of this logic can be factored out into a common
library -->
<function name="_w:load-symbols" as="element( preproc:sym )+">
<param name="package" as="element( lv:package )" />
<sequence select="$package/preproc:symtable/preproc:sym" />
</function>
<function name="_w:filter-needed-symbols" as="element( preproc:sym )*">
<param name="syms" as="element( preproc:sym )*" />
<param name="displays" as="element( w:display )*" />
<sequence select="$syms[ @name = $displays/@name ]" />
</function>
<function name="_w:get-src-package" as="element( lv:package )">
<param name="sym" as="element( preproc:sym )" />
<variable name="sym-path" as="xs:string?"
select="$sym/@src" />
<variable name="context-uri" as="xs:anyURI"
select="base-uri( $sym )" />
<!-- TODO: function to provide xmlo extension -->
<variable name="src-uri" as="xs:anyURI"
select="if ( $sym-path and not( $sym-path = '' ) ) then
resolve-uri(
concat( $sym-path, '.xmlo' ),
$context-uri )
else
$context-uri" />
<if test="not( doc-available( $src-uri ) )">
<message terminate="yes"
select="concat( 'fatal: package ',
$sym-path,
' not found; required by symbol ',
$sym/@name )" />
</if>
<sequence select="doc( $src-uri )/lv:package" />
</function>
<function name="_w:get-src-node" as="element( lv:rate )">
<param name="sym" as="element( preproc:sym )" />
<variable name="package" as="element( lv:package )"
select="_w:get-src-package( $sym )" />
<variable name="rate-name" as="xs:string"
select="if ( $sym/@parent ) then
$sym/@parent
else
$sym/@name" />
<sequence select="$package/lv:rate[ @yields = $rate-name ]" />
</function>
<template match="c:sum[@of='_CMATCH_']" mode="wc:compile" priority="9">
<apply-templates select="./c:*" mode="wc:compile" />
</template>
<template match="c:sum[@of='_CMATCH_']/c:product[c:value-of[@name='_CMATCH_']]"
mode="wc:compile" priority="9">
<!-- ignore the product and continue with the 2nd node -->
<apply-templates select="./c:*[2]" mode="wc:compile" />
</template>
<template match="c:apply" priority="7" mode="wc:compile">
<param name="display" as="element( w:display )"
tunnel="yes" />
<choose>
<!-- do not expand -->
<when test="
not(
@name = $display/ancestor::w:worksheet
/w:expand-function/@name
or (
@name = $display/w:expand-function/@name
)
)
">
<call-template name="wc:compile-calc">
<with-param name="nochildren" select="true()" />
<with-param name="runtime" select="true()" />
</call-template>
</when>
<!-- expand -->
<otherwise>
<call-template name="wc:compile-calc" />
</otherwise>
</choose>
</template>
<template match="c:value-of[c:index]" mode="wc:compile" priority="5">
<call-template name="wc:compile-calc">
<with-param name="nochildren" select="true()" />
<with-param name="runtime" select="true()" />
</call-template>
</template>
<!-- we need to take into account constants that are compiled in place (so we
cannot determine their value by name at runtime) -->
<template match="c:value-of[ @name=//lv:const[ not( * ) ]/@name ]" mode="wc:compile" priority="5">
<variable name="name" select="@name" />
<call-template name="wc:compile-calc">
<with-param name="include-value">
<value-of select="//lv:const[ @name=$name ]/@value" />
</with-param>
</call-template>
</template>
<!--
Will output JSON of the following structure:
[ "type", {desc}, [subnodes] ]
The subnodes are recursively generated in the same format as above.
-->
<template name="wc:compile-calc" match="c:*" mode="wc:compile" priority="4">
<param name="nochildren" as="xs:boolean" select="false()" />
<param name="runtime" select="false()" />
<param name="include-value" />
<call-template name="util:json">
<with-param name="array">
<!-- output node type -->
<util:value>
<call-template name="util:json">
<with-param name="value" select="local-name()" />
</call-template>
</util:value>
<!-- description -->
<util:value>
<call-template name="util:json">
<with-param name="obj">
<!-- build each attribute into the description -->
<for-each select="@*">
<util:value>
<call-template name="util:json">
<with-param name="id" select="local-name()" />
<with-param name="value" select="." />
</call-template>
</util:value>
</for-each>
<!-- certain values should be calculated at runtime -->
<if test="$runtime = true()">
<util:value>
<call-template name="util:json">
<with-param name="id" select="'runtime'" />
<with-param name="value" select="'true'" />
</call-template>
</util:value>
</if>
</with-param>
</call-template>
</util:value>
<!-- children -->
<util:value>
<call-template name="util:json">
<with-param name="array">
<if test="not( $nochildren = true() )">
<!-- sub-nodes (recursive) -->
<for-each select="c:*">
<util:value>
<apply-templates select="." mode="wc:compile" />
</util:value>
</for-each>
</if>
</with-param>
</call-template>
</util:value>
<!-- optional value (if we can determine compile-time) -->
<if test="$include-value">
<util:value>
<call-template name="util:json">
<with-param name="value" select="$include-value" />
</call-template>
</util:value>
</if>
</with-param>
</call-template>
</template>
<template name="wc:var-to-hstr">
<param name="var" />
<!-- string separators (TODO: make configurable) -->
<variable name="pre" select="substring-before( $var, '4' )" />
<choose>
<when test="not( $pre = '' )">
<!-- before separator -->
<call-template name="wc:var-to-hstr">
<with-param name="var" select="$pre" />
</call-template>
<text> for </text>
<!-- after -->
<call-template name="wc:var-to-hstr">
<with-param name="var" select="substring-after( $var, '4' )" />
</call-template>
</when>
<!-- no separator; continue -->
<otherwise>
<call-template name="wc:_var-to-hstr">
<with-param name="var" select="$var" />
</call-template>
</otherwise>
</choose>
</template>
<!-- var to human-readable string -->
<template name="wc:_var-to-hstr">
<param name="var" />
<!-- start by grabbing the prefix -->
<variable name="prefix">
<call-template name="wc:str-until-uc">
<with-param name="str" select="$var" />
</call-template>
</variable>
<!-- and grab the rest of the string after the prefix -->
<variable name="remain" select="substring-after( $var, $prefix )" />
<!-- convert the first char to lowercase so that we do not screw up the uc
prefix substr on the next call -->
<variable name="remain-recurse" select="
concat(
translate( substring( $remain, 1, 1 ), $wc:uc, $wc:lc ),
substring( $remain, 2 )
)
" />
<variable name="prelex" select="$wc:hlex//wc:lex[ @prefix=$prefix ]" />
<choose>
<when test="$prelex">
<if test="not( $remain-recurse = '' )">
<call-template name="wc:var-to-hstr">
<with-param name="var" select="$remain-recurse" />
</call-template>
</if>
<text> </text>
<value-of select="$prelex/@to" />
</when>
<!-- no knowledge of prefix; output it and then recurse -->
<otherwise>
<variable name="strlex" select="$wc:hlex//wc:lex[ @str=$prefix ]" />
<choose>
<!-- if we recognize this text as a lexeme to be replaced, then do so
-->
<when test="$strlex">
<value-of select="$strlex/@to" />
</when>
<!-- just output the text as-is -->
<otherwise>
<!-- ucfirst -->
<value-of select="
concat(
translate( substring( $prefix, 1, 1 ), $wc:lc, $wc:uc ),
substring( $prefix, 2 )
)
" />
</otherwise>
</choose>
<if test="not( $remain-recurse = '' )">
<text> </text>
<call-template name="wc:var-to-hstr">
<with-param name="var" select="$remain-recurse" />
</call-template>
</if>
</otherwise>
</choose>
</template>
<!-- get string prefix until reaching a upper-case char -->
<template name="wc:str-until-uc">
<param name="str" />
<variable name="char" select="substring( $str, 1, 1 )" />
<choose>
<when test="$str = ''">
<!-- done; nothing else to do -->
</when>
<!-- did we find an upper-case char? -->
<when test="translate( $char, $wc:uc, '' ) = ''">
<!-- we're done; do nothing and do not output -->
</when>
<otherwise>
<!-- output the char and recurse -->
<value-of select="$char" />
<call-template name="wc:str-until-uc">
<with-param name="str" select="substring( $str, 2 )" />
</call-template>
</otherwise>
</choose>
</template>
</stylesheet>

4
src/current/doc/.gitignore vendored 100644
View File

@ -0,0 +1,4 @@
*.aux
*.pdf
*.log
*.toc

View File

@ -0,0 +1,372 @@
\chapter{Classification System}
The classification system is one of the most powerful features of \lang,
allowing precise control over the classification and conditional processing of
large sets of data, whether it be external input or values generated from within
\lang\ itself. Virtually every conditional calculation is best represented
through use of the classification system.
\section{Classification Matcher}
Data classification is performed by the classification matcher (sometimes
referred to simply as the ``matcher''). Put simply, it is a function (defined by
\aref{cmatch}) that, given a vector of inputs, produces a boolean vector (which
may itself contain boolean vectors) determining if the given input conforms to a
set of stated rules. A set of rules operating on a set input vectors is
collectively known as a \term{classification}. The system that performs matching
based on classifications is referred to as a \term{classifier}.
A single classification can be separated into a set of rules, often referred to
as \term{matches} within the context of \lang. A single rule attempts to match
on a vector of inputs.\footnote{Scalar inputs are a special condition defined in
\sref{cmatch-scalar}.} A simple example of such a match is shown in
\fref{cmatch-ex-single}.
\begin{figure}[h]
$$
I = \left[
\begin{array}{c}
1 \\ 3 \\ 4 \\ 1
\end{array}
\right]
\qquad
M = \left[
\begin{array}{c}
1 \\ 4
\end{array}
\right]
\quad
\to
\quad
R = \left[
\begin{array}{c}
\top \\ \bot \\ \top \\ \top
\end{array}
\right].
$$
\caption{A simple classification match $M$ on input $I$ and its result vector
$R$.}
\label{f:cmatch-ex-single}
\end{figure}
In \fref{cmatch-ex-single}, the input vector $I$ is \term{matched} against the
rule $M$. The output is a boolean result vector $R$ which can be summarized with
the following rule:
$$
R_n = \exists m\in M(m = I_n).
$$
\noindent
In other words, $R_n$ is $\top$ if $I_n\in M$ and is $\bot$ if $I_n\notin M$.
Under this definition, $M$ can be considered to be the \term{domain} under which
a given input $I_n$ is considered to be valid (a \term{match}).
We say that a classification rule \term{matches} if \emph{any} input matches.
That is:
$$
\left[\textrm{The rule $M$ matches input $I$}\right]
\iff
\top\in R
$$
\noindent
Another way to think of this concept is the reduction of the result vector $R$
using a logical OR. Alternatively, one could assert that:
$$
\left[\textrm{The rule $M$ matches input $I$}\right]
\iff
\sum\limits_n R_n \geq 1, \qquad R \in \set{0,1},
$$
\noindent
if an implementation were willing to use the sets \boolset and \set{1,0}
interchangeably.\footnote{See \sref{cmatch-int}.}
The following sections, however, serve to demonstrate that such a simple view of
the classification system, while useful for an introductory demonstration, is
not sufficient when considering the level of flexibility that is necessary to
handle more complicated data (in particular, when $I$ is a
matrix).\footnote{See $\Omega$-reductions, introduced in
\asref{cmatch}{omega-reduce}.}
%TODO: More example sections
\subsection{Classification Match (cmatch) Algorithm}
\label{a:cmatch}
The classification match (``cmatch'') algorithm is used to determine if a given
set of data matches a given set of classification criteria.
Let $I$ be the vector of input values.\footnote{$I$ may be a matrix (a vector
of vectors).} Let $M$ be the vector of predicates to match against $I$ such
that a match will be considered successful if \emph{any} predicate is true.
Since $I$ shall always be a vector of values---even if the vector contains only
one element (see algorithm below for comments on scalar values)---$M$ should be
a vector of one element if the desire is to match against a scalar value (rather
than a vector of values). Let $c$ (clear) be a boolean value\footnote{$1$ or $0$
when used within an integer context within the algorithm.} representing whether
the results of this operation should be logically AND'd together with the
prior cmatch result ($R'$ in the algorithm below); otherwise, the results will
be OR'd (see step \ref{a:cmatch-c} below).
Let $A\!\left(M,I,c,R'\right)$ (the ``algorithm'') be defined as:
\begin{enumerate}
\item
Let $R$ be the result vector.
\item\label{a:cmatch-scalar}
If the given input vector $I$ is a scalar, it should be converted to a vector
of length 1 with the value of the single element being the original scalar
value of $I$---that is, let $s$ be the original scalar value of $I$; then: $I
= \left[ s \right]$. If $s$ is undefined, then an empty result vector should
be returned.
\item\label{a:cmatch:input-vectorize}
Step \ref{a:cmatch-scalar} should also be done to the match vector $M$,
yielding $M = \left[ s \right]$ where $s$ is the original scalar $M$. If $s$
is undefined, then it should be treated as if it were the integer
$0$.\footnote{Consistent with the behavior of the remainder of the DSL.}
\item
Step \ref{a:cmatch-scalar} should also be done to the prior result vector
$R'$, yielding $R = \left[ s \right]$ where $s$ is the original scalar $R'$.
This situation may result from recursing at step \ref{a:cmatch-mrecurse} when
$R'_k$ is a scalar. If $s$ is undefined, then $R'$ should be initialized to an
empty vector, implying a fresh match (no prior results).
\goodbreak
\item\label{a:cmatch-iter}
The length of the result vector $R$~($\#R$) shall be the larger of the length
of the input vector $I$~($\#I$) or the prior result vector $R'$~($\#R'$).
For each $I_k \in I$:
\begin{enumerate}
\item\label{a:cmatch-mrecurse}
If $I_k$ is a vector, recurse, beginning at step 1. Let $r =
A(M_k,I_k,c,R'_k)$.
\begin{align*}
u &= \left\{
\begin{array}{ll}
\bot & \textrm{if }\#R' > 0, \\
c & \textrm{otherwise.}
\end{array}
\right. \\
%
R_k &= \left\{
\begin{array}{ll}
r & \textrm{if $R'_k$ is a vector or undefined}, \\
\Omega(r,u) & \textrm{otherwise}.\footnotemark
\end{array}
\right.
\end{align*}
\footnotetext{\label{a:cmatch-order} If $R'_k$ is a scalar, we must ensure
consistency with step \ref{a:cmatch-c} to ensure that the algorithm is not
dependent on input or execution order. Note the use of $u$ in place of
$c$---this ensures that, if there are any $R'$, we are consistent with the
effects of step \ref{a:cmatch:fill} (but in reverse).}
Continue with the next $I$ at step \ref{a:cmatch-iter}.
\item
\label{a:cmatch:omega-reduce}
Otherwise, $I_k$ is a scalar. Let $t$ be a temporary (intermediate) scalar
such that $t = \exists m \in M m(I_k)$.
\item\label{a:cmatch-c}
Let $v = \Omega\left(R'_k,c\right)$ and let
$$
R_k = \left\{
\begin{array}{ll}
v \wedge t & c = \top, \\
v \vee t & c = \bot.
\end{array}
\right.,
$$
where\footnote{$\Omega$ is simply the recursive reduction of a vector using
a logical OR. This function exists to resolve the situation where $R'_k$ is
a vector of values when $I_k$ is a scalar, which will occur when $M_k$ is
scalar for any $k$ during one application of the cmatch algorithm and $M_k$
is a vector for another iteration, where $R'$ is the previous match using
scalars. Note also that $X$, according to the recursion rule, may only be
undefined on the first iteration (in effect initializing the value).}
$$
\Omega\left(X,u\right) = \left\{
\begin{array}{ll}
u & \textrm{if X is undefined,} \\
X & \textrm{if X is a scalar,} \\
\exists x\in X \Omega(x,u) & \textrm{otherwise.}
\end{array}
\right. \>
\mbox{
$X \in \left\{\textrm{undefined},\top,\bot\right\}$
or a vector.
}
$$
\end{enumerate}
\item\label{a:cmatch:fill}
Let $v = \Omega\left(R'_k,c\right) \wedge \neg c$. If $\#R' > \#I$,
$$
R_k = \left\{
\begin{array}{ll}
v & \exists n\in I(n\textrm{ is a scalar}), \\
\left[v\right] & \textrm{otherwise.}\footnotemark
\end{array}
\right.
k \in \left\{j : \#I \leq j < \#R' \right\}.
$$
\footnotetext{Note that step \ref{a:cmatch:fill} will produce results
inconsistent with the recursive step \ref{a:cmatch-mrecurse} if there exists
an $I_n$ that is a matrix; this algorithm is not designed to handle such
scenarios.}
\end{enumerate}
Given a set of classification criteria $C$ such that $C_k = M$ for some integer
$k$ and some application of $A$, and a vectorized clear flag $c$ such that $c_k$
is associated with $C_k$, the final result $F(\#C-1)$ shall be defined as
$$
F(k) = \left\{
\begin{array}{ll}
A\left(C_k,I_k,c_k\right) & \textrm{k = 0,} \\
A\bigl(C_k,I_k,c_k,F\!\left(k-1\right)\bigr) & \textrm{otherwise.}
\end{array}
\right.
$$
The order of recursion on $F$ need not be right-to-left; $A$ is defined such
that it will produce the same result when applied in any order. This is
necessary since the input may be provided in any order.\footnote{Ibid,
\ref{a:cmatch-order}.}
\subsubsection{Boolean Classification Match}
\label{s:cmatch-boolean}
A scalar boolean classification match $b$ may be obtained simply as $b =
\Omega\left(F,\bot\right)$, where $F$ and $\Omega$ are defined in the algorithm
above. Consequently, note that an empty result set $F$ will be treated as
$\bot$, since index $0$ will be undefined.
\subsubsection{Match Vector}
$M$ is defined to be a vector of predicates which serve to {\sl match} against a
vector of input values. Most frequently, predicates will likely be against scalar
values. In such a case, an implementation may choose to forego function
application for performance reasons and instead match directly against the
scalar value. However, this document will consider scalar matches in the context
of predicates as functions. As such, if $M$ is a matrix, then the results are
implementation-defined (since the value does not make sense within the algorithm
as defined).
\subsubsection{Integer Results}
\label{s:cmatch-int}
$A$ defines $R$ to be a vector/matrix of boolean values. However, it may be
useful to use the cmatch results in calculations; as such, implementations that
make use of or produce cmatch results are required to do one or both of the
following where $b$ is a boolean scalar:
\begin{enumerate}
\item
Implicitly consider $b$ to be $\textrm{int}\!\left(b\right)$ when used in
calculations, and/or
\item
Perform the implicit conversion before $R$ is returned from $A$,
\end{enumerate}
where the function {\sl int} is defined as
$$
\textrm{int}(b) = \left\{
\begin{array}{ll}
1 & \textrm{if }b = \top, \\
0 & \textrm{if }b = \bot.
\end{array}
\right.\qquad
b \in \left\{\top,\bot\right\}.
$$
\subsection{Scalar Classification Matches}
\label{s:cmatch-scalar}
Implementations may find it convenient to support scalar inputs and scalar
classification matches to represent matching ``all'' indexes of a vector.
\aref{cmatch} defines both a classification match ($R$, and consequently $F$)
and an input ($I$) to be a vector, which is generally sufficient. However, in
the case where the number of indexes of the inputs and results of other matches
may be arbitrary, it may be useful to apply a certain classification across all
indexes, which cannot be done when $c = \top$ using \aref{cmatch}.
The onus of such a feature is on the implementation---it should flag such input
($I$) as a scalar, which is necessary since $I$ is unconditionally converted to
a vector by step \asref{cmatch}{input-vectorize}. If an implementation decides
to support scalar classification matches, \emph{it must conform to this
section}. Let such a scalar flag be denoted $s_k \inbool$ respective to input
$I_k$. Handling of both $F$ and $I$ is discussed in the sections that follow.
\subsubsection{Mixing Scalar And Vectorized Inputs}
\label{s:cmatch-scalar-mixed}
Under the condition that $\exists v\in s(v=\top)$, the compiler must:
\begingroup
% this definition is local to this group
\def\siset{k \in\set{j : s_j = \top}}
\begin{enumerate}
\item
Reorder inputs $I$ such that each scalar input $I_k, \siset$ be applied
after all non-scalar inputs have been matched using \aref{cmatch}.
\begin{enumerate}
\item
Consequently (and contrary to what was mentioned in \aref{cmatch}),
application order of $A$ with respect to inputs $I$ \emph{does} in fact
matter and implementations should ensure that this restriction holds
during runtime.
\end{enumerate}
\item
Before application of a scalar input, the scalar $I_k$ should be vectorized
according to the following rule:
$$
I'_{k,l} = I_k,
\qquad \siset,
\; 0 \leq l < \#R',
$$
where $R'$ is the value immediately before the application of $I_k$ as
defined in \aref{cmatch}.
\item
Application of \aref{cmatch} should then proceed as normal, using $I'$ in
place of $I$.
\end{enumerate}
\endgroup
\subsubsection{Converting Vectorized Match To Scalar}
As defined by \aref{cmatch}, the result $R$ will always be a vector. An
implementation may \emph{only} convert a vectorized match to a scalar using the
method defined in this section under the condition that $\forall v\in
s(v=\top)$; otherwise, there will be a loss of data (due to the expansion rules
defined in \sref{cmatch-scalar-mixed}). The implementation also \emph{must not}
reduce the vectorized match to a scalar using $\Omega$. An implementation
\emph{may}, however, $\Omega$-reduce the match result $R$ into an
\emph{separate} value as mentioned in \sref{cmatch-boolean}.
Under the condition that $\forall v\in s(v=\top)$, the system may post-process
$F$ (as defined in \aref{cmatch}) such that
$$
F' = F_0,
$$
and return $F'$ in place of $F$.
Note also that $F'$ may be fed back into \aref{cmatch} as an input and that the
results will be consistent and well-defined according to
\sref{cmatch-scalar-mixed} (and, consequently, this section).

View File

@ -0,0 +1,30 @@
% manual style package
% these margins ensure that the PDF can be easily scrolled vertically without
% worrying about alternating margins (good for viewing on screen, but not on
% paper)
\usepackage[margin=1.25in]{geometry}
\usepackage{amsmath}
\setcounter{secnumdepth}{3}
\setcounter{tocdepth}{3}
% no name yet
\def\lang{the DSL}
\def\sref#1{Section \ref{s:#1}}
\def\fref#1{Figure \ref{f:#1}}
\def\aref#1{Algorithm \ref{a:#1}}
\def\asref#1#2{A\ref{a:#1}(\ref{a:#1:#2})}
\def\set#1{%
\ifmmode%
\left\{#1\right\}%
\else
$\left\{#1\right\}$%
\fi%
}
\def\boolset{\set{\top,\bot}}
\def\inbool{\in\boolset}
\def\term#1{{\sl #1}}

View File

@ -0,0 +1,19 @@
\documentclass[10pt]{book}
%%begin preamble
\usepackage{manual}
\author{Mike Gerwitz\\LoVullo Associates}
\date{\today}
%%end preamble
\begin{document}
\title{Calc DSL: Design Specification and Programmer's Manual}
\maketitle
\tableofcontents
\include{chapters/class}
\end{document}

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Outputs graph visualization of dependencies in DOT format
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:dot="http://www.lovullo.com/calc/dot">
<xsl:import href="dot/depout.xsl" />
<xsl:import href="dot/defnode.xsl" />
<!-- supported sources (entry points) -->
<xsl:include href="dot/pkg-obj.xsl" />
<xsl:include href="dot/pkg-exec.xsl" />
<xsl:output method="text" />
<!--
Newline character
-->
<xsl:variable name="dot:nl" select="'&#10;'" />
<!--
Immediately fails on unrecognized source type
-->
<xsl:template match="lv:package" priority="1">
<xsl:message terminate="yes">
<xsl:text>[dot] fatal: this is not an object/executable file: </xsl:text>
<xsl:text>no symbol dependencies found</xsl:text>
</xsl:message>
</xsl:template>
<!--
Beginning of a DOT document
-->
<xsl:template match="lv:package" mode="dot:head">
<xsl:text>/* dependency graph of </xsl:text>
<xsl:value-of select="@name" />
<xsl:text> */</xsl:text>
<xsl:text>digraph "</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>" { </xsl:text>
<xsl:text>graph [rankdir="LR", ranksep="2"]; </xsl:text>
</xsl:template>
<!--
End of a DOT document
-->
<xsl:template match="lv:package" mode="dot:tail">
<xsl:text>}</xsl:text>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Styles node color based on symbol type
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dot="http://www.lovullo.com/calc/dot"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<!--
Parameter
-->
<xsl:template mode="dot:attr-color" priority="5"
match="*[ @type='param' ]">
<dot:attr name="color">#204a87</dot:attr>
</xsl:template>
<!--
Param type
-->
<xsl:template mode="dot:attr-color" priority="5"
match="*[ @type='type' ]">
<dot:attr name="color">#729fcf</dot:attr>
</xsl:template>
<!--
Classification
-->
<xsl:template mode="dot:attr-color" priority="5"
match="*[ @type='class' or @type='cgen' ]">
<dot:attr name="color">#4e9a06</dot:attr>
</xsl:template>
<!--
Function
-->
<xsl:template mode="dot:attr-color" priority="5"
match="*[ @type='func' ]">
<dot:attr name="color">#c4a000</dot:attr>
</xsl:template>
<!--
Map
-->
<xsl:template mode="dot:attr-color" priority="5"
match="*[ @type='map' or @type='retmap' ]">
<dot:attr name="color">#888a85</dot:attr>
</xsl:template>
<!--
Default
-->
<xsl:template match="*" mode="dot:attr-color" priority="1">
<!-- no color -->
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Styles node based on locality
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dot="http://www.lovullo.com/calc/dot"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<!--
External nodes should be styled as such
-->
<xsl:template mode="dot:attr-extern" priority="5" match="
*[ @src and not( @src='' ) ]
">
<dot:attr name="style">dashed</dot:attr>
</xsl:template>
<!--
Default node attributes
-->
<xsl:template match="preproc:sym" mode="dot:defnode-attr" priority="1">
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Styles node based on keep flag
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dot="http://www.lovullo.com/calc/dot"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<!--
External nodes should be styled as such
-->
<xsl:template mode="dot:attr-keep" priority="5" match="
*[ @keep='true' ]
">
<dot:attr name="fontcolor">red</dot:attr>
</xsl:template>
<!--
Default node attributes
-->
<xsl:template match="preproc:sym" mode="dot:defnode-keep" priority="1">
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Styles node shape based on symbol type
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dot="http://www.lovullo.com/calc/dot"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<!--
Parameter
-->
<xsl:template mode="dot:attr-shape" priority="5"
match="*[ @type='param' ]">
<dot:attr name="shape">box</dot:attr>
</xsl:template>
<!--
Classification
-->
<xsl:template mode="dot:attr-shape" priority="5"
match="*[ @type='class' or @type='cgen' ]">
<dot:attr name="shape">octagon</dot:attr>
</xsl:template>
<!--
Function
-->
<xsl:template mode="dot:attr-shape" priority="5"
match="*[ @type='func' ]">
<dot:attr name="shape">component</dot:attr>
</xsl:template>
<!--
Map
-->
<xsl:template mode="dot:attr-shape" priority="5"
match="*[ @type='map' or @type='retmap' ]">
<dot:attr name="shape">note</dot:attr>
</xsl:template>
<!--
Default
-->
<xsl:template match="*" mode="dot:attr-shape" priority="1">
<!-- default shape -->
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Outputs graph visualization of dependencies in DOT format
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dot="http://www.lovullo.com/calc/dot"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<xsl:import href="./attr-color.xsl" />
<xsl:import href="./attr-shape.xsl" />
<xsl:import href="./attr-extern.xsl" />
<xsl:import href="./attr-keep.xsl" />
<!--
External nodes should be styled as such
-->
<xsl:template mode="dot:defnode-attr" priority="5" match="
preproc:sym[ @src and not( @src='' ) ]
">
<dot:attr name="label">
<xsl:value-of select="@src" />
<xsl:text>/\n</xsl:text>
<xsl:value-of select="@name" />
</dot:attr>
<dot:attr name="tooltip">
<xsl:value-of select="@src" />
<xsl:text>/</xsl:text>
<xsl:value-of select="@name" />
</dot:attr>
</xsl:template>
<!--
Default node attributes
-->
<xsl:template match="preproc:sym" mode="dot:defnode-attr" priority="1">
</xsl:template>
<!--
Render an attribute list as a comma-delimited string
Expects a tree of dot:attr nodes where @name is the name of the attribute, and
its normalized text is the value. The value will be quoted; double quotes must
be manually escaped prior to calling this template.
-->
<xsl:template name="dot:render-attr-list">
<xsl:param name="attr-list" />
<xsl:for-each select="$attr-list/dot:attr">
<xsl:if test="position() > 1">
<xsl:text>, </xsl:text>
</xsl:if>
<xsl:value-of select="@name" />
<xsl:text>="</xsl:text>
<xsl:value-of select="normalize-space( text() )" />
<xsl:text>"</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Graph node definitions
Nodes do not need to be defined (DOT will generate them upon first reference);
this defines nodes that require additional data associated with them.
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dot="http://www.lovullo.com/calc/dot"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<xsl:import href="./defnode-attr.xsl" />
<!--
Do not declare constants or generated symbols
XXX: Duplicated logic from smy-ref!
-->
<xsl:template mode="dot:defnode" priority="9" match="
preproc:sym[
@type='const'
or @type='map' or @type='retmap'
or @type='map:head' or @type='map:tail'
or @type='retmap:head' or @type='retmap:tail'
or (
@type='type'
and (
@name='integer'
or @name='float'
or @name='boolean'
)
)
or @parent
or @preproc:generated='true'
]
">
</xsl:template>
<!--
Process parent symbol in place of current symbol
Symbols with defined parents are generated as part of that parent and will
therefore be treated as a single unit.
-->
<xsl:template match="preproc:sym[ @parent ]" mode="dot:defnode" priority="7">
<xsl:variable name="parent" select="@parent" />
<xsl:apply-templates select="
parent::preproc:symtable/preproc:sym[ @name=$parent ]
" />
</xsl:template>
<!--
Default node definition
If no attributes are generated, then the node will be entirely omitted (it'll
be created automatically by DOT when referenced).
-->
<xsl:template match="preproc:sym" mode="dot:defnode" priority="1">
<xsl:variable name="attr">
<xsl:call-template name="dot:render-attr-list">
<xsl:with-param name="attr-list">
<!-- this kluge exists because of XSLT limitiations and the confusion
that would result (in this particular situation) from
xsl:apply-imports
-->
<xsl:apply-templates select="." mode="dot:defnode-attr" />
<xsl:apply-templates select="." mode="dot:attr-extern" />
<xsl:apply-templates select="." mode="dot:attr-color" />
<xsl:apply-templates select="." mode="dot:attr-shape" />
<xsl:apply-templates select="." mode="dot:attr-keep" />
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:if test="not( $attr = '' )">
<xsl:text>"</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>" [</xsl:text>
<xsl:value-of select="$attr" />
<xsl:if test="@src and not( @src='' )">
<xsl:if test="$attr">
<xsl:text>,</xsl:text>
</xsl:if>
<!-- link to other packages in the graph for navigation -->
<xsl:text>href="</xsl:text>
<xsl:value-of select="concat( @src, '.svg' )" />
<xsl:text>"</xsl:text>
</xsl:if>
<xsl:text>];</xsl:text>
<xsl:value-of select="$dot:nl" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,212 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Outputs dependency relationship to a directed graph
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:dot="http://www.lovullo.com/calc/dot"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<!--
Container for all the dependencies
-->
<xsl:template match="preproc:sym-deps" mode="dot:depout" priority="5">
<xsl:apply-templates mode="dot:depout" />
</xsl:template>
<!--
Treat the entry point (lv:yield) as the root node
-->
<xsl:template match="preproc:sym-dep[ @name='___yield' ]" mode="dot:depout" priority="6">
<xsl:value-of select="@name" />
<xsl:text> [root=ctr,fontsize=24,style=bold,label="Yield"]; </xsl:text>
<xsl:apply-templates mode="dot:depout" />
</xsl:template>
<!--
Constant and generated symbols will not be rendered
-->
<xsl:template mode="dot:depout" priority="5" match="
preproc:sym-dep[
@name=ancestor::lv:package/preproc:symtable/preproc:sym[
@type='const'
or @preproc:generated='true'
]/@name
]
">
</xsl:template>
<!--
Container for symbol dependencies
That is: this node represents a symbol, and its children are its dependencies.
-->
<xsl:template match="preproc:sym-dep" mode="dot:depout" priority="3">
<xsl:apply-templates mode="dot:depout" />
</xsl:template>
<!--
Do not output relationships to primitives or constants; or generated
-->
<xsl:template mode="dot:depout" priority="5" match="
preproc:sym-ref[
@type='type'
and (
@name='integer'
or @name='float'
or @name='boolean'
)
or @type='const'
or @type='lparam'
or @type='map' or @type='retmap'
or @type='map:head' or @type='map:tail'
or @type='retmap:head' or @type='retmap:tail'
]
">
<!-- skip -->
</xsl:template>
<!--
Process generated symbol deps as our own
Generated symbols are not known by the user, so they should be treated as part
of the unit from which they are generated.
-->
<xsl:template mode="dot:depout" priority="4" match="
preproc:sym-ref[
@name=ancestor::lv:package/preproc:symtable/preproc:sym[
@preproc:generated='true'
]/@name
]
">
<xsl:param name="usedby" select="parent::preproc:sym-dep/@name" />
<xsl:variable name="name" select="@name" />
<!-- process the generated symbol's deps as our own -->
<xsl:apply-templates mode="dot:depout" select="
ancestor::preproc:sym-deps/preproc:sym-dep[
@name=$name
]/preproc:sym-ref
">
<xsl:with-param name="usedby" select="$usedby" />
</xsl:apply-templates>
</xsl:template>
<!--
Process references with parents as if they were their parent symbol
Symbols with defined parents are generated as part of that parent and will
therefore be treated as a single unit.
-->
<xsl:template match="preproc:sym-ref[ @parent ]" mode="dot:depout" priority="3">
<xsl:param name="usedby" select="parent::preproc:sym-dep/@name" />
<xsl:variable name="parent" select="@parent" />
<xsl:apply-templates mode="dot:depout" select="
ancestor::lv:package/preproc:symtable/preproc:sym[ @name=$parent ]
">
<xsl:with-param name="usedby" select="$usedby" />
</xsl:apply-templates>
</xsl:template>
<!--
Trigger processing of symbol associated with the ref
-->
<xsl:template match="preproc:sym-ref" mode="dot:depout" priority="2">
<xsl:param name="usedby" select="parent::preproc:sym-dep/@name" />
<xsl:variable name="name" select="@name" />
<xsl:apply-templates mode="dot:depout" select="
ancestor::lv:package/preproc:symtable/preproc:sym[ @name=$name ]
">
<xsl:with-param name="usedby" select="$usedby" />
</xsl:apply-templates>
</xsl:template>
<!--
Output symbol reference to directed graph
The symbol (dependency) is referenced as a node adjacent to the node of the
symbol that uses it. The edge is directed toward the dependency and shall be
read as "uses".
For example: "foo uses bar":
(foo) -> (bar)
-->
<xsl:template match="preproc:sym" mode="dot:depout" priority="5">
<xsl:param name="usedby" />
<xsl:variable name="attr">
<xsl:call-template name="dot:render-attr-list">
<xsl:with-param name="attr-list">
<xsl:apply-templates select="." mode="dot:attr-extern" />
<xsl:apply-templates select="." mode="dot:attr-color" />
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:text>"</xsl:text>
<xsl:value-of select="$usedby" />
<xsl:text disable-output-escaping="yes">" -&gt; "</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>"</xsl:text>
<xsl:if test="not( $attr='' )">
<xsl:text> [</xsl:text>
<xsl:value-of select="$attr" />
<xsl:text>]</xsl:text>
</xsl:if>
<xsl:text>;</xsl:text>
<xsl:value-of select="$dot:nl" />
</xsl:template>
<!--
Bail out if asked to render something unexpected
-->
<xsl:template match="*" mode="dot:depout" priority="1">
<xsl:message terminate="yes">
<xsl:text>error: what do I do!?: unexpected </xsl:text>
<xsl:value-of select="name()" />
</xsl:message>
</xsl:template>
<!--
Extra comments and attributes are ignored
text() is ignored, otherwise, extra whitespace corresponding to the
indentation of nodes appears in the output.
Ignoring attributes is just in case of an xpath whoopsie, but probably isn't
necessary, and is probably dangerous (because it may veil bugs).
-->
<xsl:template match="@*|text()" mode="dot:depout" priority="1">
<!-- ignore -->
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Processes executable file dependency graph
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:l="http://www.lovullo.com/rater/linker"
xmlns:dot="http://www.lovullo.com/calc/dot"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<!--
Entry point for linked executable (.xmle) DOT generation
We wish to generate a dependency graph for an entire program. This approach is
a little bit different than the approach to processing object files, because
we know that the linker's symbol table contains *only* those symbols that are
used (or kept). We further know that each symbol (unless there's a bug in the
linker) is referenced only a single time in the symbol table.
This makes our job easy: simply walk the symbol table, look up the
preproc:sym-dep in the source package, and render as we normally would for an
object file.
Lord Jesus it's a fire.
-->
<xsl:template match="lv:package[ l:dep ]" priority="9">
<xsl:apply-templates select="." mode="dot:head" />
<!-- we know that all symbols in the linker symbol table are used, so we can
immediately generate the node definitions -->
<xsl:apply-templates mode="dot:defnode"
select="l:dep/preproc:sym" />
<!-- outputting the dependencies of those symbols is more involved and
requires processing data from each object file -->
<xsl:apply-templates select="l:dep/preproc:sym" mode="dot:ldep-sym-deps">
<xsl:with-param name="exec-name" select="concat( @__rootpath, @name )" />
</xsl:apply-templates>
<xsl:apply-templates select="." mode="dot:tail" />
</xsl:template>
<!--
Omit symbols with parent references
Symbols with parents are generated from that parent and will be considered to
be a single unit. Since the parent will also be in the symbol table (it is,
after all, a dependency), we don't have to worry about these at all.
-->
<xsl:template match="preproc:sym[ @parent ]" mode="dot:ldep-sym-deps" priority="5">
<!-- ignore -->
</xsl:template>
<!--
Process dependencies for each symbol
The linker symbol table only stores a flattened symbol list; to get the
symbol's dependencies, we must consult the source object file.
-->
<xsl:template match="preproc:sym" mode="dot:ldep-sym-deps" priority="1">
<xsl:param name="exec-name" />
<xsl:variable name="name" select="@name" />
<!-- empty @src implies program package -->
<xsl:variable name="pkg" select="
if ( @src and not( @src='' ) ) then
document( concat( @src, '.xmlo' ), / )/lv:package
else
document( concat( $exec-name, '.xmlo' ), / )/lv:package
" />
<xsl:variable name="sym-dep" select="
$pkg/preproc:sym-deps/preproc:sym-dep[
@name=$name
]
" />
<xsl:if test="not( $sym-dep )">
<xsl:message terminate="yes">
<xsl:text>error: cannot locate symbol dependencies for `</xsl:text>
<xsl:value-of select="concat( @src, '/', @name )" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:if>
<xsl:apply-templates select="$sym-dep" mode="dot:depout" />
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Processes object file dependency graph
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:dot="http://www.lovullo.com/calc/dot"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<!--
Entry point for object file DOT generation
To render the graph, we first declare all nodes associated with all referenced
symbols in the symbol tree (it's important to check against preproc:sym-deps,
since it's likely that not all imported symbols are used); this allows setting
attributes for the symbol nodes on the graph without having to worry about
duplicate code later on.
After that, we simply recurse through the dependency list and neighbor the
nodes.
-->
<xsl:template match="lv:package[ preproc:sym-deps ]" priority="5">
<xsl:apply-templates select="." mode="dot:head" />
<xsl:variable name="sym-deps" select="preproc:sym-deps" />
<!-- pre-style all referenced nodes (the symbol table is likely to contain
references to symbols that were imported but not used) -->
<xsl:apply-templates mode="dot:defnode" select="
preproc:symtable/preproc:sym[
@name=$sym-deps/preproc:sym-dep/preproc:sym-ref/@name
or @name=$sym-deps/preproc:sym-dep/preproc:sym-ref/@parent
]
" />
<!-- output graph description -->
<xsl:apply-templates select="preproc:sym-deps" mode="dot:depout" />
<xsl:apply-templates select="." mode="dot:tail" />
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,702 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Handles calculation output in LaTeX format for styling by Mathjax
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<!--
Recursively apply any child equations and then do the same in calc-after mode
@return LaTeX equation
-->
<xsl:template match="c:*" mode="calc-recurse">
<xsl:apply-templates select="./c:*" />
<!-- invoke `after' templates, which allows inserting data for display after
the initial equation -->
<xsl:apply-templates select="./c:*" mode="calc-after" />
</xsl:template>
<!--
Style sum of values as a LaTeX equation
Note that this does not deal with the summation of a series; that's left to
the handling of the @of attribute.
@return LaTeX equation
-->
<xsl:template match="c:sum">
<xsl:apply-templates select="." mode="sum-body" />
</xsl:template>
<xsl:template match="c:sum" mode="sum-body">
<xsl:for-each select="./c:*">
<!-- get value to display -->
<xsl:variable name="display">
<xsl:apply-templates select="." />
</xsl:variable>
<!-- delimit with +s if not first; if we're adding a negative, omit the
addition symbol as well (unless we're displaying a product, since
multiplying by a negative would otherwise appear to be subtraction) -->
<xsl:if test="
( position() > 1 )
and (
not( substring( $display, 1, 1 ) = '-' )
or ( local-name() = 'product' )
)
">
<xsl:text> + </xsl:text>
</xsl:if>
<!-- the only reason we would have a sum within a sum (that doesn't use
sigma-notation) is for grouping -->
<xsl:if test="( local-name() = 'sum' ) and not( @of )">
<xsl:text>\left(</xsl:text>
</xsl:if>
<xsl:copy-of select="$display" />
<xsl:if test="( local-name() = 'sum' ) and not( @of )">
<xsl:text>\right)</xsl:text>
</xsl:if>
</xsl:for-each>
<!-- since we looped manually, we must also invoke `after' templates
manually -->
<xsl:apply-templates select="./c:*" mode="calc-after" />
</xsl:template>
<!--
Style summation of a set as a LaTeX equation
Note that @of deals witht summation of sets only (rather, using the index of a
set to sum over the provided calculation). See the other template(s) for
summing values without a set.
An index may optionally be provided via an @index attribute; otherwise, one
will be chosen for you. The index is used for the lower limit; the upper limit
is omitted. The child nodes are then used to generate the equation to be
applied by the summation.
If no child nodes are provided, then the summation is meant to imply that each
value in the set should be summed. Adding child nodes overrides this behavior.
@return LaTeX equation
-->
<xsl:template match="c:sum[@of]">
<xsl:variable name="of" select="@of" />
<!-- if no index is provided, simply use its symbol to indicate all values
within its domain -->
<xsl:variable name="index">
<xsl:choose>
<xsl:when test="@index">
<xsl:value-of select="@index" />
</xsl:when>
<xsl:otherwise>
<!-- TODO: Determine an index that is not in use -->
<xsl:text>k</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="ref">
<xsl:value-of select="@of" />
</xsl:variable>
<!-- retrieve the symbol associated with this value (no index) -->
<xsl:variable name="symbol">
<xsl:call-template name="get-symbol">
<xsl:with-param name="name" select="$ref" />
<xsl:with-param name="search" select="/" />
</xsl:call-template>
</xsl:variable>
<!-- also retrieve the symbol without its index -->
<!-- if an index was provided, set the lower limit to 0 (we do this in a
separate variable so that we can display the symbol on its own elsewhere)
-->
<xsl:variable name="index-limit">
<xsl:value-of select="$index" />
<!-- we only need the explicit notation if we are summing more than the
set -->
<xsl:if test="./c:*">
<xsl:text>=0</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:text>\sum \limits_{</xsl:text>
<xsl:value-of select="$index-limit" />
<xsl:text>}</xsl:text>
<!-- upper limit is only necessary for clarification if they have provided a
more complex expression; if we're only summing over a single set, then
the extra notation is unnecessary and will just clutter -->
<xsl:if test="./c:*">
<!-- the upper limit of the summation will be denoted by #S, where S is the
symbol for a given set -->
<xsl:text>^{\grave\#</xsl:text>
<xsl:copy-of select="$symbol" />
<xsl:text>}</xsl:text>
</xsl:if>
<!-- if no children are provided, just sum @of -->
<xsl:if test="not(./c:*)">
<!-- output the symbol followed by its index, only if an index was provided
(and is therefore necessary) -->
<xsl:call-template name="get-symbol">
<xsl:with-param name="name" select="$ref" />
<xsl:with-param name="index-symbol" select="$index" />
<xsl:with-param name="search" select="/" />
</xsl:call-template>
</xsl:if>
<!-- output any additional expressions, if any -->
<xsl:apply-templates select="." mode="sum-body" />
</xsl:template>
<!--
Style product of values as a LaTeX equation
Note that this does not deal with the product of a series; that's left to
the handling of the @of attribute (TODO: @of not yet implemented for
products).
@return LaTeX equation
-->
<xsl:template match="c:product">
<xsl:variable name="enclose" select="
@dot='true'
and (
preceding-sibling::c:*
or following-sibling::c:*
)
" />
<xsl:if test="$enclose">
<xsl:text>(</xsl:text>
</xsl:if>
<xsl:for-each select="./c:*">
<!-- Function symbols can have multiple chars, so we'll need to add the
multiplication symbol. Adjacent constants should also be separated by a
dot, otherwise it'll look like one giant number. -->
<xsl:if test="
(
local-name() = 'apply'
or ../@dot = 'true'
or (
( local-name() = 'const' )
and ( local-name( preceding-sibling::*[1] ) = 'const' )
)
)
and ( position() > 1 )
">
<xsl:text> \,\cdot\, </xsl:text>
</xsl:if>
<!-- if precedence of this operation is lower, we will need to include
parenthesis -->
<!-- XXX: Relies on hard-coded precedence rules in multiple locations;
refactor! -->
<xsl:if test="
( local-name() = 'sum' )
or ( ( local-name() = 'product' ) and not( @of ) )
">
<xsl:text>\left(</xsl:text>
</xsl:if>
<xsl:apply-templates select="." />
<!-- close parenthesis -->
<xsl:if test="
( local-name() = 'sum' )
or ( ( local-name() = 'product' ) and not( @of ) )
">
<xsl:text>\right)</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:if test="$enclose">
<xsl:text>)</xsl:text>
</xsl:if>
<!-- since we looped manually, we must also invoke `after' templates
manually -->
<xsl:apply-templates select="./c:*" mode="calc-after" />
</xsl:template>
<!--
Style quotient of two values as a LaTeX equation
This is used to divide two values and will be styled as a fraction. The
numerator should be the first calculation node and the denominator the second;
there should be no additional nodes.
@return LaTeX equation
-->
<xsl:template match="c:quotient">
<!-- numerator (first child) -->
<xsl:text>\frac{</xsl:text>
<xsl:apply-templates select="./c:*[1]" />
<!-- denominator (second child) -->
<xsl:text>}{</xsl:text>
<xsl:apply-templates select="./c:*[2]" />
<xsl:text>}</xsl:text>
<!-- since we processed manually, we must also invoke `after' templates
manually -->
<xsl:apply-templates select="./c:*" mode="calc-after" />
</xsl:template>
<xsl:template match="c:let">
<!-- process the body of the let expression (the variables should have been
processed separately) -->
<xsl:apply-templates select="c:*" />
</xsl:template>
<!--
Style a value for display within a LaTeX equation
Forwards to calc-get-value template.
-->
<xsl:template match="c:value-of">
<xsl:apply-templates select="." mode="calc-get-value" />
</xsl:template>
<!--
Values from a c:let must have their names altered before looking up the symbol
-->
<xsl:template match="c:*[ @name=ancestor::c:let/c:values/c:value/@name ]" mode="calc-get-value">
<xsl:call-template name="calc-get-value">
<!-- :<let-name>:<our-name> -->
<xsl:with-param name="name" select="
concat( ':', ancestor::c:let[1]/@name, ':', @name )
" />
</xsl:call-template>
</xsl:template>
<!--
Style a value for display within a LaTeX equation
By default, the symbol for the given value (variable) will be rendered in
place of this node.
This element is not expected to have any children.
XXX: Refactor me; there are more clear and less convoluted ways to accomplish
this.
@return LaTeX equation
-->
<xsl:template name="calc-get-value" match="c:*" mode="calc-get-value">
<xsl:param name="name" select="@name" />
<xsl:variable name="index-symbol">
<xsl:if test="./c:index">
<xsl:for-each select="./c:index">
<!-- separate multiple indexes with commas -->
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:apply-templates select="./c:*[1]" />
</xsl:for-each>
</xsl:if>
</xsl:variable>
<xsl:variable name="sym" select="
/lv:*/preproc:symtable/preproc:sym[
@name=$name
]
" />
<xsl:variable name="value">
<xsl:choose>
<!-- for scalar constants that do not have a symbol, simply inline their
value (if they have a symbol, then it is assumed that their symbolic
meaning is more meaningful than its value) -->
<xsl:when test="
$sym[
@type='const'
and @dim='0'
and ( not( @text ) or @tex='' )
]
">
<xsl:value-of select="$sym/@value" />
</xsl:when>
<!-- local index (generated with @of) -->
<xsl:when test="ancestor::c:*[ @of and @index=$name ]">
<xsl:value-of select="$name" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="get-symbol">
<xsl:with-param name="name" select="$name" />
<xsl:with-param name="index" select="@index" />
<xsl:with-param name="index-symbol" select="$index-symbol" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:copy-of select="$value" />
<!-- yes, a value still may have things to appear after it -->
<xsl:apply-templates select="./c:*" mode="calc-after" />
</xsl:template>
<!--
Style a constant value for use in a LaTeX equation
Constant values are not treated as variables; instead, their value (rather
than their symbol) is immediately rendered.
Use this only if the value itself makes more sense (and is more clear) than a
variable.
This element is not expected to have any children.
@return LaTeX equation
-->
<xsl:template match="c:const">
<!-- a constant value of 1 with Iverson's brackets is redundant -->
<xsl:if test="not( ( @value = '1' ) and ./c:when )">
<!-- display constant value -->
<xsl:value-of select="@value" />
</xsl:if>
<!-- a constant may still have things to appear after it -->
<xsl:apply-templates select="./c:*" mode="calc-after" />
</xsl:template>
<xsl:template match="c:ceil|c:floor">
<xsl:text>\left\l</xsl:text>
<xsl:value-of select="local-name()" />
<xsl:text> </xsl:text>
<xsl:apply-templates select="." mode="calc-recurse" />
<xsl:text>\right\r</xsl:text>
<xsl:value-of select="local-name()" />
</xsl:template>
<!--
Styles a function application for display in a LaTeX equation
Indicates a function application. A call to the function, with each of its
arguments in parenthesis, will be rendered.
@return LaTeX equation
-->
<xsl:template match="c:apply">
<xsl:variable name="self" select="." />
<xsl:variable name="name" select="@name" />
<xsl:variable name="fsym" select="//lv:function[@name=$name]/@sym" />
<xsl:call-template name="get-symbol">
<xsl:with-param name="name" select="@name" />
<xsl:with-param name="search" select="/" />
</xsl:call-template>
<!-- if a symbol is provided, then omit the parenthesis -->
<xsl:if test="not( $fsym )">
<xsl:text>\left(</xsl:text>
</xsl:if>
<!-- output the symbol associated with the value of each argument -->
<xsl:for-each select="./c:arg">
<!-- delimit params with a comma -->
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:variable name="valname" select="./c:*[1]/@name" />
<xsl:choose>
<!-- if this variable has been defined as an index, then simply output
it -->
<xsl:when test="ancestor::c:*[ @of and @index=$valname ]">
<xsl:value-of select="$valname" />
</xsl:when>
<!-- display the value of constants -->
<xsl:when test="local-name( ./c:*[1] ) = 'const'">
<xsl:value-of select="./c:*[1]/@value" />
</xsl:when>
<!-- otherwise, it's some other variable and we must look up its
symbol -->
<xsl:otherwise>
<xsl:apply-templates select="./c:*[1]" />
</xsl:otherwise>
</xsl:choose>
<!-- we may have c:when, etc (args are their own sub-equations) -->
<xsl:apply-templates select="./c:*[1]/c:*" mode="calc-after" />
</xsl:for-each>
<xsl:if test="not( $fsym )">
<xsl:text>\right)</xsl:text>
</xsl:if>
<xsl:apply-templates select="./c:*" mode="calc-after" />
</xsl:template>
<!--
Outputs Iverson's brackets only if forced; see calc-after mode
This is hidden by default until calc-after to ensure that this is display
*after* the equation is output.
@param boolean force-show optionally force the display of the notation
@return LaTeX equation
-->
<xsl:template match="c:when">
<xsl:param name="force-show" select="false()" />
<xsl:param name="standalone" select="false()" />
<!-- by default, we want to defer showing this until after the equation has
been output; however, the caller can force it to be displayed if needed -->
<xsl:if test="$force-show = true()">
<xsl:apply-templates select="." mode="calc-after">
<xsl:with-param name="standalone" select="$standalone" />
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<!--
Outputs Iverson's brackets
This is called *after* the equation is output
@return LaTeX equation
-->
<xsl:template match="c:when" mode="calc-after" priority="5">
<xsl:param name="brackets" select="true()" />
<xsl:param name="standalone" select="false()" />
<xsl:variable name="preceding" select="preceding-sibling::c:when" />
<!-- output bracket only if (a) requested and (b) first in set -->
<xsl:if test="$brackets and ( $standalone or not( $preceding ) )">
<xsl:text>\left[</xsl:text>
</xsl:if>
<!-- if we do have a preceding sibling, prefix with "and" -->
<xsl:if test="not( $standalone ) and $preceding">
<xsl:text>\text{ and }</xsl:text>
</xsl:if>
<!-- output the symbol for the variable we are comparing against -->
<xsl:apply-templates select="." mode="calc-get-value" />
<xsl:text> </xsl:text>
<xsl:apply-templates select="./c:*" mode="calc-iversons" />
<!-- output bracket only if (a) requested and (b) last in set -->
<xsl:if test="
$brackets and ( $standalone or not( following-sibling::c:when ) )
">
<xsl:text>\right]</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="c:cases">
<xsl:text>\begin{cases}</xsl:text>
<xsl:apply-templates select="./c:case|./c:otherwise" />
<xsl:text>\end{cases}</xsl:text>
<!-- if any equations immediately follow, add some extra space so as not to
confuse the reader -->
<xsl:if test="following-sibling::c:*">
<xsl:text>\;\;\;</xsl:text>
</xsl:if>
</xsl:template>
<!--
Generate a case
When $force-show is provided, as it is when displaying only small portions of
an equation, it will display the line using Iverson's brackets.
-->
<xsl:template match="c:cases/c:case|c:cases/c:otherwise">
<xsl:param name="force-show" select="false()" />
<!-- generate value -->
<xsl:apply-templates select="./c:*[ not( local-name() = 'when' ) ][1]" />
<xsl:choose>
<xsl:when test="not( $force-show )">
<xsl:text> &amp; </xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text> [</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="local-name() != 'otherwise'">
<xsl:if test="not( $force-show )">
<xsl:text>\text{if } </xsl:text>
</xsl:if>
<!-- generate condition under which this value will apply -->
<xsl:apply-templates select="./c:when" mode="calc-after">
<xsl:with-param name="brackets" select="false()" />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:text>\text{otherwise}</xsl:text>
</xsl:otherwise>
</xsl:choose>
<!-- determine how we end the line (more cases or end?) -->
<xsl:choose>
<xsl:when test="$force-show">
<xsl:text>]</xsl:text>
</xsl:when>
<xsl:when test="following-sibling::c:case|following-sibling::c:otherwise">
<xsl:text>; \\</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>.</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
Do nothing with calc-after for any unmatched calculations
-->
<xsl:template match="c:*" mode="calc-after" priority="1">
<!-- make sure nothing is done for all other nodes with calc-after -->
</xsl:template>
<!--
Display a notation intended for use within Iverson's brackets
@return LaTeX equation
-->
<xsl:template match="c:eq|c:ne|c:gt|c:lt|c:gte|c:lte" mode="calc-iversons">
<xsl:variable name="name" select="local-name()" />
<!-- map to LaTeX equivalent -->
<xsl:variable name="map">
<c id="eq">=</c>
<c id="ne">\not=\;</c>
<c id="gt">\gt</c>
<c id="lt">\lt</c>
<c id="gte">\geq</c>
<c id="lte">\leq</c>
</xsl:variable>
<xsl:value-of select="$map/*[ @id=$name ]" />
<xsl:text> </xsl:text>
<xsl:apply-templates select="." mode="calc-recurse" />
</xsl:template>
<!--
TODO: Technically this is incorrect; sets cannot have duplicate values. This
would be best styled as a vector/matrix/etc.
-->
<xsl:template match="c:set" priority="1">
<xsl:text>\left[</xsl:text>
<xsl:for-each select="./c:*">
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:apply-templates select="." />
</xsl:for-each>
<xsl:text>\right]^T</xsl:text>
</xsl:template>
<!-- style subsets as matrices (easier to read) -->
<xsl:template match="c:set[ ./c:set ]" priority="5">
<xsl:text>\left[\begin{array}\\</xsl:text>
<xsl:for-each select="./c:set">
<xsl:if test="position() > 1">
<xsl:text>\\</xsl:text>
</xsl:if>
<xsl:for-each select="./c:*">
<xsl:if test="position() > 1">
<xsl:text disable-output-escaping="yes"> &amp; </xsl:text>
</xsl:if>
<xsl:text>{</xsl:text>
<xsl:apply-templates select="." />
<xsl:text>}</xsl:text>
</xsl:for-each>
</xsl:for-each>
<xsl:text>\end{array}\right]</xsl:text>
</xsl:template>
<xsl:template match="c:length-of">
<xsl:text>\#\left(</xsl:text>
<xsl:apply-templates select="./c:*[1]" />
<xsl:text>\right)</xsl:text>
</xsl:template>
<xsl:template match="c:cons">
<xsl:text>\textrm{cons}\left(</xsl:text>
<xsl:apply-templates select="./c:*[1]" />
<xsl:text>,</xsl:text>
<xsl:apply-templates select="./c:*[2]" />
<xsl:text>\right)</xsl:text>
</xsl:template>
<xsl:template match="c:car">
<xsl:text>\left(</xsl:text>
<xsl:apply-templates select="./c:*[1]" />
<xsl:text>\right)_0</xsl:text>
</xsl:template>
<xsl:template match="c:cdr">
<xsl:text>\textrm{cdr}\left(</xsl:text>
<xsl:apply-templates select="./c:*[1]" />
<xsl:text>\right)</xsl:text>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,526 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
TODO: we can combine this dependency discovery with the symbol table
generation, eliminating extra passes
TODO: dependency symbols should not duplicate metadata
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:t="http://www.lovullo.com/rater/apply-template"
xmlns:c="http://www.lovullo.com/calc"
xmlns:ext="http://www.lovullo.com/ext"
xmlns:util="http://www.lovullo.com/util">
<xsl:variable name="tex-defaults">
<preproc:syms>
<preproc:sym value="\alpha" vec="A" />
<preproc:sym value="\beta" vec="B" />
<preproc:sym value="\gamma" vec="\Gamma" />
<preproc:sym value="x" vec="X" />
<preproc:sym value="y" vec="Y" />
<preproc:sym value="z" vec="Z" />
</preproc:syms>
</xsl:variable>
<!-- simply allows invoking the template with dynamic input -->
<xsl:template name="preproc:gen-deps">
<xsl:param name="pkg" as="element( lv:package )" />
<xsl:apply-templates select="$pkg" mode="preproc:gen-deps" />
</xsl:template>
<xsl:template match="*" mode="preproc:gen-deps">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:message>
<xsl:text>[depgen] *determining symbol dependencies...</xsl:text>
</xsl:message>
<xsl:apply-templates select="preproc:symtable" mode="preproc:depgen" />
<xsl:sequence select="*" />
</xsl:copy>
</xsl:template>
<xsl:template match="preproc:symtable" mode="preproc:depgen" priority="9">
<xsl:variable name="symtable" select="." />
<preproc:sym-deps>
<!-- process dependencies for all non-imported symbols -->
<xsl:for-each select="preproc:sym[ not( @src ) ]">
<xsl:variable name="cursym" select="." />
<xsl:variable name="deps">
<preproc:deps>
<xsl:apply-templates select="." mode="preproc:depgen" />
</preproc:deps>
</xsl:variable>
<!-- do not output duplicates (we used to not output references
to ourselves, but we are now retaining them, since those
data are useful) -->
<xsl:variable name="uniq" select="
$deps/preproc:deps/preproc:sym-ref[
not( @name=preceding-sibling::preproc:sym-ref/@name )
]
" />
<!-- symbols must not have themselves as their own dependency -->
<xsl:if test="$uniq[ not( $cursym/@allow-circular = 'true' )
and ( @name = $cursym/@name
or @parent = $cursym/@name ) ]">
<xsl:message terminate="yes"
select="concat( '[preproc] !!! fatal: symbol ',
$cursym/@name,
' references itself ',
'(circular dependency)' )" />
</xsl:if>
<!-- grab the original source symbol for these references and augment them
with any additional dependency metadata -->
<xsl:variable name="syms-rtf">
<xsl:for-each select="$uniq">
<xsl:variable name="name" select="@name" />
<xsl:variable name="sym" select="
$symtable/preproc:sym[ @name=$name ]
" />
<!-- we should never have this problem. -->
<xsl:if test="not( $sym ) and not( @lax='true' )">
<xsl:message terminate="yes">
<xsl:text>[depgen] internal error: </xsl:text>
<xsl:text>could not locate dependency symbol `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>' in local symbol table; needed by </xsl:text>
<xsl:value-of select="$cursym/@name" />
</xsl:message>
</xsl:if>
<!-- copy and augment (we set @name because $sym/@name may not exist
if @lax) -->
<preproc:sym name="{@name}">
<xsl:if test="$sym">
<xsl:sequence select="$sym/@*" />
</xsl:if>
<preproc:meta>
<!-- retain type -->
<xsl:sequence select="$sym/@type" />
<xsl:sequence select="$sym/@dim" />
<!-- copy any additional metadata -->
<xsl:sequence select="@*[ not( local-name() = 'name' ) ]" />
</preproc:meta>
</preproc:sym>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="syms" select="$syms-rtf/preproc:sym" />
<!-- only applicable if the symbol is @lax and the symbol was not
found in the local symbol table -->
<xsl:variable name="lax" select="
$uniq[
@lax='true'
and not( @name=$syms/@name )
]
" />
<preproc:sym-dep name="{@name}">
<!-- process symbols that have not been found in the local symbol
table (only applicable when cursym is @lax) -->
<xsl:for-each select="$lax">
<!-- the @lax flag here is simply to denote that this symbol may not
actually exist and that ignoring the check was explicitly
requested (and not a bug in the depgen process) -->
<preproc:sym-ref name="{@name}" lax="true">
<xsl:sequence select="preproc:meta/@*" />
</preproc:sym-ref>
</xsl:for-each>
<!-- @tex provided an non-empty, or function -->
<xsl:for-each select="
$syms[
( @tex and not( @tex='' ) )
or @type='func'
]">
<xsl:choose>
<!-- even if function, @tex overrides symbol -->
<xsl:when test="@tex and not( @tex='' )">
<preproc:sym-ref tex="{@tex}">
<xsl:sequence select="@*" />
<xsl:sequence select="preproc:meta/@*" />
</preproc:sym-ref>
</xsl:when>
<!-- must be a function; use its name -->
<xsl:otherwise>
<preproc:sym-ref>
<xsl:sequence select="@*" />
<xsl:sequence select="preproc:meta/@*" />
<xsl:attribute name="tex">
<xsl:text>\textrm{</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>}</xsl:text>
</xsl:attribute>
</preproc:sym-ref>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<!-- no @tex, @tex empty, no function -->
<xsl:for-each select="
$syms[
( not( @tex ) or @tex='' )
and not( @type='func' )
]">
<xsl:variable name="name" select="@name" />
<xsl:variable name="sym" select="." />
<preproc:sym-ref>
<!-- minimal attribute copy (avoid data duplication as much as
possible to reduce modification headaches later on) -->
<xsl:sequence select="@name, @parent" />
<xsl:sequence select="preproc:meta/@*" />
<!-- assign a symbol -->
<xsl:variable name="pos" select="position()" />
<xsl:attribute name="tex">
<xsl:variable name="texsym" select="
$tex-defaults/preproc:syms/preproc:sym[
position() = $pos
]
" />
<xsl:choose>
<xsl:when test="$sym/@tex and not( $sym/@tex='' )">
<xsl:value-of select="$sym/@tex" />
</xsl:when>
<!-- scalar/vector default -->
<xsl:when test="$texsym and number( $sym/@dim ) lt 2">
<xsl:value-of select="$texsym/@value" />
</xsl:when>
<!-- matrix default -->
<xsl:when test="$texsym">
<xsl:value-of select="$texsym/@vec" />
</xsl:when>
<!-- no default available; generate one -->
<xsl:otherwise>
<xsl:value-of select="
if ( number( $sym/@dim ) lt 2 ) then '\theta'
else '\Theta'
" />
<xsl:text>_{</xsl:text>
<xsl:value-of select="$pos" />
<xsl:text>}</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</preproc:sym-ref>
</xsl:for-each>
</preproc:sym-dep>
</xsl:for-each>
</preproc:sym-deps>
</xsl:template>
<xsl:template match="preproc:sym[ @extern='true' ]" mode="preproc:depgen" priority="9">
<!-- externs will be processed once they are resolved in another package -->
</xsl:template>
<!-- all symbols with a @parent (e.g. generators) should depend on the parent
itself (which of course introduces the parent's dependencies into the tree) -->
<xsl:template match="preproc:sym[ @parent ]" mode="preproc:depgen" priority="7">
<preproc:sym-ref name="{@parent}" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='rate' ]" mode="preproc:depgen" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<xsl:variable name="rate" as="element( lv:rate )"
select="$pkg/lv:rate[ @yields=$name ]" />
<xsl:apply-templates mode="preproc:depgen"
select="$rate" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='class' ]" mode="preproc:depgen" priority="5">
<!-- all class symbol names are prefixed with ":class:" -->
<xsl:variable name="as" select="substring-after( @name, ':class:' )" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<xsl:apply-templates
select="$pkg/lv:classify[ @as=$as ]"
mode="preproc:depgen" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='param' ]" mode="preproc:depgen" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:apply-templates
select="root(.)/lv:param[ @name=$name ]"
mode="preproc:depgen" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='func' ]" mode="preproc:depgen" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<xsl:apply-templates
select="$pkg/lv:function[ @name=$name ]"
mode="preproc:depgen" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='type' ]" mode="preproc:depgen" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<!-- a typedef could optionally be contained within another typedef -->
<xsl:apply-templates mode="preproc:depgen" select="
$pkg/lv:typedef[ @name=$name ]
, $pkg/lv:typedef//lv:typedef[ @name=$name ]
" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='lparam' ]" mode="preproc:depgen" priority="5">
<!-- do nothing -->
</xsl:template>
<xsl:template match="preproc:sym[ @type='const' ]" mode="preproc:depgen" priority="5">
<!-- do nothing -->
</xsl:template>
<xsl:template match="preproc:sym[ @type='tpl' ]" mode="preproc:depgen" priority="5">
<!-- do nothing -->
</xsl:template>
<xsl:template match="preproc:sym[ @type='meta' ]" mode="preproc:depgen" priority="5">
<!-- do nothing -->
</xsl:template>
<xsl:template match="preproc:sym" mode="preproc:depgen" priority="1">
<xsl:message terminate="yes">
<xsl:text>[depgen] error: unexpected symbol </xsl:text>
<xsl:sequence select="." />
</xsl:message>
</xsl:template>
<xsl:template name="preproc:depgen-c-normal" match="c:value-of|c:when" mode="preproc:depgen" priority="5">
<xsl:param name="name" select="@name" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<xsl:variable name="sym"
select="$pkg/preproc:symtable/preproc:sym[ @name=$name ]" />
<!-- see if there is a c:let associated with this name -->
<xsl:variable name="let" select="
ancestor::c:let[ c:values/c:value/@name=$name ]
" />
<xsl:choose>
<!-- c:let reference -->
<xsl:when test="$let">
<preproc:sym-ref name=":{$let/@name}:{$name}" />
</xsl:when>
<!-- scalar constant -->
<xsl:when test="( $sym/@type='const' ) and ( $sym/@dim='0' )">
<!-- while these are optimized away, they are still useful for evaluating
dependency trees and generating code -->
<preproc:sym-ref name="{$sym/@name}" />
</xsl:when>
<!-- function param reference -->
<xsl:when test="$name=ancestor::lv:function/lv:param/@name">
<xsl:variable name="fname" as="xs:string"
select="ancestor::lv:function/@name" />
<preproc:sym-ref name=":{$fname}:{$name}"
varname="{$name}"/>
</xsl:when>
<!-- index reference -->
<xsl:when test="$name=ancestor::c:*[ @of ]/@index" />
<!-- unknown symbol (it is important to do this after the above checks) -->
<xsl:when test="not( $sym )">
<!-- do not terminate; validator can provide additional information -->
<xsl:message>
<xsl:text>[depgen] warning: unknown symbol `</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:when>
<xsl:when test="$sym/@parent">
<preproc:sym-ref name="{$sym/@name}" parent="{$sym/@parent}" />
</xsl:when>
<!-- just an average 'ol symbol -->
<xsl:otherwise>
<preproc:sym-ref name="{$name}" />
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates mode="preproc:depgen" />
</xsl:template>
<xsl:template match="c:sum[@of]|c:product[@of]" mode="preproc:depgen" priority="5">
<!-- process using @of -->
<xsl:call-template name="preproc:depgen-c-normal">
<xsl:with-param name="name" select="@of" />
</xsl:call-template>
</xsl:template>
<xsl:template match="c:apply" mode="preproc:depgen" priority="5">
<!-- no special treatment yet -->
<xsl:call-template name="preproc:depgen-c-normal" />
</xsl:template>
<xsl:template match="c:apply/c:arg" mode="preproc:depgen" priority="5">
<!-- arguments may have calculations, so we must recurse -->
<xsl:apply-templates mode="preproc:depgen" />
</xsl:template>
<xsl:template match="c:let/c:values/c:value" mode="preproc:depgen" priority="5">
<!-- do not consider the c:value name -->
<xsl:apply-templates mode="preproc:depgen" />
</xsl:template>
<xsl:template name="preproc:depgen-match">
<xsl:param name="on" select="@on" />
<xsl:variable name="class" select="ancestor::lv:classify" />
<xsl:variable name="sym"
select="root(.)/preproc:symtable/preproc:sym[ @name=$on ]" />
<!-- are we depending on another classification? -->
<xsl:if test="$sym/@type='cgen'">
<xsl:variable name="cname" select="substring-after( $sym/@parent, ':class:' )" />
<!-- check if one of our dependencies wants to be external to the classifier,
but we're trying to pull them in...tug-of-war -->
<xsl:if test="$sym/@extclass='true' and not( $class/@external='true' )">
<xsl:message terminate="yes">
<xsl:text>[preproc] !!! fatal: internal classification `</xsl:text>
<xsl:value-of select="$class/@as" />
<xsl:text>' cannot pull in external classification `</xsl:text>
<xsl:value-of select="$cname" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:if>
</xsl:if>
<!-- process the @on -->
<xsl:call-template name="preproc:depgen-c-normal">
<xsl:with-param name="name" select="$on" />
</xsl:call-template>
</xsl:template>
<xsl:template match="lv:match[ @value ]" mode="preproc:depgen" priority="5">
<!-- process the @value -->
<xsl:call-template name="preproc:depgen-c-normal">
<xsl:with-param name="name" select="@value" />
</xsl:call-template>
<xsl:call-template name="preproc:depgen-match" />
</xsl:template>
<xsl:template match="lv:match[ @anyOf ]" mode="preproc:depgen" priority="6">
<!-- process the "normal" match -->
<xsl:call-template name="preproc:depgen-match" />
<!-- we depend on the type -->
<preproc:sym-ref name="{@anyOf}" />
<xsl:call-template name="preproc:depgen-match" />
</xsl:template>
<xsl:template match="lv:match[ @pattern ]" mode="preproc:depgen" priority="5">
<!-- there are no pattern dependencies; process @on -->
<xsl:call-template name="preproc:depgen-match" />
</xsl:template>
<!-- match on calculated value -->
<xsl:template match="lv:match[ c:* ]" mode="preproc:depgen" priority="6">
<!-- process the "normal" match -->
<xsl:call-template name="preproc:depgen-match" />
<!-- process the calculation dependencies -->
<xsl:apply-templates select="c:*" mode="preproc:depgen" />
</xsl:template>
<xsl:template match="lv:template/lv:param" mode="preproc:depgen" priority="9">
<!-- ignore -->
</xsl:template>
<xsl:template match="lv:param" mode="preproc:depgen" priority="5">
<!-- while the type is reduced to a primitive, let's still include the
dependency symbol -->
<preproc:sym-ref name="{@type}" />
</xsl:template>
<xsl:template match="lv:typedef" mode="preproc:depgen" priority="5">
<!-- we depend on any types that we create a union of -->
<xsl:for-each select="lv:union/lv:typedef">
<preproc:sym-ref name="{@name}" />
</xsl:for-each>
</xsl:template>
<!-- @class deps -->
<xsl:template match="lv:class" mode="preproc:depgen" priority="5">
<preproc:sym-ref name=":class:{@ref}" class-no="{@no}" />
</xsl:template>
<xsl:template match="c:*|lv:*" mode="preproc:depgen" priority="3">
<!-- ignore -->
<xsl:apply-templates mode="preproc:depgen" />
</xsl:template>
<xsl:template match="text()" mode="preproc:depgen" priority="2">
<!-- not interested. nope. -->
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,551 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Display-related tasks
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:sym="http://www.lovullo.com/rater/symbol-map"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:summary="http://www.lovullo.com/rater/summary"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<!-- maps certain elements to their default symbols -->
<xsl:variable name="symbol-map" select="document( 'symbol-map.xml' )/sym:symbol-map/*" />
<!-- easy-to-reference linked dependency list -->
<xsl:variable name="edeps" select="/lv:*/preproc:deps/preproc:sym" />
<xsl:template name="get-symbol-map">
<xsl:copy-of select="$symbol-map" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='rate' ]" mode="summary:desc" priority="5">
<span class="letlist-{@name}">
<a href="#{@name}">
<xsl:value-of select="@name" />
</a>
<xsl:text> scalar</xsl:text>
</span>
</xsl:template>
<xsl:template match="preproc:sym[ @type='gen' ]" mode="summary:desc" priority="5">
<span class="letlist-{@parent}">
<a href="#{@parent}">
<xsl:value-of select="@name" />
</a>
<xsl:text> generator; vector</xsl:text>
<span class="param">
<xsl:text> (</xsl:text>
<a href="#{@parent}">
<xsl:value-of select="@parent" />
</a>
<xsl:text>)</xsl:text>
</span>
</span>
</xsl:template>
<xsl:template match="preproc:sym[ @type='cgen' ]" mode="summary:desc" priority="5">
<xsl:variable name="parent" select="@parent" />
<xsl:variable name="sym" select="
ancestor::preproc:symtable/preproc:sym[ @name=$parent ]
" />
<xsl:apply-templates select="$sym" mode="summary:desc" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='class' ]" mode="summary:desc" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:variable name="document" select="
if ( @src ) then
document( concat( @src, '.xmlo' ), . )/lv:*
else
/lv:*
" />
<xsl:variable name="class" select="
$document/lv:classify[
@as=substring-after( $name, ':class:' )
]
" />
<span class="letlist-{$class/@as}">
<xsl:text>"</xsl:text>
<xsl:value-of select="$class/@desc" />
<xsl:text>"</xsl:text>
<xsl:text> classification </xsl:text>
<xsl:choose>
<xsl:when test="@dim = '0'">
<xsl:text>scalar</xsl:text>
</xsl:when>
<xsl:when test="@dim = '1'">
<xsl:text>vector</xsl:text>
</xsl:when>
<xsl:when test="@dim = '2'">
<xsl:text>matrix</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text> [dim </xsl:text>
<xsl:value-of select="@dim" />
<xsl:text>]</xsl:text>
</xsl:otherwise>
</xsl:choose>
<!-- TODO: use generator in letlist-* -->
<span class="param">
<xsl:text> (</xsl:text>
<a href="#:class:{$class/@as}">
<xsl:value-of select="$class/@as" />
</a>
<xsl:text>)</xsl:text>
</span>
</span>
</xsl:template>
<xsl:template match="preproc:sym[ @type='const' ]" mode="summary:desc" priority="5">
<xsl:value-of select="@name" />
</xsl:template>
<xsl:template match="preproc:sym[ @type='param' ]" mode="summary:desc" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:variable name="document" select="
if ( @src ) then
document( concat( @src, '.xmlo' ), . )/lv:*
else
/lv:*
" />
<xsl:variable name="param" select="
$document/lv:param[
@name=$name
]
" />
<xsl:value-of select="$param/@desc" />
<span class="param letlist-{$param/@name}">
<xsl:text> (</xsl:text>
<a href="#{$param/@name}">
<xsl:value-of select="$param/@name" />
</a>
<xsl:text>)</xsl:text>
</span>
</xsl:template>
<xsl:template match="preproc:sym" mode="summary:desc" priority="1">
<xsl:value-of select="@name" />
<xsl:text> (!)</xsl:text>
</xsl:template>
<xsl:template name="get-symbol">
<xsl:param name="name" select="@name" />
<xsl:param name="index" />
<xsl:param name="index-symbol" />
<xsl:param name="default" />
<preproc:sym-ref name="{$name}">
<!-- might be an empty string (if provided) -->
<xsl:if test="$default">
<xsl:attribute name="default" select="$default" />
</xsl:if>
</preproc:sym-ref>
<xsl:choose>
<xsl:when test="$index-symbol != ''">
<xsl:text>_{</xsl:text>
<xsl:value-of select="$index-symbol" />
<xsl:text>}</xsl:text>
</xsl:when>
<xsl:when test="$index">
<xsl:text>_{</xsl:text>
<preproc:sym-ref name="{$index}" default="{$index}" />
<xsl:text>}</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="_get-index-symbol">
<xsl:param name="element" />
<xsl:param name="index" />
<xsl:param name="search" />
<xsl:call-template name="get-symbol">
<xsl:with-param name="name" select="$index" />
<xsl:with-param name="search" select="$search" />
<xsl:with-param name="default" select="$index" />
</xsl:call-template>
</xsl:template>
<!--
Retrieve the default symbol for the given type (in LaTeX)
If the type is "function", the given name will be used for its default symbol.
@param Node element node to retrieve symbol for
@param NodeSet search all document nodes
@return default symbol (LaTeX)
-->
<xsl:template name="_get-default-symbol">
<xsl:param name="element" />
<xsl:param name="name" />
<xsl:param name="index" />
<xsl:param name="search" />
<xsl:variable name="type">
<xsl:choose>
<xsl:when test="
( local-name( $element ) = 'param' )
and ( local-name( $element/.. ) = 'function' )">
<!-- this is a function parameter; make a distinction between a global
parameter -->
<xsl:text>fparam</xsl:text>
</xsl:when>
<!-- if matching lv:classify/@as, then it represents an accumulator -->
<xsl:when test="
( local-name( $element ) = 'classify' )
and ( $element/@as = $name )
">
<xsl:text>class</xsl:text>
</xsl:when>
<xsl:when test="$element/@generates = $name">
<xsl:text>generator</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="local-name( $element )" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="symbol" select="$symbol-map[@type=$type]" />
<!-- output the symbol default -->
<xsl:choose>
<!-- certain types use their own name for a default (e.g. functions) -->
<xsl:when test="$symbol/sym:name">
<xsl:text>\textrm{</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>}</xsl:text>
</xsl:when>
<xsl:when test="$symbol/sym:nothing">
<!-- do nothing; no symbol is desired -->
</xsl:when>
<xsl:otherwise>
<xsl:if test="$index and ( $index != '' )">
<xsl:text>(</xsl:text>
</xsl:if>
<xsl:value-of select="$symbol" />
<!-- determine if our default index should be subscript or superscript -->
<xsl:variable name="subsup">
<xsl:choose>
<xsl:when test="$symbol/@index-pos">
<xsl:value-of select="$symbol/@index-pos" />
</xsl:when>
<!-- default to subscript -->
<xsl:otherwise>
<xsl:text>_</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- in addition to the symbol itself, which alone is not likely to be
unique, we will add a subscript to uniquely identify it by number -->
<xsl:if test="$search">
<xsl:value-of select="$subsup" />
<xsl:text>{</xsl:text>
<xsl:call-template name="_get-name-index">
<xsl:with-param name="element" select="$element" />
<xsl:with-param name="name" select="$name" />
<xsl:with-param name="search" select="$search" />
</xsl:call-template>
<xsl:text>}</xsl:text>
</xsl:if>
<xsl:if test="$index and ( $index != '' )">
<xsl:text>)</xsl:text>
</xsl:if>
<!-- if an index was given, and our default index was *not* a subscript,
then we can dedicate the subscript to the index -->
<xsl:if test="$index and ( $index != '' )">
<xsl:text>_{</xsl:text>
<xsl:value-of select="$index" />
<xsl:text>}</xsl:text>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
Retrieve index of the element associated with the given name across all named
elements of the same type and parent type in all of $search
TODO: surely there's a more performant manner...not that speed is an issue
right now
@param Node element node to retrieve symbol for
@param NodeSet search all document nodes
@return index
-->
<xsl:template name="_get-name-index">
<xsl:param name="element" />
<xsl:param name="name" />
<xsl:param name="search" />
<xsl:choose>
<!-- functions are handled slightly differently, as they introduce scope -->
<xsl:when test="local-name( $element/.. ) = 'function'">
<xsl:for-each select="$element/../lv:param">
<xsl:if test="@name = $name">
<xsl:value-of select="position()" />
</xsl:if>
</xsl:for-each>
</xsl:when>
<!-- non-function -->
<xsl:otherwise>
<xsl:value-of select="
$search//summary:default-indexes/summary:index[ @name=$name ]/@value"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
Retrieve description for the given element by name
$vardesc, for those who support it, is useful if the description describes the
node, not a variable generated from it. For example, lv:classify's description
is a short description of the classification, but if documenting @yields, we
want to describe what it is yielding, which would not be immediately clear
from the description.
@param string name name of element
@param NodeSet search all documents to search
@return element description
-->
<xsl:template name="get-desc">
<xsl:param name="name" />
<xsl:param name="search" />
<!-- XXX: Have to maintain this list! -->
<xsl:variable name="desc"
select="$search//summary:descs/summary:desc[ @name=$name ]/@desc" />
<xsl:choose>
<xsl:when test="$desc">
<xsl:copy-of select="$desc" />
</xsl:when>
<!-- if we cannot find the element, then display an error -->
<xsl:otherwise>
<span class="error">
<xsl:text>Unknown @name reference: </xsl:text>
<xsl:value-of select="$name" />
</span>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
Retrieve processed name for the given element by name
@param string name name of element
@param NodeSet search all documents to search
@return element description
-->
<xsl:template name="get-name">
<xsl:param name="name" />
<xsl:param name="search" />
<xsl:value-of select="
$search//summary:descs/summary:desc[ @name=$name ]/@display
" />
</xsl:template>
<xsl:template match="lv:rate" mode="gen-let-list" priority="5">
<xsl:param name="deps" />
<xsl:param name="context" />
<xsl:call-template name="do-gen-let-list">
<xsl:with-param name="symname" select="@yields" />
<xsl:with-param name="context" select="$context" />
</xsl:call-template>
</xsl:template>
<xsl:template match="lv:function" mode="gen-let-list" priority="5">
<xsl:param name="deps" />
<xsl:call-template name="do-gen-let-list">
<xsl:with-param name="symname" select="@name" />
</xsl:call-template>
</xsl:template>
<xsl:template match="*" mode="gen-let-list" priority="1">
<xsl:message terminate="yes">
<xsl:text>[summary] !!! unknown let-list type </xsl:text>
<xsl:value-of select="name()" />
</xsl:message>
</xsl:template>
<!--
Generate list of let statements describing each variable in the given node set
Variables come from various sources depending on the operation being
performed.
-->
<xsl:template name="do-gen-let-list">
<xsl:param name="context" />
<xsl:param name="symname" />
<xsl:variable name="deps" select="
/lv:*/preproc:sym-deps/preproc:sym-dep[
@name=$symname
]
" />
<ul class="let">
<!-- output a description for each dependency -->
<xsl:variable name="result">
<xsl:for-each select="
/lv:*/preproc:symtable/preproc:sym[
not( @type='lparam' )
and @name=$deps/preproc:sym-ref/@name
]
">
<xsl:call-template name="_gen-let-list-item">
<xsl:with-param name="context" select="$context" />
</xsl:call-template>
</xsl:for-each>
<!-- handle c:let formatting separately -->
<xsl:for-each select="
/lv:*/preproc:symtable/preproc:sym[
@type='lparam'
and @name=$deps/preproc:sym-ref/@name
]
">
<xsl:call-template name="_gen-let-list-item">
<xsl:with-param name="context" select="$context" />
<xsl:with-param name="class" select="'letequ'" />
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:apply-templates select="$result" mode="typeset-final">
<xsl:with-param name="deps" select="$deps" />
</xsl:apply-templates>
</ul>
</xsl:template>
<xsl:template name="_gen-let-list-item">
<xsl:param name="context" />
<xsl:param name="class" />
<li>
<xsl:if test="$class">
<xsl:attribute name="class" select="$class" />
</xsl:if>
<xsl:choose>
<xsl:when test="@type='lparam' and $context">
<xsl:text>\(</xsl:text>
<preproc:sym-ref name="{@name}" />
<xsl:text> = </xsl:text>
<xsl:variable name="varname" select="@varname" />
<xsl:apply-templates select="
$context//c:let/c:values/c:value[
@name=$varname
]/c:*
" />
<xsl:text>\) </xsl:text>
<span class="letdesc">
<xsl:text>(</xsl:text>
<xsl:value-of select="@desc" />
<xsl:text>)</xsl:text>
</span>
</xsl:when>
<xsl:otherwise>
<xsl:text>let \(</xsl:text>
<preproc:sym-ref name="{@name}" />
<xsl:text>\) = </xsl:text>
<xsl:apply-templates select="." mode="summary:desc" />
</xsl:otherwise>
</xsl:choose>
<!--
<xsl:variable name="param-name">
<xsl:call-template name="get-name">
<xsl:with-param name="name" select="$param" />
<xsl:with-param name="search" select="/" />
</xsl:call-template>
</xsl:variable>
<xsl:if test="$param-name != ''">
<span class="param letlist-{$param-name}">
<xsl:text> (</xsl:text>
<a href="#{$param-name}">
<xsl:value-of select="$param-name" />
</a>
<xsl:text>)</xsl:text>
</span>
</xsl:if>
-->
</li>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Additional functionality provided by dslc
XSL does not provide every feature suitable for compilation (which is no
suprise, since this was not its intended use case). As such, dslc provides
additional features that are defined/abstracted within this file; every
template that is intended for use with dslc should include this.
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!--
Package source path, stripped of its extension
XSL does not provide a means of exposing the file path (nor should it,
really). This param will hold the source path of the package, sans its
extension, relative to the project root that was used during compilation.
I.e., given this source path:
suppliers/common/foo.xml
we would expect this value for __srcpkg:
suppliers/common/foo
By stripping the extension, we have the benefit of being void of any semantics
that may be associated with it (e.g. xml vs xmlo vs xmle); rather, that
information should be derived from the structe of the document itself and the
path can be used as an identifier to describe the document as a whole,
regardless of what form it is in.
Consequently, no two files are able to have the same __srcpkg string; this
value may therefore be used for disambiguation.
-->
<xsl:param name="__srcpkg" />
<!--
Relative path to project root
The project root is determined entirely by __srcpath by repeating the string
"../" for the number of occurrances of "/".
-->
<xsl:param name="__relroot" />
<!--
Random value that may be used to seed random values
XSLT is deterministic and does not offer support for generating random values;
its generate-id() function is not sufficient for cross-package generation.
-->
<xsl:param name="__rseed" />
<!--
Root node of template on which stylesheet was invoked
This points to the original, unprocessed document. This is especially
important for `document' function calls, which use nodes as a reference
point for resolving relative paths.
-->
<xsl:variable name="__entry-root" select="/" />
<!--
Apply relative root to PATH
If PATH is an absolute path, it will be prefixed with the relative root
with the leading path delimiter stripped; otherwise, it will be echoed
as-is.
-->
<xsl:template name="__apply-relroot">
<xsl:param name="path" />
<xsl:choose>
<xsl:when test="starts-with( $path, '/' )">
<xsl:value-of select="$__relroot" />
<xsl:value-of select="substring-after( $path, '/' )" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$path" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,323 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Outputs HTML form that can be used to feed values to the rater for testing
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:l="http://www.lovullo.com/rater/linker"
xmlns:util="http://www.lovullo.com/util"
xmlns:ext="http://www.lovullo.com/ext"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:exsl="http://exslt.org/common"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="exsl str">
<!--
Generate HTML entry form for testing
Allows for collection of data to feed to the rater.
The entry form will only be generated for raters, not other packages (since
actual rating will need to be performed).
@return form HTML
-->
<xsl:template match="lv:package" mode="entry-form">
<xsl:param name="root-pkg" />
<form class="entry-form">
<h1>Rating Test Case</h1>
<div class="foot">
<p id="prior-message"></p>
<div>
<input type="submit" value="Calculate Premium" />
<input type="reset" value="Reset" />
</div>
<div class="final-premium"></div>
<div class="final-accept">
<button id="final-accept-good">Looks Good!</button>
<button id="final-accept-bad">Incorrect</button>
</div>
<div class="final-comments">
<h1>Submit Test Case</h1>
<p>Submission comments (please describe what you were testing, the
desired result and, if the premium was incorrect, what went wrong):</p>
<textarea id="final-comments"></textarea>
<div id="final-expect-container">
<p>Expected premium (if known; must be exact); this will allow us to
automatically re-run this test when we believe that the problem has been
fixed. <strong>Otherwise, you must re-test manually:</strong></p>
<input type="text" id="final-expected" value="" />
<em>(Only fill out if it does not hit the minimum premium.)</em>
</div>
<br />
<label><input type="checkbox" id="final-waiting"> Requires Testing</input></label>
<br />
<button id="final-submit">Submit</button>
<button id="final-submit-new">Submit As New Test Case</button>
<button id="final-cancel">Nevermind. Cancel.</button>
</div>
</div>
<dl>
<!-- generate HTML elements for each *global* parameter, *but only if it
is used in the rater* -->
<xsl:apply-templates
select="/lv:package/l:dep/preproc:sym[ @type='param' ]"
mode="entry-form">
<xsl:with-param name="root-pkg" select="$root-pkg" />
</xsl:apply-templates>
</dl>
</form>
<script type="text/javascript" src="{$fw-path}/rater/scripts/entry-form.js"></script>
</xsl:template>
<!--
Generate text and input for a global parameter
@return parameter HTML
-->
<xsl:template match="preproc:sym" mode="entry-form">
<xsl:param name="root-pkg" />
<xsl:variable name="self" select="." />
<xsl:variable name="package" select="
if ( @src and not( @src='' ) ) then
document( concat( @src, '.xmlo' ), . )/lv:*
else
$root-pkg
" />
<xsl:variable name="name">
<xsl:value-of select="@name" />
<!-- if this is a set, then we will need to generate an array of
elements -->
<xsl:if test="number(@dim) gt 0">
<xsl:text>[]</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:variable name="param"
select="$package/lv:param[ @name=$self/@name ]" />
<dt id="param-{@name}">
<xsl:value-of select="@desc" />
</dt>
<xsl:variable name="matrix">
<xsl:if test="number(@dim) gt 1">
<xsl:text> matrix</xsl:text>
</xsl:if>
</xsl:variable>
<!-- generate field itself -->
<dd id="param-input-{@name}">
<div class="entry-row{$matrix}">
<div class="entry-field">
<xsl:apply-templates select="$param" mode="entry-form-field">
<xsl:with-param name="name" select="$name" />
<xsl:with-param name="sym" select="$self" />
<xsl:with-param name="pkg" select="$package" />
</xsl:apply-templates>
<!-- if this is a set, add the ability to remove values -->
<xsl:if test="number(@dim) gt 0">
<button class="entry-rm">-</button>
</xsl:if>
</div>
<xsl:if test="number(@dim) gt 1">
<button class="entry-add-matrix">+</button>
</xsl:if>
</div>
<!-- if this is a set, add ability to add values -->
<xsl:if test="number(@dim) gt 0">
<button class="entry-add">+</button>
</xsl:if>
</dd>
</xsl:template>
<!--
Generate input field for integer parameters
@return parameter HTML
-->
<xsl:template match="lv:param[@type='integer']" mode="entry-form-field">
<xsl:param name="name" select="@name" />
<input type="text" name="{$name}" value="{@default}" />
</xsl:template>
<!--
Generate input field for float parameters
@return parameter HTML
-->
<xsl:template match="lv:param[@type='float']" mode="entry-form-field">
<xsl:param name="name" select="@name" />
<input type="text" name="{$name}" value="{@default}" />
</xsl:template>
<!--
Generate radio fields for boolean parameters
@return parameter HTML
-->
<xsl:template match="lv:param[@type='boolean']" mode="entry-form-field">
<xsl:param name="name" select="@name" />
<xsl:variable name="default-y">
<xsl:if test="@default = '1'">
<xsl:text>selected</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:variable name="default-n">
<xsl:if test="@default = '0'">
<xsl:text>selected</xsl:text>
</xsl:if>
</xsl:variable>
<select name="{$name}">
<option selected="{$default-y}" value="1">Yes</option>
<option selected="{$default-n}" value="0">No</option>
</select>
</xsl:template>
<!--
Handle parameters that are either of unknown or user-defined types
@return parameter HTML
-->
<xsl:template match="lv:param" mode="entry-form-field">
<xsl:param name="name" select="@name" />
<xsl:param name="sym" />
<xsl:param name="pkg" />
<xsl:variable name="type" select="@type" />
<!-- the typedef may or may not be in the same package as the param -->
<xsl:variable name="typesym" select="
$pkg/preproc:symtable/preproc:sym[
@type='type'
and @name=$type
]
" />
<!-- if the @src attribute is empty, then it resides within the same package
-->
<xsl:variable name="typesrc">
<xsl:choose>
<xsl:when test="@src">
<xsl:value-of select="@src" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$sym/@src" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- load the typedef from the appropriate package -->
<xsl:variable name="typepkg" select="
if ( @src and not( @src='' ) ) then
document( concat( $typesrc, '.xmlo' ), $sym )/lv:*
else
$pkg
" />
<!-- this makes maintinance more difficult, but speeds up searching large
trees -->
<xsl:variable name="typedef" select="
$typepkg/lv:typedef[ @name=$type ]
|$typepkg/lv:typedef/lv:union/lv:typedef[ @name=$type ]
" />
<xsl:choose>
<xsl:when test="$typedef/lv:enum|$typedef/lv:union">
<xsl:apply-templates select="." mode="entry-form-field-enum">
<xsl:with-param name="name" select="$name" />
<xsl:with-param name="typedef" select="$typedef" />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:message>
<xsl:text>[summary] warning: unknown param type `</xsl:text>
<xsl:value-of select="$typesym/@src" />
<xsl:text>/</xsl:text>
<xsl:value-of select="@type" />
<xsl:text>'</xsl:text>
</xsl:message>
<span class="error">
<xsl:text>Unknown type: </xsl:text>
<xsl:value-of select="@type" />
</span>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
Generate HTML for enumerated lists
@return parameter HTML
-->
<xsl:template match="lv:param" mode="entry-form-field-enum">
<xsl:param name="name" select="@name" />
<xsl:param name="typedef" />
<xsl:variable name="type" select="@type" />
<!-- get all fields, even if they're within a union -->
<xsl:variable name="fields" select="$typedef//lv:enum/lv:item" />
<select name="{$name}" value="{@default}">
<option value=""></option>
<xsl:for-each select="$fields">
<xsl:variable name="value">
<xsl:choose>
<xsl:when test="@value">
<xsl:value-of select="@value" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@name" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<option value="{$value}">
<xsl:value-of select="@name" />
<xsl:text>: </xsl:text>
<xsl:value-of select="@desc" />
</option>
</xsl:for-each>
</select>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,65 @@
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="str">
<xsl:template name="str:tokenize">
<xsl:param name="string" select="''" />
<xsl:param name="delimiters" select="' &#x9;&#xA;'" />
<xsl:choose>
<xsl:when test="not($string)" />
<xsl:when test="not($delimiters)">
<xsl:call-template name="str:_tokenize-characters">
<xsl:with-param name="string" select="$string" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="str:_tokenize-delimiters">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="delimiters" select="$delimiters" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="str:_tokenize-characters">
<xsl:param name="string" />
<xsl:if test="$string">
<token><xsl:value-of select="substring($string, 1, 1)" /></token>
<xsl:call-template name="str:_tokenize-characters">
<xsl:with-param name="string" select="substring($string, 2)" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="str:_tokenize-delimiters">
<xsl:param name="string" />
<xsl:param name="delimiters" />
<xsl:variable name="delimiter" select="substring($delimiters, 1, 1)" />
<xsl:choose>
<xsl:when test="not($delimiter)">
<token><xsl:value-of select="$string" /></token>
</xsl:when>
<xsl:when test="contains($string, $delimiter)">
<xsl:if test="not(starts-with($string, $delimiter))">
<xsl:call-template name="str:_tokenize-delimiters">
<xsl:with-param name="string" select="substring-before($string, $delimiter)" />
<xsl:with-param name="delimiters" select="substring($delimiters, 2)" />
</xsl:call-template>
</xsl:if>
<xsl:call-template name="str:_tokenize-delimiters">
<xsl:with-param name="string" select="substring-after($string, $delimiter)" />
<xsl:with-param name="delimiters" select="$delimiters" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="str:_tokenize-delimiters">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="delimiters" select="substring($delimiters, 2)" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,268 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
No, not "odorizer".
This is a powerful system that takes an immensely complex (and insurmountable)
task out of the programmer's hands. In particular, the system:
- Uses the map to recognize the order in which params appear in a UI
- Using that order, determines which fields in the UI could potentially
cause fields that appear previous to them to become invalid (e.g. hide)
due to classification criteria.
- Based on those conflicts, constructs a custom classification that ignores
only the conflicting portions, allowing a great deal of precision in
controlling field validity up to that particular point in the UI.
The result is highly-refined, custom-generated classifications per-question
for only the criteria that are needed to ensure that fields cannot hide fields
previous to them: A task that would otherwise be prohibitively complicated (or
would otherwise have to be far too coarse) if done manually in systems with
highly sophisticated classification schemes. Furthermore, the result is wholly
deterministic.
It should be noted that futher benefit can be realized when the map is altered
or questions in the UI are reordered; the system will simply re-calculate new
classifications that yield desirable results.
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:lvp="http://www.lovullo.com"
xmlns:lvm="http://www.lovullo.com/rater/map"
xmlns:lvmc="http://www.lovullo.com/rater/map/compiler"
xmlns:gc="http://www.lovullo.com/calc/global-classifier"
xmlns:c="http://www.lovullo.com/calc"
xmlns:o="http://www.lovullo.com/rater/compiler/orderizer"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<!--
Determines param conflicts based on UI ordering
-->
<xsl:template name="o:analyze">
<xsl:param name="class-deps" />
<xsl:param name="ui" />
<xsl:param name="sdmap" />
<xsl:param name="param-classes" />
<xsl:variable name="rater" select="." />
<!-- put fields in the order that they appear in the UI -->
<!-- TODO: duplicate check (error) -->
<xsl:variable name="ordered">
<o:ordered>
<xsl:for-each select="$ui//lvp:question">
<xsl:variable name="id" select="@id" />
<xsl:copy-of select="$sdmap/lvmc:map[ @from=$id ]" />
</xsl:for-each>
</o:ordered>
</xsl:variable>
<!-- the param-class list gives us the classes that are directly used; let's
get a list of all classes that are used by those classes as well, which
will simplify the queries to come -->
<xsl:message>[orderizer] recursively discovering param classes...</xsl:message>
<xsl:variable name="param-classes-expanded">
<xsl:apply-templates select="$param-classes" mode="o:gen-param-reflist">
<xsl:with-param name="class-deps" select="$class-deps" />
</xsl:apply-templates>
</xsl:variable>
<xsl:variable name="initial">
<o:analysis>
<xsl:for-each select="$ordered//lvmc:*">
<xsl:variable name="cur" select="." />
<xsl:variable name="pref" select="@to" />
<!-- progress indicator -->
<xsl:message>
<xsl:text>[orderizer] checking previous UI siblings: </xsl:text>
<xsl:value-of select="@to" />
</xsl:message>
<!-- preceding -->
<xsl:for-each select="
$param-classes/gc:param[
@ref=$cur/preceding-sibling::lvmc:map/@to
]">
<!-- immediate conflicts (we check these separately rather than in the
xpath above, which would be cleaner, so that they can be
processed -->
<xsl:variable name="conflicts" select="
.//gc:class[
@ref=$param-classes-expanded/o:param-refs/o:param[
@ref=$pref
]/o:class/@ref
]
" />
<xsl:if test="$conflicts">
<o:conflict ref="{@ref}" relative-to="{$pref}">
<xsl:for-each select="$conflicts">
<!-- record the immediate problem -->
<o:class ref="{@ref}" yields="{@yields}" />
</xsl:for-each>
</o:conflict>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</o:analysis>
</xsl:variable>
<xsl:apply-templates select="$initial/o:analysis" mode="o:reduce-analysis" />
</xsl:template>
<xsl:template match="gc:param-classes" mode="o:gen-param-reflist" priority="5">
<xsl:param name="class-deps" />
<o:param-refs>
<xsl:apply-templates select="gc:param" mode="o:gen-param-reflist">
<xsl:with-param name="class-deps" select="$class-deps" />
</xsl:apply-templates>
</o:param-refs>
</xsl:template>
<xsl:template match="gc:param" mode="o:gen-param-reflist" priority="5">
<xsl:param name="class-deps" />
<o:param ref="{@ref}">
<xsl:apply-templates select="gc:class" mode="o:gen-param-reflist">
<xsl:with-param name="class-deps" select="$class-deps" />
</xsl:apply-templates>
</o:param>
</xsl:template>
<xsl:template match="gc:class" mode="o:gen-param-reflist" priority="5">
<xsl:param name="class-deps" />
<!-- well, this class is certainly used -->
<o:class ref="{@ref}" />
<xsl:variable name="ref" select="@ref" />
<!-- but how about things that use this class? -->
<xsl:apply-templates select="$class-deps/preproc:class[ @ref=$ref ]"
mode="o:gen-param-reflist">
<xsl:with-param name="class-deps" select="$class-deps" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="preproc:class" mode="o:gen-param-reflist" priority="5">
<xsl:param name="class-deps" />
<o:class ref="{@ref}" />
<xsl:apply-templates mode="o:gen-param-reflist">
<xsl:with-param name="class-deps" select="$class-deps" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*" mode="o:gen-param-reflist" priority="1">
<!-- do nothing -->
</xsl:template>
<xsl:template match="preproc:class" mode="o:get-indirect-params" priority="5">
<xsl:apply-templates mode="o:get-indirect-params" />
</xsl:template>
<xsl:template match="preproc:class-dep" mode="o:get-indirect-params" priority="5">
<xsl:variable name="ref" select="@ref" />
<xsl:apply-templates
select="ancestor::preproc:class-deps/preproc:class[ @ref=$ref ]"
mode="o:get-indirect-params" />
</xsl:template>
<xsl:template match="preproc:rate-dep" mode="o:get-indirect-params" priority="5">
<xsl:variable name="ref" select="@ref" />
<xsl:apply-templates
select="//preproc:rate-deps/preproc:rate[ @ref=$ref ]"
mode="o:get-indirect-params" />
</xsl:template>
<xsl:template match="preproc:param" mode="o:get-indirect-params" priority="5">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="*" mode="o:get-indirect-params" priority="1">
<!-- do nothing -->
</xsl:template>
<!--
Combines initial analysis results, merging common refs and de-duplicating
conflicts.
-->
<xsl:template match="o:analysis" mode="o:reduce-analysis">
<!-- start by combining the contents of all unique refs -->
<xsl:variable name="combined">
<xsl:for-each select="
o:conflict[
not( @ref=preceding-sibling::o:conflict/@ref )
]
">
<xsl:variable name="ref" select="@ref" />
<xsl:variable name="dups" select="
following-sibling::o:conflict[ @ref=$ref ]
" />
<o:conflict ref="{@ref}">
<!-- add relativity nodes -->
<xsl:for-each select="$dups|.">
<o:relative-to ref="{@relative-to}" />
</xsl:for-each>
<!-- add class deps -->
<o:reasons>
<xsl:copy-of select="($dups|.)/o:class" />
</o:reasons>
</o:conflict>
</xsl:for-each>
</xsl:variable>
<!-- now, remove duplicates resulting from the above operation -->
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates select="$combined/o:conflict"
mode="o:reduce-analysis" />
</xsl:copy>
</xsl:template>
<xsl:template match="o:conflict" mode="o:reduce-analysis">
<xsl:copy>
<xsl:copy-of select="@*" />
<!-- unique relative-to nodes -->
<xsl:copy-of select="
o:relative-to[
not( @ref=preceding-sibling::o:relative-to/@ref )
]
" />
<xsl:apply-templates select="o:reasons" mode="o:reduce-analysis" />
</xsl:copy>
</xsl:template>
<xsl:template match="o:reasons" mode="o:reduce-analysis">
<xsl:copy>
<xsl:copy-of select="@*" />
<!-- unique reasons -->
<xsl:copy-of select="
o:class[
not( @ref=preceding-sibling::o:class/@ref )
]
" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,226 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Compiles domains from typedefs
The ultime goal is to implement typedefs as macros and move to a generic
domain system that is much more powerful.
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc">
<!--
Base typedefs (internal) are declarations only
TODO: We still want a domain generated.
-->
<xsl:template match="lv:typedef[ lv:base-type ]"
mode="preproc:mkdomain" priority="9">
</xsl:template>
<!--
Union typedefs
This generates not only the contained typedefs, but also a denormalized
domain containing the union of all the subdomain elements. This is
intended to improve lookup performance and reduce algorithmic complexity.
-->
<xsl:template match="
lv:typedef[
lv:union
]
"
mode="preproc:mkdomain" priority="5">
<!-- generate all contained domains -->
<xsl:variable name="subdomains">
<xsl:variable name="union-types" select="
lv:union/lv:typedef" />
<!-- if a union is empty, then somebody probably made an oopsie or wrote
a defective/deficient template -->
<xsl:if test="count( $union-types ) = 0">
<xsl:message>
<xsl:text>warning: union `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>' has no subdomains; something is probably wrong</xsl:text>
</xsl:message>
</xsl:if>
<xsl:apply-templates mode="preproc:mkdomain"
select="$union-types" />
</xsl:variable>
<!-- provide a denormalized domain for performance and to reduce
algorithmic complexity-->
<xsl:call-template name="preproc:mkdomain-union">
<xsl:with-param name="name" select="@name" />
<xsl:with-param name="subdomains" select="$subdomains" />
</xsl:call-template>
<xsl:copy-of select="$subdomains" />
</xsl:template>
<!--
Enumerated typedefs
-->
<xsl:template match="
lv:typedef[
lv:enum
]
"
mode="preproc:mkdomain" priority="5">
<lv:domain name="{@name}">
<xsl:variable name="items" select="lv:enum/lv:item" />
<!-- if a typedef is empty, then somebody probably made an oopsie or
wrote a defective/deficient template -->
<xsl:if test="count( $items ) = 0">
<xsl:message>
<xsl:text>warning: typedef `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>' is empty; something is probably wrong</xsl:text>
</xsl:message>
</xsl:if>
<xsl:apply-templates mode="preproc:mkdomain"
select="$items" />
</lv:domain>
</xsl:template>
<!--
Prohibit mixing explicit and auto-generated values
For the time being at least.
-->
<xsl:template match="
lv:typedef[
lv:enum/lv:item[ @value ]
and lv:enum/lv:item[ not( @value ) ]
]"
mode="preproc:mkdomain" priority="2">
<xsl:message terminate="yes">
<xsl:text>error: typedef `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>' must not contain both @value and non-@value items</xsl:text>
</xsl:message>
</xsl:template>
<!--
Unsupported typedef
Well, we know that it is a typedef, but its format is unknown. This
wouldn't be surprising, since it is presently very limited.
-->
<xsl:template match="lv:typedef"
mode="preproc:mkdomain" priority="2">
<xsl:message terminate="yes">
<xsl:text>error: malformed typedef `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:template>
<!--
Generate a denormalized domain consisting of the union of its subdomains'
elements
As this is a union, duplicate elements will be removed; the user will not
be notified of this fact, as this allows domains to overlap in order to
interpret the same data in different manners.
-->
<xsl:template name="preproc:mkdomain-union">
<xsl:param name="name" />
<xsl:param name="subdomains" />
<xsl:variable name="union">
<preproc:elements>
<xsl:copy-of select="$subdomains/lv:domain/lv:element" />
</preproc:elements>
</xsl:variable>
<!-- remove duplicate values (yes, this will take the first description if
there are duplicates; whatever, for now) -->
<lv:domain name="{@name}">
<xsl:copy-of select="
$union/preproc:elements/lv:element[
not( @value = preceding-sibling::lv:element/@value )
]" />
</lv:domain>
</xsl:template>
<!--
Enumerated items without values require calculation
Note the above validation that ensures that our value generation is
sufficient.
-->
<xsl:template match="lv:enum/lv:item[ not( @value ) ]"
mode="preproc:mkdomain" priority="5">
<xsl:variable name="augmented">
<xsl:copy>
<xsl:attribute name="value" select="position()" />
</xsl:copy>
</xsl:variable>
<!-- re-process using an augmented item with the value calculated -->
<xsl:apply-templates mode="preproc:mkdomain"
select="$augmented/lv:item" />
</xsl:template>
<!--
Convert typedef item into a domain element
This is a straightforward rename with sanity checking. Note that the
element may have an empty description.
We do not care about the name, since we use that to generate constants
elsewhere.
-->
<xsl:template match="lv:item"
mode="preproc:mkdomain" priority="4">
<!-- previous templates should have prevented this, but just in case -->
<xsl:if test="not( @value )">
<xsl:message terminate="yes">
<xsl:text>internal error: preproc:mkdomain on non-value item: </xsl:text>
<xsl:copy-of select="." />
</xsl:message>
</xsl:if>
<lv:element value="{@value}" desc="{@desc}" />
</xsl:template>
<!--
Unexpected node; terminate
-->
<xsl:template match="*"
mode="preproc:mkdomain" priority="1">
<xsl:message terminate="yes">
<xsl:text>internal error: unknown domain source: </xsl:text>
<xsl:copy-of select="." />
</xsl:message>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,261 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Package eligibility class generation
Here, the term "eligibility" means whether the package is eligible to be used
in a result set basead on the values of its params within their respective
domains and other factors such as the results of terminating classifications
and the eligibility of imported packages.
The goal of the eligibility classification is to create a cascading failure in
the event of bad data.
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc">
<!--
Trigger eligibility class generation
-->
<xsl:template match="lv:package[ not( @preproc:elig-class-yields ) ]"
as="element( lv:package )"
priority="5"
mode="preproc:expand-elig-class">
<xsl:param name="orig-root" as="element( lv:package )" />
<xsl:variable name="elig-class" as="element( lv:classify )">
<xsl:apply-templates select="." mode="preproc:gen-elig-class">
<xsl:with-param name="orig-root" select="$orig-root" />
</xsl:apply-templates>
</xsl:variable>
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:attribute name="preproc:elig-class"
select="$elig-class/@as" />
<xsl:attribute name="preproc:elig-class-yields"
select="$elig-class/@yields" />
<xsl:sequence select="$elig-class" />
<xsl:apply-templates mode="preproc:macros" />
</xsl:copy>
</xsl:template>
<xsl:template match="lv:package" as="element( lv:package )"
priority="1"
mode="preproc:expand-elig-class">
<!-- already processed -->
<xsl:sequence select="." />
</xsl:template>
<!--
Generate eligibility classification asserting all data integrity aspects of a
package
The eligibility classification will yield a scalar.
-->
<xsl:template match="lv:package" as="element( lv:classify )"
mode="preproc:gen-elig-class">
<xsl:param name="orig-root" as="element( lv:package )" />
<xsl:message>[preproc/eligclass] generating eligibility class</xsl:message>
<!-- class-ify name -->
<xsl:variable name="as" as="xs:string"
select="preproc:gen-elig-class-name( @name )" />
<xsl:variable name="yields" as="xs:string"
select="preproc:gen-elig-class-yields( @name )" />
<lv:classify as="{$as}" yields="{$yields}"
desc="{@name} package is eligible">
<!-- TODO: this should really be a compile-time value -->
<xsl:if test="@keep-elig-class = 'true'">
<xsl:attribute name="keep" select="'true'" />
</xsl:if>
<!-- each of our imported packages' elig classes must be truthful -->
<xsl:apply-templates mode="preproc:gen-elig-class-matches"
select="lv:import">
<xsl:with-param name="orig-root" select="$orig-root" />
</xsl:apply-templates>
<!-- param values must be within their domain -->
<!-- XXX: does not work when param is undefined due to no mapping
<xsl:apply-templates mode="preproc:gen-elig-param-class"
select="lv:param" />
-->
<!-- terminating classifications must not have matched -->
<xsl:apply-templates mode="preproc:gen-elig-term-class"
select="preproc:symtable/preproc:sym[ @type='class' ]" />
</lv:classify>
</xsl:template>
<!--
Generate eligibility classification name for package
-->
<xsl:function name="preproc:gen-elig-class-name"
as="xs:string">
<xsl:param name="name" />
<xsl:sequence select="
concat( '--elig-',
translate(
translate( $name, '.', '' ),
'/', '-'
)
)
" />
</xsl:function>
<!--
Generate eligibility result scalar name for package
-->
<xsl:function name="preproc:gen-elig-class-yields"
as="xs:string">
<xsl:param name="name" />
<xsl:sequence select="
concat(
'isElig',
translate(
translate( $name, '.', '' ),
'/-', '' ) )" />
</xsl:function>
<!--
Generate matches on eligibility of imported packages
For each imported package, its eligibility classification must be true.
-->
<xsl:template match="lv:import[ @package ]"
as="element( lv:match )?"
priority="5"
mode="preproc:gen-elig-class-matches">
<xsl:param name="orig-root" as="element( lv:package )" />
<!-- FIXME: path may not yet be resolved due to preprocessing order -->
<xsl:variable name="pkg-path" as="xs:string">
<xsl:call-template name="__apply-relroot">
<xsl:with-param name="path" select="@package" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="pkg" as="element( lv:package )"
select="document( concat( $pkg-path, '.xmlo' ),
$__entry-root )
/lv:package" />
<xsl:if test="not( $pkg )">
<xsl:message terminate="yes">
<xsl:text>[preproc/eligclass] error: could not load `</xsl:text>
<xsl:value-of select="$pkg-path" />
<xsl:text>' object file</xsl:text>
</xsl:message>
</xsl:if>
<xsl:variable name="chk" as="xs:string?"
select="$pkg/@preproc:elig-class-yields" />
<xsl:choose>
<xsl:when test="not( $chk ) or ( $chk = '' )">
<!-- TODO: make this an error once we make maps part of the
conventional build process -->
<xsl:message>
<xsl:text>[preproc/eligclass] internal: empty eligibility </xsl:text>
<xsl:text>class for `</xsl:text>
<xsl:value-of select="$pkg/@name" />
<xsl:text>'; skipping</xsl:text>
</xsl:message>
</xsl:when>
<xsl:otherwise>
<!-- use eligibility class as stated by the package -->
<lv:match on="{$chk}" value="TRUE" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="lv:import" priority="1"
mode="preproc:gen-elig-class-matches">
<!-- do nothing -->
</xsl:template>
<!--
Param values must be within their domain
This is a trivial operation.
-->
<xsl:template match="lv:param"
as="element( lv:any )"
mode="preproc:gen-elig-param-class" priority="5">
<lv:any>
<lv:match on="{@name}" anyOf="{@type}" />
<!-- TODO: defaults should always be within the domain! -->
<xsl:if test="@default">
<lv:match on="{@name}" anyOf="empty" />
</xsl:if>
</lv:any>
</xsl:template>
<!--
Terminating classification dependencies
All terminiating classifications defined in the package must yield false
for the package to be eligible.
N.B. This checks to ensure @extclass is not set; this prevents errors when
the eligibility classification attempts to pull in a terminating
classification marked as external to the classifier. There may or may not
be something we want to do about this in the future.
-->
<xsl:template match="preproc:sym[
not( @src )
and not( @pollute='true' )
and @type='class'
and @terminate='true'
and not( @extclass='true' )
]"
as="element( lv:match )"
priority="5"
mode="preproc:gen-elig-term-class">
<lv:match on="{@yields}" value="FALSE" />
</xsl:template>
<xsl:template match="preproc:sym" priority="1"
mode="preproc:gen-elig-term-class">
<!-- do nothing -->
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,691 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Handles node expansion
This process is responsible for expanding shorthand and various other data
into a consistent format for the compiler and other processes.
-->
<xsl:stylesheet
version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:t="http://www.lovullo.com/rater/apply-template"
xmlns:c="http://www.lovullo.com/calc"
xmlns:w="http://www.lovullo.com/rater/worksheet">
<xsl:include href="domain.xsl" />
<xsl:template match="lv:package[ not( @preproc:name ) ]"
mode="preproc:expand" priority="5"
as="element( lv:package )">
<xsl:copy>
<xsl:sequence select="@*" />
<!-- generate name from source package identifier -->
<xsl:attribute name="name" select="$__srcpkg" />
<!-- relative path to root src directory (for resolving absolute include
paths) -->
<xsl:attribute name="__rootpath" select="$__relroot" />
<!-- TODO: temporary; remove -->
<xsl:attribute name="preproc:name" select="$__srcpkg" />
<xsl:apply-templates mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="preproc:expand" priority="1">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:apply-templates mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<!-- imports relative to project root -->
<xsl:template match="lv:import[ starts-with( @package, '/' ) ]" mode="preproc:expand" priority="5">
<xsl:copy>
<xsl:sequence select="@*" />
<!-- resolve path into path relative to project root -->
<xsl:attribute name="package">
<xsl:call-template name="__apply-relroot">
<xsl:with-param name="path" select="@package" />
</xsl:call-template>
</xsl:attribute>
</xsl:copy>
</xsl:template>
<!--
Domain data are extracted from typedefs
Eventually, the typedefs will be converted into templates and removed entirely.
-->
<xsl:template match="lv:typedef"
mode="preproc:expand" priority="5">
<xsl:apply-templates select="." mode="preproc:mkdomain" />
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:apply-templates mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<!--
Infer primitive type if not provided.
-->
<xsl:template mode="preproc:expand"
match="c:const[ not( @type ) ]
|lv:const[ not( @type ) ]"
priority="5">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:attribute name="type"
select="if ( substring-before( @value, '.' ) ) then
'float'
else
'integer'" />
<xsl:sequence select="*" />
</xsl:copy>
</xsl:template>
<!--
Give let's a name so that they may be easily referenced uniquely
-->
<xsl:template match="c:let[ not( @name ) ]" mode="preproc:expand" priority="5">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:attribute name="name" select="generate-id(.)" />
<xsl:apply-templates select="*" mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<!--
Default label of c:let value expressions to description if no label is provided
This is useful for breakdown display.
TODO: play well with others; if we change the priority back to 5, we introduce
match ambiguities
-->
<xsl:template match="c:let/c:values/c:value/c:*[1][ not( @label ) ]" mode="preproc:expand" priority="4">
<xsl:copy>
<xsl:sequence select="@*" />
<!-- default the label to the description of the parent c:value -->
<xsl:attribute name="label" select="../@desc" />
<xsl:apply-templates select="*" mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<!--
c:when with no children is shorthand for > 0
Note: we check for any children because we may have things like
template applications that we do not want wiped out.
-->
<xsl:template match="c:when[ not( * ) ]" mode="preproc:expand" priority="5">
<xsl:copy>
<xsl:sequence select="@*" />
<c:gt>
<c:value-of name="FALSE" />
</c:gt>
</xsl:copy>
</xsl:template>
<!--
c:when with multiple children
-->
<xsl:template match="c:when[ count( c:* ) gt 1 ]" mode="preproc:expand" priority="5">
<xsl:variable name="when" select="." />
<!-- expand into adjacent c:when's -->
<xsl:for-each select="c:*">
<c:when>
<xsl:sequence select="$when/@*" />
<xsl:apply-templates select="." mode="preproc:expand" />
</c:when>
</xsl:for-each>
</xsl:template>
<!--
Recursion shorthand
This exists simply because function application is so verbose and, when
recursing, generally only a small fraction of the arguments actually change.
-->
<xsl:template match="c:recurse" mode="preproc:expand" priority="5">
<xsl:variable name="self" select="." />
<xsl:variable name="fname" select="ancestor::lv:function/@name" />
<xsl:variable name="overrides" select="./c:arg" />
<c:apply name="{$fname}">
<!-- every non-@name attribute should be converted into an argument -->
<xsl:call-template name="preproc:arg-short-expand" />
<!-- include all non-overridden args -->
<xsl:for-each select="
ancestor::lv:function/lv:param[
not(
@name=$overrides/@name
or @name=$self/@*/local-name()
)
]
">
<!-- copy the arg value -->
<c:arg name="{@name}">
<c:value-of name="{@name}" />
</c:arg>
</xsl:for-each>
<!-- copy in the overrides -->
<xsl:apply-templates select="$overrides" mode="preproc:expand" />
</c:apply>
</xsl:template>
<!-- metadata constants have different semantics -->
<!-- TODO: maybe ignore single-quoted? -->
<xsl:template mode="preproc:expand" priority="6"
match="lv:meta/lv:prop/lv:const">
<xsl:sequence select="." />
</xsl:template>
<!-- constants that contain 'e' (scientific notation) should be expanded; allows
for avoiding constants with many zeroes, which is hard to read -->
<xsl:template mode="preproc:expand" priority="5"
match="c:const[ substring-before( @value, 'e' ) ]
|lv:const[ substring-before( @value, 'e' ) ]">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:attribute name="value">
<xsl:call-template name="preproc:expand-e">
<xsl:with-param name="number" select="@value" />
</xsl:call-template>
</xsl:attribute>
<xsl:apply-templates mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<xsl:template mode="preproc:expand" priority="5"
match="c:const[ substring-before( @value, 'm' ) ]
|lv:const[ substring-before( @value, 'm' ) ]">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:attribute name="value">
<xsl:call-template name="preproc:expand-e">
<xsl:with-param name="number"
select="concat( substring-before( @value, 'm' ), 'e6' )" />
</xsl:call-template>
</xsl:attribute>
<xsl:apply-templates mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<xsl:template mode="preproc:expand" priority="5"
match="c:const[ substring-before( @value, 'k' ) ]
|lv:const[ substring-before( @value, 'k' ) ]">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:attribute name="value">
<xsl:call-template name="preproc:expand-e">
<xsl:with-param name="number"
select="concat( substring-before( @value, 'k' ), 'e3' )" />
</xsl:call-template>
</xsl:attribute>
<xsl:apply-templates mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<!-- expand scientific notation -->
<!-- XXX: negatives not currently supported -->
<xsl:template name="preproc:expand-e">
<xsl:param name="number" />
<xsl:param name="whole" select="substring-before( $number, '.' )" />
<xsl:param name="dec" select="substring-before( substring-after( $number, '.' ), 'e' )" />
<xsl:param name="count" as="xs:double"
select="number( substring-after( $number, 'e' ) )" />
<!-- output the whole number portion -->
<xsl:choose>
<xsl:when test="$whole and not( $whole = '' )">
<xsl:value-of select="$whole" />
</xsl:when>
<!-- if no decimal was provided, then use the entire number before 'e' -->
<xsl:when test="$number and not( $number = '' ) and ( $whole = '' )">
<xsl:value-of select="substring-before( $number, 'e' )" />
</xsl:when>
</xsl:choose>
<xsl:choose>
<xsl:when test="$count > 0">
<xsl:choose>
<!-- if we have a decimal, then use the first digit (as if we moved one
place to the right) -->
<xsl:when test="$dec and not( $dec = '' )">
<xsl:value-of select="substring( $dec, 1, 1 )" />
</xsl:when>
<!-- no decimal portion remaining; fill with 0 -->
<xsl:otherwise>
<xsl:text>0</xsl:text>
</xsl:otherwise>
</xsl:choose>
<!-- recursively expand -->
<xsl:call-template name="preproc:expand-e">
<!-- already processed the whole -->
<xsl:with-param name="whole" select="''" />
<xsl:with-param name="dec">
<!-- move to the right one decimal place; otherwise, no decimal -->
<xsl:if test="$dec">
<xsl:value-of select="substring( $dec, 2 )" />
</xsl:if>
</xsl:with-param>
<xsl:with-param name="count" select="$count - 1" />
</xsl:call-template>
</xsl:when>
<!-- output the remaining decimal, if any -->
<xsl:otherwise>
<xsl:if test="$dec and not( $dec = '' )">
<xsl:text>.</xsl:text>
<xsl:value-of select="$dec" />
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
Optimize away c:cases if they contain only c:otherwise
This is useful primarily for templates that may create a case statement for
conditional operations (using lv:if/lv:unless) and ensures that there is no
penalty for doing so if none of the template conditions result in a c:case.
Note that we should *not* perform these optimizations if there are templates
awaiting application or any other lv:* nodes that have not been expanded.
-->
<xsl:template mode="preproc:expand" priority="5" match="
c:cases[
not( lv:* or t:* )
and c:otherwise[
not( preceding-sibling::c:* or following-sibling::c:* )
]
]
">
<!-- just replace with the content of the otherwise block (do not explicitly
process c:*, since there may be templates) -->
<xsl:apply-templates select="c:otherwise/*" mode="preproc:expand" />
</xsl:template>
<!--
Optimize away c:sum/c:product blocks that contain one or zero elements, so
long as they do not contain a generator (since that would remove a ref) or @of
(since that will actually loop through multiple).
Note that we should *not* perform these optimizations if there are templates
awaiting application or any other lv:* nodes that have not been expanded.
-->
<xsl:template match="c:sum[ lv:* or t:* ]|c:product[ lv:* or t:* ]" mode="preproc:expand" priority="7">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:apply-templates mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<xsl:template match="c:sum[ not( @of or @generates ) and count( c:* ) &lt; 2 ]" mode="preproc:expand" priority="5">
<xsl:apply-templates select="c:*" mode="preproc:expand" />
</xsl:template>
<xsl:template match="c:product[ not( @of or @generates ) and count( c:* ) &lt; 2 ]" mode="preproc:expand" priority="5">
<xsl:apply-templates select="c:*" mode="preproc:expand" />
</xsl:template>
<!-- TODO: We could add shorthand for indexes too, e.g. name[i] or name[0] -->
<xsl:template match="
c:apply[
@*[
not(
local-name() = 'name'
or local-name() = 'label'
)
]
]
"
mode="preproc:expand" priority="5">
<xsl:copy>
<!-- keep the name attribute, which specifies what function to apply -->
<xsl:sequence select="@name, @label" />
<!-- every other attribute should be converted into an argument -->
<xsl:call-template name="preproc:arg-short-expand" />
<xsl:apply-templates select="c:arg" mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<xsl:template name="preproc:arg-short-expand">
<xsl:for-each select="@*[
not(
local-name() = 'name'
or local-name() = 'label'
)
]">
<c:arg name="{local-name()}">
<c:value-of name="{.}" />
</c:arg>
</xsl:for-each>
</xsl:template>
<xsl:template match="lv:rate[ lv:class ]|lv:function[ lv:class ]|lv:yield[ lv:class ]"
mode="preproc:expand" priority="9">
<!-- already processed -->
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:apply-templates mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<!--
Add lv:class nodes containing the values of each individual class
This eliminates the need to tokenize later and drastically simplifies xpath
queries.
-->
<xsl:template match="lv:rate|lv:function|lv:yield" mode="preproc:expand" priority="5">
<xsl:variable name="self" select="." />
<xsl:variable name="classes" select="tokenize( @class, ' ' )" />
<xsl:variable name="no-classes" select="tokenize( @no, ' ' )" />
<xsl:copy>
<xsl:sequence select="@*" />
<!-- convert classes into nodes to make life easier down the road (if any) -->
<xsl:for-each select="$classes">
<xsl:if test=".">
<lv:class ref="{.}" no="false" />
</xsl:if>
</xsl:for-each>
<xsl:for-each select="$no-classes">
<xsl:if test=".">
<lv:class ref="{.}" no="true" />
</xsl:if>
</xsl:for-each>
<xsl:apply-templates mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<!--
To make life a bit easier, calculate the set type of a classification @yields
and add it to the node as a @set attribute
-->
<xsl:template match="lv:classify" mode="preproc:expand" priority="5">
<xsl:variable name="self" select="." />
<xsl:copy>
<!-- we just want to add an attribute that allows easy referencing of this
@yields set type, which will be a matrix if any matches match on a
matrix of values, otherwise it will be a vector -->
<xsl:attribute name="set">
<xsl:variable name="params"
select="root(.)//lv:param[ @name=$self//lv:match/@on ]" />
<xsl:choose>
<!-- XXX: This does not work properly with classes depending on other
classes -->
<xsl:when test="$params/@set = 'matrix'">
<xsl:text>matrix</xsl:text>
</xsl:when>
<!-- XXX: remove this when the above is fixed...note also that we have
to check for lv:join since it hasn't necessarily been preprocessed
yet...what a mess. Also note that, since templates and other things
may not have been expanded, we also fail this test if the
classification does not match on either a param or another
classification (since then things will get more complicated)-->
<xsl:when test="
not(
$self//lv:match
or $self//lv:join
)
or (
not( $params/@set )
and not(
.//lv:match[ @on=root(.)/lv:classify/@yields ]
)
and not( .//lv:join )
and (
$params
or .//lv:match[ @on=root(.)/lv:classify ]
)
)
">
<!-- output nothing; it's just a scalar -->
</xsl:when>
<xsl:otherwise>
<xsl:text>vector</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<!-- if there is no @yields attribute, then generate one -->
<xsl:if test="not( @yields )">
<xsl:attribute name="yields">
<xsl:text>__is</xsl:text>
<!-- certain characters are not valid for @yields -->
<xsl:value-of select="translate( @as, '-', '' )" />
</xsl:attribute>
</xsl:if>
<xsl:sequence select="@*" />
<!-- force @keep on @terminate -->
<xsl:if test="@terminate='true'">
<xsl:attribute name="keep" select="'true'" />
</xsl:if>
<!-- copy everything else -->
<xsl:apply-templates mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<!-- default lv:match/@on short-hand to assert on a value of TRUE -->
<xsl:template match="lv:match[ not( @value
or @anyOf
or @pattern
or * ) ]"
mode="preproc:expand" priority="7">
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:attribute name="value"
select="'TRUE'" />
</xsl:copy>
</xsl:template>
<xsl:template mode="preproc:expand"
match="lv:join[ @all='true' ]"
priority="8">
<xsl:call-template name="preproc:mk-class-join-contents" />
</xsl:template>
<xsl:template mode="preproc:expand"
match="lv:join"
priority="7">
<lv:any>
<xsl:call-template name="preproc:mk-class-join-contents" />
</lv:any>
</xsl:template>
<xsl:template name="preproc:mk-class-join-contents">
<xsl:variable name="prefix" select="@prefix" />
<!-- TODO: remove lv:template nodes in a pass before this so that this
check is not necessary -->
<xsl:for-each select="root(.)/lv:classify[
starts-with( @as, $prefix )
and not( ancestor::lv:template )
]">
<lv:match value="TRUE">
<xsl:attribute name="on">
<xsl:choose>
<xsl:when test="@yields">
<xsl:value-of select="@yields" />
</xsl:when>
<xsl:otherwise>
<xsl:text>__is</xsl:text>
<xsl:value-of select="@as" />
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</lv:match>
</xsl:for-each>
</xsl:template>
<!-- enums have implicit values (as they are, well, enumerated; @value overrides) -->
<!-- TODO: should @value set the next implicit index? -->
<xsl:template match="lv:item[ not( @value ) ]" mode="preproc:expand" priority="5">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:attribute name="value" select="count( preceding-sibling::* )" />
<xsl:apply-templates mode="preproc:expand" />
</xsl:copy>
</xsl:template>
<xsl:template match="w:display[ @prefix ]" mode="preproc:expand" priority="5">
<xsl:variable name="prefix" select="@prefix" />
<xsl:variable name="children" select="w:*" />
<xsl:for-each select="root(.)//lv:rate[ starts-with( @yields, $prefix ) ]">
<w:display name="{@yields}">
<xsl:sequence select="$children" />
</w:display>
</xsl:for-each>
</xsl:template>
<!-- remove templates that have been copied from an external source for
processing -->
<xsl:template match="lv:template[
@name=root()
/preproc:symtable/preproc:sym[ @src ]/@name ]"
mode="preproc:expand" priority="5">
</xsl:template>
<!-- IMPORTANT: do not process unexpanded templates -->
<xsl:template match="lv:template" mode="preproc:expand" priority="4">
<xsl:sequence select="." />
</xsl:template>
<xsl:template match="preproc:symtable" mode="preproc:expand" priority="5">
<!-- ignore -->
<xsl:sequence select="." />
</xsl:template>
<xsl:template match="lv:__external-data" mode="preproc:expand" priority="5">
<!-- intended for use by code generators; data is not retained in object file
unless some other process overrides this template -->
</xsl:template>
</xsl:stylesheet>
<!--
Footnotes
What!? We need footnotes for this project!?
[0] This was a complicated issue to begin dealing with due to the information we
need and the information that is available. In particular, at this point, we
would like to exclude any non-external objects from appearing in the
rate-only output, but we cannot do that, since the preprocessor has not yet
reached that block! We cannot reorder, because the class order block depends
on the rate block!
(See the commit that introduced this footnote: In the past, the template at
this position passed an `external-only' flag to the template.)
Originally, the method was to ``guess'' based on the how the system was
currently being used (dangerous, but was meant to be temporary...though we
know how that goes...): implicit externals were ignored, meaning it may not
function as we would like. Now, this could be resolved in the short-term by
actually addinging explicit external attributes to everything that needed it
so that this code would run smoothly. Worked great! Well, so I had thought.
Then it came down to a certain classification that used the `frame'
classification. This classification was only used by an external
classification in scottsdale, but other companies used it for rating, so the
@external classifier was inappropriate. Of course, the system could easily
figure out if it was to be marked as external or not (it already does), but
we do not yet have access to that information. Therefore, what ended up
happening, was the frame classification was excluded from the classifier,
excluded from one of the iterations that this footnote references (because
it was not explicitly external) and included elsewhere where we didn't care
if it was external or not. When the dependency tree was flattened to
determine compilation order, the classification that uses `frame' was
compiled *before* the `frame' classification itself, due to that exclusion!
Since `frame' was excluded from the classifier, therefore, it was always
false! That is the situation I was trying to avoid with the explicit
@external attributes, but here, that solution would not work.
Therefore, checking for external-only here will not work; we must output
everything and work on post-processing the data once everything is said and
done, to remove the duplicates that are also present in the classifier.
-->

View File

@ -0,0 +1,456 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Handles macro preprocessing
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:t="http://www.lovullo.com/rater/apply-template"
xmlns:c="http://www.lovullo.com/calc"
xmlns:ext="http://www.lovullo.com/ext">
<xsl:include href="template.xsl" />
<xsl:include href="eligclass.xsl" />
<!--
Perform a macro expansion pass
This will continue to recurse until no preproc:repass nodes are found; this
allos macros to expand into macros for further processing.
-->
<xsl:template match="*" mode="preproc:macropass" priority="1"
as="node()+">
<xsl:variable name="result" as="node()+">
<xsl:apply-templates select="." mode="preproc:macros" />
</xsl:variable>
<xsl:variable name="nodeset" select="$result" />
<xsl:variable name="repass"
select="$nodeset//preproc:repass" />
<!-- halt if we are in error -->
<xsl:for-each select="$nodeset//preproc:error">
<xsl:message terminate="yes">
<xsl:text>!!! [preproc] error: </xsl:text>
<xsl:value-of select="." />
</xsl:message>
</xsl:for-each>
<xsl:choose>
<!-- if it was indicated that we must do so, recurse -->
<xsl:when test="$repass and not( $repass[ @need-sym ] )">
<!-- record the repass to keep a count -->
<!-- TODO: reintroduce
<preproc:repass-record />
-->
<xsl:message>[preproc] *REPASS*</xsl:message>
<!-- perform the repass -->
<xsl:apply-templates select="$nodeset" mode="preproc:macropass">
<xsl:with-param name="clear-tpl-step"
tunnel="yes"
select="false()" />
</xsl:apply-templates>
</xsl:when>
<!-- no more passes needed; macro expansion complete -->
<xsl:otherwise>
<xsl:sequence select="$nodeset" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*" mode="preproc:macros" priority="1">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:apply-templates mode="preproc:macros" />
</xsl:copy>
</xsl:template>
<!--
Remove repass nodes left over from the previous pass
Otherwise, we would recurse indefinately.
-->
<xsl:template match="preproc:repass" mode="preproc:macros" priority="5">
<!-- remove; no longer needed -->
</xsl:template>
<xsl:template match="preproc:tpl-step" mode="preproc:macros" priority="5">
<xsl:param name="clear-tpl-step"
tunnel="yes"
select="true()" />
<xsl:choose>
<xsl:when test="$clear-tpl-step">
<!-- strip -->
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
lv:rater is just a special type of package
-->
<xsl:template match="lv:rater" mode="preproc:macros" priority="9">
<lv:package program="true">
<xsl:sequence select="@*, *" />
</lv:package>
<preproc:repass src="lv:rater" />
</xsl:template>
<!--
FOR PERFORMANCE ONLY:
These nodes (usually) contain nothing that can be processed on the macro pass,
so recursion is unnecessary; note the low priority.
-->
<xsl:template match="lv:typedef"
mode="preproc:macros" priority="2">
<xsl:sequence select="." />
</xsl:template>
<!--
Expand values beginning with `#' into constants
It is a nuisance to have separate params (e.g. templates) for constants
and values.
-->
<xsl:template mode="preproc:macros"
match="c:value-of[ starts-with( @name, '#' ) ]"
priority="7">
<c:const value="{substring-after( @name, '#' )}"
type="float"
desc="Generated short-hand constant" />
</xsl:template>
<!--
Expand index values beginning with `#' into constants
It is a nuisance to have separate params (e.g. templates) for constants
and values.
-->
<xsl:template mode="preproc:macros"
match="c:value-of[ starts-with( @index, '#' ) ]"
priority="7">
<xsl:copy>
<xsl:copy-of select="@*[ not( name() = 'index' ) ]" />
<c:index>
<c:const value="{substring-after( @index, '#' )}"
type="float"
desc="Generated short-hand constant" />
</c:index>
</xsl:copy>
</xsl:template>
<!--
It does not make sense to try to take an index of a scalar
-->
<xsl:template mode="preproc:macros"
match="c:value-of[
@index
and starts-with( @name, '#' ) ]"
priority="9">
<preproc:error>
<xsl:text>Cannot take index of scalar value: </xsl:text>
<xsl:value-of select="@name" />
</preproc:error>
</xsl:template>
<!--
Classifications containing only an lv:any child node can be converted into
existential classifications
-->
<xsl:template match="lv:classify[ lv:any and count(*) = 1 ]" mode="preproc:macros" priority="8">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:attribute name="any" select="'true'" />
<xsl:sequence select="lv:any/*" />
</xsl:copy>
<preproc:repass src="lv:classify any" />
</xsl:template>
<xsl:template match="lv:classify[ .//lv:any|.//lv:all ]" mode="preproc:macros" priority="6">
<xsl:variable name="result">
<xsl:apply-templates select="." mode="preproc:class-groupgen" />
</xsl:variable>
<xsl:apply-templates select="$result/lv:classify" mode="preproc:class-extract" />
<preproc:repass src="lv:classify any|all" />
</xsl:template>
<xsl:template match="lv:classify" mode="preproc:class-groupgen" priority="5">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:apply-templates mode="preproc:class-groupgen" />
</xsl:copy>
</xsl:template>
<xsl:template mode="preproc:class-groupgen" priority="9"
match="lv:any[ not( element() ) ]
|lv:all[ not( element() ) ]">
<!-- useless; remove -->
</xsl:template>
<xsl:template match="lv:any|lv:all" mode="preproc:class-groupgen" priority="5">
<!-- this needs to be unique enough that there is unlikely to be a conflict
between generated ids in various packages; generate-id is not enough for
cross-package guarantees (indeed, I did witness conflicts), so there is
a random seed passed into the stylesheet externally -->
<xsl:variable name="id" select="concat( $__rseed, generate-id(.) )" />
<xsl:variable name="parent-name" select="ancestor::lv:classify/@as" />
<xsl:variable name="yields" select="concat( 'is', $id )" />
<xsl:variable name="external" as="xs:string?"
select="ancestor::lv:classify/@external" />
<!-- this will be raised outside of the parent classification during
post-processing -->
<lv:classify as="{$id}" yields="{$yields}"
preproc:generated="true"
preproc:generated-from="{$parent-name}"
external="{$external}"
desc="(generated from predicate group of {$parent-name}">
<xsl:if test="local-name() = 'any'">
<xsl:attribute name="any" select="'true'" />
</xsl:if>
<xsl:apply-templates mode="preproc:class-groupgen" />
</lv:classify>
<!-- this will remain in its place -->
<lv:match on="{$yields}" value="TRUE" preproc:generated="true" />
</xsl:template>
<!-- retain everything else -->
<xsl:template match="*" mode="preproc:class-groupgen" priority="1">
<xsl:sequence select="." />
</xsl:template>
<xsl:template match="lv:classify" mode="preproc:class-extract" priority="5">
<xsl:apply-templates select="lv:classify" mode="preproc:class-extract" />
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:apply-templates mode="preproc:class-filter" />
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="preproc:class-extract" priority="1">
<!-- ignore non-class -->
</xsl:template>
<xsl:template match="lv:classify" mode="preproc:class-filter" priority="5">
<!-- remove -->
</xsl:template>
<xsl:template match="*" mode="preproc:class-filter" priority="1">
<xsl:sequence select="." />
</xsl:template>
<!--
Sections exist purely for organization and documentation. Move all
nodes out of it, so that we do not complicate parsing.
-->
<xsl:template mode="preproc:macros" priority="2"
match="lv:section">
<xsl:apply-templates select="*" mode="preproc:macros" />
</xsl:template>
<!--
lv:yield is simply another rate block with a special name that is recognized
by the linker
-->
<xsl:template match="lv:yield" mode="preproc:macros" priority="5">
<lv:rate yields="___yield" local="true">
<xsl:apply-templates mode="preproc:macros" />
</lv:rate>
</xsl:template>
<!-- this situation may occur both manually and from lv:rate-each-template -->
<xsl:template match="lv:rate-each[ lv:apply-template ]" mode="preproc:macros" priority="9">
<xsl:variable name="apply">
<preproc:apply>
<xsl:apply-templates select="lv:apply-template" mode="preproc:macros" />
</preproc:apply>
</xsl:variable>
<xsl:choose>
<!-- did the template apply? (note that we only check for a single one,
since that's all that we should have) -->
<xsl:when test="$apply/preproc:apply/lv:apply-template">
<xsl:sequence select="." />
<xsl:message>
<xsl:text>[preproc] waiting to expand rate-each </xsl:text>
<xsl:value-of select="@yields" />
<xsl:text> (immediate template(s) need expansion)...</xsl:text>
</xsl:message>
</xsl:when>
<xsl:otherwise>
<!-- it applied! -->
<xsl:copy>
<xsl:sequence select="@*, *[ not( local-name()='apply-template' ) ]" />
<xsl:sequence select="$apply/preproc:apply/*" />
</xsl:copy>
<!-- we'll process this block next time around -->
<preproc:repass src="lv:rate-each lv:apply-template" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
Convenience macro that expands to a lv:rate block summing over the magic
_CMATCH_ set with the product of its value
The intent here is to reduce highly repetitive code.
-->
<xsl:template match="lv:rate-each" mode="preproc:macros" priority="5">
<!-- TODO: debug flag
<xsl:message>
<xsl:text>[preproc] expanding rate-each </xsl:text>
<xsl:value-of select="@yields" />
<xsl:text>...</xsl:text>
</xsl:message>
-->
<lv:rate>
<xsl:sequence select="@*[
not( local-name() = 'index' )
and not( local-name() = 'generates' )
]" />
<xsl:if test="not( @yields )">
<!-- if @generates is not supplied either, then we cannot continue -->
<xsl:choose>
<xsl:when test="not( @generates )">
<!-- TODO: some means of identifying this...the error isn't terribly
helpful... :x -->
<preproc:error>
<xsl:text>rate-each must provide either @yields or @generates</xsl:text>
</preproc:error>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="yields"
select="concat( '_', @generates )" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
<xsl:sequence select="./lv:class" />
<c:sum of="_CMATCH_" index="{@index}" sym="{@gensym}">
<!-- copy @generates, if it exists (has the benefit of copying nothing
if it does not exist) -->
<xsl:sequence select="@generates" />
<xsl:attribute name="desc">
<xsl:text>Set of individual </xsl:text>
<xsl:value-of select="@yields" />
<xsl:text> premiums</xsl:text>
</xsl:attribute>
<c:product>
<c:value-of name="_CMATCH_" index="{@index}">
<xsl:attribute name="label">
<xsl:text>Zero if not </xsl:text>
<xsl:value-of select="@class" />
<xsl:text>, otherwise one</xsl:text>
</xsl:attribute>
</c:value-of>
<xsl:apply-templates
select="*[
not(
local-name() = 'class'
)
]"
mode="preproc:macros" />
</c:product>
</c:sum>
</lv:rate>
</xsl:template>
<!--
Generates a classifier for each boolean param
This is for convenience; a boolean can esssentially be considered its own
classifier, so let's generate one to cut down on the amount of code.
Technically not a macro, but needs to be done before preproc:expand.
XXX: Get rid of me! Now unused!
-->
<xsl:template match="lv:params[ not( @preproc:processed ) ]"
mode="preproc:macros" priority="5">
<xsl:copy>
<xsl:attribute name="preproc:processed" select="'true'" />
<xsl:sequence select="@*" />
<xsl:apply-templates mode="preproc:macros" />
</xsl:copy>
<xsl:for-each select="lv:param[ @type='boolean' ]">
<xsl:variable name="as" select="translate( @name, '_', '-' )" />
<xsl:variable name="genas" select="concat( 'is-', $as )" />
<!-- ensure that this name does not already exist -->
<xsl:if test="not( /lv:*/lv:classify[ @as=$genas ] )">
<!-- TODO: We're flagging as @keep for now due to gclass needs, but this
should be removed later -->
<lv:classify as="{$genas}" desc="{@desc}" keep="true">
<lv:match on="{@name}" value="TRUE" />
</lv:classify>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,817 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Preprocesses package XML
This will preprocess a package XML suitable for compilation into an object
file.
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:t="http://www.lovullo.com/rater/apply-template"
xmlns:c="http://www.lovullo.com/calc"
xmlns:w="http://www.lovullo.com/rater/worksheet"
xmlns:lvv="http://www.lovullo.com/rater/validate"
xmlns:ext="http://www.lovullo.com/ext"
xmlns:util="http://www.lovullo.com/util">
<xsl:include href="../dslc-base.xsl" />
<!-- phases -->
<xsl:include href="macros.xsl" />
<xsl:include href="expand.xsl" />
<xsl:include href="symtable.xsl" />
<xsl:include href="../../compiler/fragments.xsl" />
<!-- begin preprocessing from an arbitrary node -->
<xsl:template name="preproc:pkg-compile" as="element( lv:package )"
match="*" mode="preproc:compile" priority="1">
<xsl:param name="orig-root" as="element()"
select="root(.)/lv:package" />
<xsl:param name="stopshort" />
<!-- should be provided externally -->
<xsl:if test="not( $__rseed ) or ( $__rseed = '' )">
<xsl:message terminate="yes">
<xsl:text>[preproc] error: missing random seed `__rseed'</xsl:text>
</xsl:message>
</xsl:if>
<xsl:message>
<xsl:text>[preproc] *beginning macro expansion...</xsl:text>
</xsl:message>
<!-- these can contain repass nodes, thus the element()+ -->
<!-- macro expansion -->
<xsl:variable name="stage1" as="element()+">
<xsl:apply-templates select="." mode="preproc:macropass" />
<xsl:message>
<xsl:text>[preproc] *macro pass complete; expanding...</xsl:text>
</xsl:message>
</xsl:variable>
<!-- expand shorthands, etc -->
<xsl:variable name="stage2" as="element()+">
<xsl:apply-templates select="$stage1"
mode="preproc:expand" />
<xsl:message>
<xsl:text>[preproc] *expansion complete; generating symbol table...</xsl:text>
</xsl:message>
</xsl:variable>
<xsl:variable name="stage3" as="element()+">
<xsl:apply-templates select="$stage2"
mode="preproc:sym-discover">
<xsl:with-param name="orig-root" select="$orig-root" />
</xsl:apply-templates>
<xsl:message>
<xsl:text>[preproc] *symbol table generated; checking for </xsl:text>
<xsl:text>unprocessed templates...</xsl:text>
</xsl:message>
</xsl:variable>
<!-- TODO: resolve this mess -->
<xsl:variable name="stage3-pkg" as="element( lv:package )"
select="$stage3/preproc:symtable/parent::*" />
<!-- TODO: move me somewhere more appropriate and create an error
system that is _guaranteed_ to catch everything -->
<xsl:for-each select="$stage3-pkg/preproc:symtable/preproc:error">
<xsl:message terminate="yes">
<xsl:text>!!! [preproc] error: </xsl:text>
<xsl:value-of select="." />
</xsl:message>
</xsl:for-each>
<!-- determine if we should finish or simply return for further processing -->
<xsl:choose>
<xsl:when test="not( $stopshort )">
<!-- template expansions may have been deferred until their symbols were made
available -->
<xsl:variable name="final" as="element( lv:package )">
<xsl:call-template name="preproc:tpl-sym-recurse">
<xsl:with-param name="package" select="$stage3-pkg" />
<xsl:with-param name="orig-root" select="$orig-root" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="extern-chk" as="element( preproc:error )*"
select="preproc:final-extern-check(
$final/preproc:symtable )" />
<xsl:if test="$extern-chk">
<xsl:for-each select="$extern-chk">
<xsl:message>
<xsl:text>!!! [preproc] error: </xsl:text>
<xsl:value-of select="." />
</xsl:message>
</xsl:for-each>
<xsl:message>~~~~[begin document dump]~~~~</xsl:message>
<xsl:message select="$final" />
<xsl:message>~~~~[end document dump]~~~~</xsl:message>
<xsl:message select="'Aborting due to unresolved externs'" />
<xsl:message terminate="yes"
select="'Document dumped.'" />
</xsl:if>
<!-- ensure that all template parameters have been expanded -->
<xsl:apply-templates select="$final" mode="preproc:tpl-check" />
<!-- perform validation before dependency generation to ensure that all
dependencies are available -->
<xsl:apply-templates select="$final" mode="preproc:pkg-validate" />
<!-- determine how many passes have been made -->
<!-- TODO: reintroduce
<xsl:variable name="repass-count"
select="count( $final//preproc:repass-record )" />
-->
<!-- assign unique ids to each node -->
<xsl:variable name="idized" as="element( lv:package )">
<xsl:apply-templates select="$final" mode="preproc:idize" />
</xsl:variable>
<!-- generate deps -->
<xsl:variable name="depd" as="element( lv:package )">
<xsl:apply-templates select="$idized" mode="preproc:gen-deps" />
</xsl:variable>
<!-- post-process symbol table to resolve any unknowns that require a
dependency tree -->
<xsl:variable name="resolvd" as="element( lv:package )">
<xsl:apply-templates select="$depd" mode="preproc:resolv-syms">
<xsl:with-param name="orig-root" select="$orig-root" />
</xsl:apply-templates>
</xsl:variable>
<!-- compile fragments -->
<xsl:message>
<xsl:text>[preproc] compiling fragments...</xsl:text>
</xsl:message>
<xsl:apply-templates select="$resolvd"
mode="preproc:compile-fragments" />
<!-- output a repass count, which could be a strong indicator of performance
issues -->
<!-- TODO: reintroduce
<xsl:message>
<xsl:text>[Preprocessor repass count: </xsl:text>
<xsl:value-of select="$repass-count" />
<xsl:text>] [Node count: </xsl:text>
<xsl:value-of select="count( $final//* )" />
<xsl:text>]</xsl:text>
</xsl:message>
-->
</xsl:when>
<!-- return for further processing -->
<xsl:otherwise>
<xsl:sequence select="$stage3" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
A very primitive guard against unexpanded template parameters.
-->
<xsl:template match="*[ starts-with( @*, '@' ) ]" mode="preproc:tpl-check" priority="5">
<xsl:message>
<xsl:text>[preproc] fatal: unexpanded template parameter: </xsl:text>
<xsl:sequence select="@*[ starts-with( ., '@' ) ]" />
</xsl:message>
<xsl:message>
<xsl:text>[preproc] fatal: reference node: </xsl:text>
<xsl:sequence select="." />
</xsl:message>
<xsl:message>~~~~[begin document dump]~~~~</xsl:message>
<xsl:message select="root(.)" />
<xsl:message>~~~~[end document dump]~~~~</xsl:message>
<xsl:message terminate="yes">[preproc] notice: Document dumped.</xsl:message>
</xsl:template>
<!-- this should never happen; but is has, so here's a failsafe -->
<xsl:template match="lv:apply-template" mode="preproc:tpl-check" priority="5">
<xsl:message>
<xsl:text>[preproc] fatal: unexpanded template: </xsl:text>
<xsl:sequence select="." />
</xsl:message>
<xsl:message>
<xsl:text>[preproc] fatal: reference node: </xsl:text>
<xsl:sequence select="." />
</xsl:message>
<xsl:message select="'[preproc] internal error: there is a bug in the',
'preprocessor; this should never happen!'" />
<xsl:message>~~~~[begin document dump]~~~~</xsl:message>
<xsl:message select="root(.)" />
<xsl:message>~~~~[end document dump]~~~~</xsl:message>
<xsl:message terminate="yes">[preproc] notice: Document dumped.</xsl:message>
</xsl:template>
<!-- skip things that cannot contain template applications -->
<xsl:template match="lv:template|lv:const|lv:typedef"
mode="preproc:tpl-check" priority="9">
</xsl:template>
<xsl:template match="*" mode="preproc:tpl-check" priority="1">
<xsl:apply-templates select="*" mode="preproc:tpl-check" />
</xsl:template>
<!--
TODO: This needs to go away in the form of a more performant system
that marks a repass as needed *without* scanning the entire tree.
-->
<xsl:template name="preproc:tpl-sym-recurse" as="element( lv:package )">
<xsl:param name="package" as="element( lv:package )" />
<xsl:param name="orig-root" as="element()" />
<!-- number of iterations where no template applications have
happened -->
<xsl:param name="tpl-stall-count"
tunnel="yes"
select="0" />
<!-- get a list of needed template applications (ignoring applications that
are within templates and nested applications) -->
<xsl:variable name="apply" as="element( lv:apply-template )*"
select="$package//lv:apply-template[
not(
@name=$package/lv:template/@name
or ancestor::lv:template
or ancestor::lv:apply-template
)
]" />
<xsl:variable name="napply" select="count( $apply )" />
<xsl:choose>
<xsl:when test="$apply">
<!-- get a list of required templates -->
<xsl:variable name="req">
<tpl>
<xsl:for-each select="$apply">
<xsl:copy>
<xsl:sequence select="@name" />
</xsl:copy>
</xsl:for-each>
</tpl>
</xsl:variable>
<xsl:variable name="requniq" as="element( lv:apply-template )*" select="
$req//lv:apply-template[
not( @name=preceding-sibling::lv:apply-template/@name
or @name=$package//lv:template/@name ) ]
" />
<xsl:message>
<xsl:text>[preproc] </xsl:text>
<xsl:value-of select="count( $apply )" />
<xsl:text> template(s) still need application: </xsl:text>
<xsl:for-each select="$apply">
<xsl:if test="position() gt 1">
<xsl:text>, </xsl:text>
</xsl:if>
<xsl:value-of select="@name" />
</xsl:for-each>
</xsl:message>
<!-- load each of the requested templates (but only once) -->
<xsl:variable name="tpls">
<!-- TODO: we no longer need to load the templates; the
template replacement looks up the template on-demand from
the symbol table; we're no longer injecting them -->
<xsl:apply-templates mode="preproc:tpl-from-sym" select="$requniq">
<xsl:with-param name="orig-root" select="$orig-root" />
<xsl:with-param name="symtable" select="$package/preproc:symtable" />
</xsl:apply-templates>
</xsl:variable>
<!-- if we have recursed and have not decreased the application count at all,
then we have a problem -->
<xsl:if test="$requniq
and ( $package//preproc:sym-available )
and not( $package//preproc:repass[ @tpl-applied ] )">
<xsl:message terminate="yes">
<xsl:text>!!! [preproc] fatal: unable to locate symbols for </xsl:text>
<xsl:text>remaining templates: </xsl:text>
<xsl:for-each select="$requniq">
<xsl:if test="position() > 1">
<xsl:text>; </xsl:text>
</xsl:if>
<xsl:value-of select="@name" />
</xsl:for-each>
</xsl:message>
</xsl:if>
<!-- if there was an error during this part of the process, halt -->
<xsl:if test="$tpls//preproc:error">
<xsl:message terminate="yes">
<xsl:text>!!! [preproc] fatal: terminating due to errors</xsl:text>
</xsl:message>
</xsl:if>
<!-- perform expansion on the new package with the needed templates -->
<xsl:variable name="result" as="element( lv:package )">
<xsl:apply-templates select="$package" mode="preproc:compile">
<xsl:with-param name="orig-root" select="$orig-root" />
<xsl:with-param name="stopshort" select="true()" />
</xsl:apply-templates>
<xsl:message>
<xsl:text>[preproc] *expansion complete (recursive)</xsl:text>
</xsl:message>
</xsl:variable>
<!-- failsafe to prevent infinite recursion (5 (0-indexed)
should be plenty, since everything in the system results in
a template application in fewer steps -->
<xsl:if test="$requniq and $tpl-stall-count eq 4">
<xsl:message select="'!!! [preproc] internal: expansion deadlock!'" />
<xsl:message
select="'!!! [preproc] internal: stalled for 5 iterations'" />
<xsl:sequence select="preproc:dump-document( $result )" />
<xsl:message terminate="yes">
<xsl:text></xsl:text>
<xsl:text>!!! [preproc] fatal: expansion of remaining </xsl:text>
<xsl:text>templates aborted (have all been imported?): </xsl:text>
<xsl:for-each select="$requniq">
<xsl:if test="position() > 1">
<xsl:text>; </xsl:text>
</xsl:if>
<xsl:value-of select="@name" />
</xsl:for-each>
</xsl:message>
</xsl:if>
<!-- recurse to continue expanding if need be -->
<xsl:call-template name="preproc:tpl-sym-recurse">
<xsl:with-param name="orig-root" select="$orig-root" />
<xsl:with-param name="package" select="$result" />
<xsl:with-param name="tpl-stall-count"
tunnel="yes"
select="if ( $package//preproc:tpl-step ) then
0
else
$tpl-stall-count + 1" />
</xsl:call-template>
</xsl:when>
<!-- expansion sequences and template short-hand expansions that
have not yet taken place, due to one reason or another (too
few passes: bug as far as I'm concerned) -->
<xsl:when test="$package//lv:expand-sequence[
not( ancestor::lv:template
or ancestor::lv:apply-template ) ]
|$package//t:*[
not( ancestor::lv:template
or ancestor::lv:apply-template ) ]">
<xsl:message select="'[preproc] pending expansions still present'" />
<xsl:variable name="result" as="element( lv:package )">
<xsl:apply-templates select="$package" mode="preproc:compile">
<xsl:with-param name="orig-root" select="$orig-root" />
<xsl:with-param name="stopshort" select="true()" />
</xsl:apply-templates>
</xsl:variable>
<xsl:call-template name="preproc:tpl-sym-recurse">
<xsl:with-param name="orig-root" select="$orig-root" />
<xsl:with-param name="package" select="$result" />
</xsl:call-template>
</xsl:when>
<!-- no further applications are necessary -->
<xsl:otherwise>
<!-- apply one final pass for the eligibility class generation -->
<xsl:call-template name="preproc:elig-class-pass">
<xsl:with-param name="package" select="$package" />
<xsl:with-param name="orig-root" select="$orig-root" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="preproc:elig-class-pass" as="element( lv:package )">
<xsl:param name="package" as="element( lv:package )" />
<xsl:param name="orig-root" as="element()" />
<xsl:variable name="final-pkg" as="element( lv:package )">
<xsl:apply-templates select="$package" mode="preproc:expand-elig-class">
<xsl:with-param name="orig-root" select="$package" />
</xsl:apply-templates>
</xsl:variable>
<!-- yield the final pass against the elig-class-augmented package -->
<xsl:apply-templates select="$final-pkg" mode="preproc:compile">
<xsl:with-param name="orig-root" select="$orig-root" />
<xsl:with-param name="stopshort" select="true()" />
</xsl:apply-templates>
</xsl:template>
<!--
This is but a shell of its former self.
We are no longer injecting templates, so this can be removed or
adapted to do just enough to verify that the template symbols exist.
-->
<xsl:template match="lv:apply-template" mode="preproc:tpl-from-sym">
<xsl:param name="orig-root" as="element()" />
<xsl:param name="symtable" as="element( preproc:symtable )"
select="root(.)/preproc:symtable" />
<xsl:variable name="tplname" select="@name" />
<xsl:variable name="sym" as="element( preproc:sym )?" select="
$symtable/preproc:sym[
@name=$tplname
and @type='tpl'
]
" />
<!-- if we have a symbol table, then attempt to locate it -->
<xsl:choose>
<!-- if we have located the template, then we know its source (@src must be
set, otherwise the template is defined in this package and should have
been available to begin with) -->
<xsl:when test="$sym">
<preproc:sym-available for="{$tplname}" />
</xsl:when>
<!-- nothing we can do yet -->
<xsl:otherwise>
<xsl:message>
<xsl:text>[preproc] template symbol not yet available: </xsl:text>
<xsl:value-of select="$tplname" />
</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- TODO: we should use an attr besides _id; one more definitive -->
<xsl:template match="*[ @_id ]" mode="preproc:macropass" priority="9">
<!-- already preprocessed -->
<xsl:sequence select="." />
</xsl:template>
<xsl:template match="*[ @_id ]" mode="preproc:idize" priority="9">
<!-- already preprocessed -->
<xsl:sequence select="." />
</xsl:template>
<xsl:template match="preproc:repass-record" mode="preproc:idize" priority="9">
<!-- no longer needed; remove -->
</xsl:template>
<!-- do not idize preproc nodes (unneeded waste of cycles) -->
<xsl:template match="preproc:*" mode="preproc:idize" priority="5">
<xsl:sequence select="." />
</xsl:template>
<!-- do not idize templates (will cause processing problems) -->
<xsl:template match="lv:template" mode="preproc:idize" priority="5">
<xsl:sequence select="." />
</xsl:template>
<!--
Generates a unique id for each element and stores it as @_id
This allows the node set to be copied and maintain its identity.
-->
<xsl:template match="*" mode="preproc:idize" priority="1">
<xsl:variable name="id">
<xsl:value-of select="generate-id(.)" />
</xsl:variable>
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:attribute name="_id">
<xsl:value-of select="$id" />
</xsl:attribute>
<xsl:apply-templates mode="preproc:idize" />
</xsl:copy>
</xsl:template>
<xsl:template match="lv:package" mode="preproc:resolv-syms" as="element( lv:package )"
priority="9">
<xsl:param name="orig-root" as="element()" />
<xsl:param name="rpcount" select="0" />
<!-- arbitrary; intended to prevent infinite recursion -->
<!-- TODO: same method as for templates; ensure changes, but do not create
arbitrary limit -->
<xsl:if test="$rpcount = 100">
<xsl:sequence select="preproc:dump-document( root() )" />
<xsl:message terminate="yes">
<xsl:text>[preproc] !!! recursion limit reached in resolving `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>' symbols</xsl:text>
</xsl:message>
</xsl:if>
<xsl:variable name="result" as="element( lv:package )">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:message>
<xsl:text>[preproc] *resolving symbol attributes...</xsl:text>
</xsl:message>
<xsl:apply-templates mode="preproc:resolv-syms">
<xsl:with-param name="orig-root" select="$orig-root" />
</xsl:apply-templates>
</xsl:copy>
</xsl:variable>
<xsl:variable name="repass"
select="$result//preproc:symtable/preproc:repass" />
<xsl:choose>
<!-- repass scheduled; go for it -->
<xsl:when test="$repass">
<xsl:message>[preproc] *SYM REPASS*</xsl:message>
<xsl:message>
<xsl:text>[preproc] The following </xsl:text>
<xsl:value-of select="count( $repass )" />
<xsl:text> symbol(s) are still unresolved:</xsl:text>
</xsl:message>
<xsl:for-each select="$repass">
<xsl:message>
<xsl:text>[preproc] - </xsl:text>
<xsl:value-of select="@ref" />
</xsl:message>
</xsl:for-each>
<xsl:apply-templates select="$result" mode="preproc:resolv-syms">
<xsl:with-param name="orig-root" select="$orig-root" />
<xsl:with-param name="rpcount" select="$rpcount + 1" />
</xsl:apply-templates>
</xsl:when>
<!-- no repass needed; done -->
<xsl:otherwise>
<xsl:sequence select="$result" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="preproc:symtable" mode="preproc:resolv-syms" priority="5">
<xsl:param name="orig-root" as="element()" />
<xsl:copy>
<xsl:apply-templates mode="preproc:resolv-syms">
<xsl:with-param name="orig-root" select="$orig-root" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="preproc:sym[ not( @src ) and @dim='?' ]" mode="preproc:resolv-syms" priority="5">
<xsl:param name="orig-root" as="element()" />
<xsl:variable name="name" select="@name" />
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
<xsl:variable name="deps" as="element( preproc:sym-dep )*" select="
$pkg/preproc:sym-deps/preproc:sym-dep[ @name=$name ]
" />
<xsl:variable name="depsyms-unresolv" as="element( preproc:sym )*" select="
$pkg/preproc:symtable/preproc:sym[
@name=$deps/preproc:sym-ref/@name
]
" />
<xsl:variable name="depsyms-resolv">
<xsl:for-each select="$depsyms-unresolv">
<xsl:choose>
<xsl:when test="not( @src )">
<xsl:sequence select="." />
</xsl:when>
<!-- look up complete symbol -->
<xsl:otherwise>
<xsl:variable name="name" select="@name" />
<xsl:variable name="sym" select="
document( concat( @src, '.xmlo' ), $orig-root )
/lv:package/preproc:symtable/preproc:sym[
@name=$name
]
" />
<xsl:if test="not( $sym )">
<xsl:message terminate="yes">
<xsl:text>[preproc] !!! failed to look up symbol `</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:if>
<xsl:sequence select="$sym" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="depsyms" select="$depsyms-resolv/preproc:sym" />
<xsl:choose>
<!-- unresolved dependency dimensions; defer until next pass -->
<xsl:when test="
$depsyms/@dim = '?'
">
<xsl:message>
<xsl:text>[preproc] deferring `</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>' dimensions with unresolved dependencies</xsl:text>
</xsl:message>
<!-- schedule repass :x -->
<xsl:sequence select="." />
<preproc:repass src="preproc:sym resolv-syms"
ref="{$name}" />
</xsl:when>
<!-- all dependencies are resolved; calculate dimensions -->
<xsl:otherwise>
<!-- sort dependencies so that the largest dimension is at the top -->
<xsl:variable name="maxset">
<xsl:for-each select="$depsyms">
<xsl:sort select="@dim" data-type="number" order="descending" />
<xsl:sequence select="." />
</xsl:for-each>
</xsl:variable>
<xsl:variable name="max">
<xsl:choose>
<xsl:when test="count( $deps/preproc:sym-ref ) = 0">
<!-- no dependencies, unknown size, so it's a scalar -->
<xsl:text>0</xsl:text>
</xsl:when>
<xsl:otherwise>
<!-- largest value -->
<xsl:value-of select="$maxset/preproc:sym[1]/@dim" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- failure? -->
<xsl:if test="not( $max ) or $max = ''">
<xsl:message terminate="yes">
<xsl:text>[preproc] !!! failed to determine dimensions of `</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:if>
<!-- logging -->
<xsl:message>
<xsl:text>[preproc] resolved `</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>' dimensions as `</xsl:text>
<xsl:value-of select="$max" />
<xsl:text>'</xsl:text>
</xsl:message>
<!-- copy, substituting calculated dimensions -->
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:attribute name="dim" select="$max" />
<xsl:sequence select="*" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="preproc:repass" mode="preproc:resolv-syms" priority="9">
<!-- strip -->
</xsl:template>
<xsl:template match="*" mode="preproc:resolv-syms" priority="1">
<xsl:sequence select="." />
</xsl:template>
<xsl:template match="lv:package" mode="preproc:pkg-validate">
<xsl:variable name="symbol-map">
<xsl:call-template name="get-symbol-map" />
</xsl:variable>
<xsl:variable name="err">
<xsl:apply-templates select="." mode="lvv:validate">
<xsl:with-param name="symbol-map" select="$symbol-map" />
</xsl:apply-templates>
</xsl:variable>
<xsl:apply-templates select="$err//lvv:error" mode="preproc:handle-lvv-errors">
<xsl:with-param name="document" select="." />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*|text()" mode="preproc:pkg-validate">
</xsl:template>
<!-- errors should cause a failure -->
<xsl:template match="lvv:error" mode="preproc:handle-lvv-errors" priority="5">
<xsl:param name="document" />
<!-- output error -->
<xsl:message>
<xsl:text>!!! </xsl:text>
<xsl:value-of select="@desc" />
<xsl:if test="@path != ''">
<xsl:text> (</xsl:text>
<xsl:value-of select="@path" />
<xsl:text>)</xsl:text>
</xsl:if>
<xsl:text>: </xsl:text>
<xsl:value-of select="." />
</xsl:message>
<!-- terminate after we've output each error -->
<xsl:if test="not( following-sibling::lvv:error )">
<!-- dump document for debugging -->
<xsl:sequence select="preproc:dump-document( $document )" />
<xsl:message terminate="yes">Compilation failed due to validation errors.</xsl:message>
</xsl:if>
</xsl:template>
<xsl:function name="preproc:dump-document">
<xsl:param name="document" />
<xsl:message>~~~~[begin document dump]~~~~</xsl:message>
<xsl:message select="$document" />
<xsl:message>~~~~[end document dump]~~~~</xsl:message>
<xsl:message>internal: document dumped.</xsl:message>
</xsl:function>
<xsl:template match="node()|text()" mode="preproc:handle-lvv-errors" priority="1">
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,230 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Operations on paths
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<xsl:template name="preproc:get-path">
<xsl:param name="path" />
<xsl:param name="prev" select="''" />
<xsl:variable name="first" select="substring-before( $path, '/' )" />
<xsl:variable name="rest" select="substring-after( $path, '/' )" />
<xsl:choose>
<!-- if there's no $first, then there is no path separator, in which case
we're done; if there's no rest, then there is a path separator, but it
resulted in an empty string, meanaing that it ends in a path
separator, in which case we are also done -->
<xsl:when test="not( $first ) or not( $rest )">
<!-- no more path separators; we're done -->
<xsl:value-of select="$prev" />
</xsl:when>
<!-- keep recursing -->
<xsl:otherwise>
<xsl:call-template name="preproc:get-path">
<xsl:with-param name="path" select="$rest" />
<xsl:with-param name="prev">
<xsl:if test="not( $prev = '' )">
<xsl:value-of select="concat( $prev, '/' )" />
</xsl:if>
<xsl:value-of select="$first" />
</xsl:with-param>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- FIXME: duplicate code with above -->
<xsl:template name="preproc:get-basename">
<xsl:param name="path" />
<xsl:param name="prev" select="''" />
<xsl:variable name="first" select="substring-before( $path, '/' )" />
<xsl:variable name="rest" select="substring-after( $path, '/' )" />
<xsl:choose>
<!-- if there's no $first, then there is no path separator, in which case
we're done; if there's no rest, then there is a path separator, but it
resulted in an empty string, meanaing that it ends in a path
separator, in which case we are also done -->
<xsl:when test="not( $first ) or not( $rest )">
<!-- no more path separators; we're done -->
<xsl:value-of select="$path" />
</xsl:when>
<!-- keep recursing -->
<xsl:otherwise>
<xsl:call-template name="preproc:get-basename">
<xsl:with-param name="path" select="$rest" />
<xsl:with-param name="prev">
<xsl:if test="not( $prev = '' )">
<xsl:value-of select="concat( $prev, '/' )" />
</xsl:if>
<xsl:value-of select="$first" />
</xsl:with-param>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="preproc:resolv-path">
<xsl:param name="path" />
<!-- in order: strip //, process ../, strip ./ -->
<xsl:call-template name="preproc:strip-sdot-path">
<xsl:with-param name="path">
<xsl:call-template name="preproc:resolv-rel-path">
<xsl:with-param name="path">
<xsl:call-template name="preproc:strip-extra-path">
<xsl:with-param name="path" select="$path" />
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<!-- XXX: warning, this won't like 'foo../' -->
<xsl:template name="preproc:resolv-rel-path">
<xsl:param name="path" />
<!-- relative paths -->
<xsl:variable name="before" select="substring-before( $path, '../' )" />
<xsl:variable name="after" select="substring-after( $path, '../' )" />
<xsl:choose>
<xsl:when test="$before">
<xsl:call-template name="preproc:resolv-rel-path">
<xsl:with-param name="path">
<!-- remove the last directory before the ../ -->
<xsl:variable name="before-path">
<xsl:call-template name="preproc:get-path">
<xsl:with-param name="path" select="$before" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$before-path" />
<!-- the above get-path call will strip the trailing slash -->
<xsl:if test="not( $before-path = '' ) and not( $after = '' )">
<xsl:text>/</xsl:text>
</xsl:if>
<xsl:value-of select="$after" />
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<!-- if there's no $before but there is an $after, then we must begin with
'../', which we can do nothing with; output it and continue processing
the remainder of the path -->
<xsl:when test="$after">
<xsl:text>../</xsl:text>
<xsl:call-template name="preproc:resolv-rel-path">
<xsl:with-param name="path" select="$after" />
</xsl:call-template>
</xsl:when>
<!-- no relative paths remaining -->
<xsl:otherwise>
<xsl:value-of select="$path" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="preproc:strip-sdot-path">
<xsl:param name="path" />
<xsl:choose>
<!-- the only time this should be called with an unresolved relative path
is if it begins with one, in which case we'll simply output it and
continue processing without it -->
<xsl:when test="starts-with( $path, '../' )">
<xsl:text>../</xsl:text>
<!-- continue processing without it -->
<xsl:call-template name="preproc:strip-sdot-path">
<xsl:with-param name="path" select="substring-after( $path, '../' )" />
</xsl:call-template>
</xsl:when>
<!-- path is safe for processing -->
<xsl:otherwise>
<xsl:variable name="a" select="substring-before( $path, './' )" />
<xsl:variable name="b" select="substring-after( $path, './' )" />
<xsl:choose>
<!-- if we found one, recurse -->
<xsl:when test="$a or $b">
<xsl:call-template name="preproc:strip-sdot-path">
<xsl:with-param name="path">
<xsl:value-of select="$a" />
<xsl:if test="$a and $b">
<xsl:text>/</xsl:text>
</xsl:if>
<xsl:value-of select="$b" />
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<!-- done -->
<xsl:otherwise>
<xsl:value-of select="$path" />
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="preproc:strip-extra-path">
<xsl:param name="path" />
<xsl:variable name="a" select="substring-before( $path, '//' )" />
<xsl:variable name="b" select="substring-after( $path, '//' )" />
<xsl:choose>
<!-- if we found one, recurse -->
<xsl:when test="$a or $b">
<xsl:call-template name="preproc:strip-extra-path">
<xsl:with-param name="path">
<xsl:value-of select="$a" />
<xsl:if test="$a and $b">
<xsl:text>/</xsl:text>
</xsl:if>
<xsl:value-of select="$b" />
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<!-- we're done! -->
<xsl:otherwise>
<xsl:value-of select="$path" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,959 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Generates a symbol table from fully a expanded (preprocessed) package
It is important that this table be generated after fully expanding all
templates, macros, etc; otherwise, the table may be incomplete.
The preproc:sym/@tex attribute is a TeX symbol used for typsetting. This
process is not responsible for generating defaults; that should be done by the
linker to ensure that there are no conflicts after all symbols are known.
Here are the recognized types:
rate - lv:rate block
gen - generator (c:*/@generates)
cgen - class generator (lv:classify/@yields)
class - classification (lv:classify/@as)
param - global param (lv:param)
lparam - local param (lv:function/lv:param)
const - global constant (lv:const; lv:enum/lv:item)
tpl - template (lv:template)
type - datatype (lv:typedef)
func - function
Dimensions (think 0=point, 1=line, 2=plane, etc):
0 - scalar
1 - vector
2 - matrix
...
Symbols from imported packages will be consumed and added to the output,
unless local; this has a similiar effect to including a C header file.
External symbols are denoted by preproc:sym/@src, which specifies the name of
the package from which it was imported. If an imported symbol conflicts with
another symbol (imported or otherwise), an error will be produced. Symbols
that are imported are implicitly marked as local.
Certain symbols will "polute" the symbol table of every package that imports
it, every package that imports that one, etc; this is for compatibility with
the old system and will hopefully be phased out eventually. Pollution will
reserve the symbol, but will not provide enough information about that symbol
to be useful, which will ensure that a package has to import the symbol
explicitly in order to actually make use of it.
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:symtable="http://www.lovullo.com/tame/symtable"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc">
<xsl:include href="path.xsl" />
<xsl:include href="../../tame/src/symtable.xsl" />
<!-- we will recurse through the entire tree rather than performing a series of
xpaths that are likely to hit the same nodes many times -->
<xsl:template match="*" mode="preproc:symtable" priority="1">
<xsl:apply-templates mode="preproc:symtable" />
</xsl:template>
<xsl:template match="text()" mode="preproc:symtable" priority="9">
<!-- output nothing -->
</xsl:template>
<!-- an alternative way to invoke the preproc:sym-discover template; useful for
use on variables -->
<xsl:template match="*" mode="preproc:sym-discover" as="element()">
<xsl:param name="orig-root" />
<xsl:call-template name="preproc:sym-discover">
<xsl:with-param name="orig-root" select="$orig-root" />
</xsl:call-template>
</xsl:template>
<xsl:template match="preproc:*" mode="preproc:sym-discover"
as="element()" priority="9">
<xsl:sequence select="." />
</xsl:template>
<!--
Other systems may contribute to a symbol table by invoking this template and
supplying the necessary preproc:symtable templates
TODO: This guy needs some refactoring
-->
<xsl:template name="preproc:sym-discover" as="element()">
<xsl:param name="orig-root" />
<xsl:variable name="this-pkg" as="element( lv:package )"
select="." />
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:variable name="new">
<xsl:message>
<xsl:text>[preproc/symtable] discovering symbols...</xsl:text>
</xsl:message>
<preproc:syms>
<xsl:apply-templates mode="preproc:symtable">
<!-- we only need this param for the root children, so this is the only
template application that passes this -->
<xsl:with-param name="orig-root" select="$orig-root" />
</xsl:apply-templates>
</preproc:syms>
</xsl:variable>
<!-- gather a list of overrides -->
<xsl:variable name="overrides" select="
$new/preproc:syms/preproc:sym[ @override='true' ]
" />
<!-- check for duplicates -->
<xsl:variable name="symdup" as="element( preproc:sym )*"
select="symtable:find-duplicates( $new/preproc:syms )" />
<!-- overrides that override nothing may be the sign of a bug (expectation
of something that isn't there) -->
<xsl:for-each select="$overrides[ not( @name=$symdup/@name ) ]">
<xsl:message>
<xsl:text>[preproc/symtable] warning: symbol /</xsl:text>
<xsl:value-of select="$this-pkg/@name" />
<xsl:text>/</xsl:text>
<xsl:value-of select="@name" />
<xsl:text> has @override set, but does not override anything</xsl:text>
</xsl:message>
</xsl:for-each>
<!-- perform non-override duplicate checks (TODO: @ignore-dup is
intended to be temporary while map __head and __tail
generation conflicts are resolved) -->
<xsl:variable name="symdup-problems" as="element( preproc:sym )*" select="
$symdup[
not( @name = $overrides/@name
and @virtual = 'true' )
and not( @ignore-dup = 'true' ) ]
" />
<xsl:for-each select="$symdup-problems">
<xsl:variable name="dupname" select="@name" />
<xsl:choose>
<!-- attempt to override a non-virtual symbol -->
<xsl:when test="@name=$overrides/@name and not( @virtual='true' )">
<xsl:message>
<xsl:text>[preproc/symtable] error: cannot override non-virtual symbol /</xsl:text>
<xsl:value-of select="$this-pkg/@name" />
<xsl:text>/</xsl:text>
<xsl:value-of select="@name" />
</xsl:message>
</xsl:when>
<!-- just a plain duplicate -->
<xsl:otherwise>
<xsl:message>
<xsl:text>[preproc/symtable] error: duplicate symbol /</xsl:text>
<xsl:value-of select="$this-pkg/@name" />
<xsl:text>/</xsl:text>
<xsl:value-of select="@name" />
<xsl:text> (defined in ./</xsl:text>
<!-- output sources -->
<xsl:for-each select="
$new/preproc:syms/preproc:sym[
@name=$dupname
and @src
and not( @extern='true' )
and not( @name=$overrides/@name and @virtual='true' )
]
">
<xsl:if test="position() gt 1">
<xsl:text> and ./</xsl:text>
</xsl:if>
<xsl:value-of select="@src" />
</xsl:for-each>
<xsl:text>)</xsl:text>
<!-- if virtual, suggest override as an alternative solution -->
<xsl:if test="@virtual='true'">
<xsl:text>; did you forget @override?</xsl:text>
</xsl:if>
</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<!-- terminate if any duplicates are found, dumping documents for debugging -->
<xsl:if test="count( $symdup-problems ) gt 0">
<xsl:message>~~~~[begin document dump]~~~~</xsl:message>
<xsl:message select="$this-pkg" />
<xsl:message>~~~~[end document dump]~~~~</xsl:message>
<xsl:message>~~~~[begin symbol dump]~~~~</xsl:message>
<xsl:message select="$new" />
<xsl:message>~~~~[end symbol dump]~~~~</xsl:message>
<xsl:message terminate="yes">
<xsl:text>[preproc/symtable] fatal: aborting due to symbol errors</xsl:text>
</xsl:message>
</xsl:if>
<xsl:variable name="result" as="element( preproc:symtable )">
<preproc:symtable>
<!-- copy any existing symbols table -->
<preproc:syms>
<xsl:sequence select="preproc:symtable/preproc:sym" />
<xsl:sequence select="$new/preproc:syms/preproc:sym" />
</preproc:syms>
</preproc:symtable>
</xsl:variable>
<!-- output the symbols, checking for duplicates -->
<preproc:symtable>
<!-- validate imported externs -->
<xsl:variable name="extresults" as="element( preproc:syms )">
<xsl:call-template name="preproc:symtable-process-extern">
<xsl:with-param name="result" select="$result" />
</xsl:call-template>
</xsl:variable>
<!-- remove duplicates (if any) -->
<xsl:sequence select="
$extresults/preproc:sym[
not( @name=preceding-sibling::preproc:sym/@name )
]
, $extresults//preproc:error
" />
<!-- process symbols (except imported externs) -->
<xsl:variable name="newresult" as="element( preproc:syms )">
<xsl:call-template name="preproc:symtable-process-symbols">
<xsl:with-param name="extresults" select="$extresults" />
<xsl:with-param name="new" select="$new/preproc:syms" />
<xsl:with-param name="this-pkg" select="$this-pkg" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="dedup" as="element( preproc:sym )*"
select="$newresult/preproc:sym[
not(
(
@pollute='true'
and not( @type )
and (
@name=preceding-sibling::preproc:sym/@name
or @name=$newresult/preproc:sym[ @type ]/@name
)
)
)
]" />
<xsl:apply-templates mode="preproc:symtable-complete"
select="$dedup">
<xsl:with-param name="syms" select="$dedup" />
</xsl:apply-templates>
</preproc:symtable>
<xsl:message>
<xsl:text>[preproc/symtable] done.</xsl:text>
</xsl:message>
<!-- copy all of the original elements after the symbol table; we're not
outputting them as we go, so we need to make sure that we don't get
rid of them; discard any existing symbol table -->
<xsl:apply-templates mode="preproc:symtable-inject" />
</xsl:copy>
</xsl:template>
<xsl:template match="preproc:symtable" mode="preproc:symtable-inject"
priority="5">
<!-- strip old symbol table -->
</xsl:template>
<xsl:template match="*" mode="preproc:symtable-inject">
<xsl:sequence select="." />
</xsl:template>
<xsl:template name="preproc:symtable-process-extern"
as="element( preproc:syms )">
<xsl:param name="result" as="element( preproc:symtable )" />
<xsl:variable name="syms" as="element( preproc:syms )"
select="$result/preproc:syms" />
<preproc:syms>
<xsl:for-each select="$syms/preproc:sym[
@extern='true'
and @src
and not( @held ) ]">
<xsl:variable name="name" select="@name" />
<!-- our value may be concrete or an extern itself; we may also import
a package with a concrete definition -->
<xsl:variable name="ours" select="
$syms/preproc:sym[
@name=$name
and (
not( @src )
or ( @dtype and @src and not( @extern ) )
)
]
" />
<xsl:choose>
<!-- we have our own symbol; ensure the important values match the
expected (unless @dim has not yet been resolved) -->
<!-- XXX: the @dim='?' check leaves room for a dimension mismatch to
slip through; re-order checks (see package.xsl) as necessary to
ensure that this doesn't happen -->
<xsl:when test="$ours">
<xsl:if test="
not(
@type=$ours/@type
and @dtype=$ours/@dtype
and (
@dim=$ours/@dim
or $ours/@dim='?'
)
)
">
<preproc:error>
<xsl:text>extern mismatch: '</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>' (imported from </xsl:text>
<xsl:value-of select="@src" />
<xsl:text>)</xsl:text>
<xsl:for-each select="@type, @dtype, @dim">
<xsl:variable name="aname" select="local-name()" />
<xsl:text>; </xsl:text>
<xsl:value-of select="local-name()" />
<xsl:text>=</xsl:text>
<xsl:value-of select="$ours/@*[ local-name() = $aname ]" />
<xsl:text>, </xsl:text>
<xsl:value-of select="." />
<xsl:text> expected</xsl:text>
</xsl:for-each>
</preproc:error>
</xsl:if>
<!-- N.B.: there could potentially be multiple matches -->
<!-- TODO: pollution should be removed and l:resolv-extern
in the linker should look up the package that should
include the resolved extern from the processing stack -->
<preproc:sym pollute="true">
<xsl:sequence select="$ours[1]/@*|*" />
</preproc:sym>
</xsl:when>
<!-- we do not have our own symbol matching this extern;
ignore this for now, as it may be handled by a future
template expansion -->
<xsl:otherwise>
<preproc:sym held="true">
<xsl:sequence select="@*" />
</preproc:sym>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</preproc:syms>
</xsl:template>
<xsl:function name="preproc:final-extern-check" as="element(preproc:error )*">
<xsl:param name="symtable" as="element( preproc:symtable )" />
<!-- any remaining unresolved externs at this point are bad -->
<xsl:for-each select="$symtable/preproc:sym[
@extern = 'true'
and @src ]">
<!-- since @missing may be provided, let's include the actual
symbol in the runlog for debugging -->
<xsl:message select="'[preproc] missing extern: ', @name" />
<preproc:error>
<xsl:choose>
<xsl:when test="@missing and not( @missing = '' )">
<xsl:value-of select="normalize-space( @missing )" />
</xsl:when>
<xsl:otherwise>
<xsl:text>unresolved extern '</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>'</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text> (required by </xsl:text>
<xsl:value-of select="@src" />
<xsl:text>)</xsl:text>
</preproc:error>
</xsl:for-each>
</xsl:function>
<xsl:template name="preproc:symtable-process-symbols">
<xsl:param name="extresults" as="element( preproc:syms )" />
<xsl:param name="new" as="element( preproc:syms )" />
<xsl:param name="this-pkg" as="element( lv:package )" />
<preproc:syms>
<xsl:variable name="cursym" as="element( preproc:sym )*"
select="preproc:symtable/preproc:sym[
not( @held = 'true' ) ]" />
<xsl:sequence select="$cursym" />
<xsl:message>
<xsl:text>[preproc/symtable] processing symbol table...</xsl:text>
</xsl:message>
<xsl:for-each select="$new/preproc:sym[ not( @extern='true' and @src ) ]">
<xsl:variable name="name" select="@name" />
<xsl:variable name="src" select="@src" />
<xsl:variable name="dupall" select="
(
preceding-sibling::preproc:sym,
$cursym,
$extresults/preproc:sym
)[
@name=$name
]
" />
<xsl:variable name="dup" select="
$dupall[
not(
@src=$src
or ( not( @src ) and not( $src ) )
)
]
" />
<xsl:choose>
<xsl:when test="@pollute='true' and not( @type )">
<!-- we'll strip these out later -->
<xsl:sequence select="." />
</xsl:when>
<!-- note that dupall uses preceding-sibling, which will catch
duplicates in that case even if @override is not set -->
<xsl:when test="following-sibling::preproc:sym[ @name=$name and @override='true' ]">
<!-- overridden; we're obsolete :( -->
</xsl:when>
<!-- if we've gotten this far, then the override is good; clear it -->
<xsl:when test="@override='true'">
<xsl:copy>
<xsl:sequence select="@*[ not( name()='override' ) ], *" />
</xsl:copy>
</xsl:when>
<xsl:when test="$dupall[ @type ]">
<!-- there is already a symbol of this name from the same package;
let's not add duplicates -->
</xsl:when>
<xsl:otherwise>
<!-- this symbol is good; use it -->
<xsl:sequence select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</preproc:syms>
</xsl:template>
<xsl:template match="preproc:symtable" mode="preproc:symtable" priority="9">
<!-- ignore existing symbol tables (for now at least) -->
</xsl:template>
<!-- do not re-import symbol tables that have already been imported -->
<xsl:template match="lv:import[
@package=root(.)/preproc:symtable/preproc:sym[
@dtype
]/@src
]"
mode="preproc:symtable" priority="9">
</xsl:template>
<xsl:template name="preproc:symimport" match="lv:import[ @package ]" mode="preproc:symtable" priority="8">
<xsl:param name="orig-root" />
<xsl:param name="package" select="@package" />
<xsl:param name="export" select="@export" />
<xsl:param name="ignore-keep" select="@ignore-keep" />
<xsl:param name="no-extclass" select="@no-extclass" />
<xsl:param name="keep-classes" select="@keep-classes" />
<xsl:variable name="path" select="concat( $package, '.xmlo' )" />
<xsl:variable name="syms"
select="document( $path, $orig-root )/lv:*/preproc:symtable" />
<xsl:variable name="import-path" select="$package" />
<!-- if they're including a program package, do they realize what they're
doing!? -->
<!-- FIXME: @allow-nonpkg is no longer accurate terminology; change to
@allow-nonprg -->
<xsl:if test="
not( @allow-nonpkg = 'true' )
and $syms/parent::lv:package[ @program='true' ]
">
<xsl:message terminate="yes">
<xsl:text>[preproc/symtable] error: refusing to import non-package </xsl:text>
<xsl:value-of select="$import-path" />
<xsl:text>; use @allow-nonpkg to force (if you know what you are doing)</xsl:text>
</xsl:message>
</xsl:if>
<!-- determine our path from our name -->
<xsl:variable name="our-path">
<xsl:call-template name="preproc:get-path">
<xsl:with-param name="path"
select="ancestor::lv:package/@name" />
</xsl:call-template>
</xsl:variable>
<!-- to keep everything consistent and to simplify package equality
assertions, resolve relative paths -->
<xsl:variable name="import-default-path" select="$import-path" />
<xsl:message>
<xsl:text>[preproc/symtable] importing symbol table of </xsl:text>
<xsl:value-of select="$import-path" />
<xsl:text>...</xsl:text>
</xsl:message>
<!-- attempt to import symbols from the processed package -->
<xsl:if test="not( $syms )">
<xsl:message terminate="yes">
<xsl:text>[preproc/symtable] internal error: </xsl:text>
<xsl:text>failed to locate symbol table: </xsl:text>
<xsl:value-of select="$path" />
</xsl:message>
</xsl:if>
<!-- copy directly into symbol table, setting external source; local symbols
will not be imported -->
<xsl:for-each select="
$syms/preproc:sym[
(
not( @local='true' )
or @pollute='true'
or (
( @type='class' or @type='cgen' )
and $keep-classes='true'
)
)
and not( $no-extclass='true' and @extclass='true' )
]
">
<xsl:copy>
<xsl:choose>
<!-- pollution should have only the name and pollution status copied
over, which has the effect of reserving the symbol but not
providing enough information to actually make use of it; only
strip the data if this is a second-hand import -->
<!-- TODO: this list has gotten too large, but reducing it will require
refactoring other compilers and may reduce performance -->
<xsl:when test="@pollute='true' and @local='true'">
<xsl:sequence select="@name, @src, @pollute, @keep, @parent, @extclass" />
</xsl:when>
<!-- copy all the symbol information -->
<xsl:otherwise>
<xsl:sequence select="@*" />
</xsl:otherwise>
</xsl:choose>
<!-- all imported symbols are implicitly local (so including one package
will not include symbols down the entire hierarchy), unless the
symbol is explicitly marked global or @export was provided on the
import node -->
<xsl:if test="not( $export='true' )">
<xsl:attribute name="local" select="'true'" />
</xsl:if>
<!-- determine the relative path to the import -->
<xsl:attribute name="src">
<xsl:choose>
<!-- if no @src is set, then the answer is simple: the relative path is
the import path -->
<xsl:when test="not( @src )">
<xsl:value-of select="$import-default-path" />
</xsl:when>
<!-- otherwise, we need to merge the import path into the existing
relative path by prepending the import path (sans the package name
itself) onto the existing relative path and resolving relative
paths -->
<xsl:otherwise>
<xsl:call-template name="preproc:resolv-path">
<xsl:with-param name="path">
<!-- get the path of the import, sans package name -->
<xsl:variable name="path">
<xsl:call-template name="preproc:get-path">
<xsl:with-param name="path" select="$import-path" />
</xsl:call-template>
</xsl:variable>
<!-- concatenate it with the existing relative path -->
<xsl:if test="not( $path = '' )">
<xsl:value-of select="concat( $path, '/' )" />
</xsl:if>
<xsl:value-of select="@src" />
</xsl:with-param>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<!-- keep manipulation: *always* keep classes if requested, even if
@ignore-keep is provided; in the case of the latter, unsets @keep -->
<xsl:choose>
<!-- keep classes when requested -->
<xsl:when test="
( @type = 'class' or @type = 'cgen' )
and $keep-classes = 'true'">
<xsl:attribute name="keep" select="'false'" />
</xsl:when>
<!-- demolish @keep if requested -->
<xsl:when test="$ignore-keep = 'true'">
<xsl:attribute name="keep" select="'false'" />
</xsl:when>
</xsl:choose>
<!-- children should always be copied, unless poluting -->
<xsl:if test="not( @pollute='true' and @local='true' )">
<xsl:sequence select="preproc:*" />
</xsl:if>
</xsl:copy>
</xsl:for-each>
</xsl:template>
<xsl:template match="lv:template" mode="preproc:symtable" priority="9">
<!-- do not process template bodies; we're only interested in the symbols
they produce after expansion -->
<preproc:sym name="{@name}"
type="tpl" dim="0" desc="{@desc}" />
</xsl:template>
<xsl:template match="lv:rate" mode="preproc:symtable" priority="5">
<xsl:variable name="external" select="boolean( @external='true' )" />
<preproc:sym name="{@yields}" type="rate"
extclass="{$external}" keep="{boolean( @keep )}"
local="{@local}" dtype="float" dim="0" tex="{@sym}" />
<xsl:apply-templates mode="preproc:symtable" />
</xsl:template>
<xsl:template match="lv:const" mode="preproc:symtable" priority="5">
<xsl:variable name="dim">
<xsl:choose>
<!-- TODO: matrix/vector predicate to support either type via
@values -->
<xsl:when test="./lv:set or @values">
<xsl:text>2</xsl:text>
</xsl:when>
<xsl:when test="./lv:item">
<xsl:text>1</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>0</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- TODO: remove magic support -->
<preproc:sym name="{@name}"
magic="{boolean( @magic='true' )}"
type="const" dtype="{@type}" dim="{$dim}" desc="{@desc}" tex="{@sym}">
<!-- may or may not exist -->
<xsl:sequence select="@value" />
</preproc:sym>
<!-- for performance, we will not recurse any further; the rest are simply
data declarations -->
</xsl:template>
<xsl:template match="c:*[ @generates ]" mode="preproc:symtable" priority="5">
<xsl:variable name="parent" select="ancestor::lv:rate" />
<preproc:sym name="{@generates}" keep="{boolean( $parent/@keep )}"
parent="{$parent/@yields}"
type="gen" dtype="float" dim="1" desc="{@desc}" tex="{@sym}" />
<xsl:apply-templates mode="preproc:symtable" />
</xsl:template>
<!-- note the @dim value; this is determined later from its dependencies -->
<xsl:template match="lv:classify" mode="preproc:symtable" priority="5">
<xsl:variable name="external" select="boolean( @external='true' )" />
<xsl:variable name="terminate" select="boolean( @terminate='true' )" />
<xsl:variable name="keep" select="boolean( @keep='true' )" />
<preproc:sym name=":class:{@as}"
extclass="{$external}" terminate="{$terminate}" keep="{$keep}"
type="class" dim="?" desc="{@desc}" yields="{@yields}"
orig-name="{@as}">
<!-- copy preprocessor metadata to symbol for easy reference -->
<xsl:sequence select="@preproc:*" />
</preproc:sym>
<!-- generator if @yields is provided (note that we also have a @yields above
to avoid scanning separate object files for such common information)
-->
<xsl:if test="@yields">
<preproc:sym name="{@yields}" keep="{$keep}"
parent=":class:{@as}"
extclass="{$external}" terminate="{$terminate}"
type="cgen" dtype="boolean" dim="?" desc="{@desc}">
<xsl:sequence select="@preproc:*" />
</preproc:sym>
</xsl:if>
<xsl:apply-templates mode="preproc:symtable" />
</xsl:template>
<!-- Will be completed during post-processing so that typedefs can be
properly resolved -->
<xsl:template match="lv:param" mode="preproc:symtable" priority="5">
<xsl:variable name="dim">
<xsl:call-template name="preproc:param-dim" />
</xsl:variable>
<!-- we use the primitive data type derived from the typedef to ensure that
the system can still make use of the type even when the typedef is not
exported; indeed, typedefs are simply restrictions that need only be
known for compiling (at least at present). Also note the keep="true" to
ensure that all param symbols are retained after linking -->
<preproc:sym name="{@name}" keep="true"
type="param" dtype="{@type}" dim="{$dim}" desc="{@desc}" tex="{@sym}" />
</xsl:template>
<xsl:template match="lv:typedef" mode="preproc:symtable" priority="5">
<!-- FIXME: this is a kluge -->
<xsl:variable name="dtype" as="xs:string?"
select="if ( lv:base-type ) then
@name
else
lv:enum/@type
, lv:union/lv:typedef[1]/lv:enum/@type" />
<xsl:if test="not( $dtype )">
<xsl:message terminate="yes">
<xsl:text>[preproc/symtable] internal error: </xsl:text>
<xsl:text>failed to resolve type primitve of `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:if>
<preproc:sym name="{@name}" dtype="{$dtype}"
type="type" dim="0" desc="{@desc}" />
<xsl:apply-templates mode="preproc:symtable" />
</xsl:template>
<xsl:template match="lv:typedef/lv:enum/lv:item" mode="preproc:symtable" priority="5">
<xsl:variable name="dtype" select="parent::lv:enum/@type" />
<preproc:sym name="{@name}" value="{@value}"
type="const" dtype="{$dtype}" dim="0" desc="{@desc}" />
</xsl:template>
<xsl:template match="lv:function" mode="preproc:symtable" priority="5">
<!-- default TeX symbol to the function name -->
<xsl:variable name="tex">
<xsl:choose>
<xsl:when test="@sym">
<xsl:value-of select="@sym" />
</xsl:when>
<xsl:otherwise>
<xsl:text>\textrm{</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>}</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- TODO: determine return data type from tail -->
<!-- TODO: same for dim -->
<!-- functions can have circular dependencies (recursion) -->
<preproc:sym name="{@name}" type="func" dtype="float" dim="0" desc="{@desc}"
tex="{$tex}" allow-circular="true">
<!-- we need to include the argument order and symbol refs so that the
compiler knows how to call the function -->
<xsl:variable name="fname" select="@name" />
<xsl:for-each select="lv:param">
<preproc:sym-ref name=":{$fname}:{@name}" />
</xsl:for-each>
</preproc:sym>
<xsl:apply-templates mode="preproc:symtable" />
</xsl:template>
<!--
Function parameters are local to the function and are represented differently
in the symbol table than most other symbols. In particular:
- They have type lparam, not param
- Their name begins with a colon, which is normally invalid (ensuring that
there will be no naming conflicts)
- The name following the colon is the concatenation of the function name,
an underscore and the param name
-->
<xsl:template match="lv:function/lv:param" mode="preproc:symtable" priority="6">
<!-- determine number of dimensions -->
<xsl:variable name="dim">
<xsl:call-template name="preproc:param-dim" />
</xsl:variable>
<xsl:variable name="fname" select="parent::lv:function/@name" />
<preproc:sym name=":{$fname}:{@name}" parent="{$fname}" varname="{@name}"
type="lparam" dtype="{@type}" dim="{$dim}" desc="{@desc}" tex="{@sym}">
<!-- may or may not be defined -->
<xsl:sequence select="@default" />
</preproc:sym>
</xsl:template>
<!--
Same concept as function params
-->
<xsl:template match="c:let/c:values/c:value" mode="preproc:symtable" priority="5">
<xsl:variable name="name" select="@name" />
<!-- determine number of dimensions -->
<xsl:variable name="dim">
<xsl:call-template name="preproc:param-dim" />
</xsl:variable>
<!-- the name is generated automatically by the preprocessor; the user cannot
set it -->
<xsl:variable name="lname" select="
ancestor::c:let[
c:values/c:value[ @name=$name ]
]/@name
" />
<preproc:sym name=":{$lname}:{@name}" local="true" varname="{@name}"
type="lparam" dtype="{@type}" dim="{$dim}" desc="{@desc}" tex="{@sym}" />
<xsl:apply-templates mode="preproc:symtable" />
</xsl:template>
<xsl:template match="lv:extern" mode="preproc:symtable" priority="5">
<preproc:sym desc="{@name} extern" extern="true" missing="{@missing}">
<!-- copy all the user-supplied params -->
<xsl:sequence select="@*" />
</preproc:sym>
</xsl:template>
<xsl:template match="lv:meta" mode="preproc:symtable" priority="5">
<xsl:for-each select="lv:prop">
<preproc:sym name=":meta:{@name}" type="meta"
keep="true" />
</xsl:for-each>
</xsl:template>
<xsl:template match="preproc:sym[ @type='param' ]" mode="preproc:symtable-complete" priority="5">
<xsl:param name="syms" as="element( preproc:sym )*" />
<!-- attempt to derive type information from a typedef -->
<!-- TODO: also check symbol table after import (post-process) -->
<xsl:variable name="type" select="@dtype" />
<xsl:variable name="typedef" as="element( preproc:sym )?"
select="$syms[ @type = 'type'
and @name = $type ]" />
<xsl:if test="not( $typedef and $typedef/@dtype )">
<xsl:message terminate="yes">
<xsl:text>[preproc/symtable] internal error: </xsl:text>
<xsl:text>failed to resolve type: </xsl:text>
<xsl:value-of select="$type" />
</xsl:message>
</xsl:if>
<!-- complete datatype with primitive -->
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:attribute name="dtype" select="$typedef/@dtype" />
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="preproc:symtable-complete" priority="1">
<!-- symbol does not need completion -->
<xsl:sequence select="." />
</xsl:template>
<!--
Determines param dimension from its string definition:
vector = 1-dimensional;
matrix = 2-dimensional;
otherwise, scalar = 0-dimensional
Other dimensions are certainly supported, but @set's syntax does not support
their specification.
-->
<xsl:template name="preproc:param-dim">
<xsl:choose>
<xsl:when test="@set = 'vector'">
<xsl:text>1</xsl:text>
</xsl:when>
<xsl:when test="@set = 'matrix'">
<xsl:text>2</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>0</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Preprocesses the XML
This progress is aggressive; the resulting tree will follow the structure of
the original XML, but will be heavily augmented and some parts rewritten.
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:t="http://www.lovullo.com/rater/apply-template"
xmlns:c="http://www.lovullo.com/calc"
xmlns:w="http://www.lovullo.com/rater/worksheet"
xmlns:ext="http://www.lovullo.com/ext"
xmlns:util="http://www.lovullo.com/util">
<xsl:include href="depgen.xsl" />
<xsl:include href="preproc/package.xsl" />
<!--
Raters themselves get special treatment
-->
<xsl:template match="lv:rater" mode="preproc:compile" priority="9">
<xsl:param name="orig-root" select="." />
<xsl:param name="stopshort" />
<xsl:message>
<xsl:text>[preproc] [rater]</xsl:text>
</xsl:message>
<!-- handle package preprocessing -->
<xsl:variable name="pkg-result">
<xsl:call-template name="preproc:pkg-compile">
<xsl:with-param name="orig-root" select="$orig-root" />
<xsl:with-param name="stopshort" select="$stopshort" />
</xsl:call-template>
</xsl:variable>
<xsl:copy-of select="$pkg-result" />
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,80 @@
<symbol-map
xmlns="http://www.lovullo.com/rater/symbol-map"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.lovullo.com symbol-map.xsd">
<!-- global parameter -->
<symbol type="param">\Theta</symbol>
<!-- function parameter -->
<symbol type="fparam">\theta</symbol>
<!-- constant -->
<symbol type="const">C</symbol>
<!-- enum item -->
<symbol type="item">E</symbol>
<symbol type="classify">\kappa</symbol>
<!-- the class accumulator will accumulate the values returned by any lv:rate
that matches a given classification -->
<symbol type="class-accumulator">K</symbol>
<!-- map functions to their own name -->
<symbol type="function">
<name />
</symbol>
<!-- lv:rate yield (omega to denote "final result") -->
<symbol type="rate">\omega</symbol>
<!-- generating expressions -->
<symbol type="generator">G</symbol>
<symbol type="accumulator">\sigma</symbol>
<!-- typedefs should have no symbol by default; their plain-text domain can be
listed -->
<symbol type="typedef">
<nothing />
</symbol>
<!--
Explicit symbol reservations
Any symbol mentioned within this file will be disallowed from being used in
any @sym attribute within raters/packages.
Note that this list need not contain symbols defined elsewhere in the
framework. For example, symbols mentioned in symbol-map need not appear here.
Do not be overly picky with this list; include only those that are established
in the field of mathematics as a whole (not a specific category, unless it is
CS), where the symbol is almost always understood to have a particular
meaning. For example:
- We wouldn't want to allow \pi, since that is already established as the
ratio of a circle's circumference to its diameter
- Nor should we allow \delta, since that is commonly used in CS to represent
Kronecker's delta
- We should *not*, however, disallow \mu just because it is the Möbius
function in number theory
-->
<reserved>
<!-- commonly used as an alterative to Iverson's convention under certain
circumstances, so it's best to avoid the notation entirely even if it is
not used within the framework -->
<reserve sym="\delta" reason="Kronecker delta" />
<reserve sym="\pi" reason="Ratio of a circle's circumference to its diameter" />
<reserve sym="\phi" reason="The golden ratio (another established mathematical constant)" />
<reserve sym="\Sigma" reason="Summation symbol; sigma-notation" />
<reserve sym="\Pi" reasons="Product symbol; pi-notation" />
<!-- as common as this is in programming as an index, this is another
established mathematical constant (sqrt(-1)) -->
<reserve sym="i" reason="Imaginary number" />
</reserved>
</symbol-map>

View File

@ -0,0 +1,186 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Utility templates
-->
<xsl:stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:util="http://www.lovullo.com/util"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:lvm="http://www.lovullo.com/rater/map"
xmlns:c="http://www.lovullo.com/calc"
xmlns:ext="http://www.lovullo.com/ext">
<xsl:variable name="_chrlower" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="_chrupper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<!--
Path to map directory
-->
<xsl:param name="map-root" select="'../map/'" />
<xsl:param name="retmap-root" select="concat( $map-root, 'return/' )" />
<xsl:template name="util:load-package">
<xsl:param name="package" />
<xsl:param name="self" />
<xsl:variable name="path" select="concat($package, '.xml')" />
<xsl:variable name="pkg" select="document( $path, $self )/lv:package" />
<ext:package name="{$pkg/@name}">
<!-- path, including extension -->
<xsl:attribute name="path">
<xsl:value-of select="$path" />
</xsl:attribute>
<!-- path, excluding extension (as it appears in @package) -->
<xsl:attribute name="import-path">
<xsl:value-of select="@package" />
</xsl:attribute>
<xsl:copy-of select="$pkg" />
</ext:package>
</xsl:template>
<xsl:template match="lvm:*" mode="util:map-expand" priority="1">
<xsl:param name="path" select="'.'" />
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates mode="util:map-expand">
<xsl:with-param name="path" select="$path" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!-- recursively inline imports -->
<xsl:template match="lvm:import" mode="util:map-expand" priority="5">
<xsl:param name="path" select="'.'" />
<xsl:apply-templates
select="document( concat( @path, '.xml' ), . )/lvm:*/*"
mode="util:map-expand">
<xsl:with-param name="path" select="concat( $path, '/', @path )" />
</xsl:apply-templates>
</xsl:template>
<!-- must be lower priority than lv:import -->
<xsl:template match="/lvm:*/lvm:*" mode="util:map-expand" priority="3">
<xsl:param name="path" select="'.'" />
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:attribute name="__src" select="$path" />
<xsl:apply-templates mode="util:map-expand">
<xsl:with-param name="path" select="$path" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!--
Converts first character to uppercase
Functions like ucfirst in PHP
@param string str string to ucfirst
@return string provided string with first character converted to uppercase
-->
<xsl:template name="util:ucfirst">
<xsl:param name="str" />
<!-- convert first char to uppercase -->
<xsl:value-of
select="translate( substring( $str, 1, 1 ), $_chrlower, $_chrupper )" />
<!-- remainder of string as it was provided -->
<xsl:value-of select="substring( $str, 2 )" />
</xsl:template>
<!--
Converts a string to uppercase
-->
<xsl:template name="util:uppercase">
<xsl:param name="str" />
<xsl:value-of select="translate( $str, $_chrlower, $_chrupper )" />
</xsl:template>
<!--
Converts a string to lowercase
-->
<xsl:template name="util:lowercase">
<xsl:param name="str" />
<xsl:value-of select="translate( $str, $_chrupper, $_chrlower )" />
</xsl:template>
<xsl:template name="util:json">
<xsl:param name="id" />
<xsl:param name="value" />
<xsl:param name="obj" />
<xsl:param name="array" />
<xsl:if test="$id">
<xsl:text>"</xsl:text>
<xsl:call-template name="util:json-escape">
<xsl:with-param name="string" select="$id" />
</xsl:call-template>
<xsl:text>":</xsl:text>
</xsl:if>
<xsl:choose>
<xsl:when test="$array">
<xsl:text>[</xsl:text>
<xsl:for-each select="$array/*">
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:value-of select="." />
</xsl:for-each>
<xsl:text>]</xsl:text>
</xsl:when>
<xsl:when test="$obj">
<xsl:text>{</xsl:text>
<xsl:for-each select="$obj/*">
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:value-of select="." />
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:when>
<xsl:when test="$value">
<xsl:text>"</xsl:text>
<xsl:call-template name="util:json-escape">
<xsl:with-param name="string" select="$value" />
</xsl:call-template>
<xsl:text>"</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">[util] !!! invalid util:json</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="util:json-escape">
<xsl:param name="string" />
<xsl:value-of select="$string" />
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:include href="compiler/linker.xsl" />
</xsl:stylesheet>

412
src/current/map.xsd 100644
View File

@ -0,0 +1,412 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lovullo.com/rater/map"
xmlns="http://www.lovullo.com/rater/map"
elementFormDefault="qualified">
<xs:complexType name="classDescriptorType">
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Classification name (lv:classify/@as)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ignores-eligibility" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
Classification should not add a match requiring that at least one
eligible supplier use it (if supported by implementation)
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:simpleType name="valueType">
<xs:annotation>
<xs:documentation xml:lang="en">
String constant
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string" />
</xs:simpleType>
<xs:complexType name="defaultType">
<xs:annotation>
<xs:documentation xml:lang="en">
Default complex value for a translation
Concatenates each value.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="from" type="fromType"
minOccurs="0" maxOccurs="unbounded" />
<xs:element name="value" type="valueType"
minOccurs="0" maxOccurs="unbounded" />
</xs:choice>
</xs:complexType>
<xs:complexType name="translateType">
<xs:annotation>
<xs:documentation xml:lang="en">
Defines a translation for a given value
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="from" type="fromType"
minOccurs="0" maxOccurs="unbounded" />
<xs:element name="value" type="valueType"
minOccurs="0" maxOccurs="unbounded" />
</xs:choice>
<xs:attribute name="key" type="xs:string" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Source value to match against for translation
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="value" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Value to translate to
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="empty" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Default value when the resulting translation is the
empty string
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="fromType">
<xs:annotation>
<xs:documentation xml:lang="en">
Defines translation rules between source and destination fields; not
to be used with @from
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<!-- translation rule -->
<xs:element name="translate" type="translateType"
minOccurs="0" maxOccurs="unbounded" />
<xs:element name="default" type="defaultType"
minOccurs="0" maxOccurs="unbounded" />
</xs:choice>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Identifier of source field
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="default" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional default value if no source value is available
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="affects-eligibility" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
Whether this field should be considered when generating eligibility
classes (if supported by implementation); default true
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:group name="setTransformGroup">
<xs:choice>
<xs:element name="set" type="setType" />
<xs:element name="const">
<xs:annotation>
<xs:documentation xml:lang="en">
Maps a static value
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="value" type="xs:string" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Static value to map to destination
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="from">
<xs:annotation>
<xs:documentation xml:lang="en">
Maps a source value to its destination by index
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Source field identifier
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="index" type="xs:string" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Iterator variable or constant integer representing the source
value index
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:group>
<xs:complexType name="setType">
<xs:annotation>
<xs:documentation xml:lang="en">
Translates source data into a set
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:group ref="setTransformGroup" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="each" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Source field identifier to use as index for the iterator
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="index" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Variable to serve as a reference for the iterator index
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ignore-empty" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
When true, if a transformation yields a value that is undefined, an
empty string, or the string "0", then omit it from the set
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="mapType">
<xs:annotation>
<xs:documentation xml:lang="en">
Defines a map from a source field to a destination field with optional
transformation rules
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="from" type="fromType" minOccurs="0" maxOccurs="1" />
<xs:element name="set" type="setType" minOccurs="0" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="to" type="xs:string" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Identifier of destination field
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="from" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Identifier of source field if no data transformations are to be
performed; cannot be used with @value
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="value" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Constant value to be mapped to destination; cannot be used with @from
or from node
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="affects-eligibility" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
Whether this field should be considered when generating eligibility
classes (if supported by implementation); default true
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="override" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
Override mapping sharing the same destination identifier in an
imported package
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="passType">
<xs:annotation>
<xs:documentation xml:lang="en">
Maps from a source field to a destination field of the same name without
any translation; shorthand for identical @to and @from attributes on a
general map node
</xs:documentation>
</xs:annotation>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Identifier of the source (and consequently destination) field
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="novalidate" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
Do not validate that the field exists; useful for mapping data that is
not explicitly recognized as a field by the source
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="affects-eligibility" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
Whether this field should be considered when generating eligibility
classes (if supported by implementation); default true
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="override" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
Override mapping sharing the same destination identifier in an
imported package
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<!-- represents some type of mapping -->
<xs:group name="mappingGroup">
<xs:choice>
<xs:element name="map" type="mapType"
minOccurs="0" maxOccurs="unbounded" />
<xs:element name="pass" type="passType"
minOccurs="0" maxOccurs="unbounded" />
</xs:choice>
</xs:group>
<xs:complexType name="importType">
<xs:annotation>
<xs:documentation xml:lang="en">
Includes mappings from another source file
</xs:documentation>
</xs:annotation>
<xs:attribute name="path" type="xs:string" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Path to source file
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:element name="program-map">
<xs:annotation>
<xs:documentation xml:lang="en">
Maps program data between two points
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="import" type="importType"
minOccurs="0" maxOccurs="unbounded" />
<xs:element name="class" type="classDescriptorType"
minOccurs="0" maxOccurs="unbounded" />
<xs:group ref="mappingGroup" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="src" type="xs:string" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Source document to validate against (document root node must be
known to the implementation)
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="return-map">
<xs:annotation>
<xs:documentation xml:lang="en">
Specifies data transformations/filtering to be performed on data before
being returned to a caller
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="import" type="importType"
minOccurs="0" maxOccurs="unbounded" />
<xs:group ref="mappingGroup" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Writes preprocessor output to disk to eliminate the overhead of reprocessing
for each task that requires such output.
Also performs validation.
N.B.: Requires XSLT >=2.0
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lvm="http://www.lovullo.com/rater/map"
xmlns:w="http://www.lovullo.com/rater/worksheet"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:lvp="http://www.lovullo.com">
<xsl:output method="text" />
<xsl:variable name="nl" select="'&#10;'" />
<xsl:template match="lv:rater|lv:package">
<!-- output deps, one per line -->
<xsl:for-each select="lv:import[ @package ]">
<xsl:value-of select="concat( @package, $nl )" />
</xsl:for-each>
</xsl:template>
<xsl:template match="lvp:program">
<!-- output deps, one per line -->
<xsl:for-each select="lvp:import[ @package ]">
<xsl:value-of select="concat( @package, $nl )" />
</xsl:for-each>
</xsl:template>
<xsl:template match="lvm:program-map|lvm:return-map">
<!-- output deps, one per line -->
<xsl:for-each select="lvm:import[ @path ]">
<xsl:value-of select="concat( @path, $nl )" />
</xsl:for-each>
</xsl:template>
<xsl:template match="w:worksheet">
<xsl:value-of select="concat( @package, $nl )" />
</xsl:template>
<xsl:template match="*">
<!-- do nothing -->
</xsl:template>
</xsl:stylesheet>

File diff suppressed because it is too large Load Diff

6
src/current/src/.gitignore vendored 100644
View File

@ -0,0 +1,6 @@
# may be copied into the cwd for testing, since it is a dep
saxon8.jar
*.class
*.jar
*.manifest

View File

@ -0,0 +1,18 @@
dslc_src := $(wildcard com/lovullo/dslc/*.java)
dslc_bin := $(dslc_src:.java=.class)
.PHONY: dslc clean
dslc: dslc.jar
%.class: %.java
javac $<
# we explicitly specify a glob on the path because inner classes are compiled
# into their own files
dslc.jar: $(dslc_bin)
jar cfm $@ dslc.manifest com/lovullo/dslc/*.class
clean:
rm -f $(dslc_bin) dslc.jar

View File

@ -0,0 +1,303 @@
package com.lovullo.dslc;
import java.io.*;
import java.util.Map;
import java.util.HashMap;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.*;
import javax.xml.validation.*;
import javax.xml.transform.sax.SAXTransformerFactory;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
public class DslCompiler
{
private static class _DslCompiler
{
private Validator _xsd;
private HashMap<String,Transformer> _xsl;
public _DslCompiler()
{
_xsd = _createXsd();
_xsl = new HashMap<String,Transformer>();
}
public void compile(
Source doc,
String cmd,
String src,
String dest,
HashMap<String,String> params
) throws Exception
{
if ( cmd.equals( "validate" ) )
{
_xsd.validate( doc );
return;
}
else if ( cmd.equals( "rm" ) )
{
// remove file (purposely uncaught)
( new File( src ) ).delete();
return;
}
if ( dest.equals( "" ) )
{
System.err.printf(
"fatal: no destination path provided\n"
);
System.exit( 4 );
}
// transform to dest
File destfile = new File( dest );
try
{
_transform(
src,
doc,
cmd,
new StreamResult( new File( dest ) ),
params
);
}
catch ( Exception e )
{
// delete the output file; it's garbage
destfile.delete();
// be verbose and unprofessional.
throw e;
}
}
private void _transform(
String src,
Source doc,
String cmd,
StreamResult dest,
HashMap<String,String> params
) throws Exception
{
// load the stylesheet if it has not been already (we load lazily in
// case the stylesheet is never needed)
if ( !( _xsl.containsKey( cmd ) ) )
{
_xsl.put( cmd, _createXslt( cmd ) );
}
// since XSL's abstraction does not provide a means to retrieve the
// document path (nor does it make sense to), we will pass it in
// ourselves, stripped of the file extension
String srcpkg = src.substring( 0, src.lastIndexOf( '.' ) );
// similarily, quickly resolve the relative root path
Integer dircount = srcpkg.replaceAll( "[^/]", "" ).length();
String relroot = new String( new char[ dircount ] ).replace( "\0", "../" );
Transformer t = _xsl.get( cmd );
t.setParameter( "__srcpkg", srcpkg );
t.setParameter( "__relroot", relroot );
t.setParameter( "__rseed", (int)( Math.random() * 10e6 ) );
_setTemplateParams( t, params );
t.transform( doc, dest );
}
private void _setTemplateParams(
Transformer t,
HashMap<String,String> params
) throws Exception
{
for ( Map.Entry<String, String> param : params.entrySet() )
{
t.setParameter( param.getKey(), param.getValue() );
}
}
private Validator _createXsd()
{
final SchemaFactory factory = SchemaFactory.newInstance(
XMLConstants.W3C_XML_SCHEMA_NS_URI
);
// we must disable Unique Particle Attribution (UPC) checking; the
// validator used during development did not check for this and it
// currently does not pass this test (note that disabling this also
// improves the speed of the validator)
try
{
factory.setFeature(
"http://apache.org/xml/features/validation/schema-full-checking",
false
);
}
catch ( Exception e )
{
System.err.println(
"fatal: cannot disable UPA checking; " +
e.getMessage()
);
System.exit( 1 );
}
try
{
final Schema schema =
factory.newSchema( new File( "rater/rater.xsd" ) );
return schema.newValidator();
}
catch ( SAXException e )
{
System.err.printf(
"fatal: %s\n",
e.getMessage()
);
System.exit( 1 );
}
return null;
}
private Transformer _createXslt( String src )
{
try
{
final TransformerFactory factory = TransformerFactory.newInstance(
"net.sf.saxon.TransformerFactoryImpl", null
);
final Source xsl = new StreamSource( "rater/" + src + ".xsl" );
return factory.newTransformer( xsl );
}
catch ( Exception e )
{
System.err.printf(
"fatal: compilation failed; %s\n",
e.getMessage()
);
System.exit( 2 );
}
return null;
}
}
public static void main( String[] args ) throws Exception
{
BufferedReader stdin = new BufferedReader(
new InputStreamReader( System.in )
);
String src = ( args.length > 0 ) ? args[0] : "";
_DslCompiler dslc = new _DslCompiler();
try
{
if ( src != "" )
{
compileSrc( dslc, src );
}
else
{
while ( ( src = stdin.readLine() ) != null )
{
compileSrc( dslc, src );
}
}
}
catch ( IOException e )
{
System.err.println(
"fatal: I/O error while reading input files: " +
e.getMessage()
);
System.exit( 1 );
}
catch ( Exception e )
{
System.err.printf(
"fatal: `%s': %s\n",
src,
e.getMessage()
);
// generic exception..ruh roh
throw e;
}
}
private static void compileSrc( _DslCompiler dslc, String cmdline ) throws Exception
{
System.err.println( cmdline );
String[] args = cmdline.split( " " );
String dest = "";
if ( args.length < 2 )
{
System.err.printf( "fatal: invalid command: %s\n", cmdline );
System.exit( 3 );
}
else if ( args.length >= 3 )
{
dest = args[2];
}
String cmd = args[0];
String src = args[1];
HashMap<String,String> params = _getXslParams( args );
Source doc = new StreamSource( src );
dslc.compile( doc, cmd, src, dest, params );
}
private static HashMap<String,String> _getXslParams( String[] args )
throws Exception
{
HashMap<String,String> params = new HashMap<String,String>();
for ( int i = 3; i < args.length; i++ )
{
String[] keyval = args[ i ].split( "=" );
if ( keyval.length < 2 )
{
throw new Exception(
"Invalid template param assignment: " +
args[ i ]
);
}
params.put( keyval[ 0 ], keyval[ 1 ] );
}
return params;
}
}

View File

@ -0,0 +1,226 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Outputs the global classifier that can be used to determine classifications
across all eligible suppliers.
-->
<xsl:stylesheet version="2.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:lvp="http://www.lovullo.com"
xmlns:lvm="http://www.lovullo.com/rater/map"
xmlns:lvmc="http://www.lovullo.com/rater/map/compiler"
xmlns:c="http://www.lovullo.com/calc"
xmlns:l="http://www.lovullo.com/rater/linker"
xmlns:compiler="http://www.lovullo.com/rater/compiler"
xmlns:calc-compiler="http://www.lovullo.com/calc/compiler"
xmlns:util="http://www.lovullo.com/util"
xmlns:ext="http://www.lovullo.com/ext"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<xsl:output
indent="yes"
omit-xml-declaration="yes"
/>
<xsl:include href="include/dslc-base.xsl" />
<!-- compiler -> JS -->
<xsl:include href="compiler/linker.xsl" />
<xsl:include href="compiler/map.xsl" />
<xsl:include href="include/depgen.xsl" />
<!-- path to program XML -->
<xsl:param name="path-program-ui" />
<xsl:template match="/" priority="5">
<!-- the rater itself -->
<xsl:text>var rater = </xsl:text>
<xsl:value-of disable-output-escaping="yes" select="/lv:package/l:exec/text()" />
<xsl:text>; </xsl:text>
<!-- maps may or may not exist -->
<xsl:variable name="map" select="/lv:package/l:map-exec" />
<xsl:variable name="retmap" select="/lv:package/l:retmap-exec" />
<!-- store a reference to the mapper in rater.fromMap() -->
<xsl:text>rater.fromMap = </xsl:text>
<xsl:choose>
<xsl:when test="$map">
<xsl:value-of disable-output-escaping="yes" select="$map/text()" />
</xsl:when>
<!-- no map available -->
<xsl:otherwise>
<!-- simply invoke the conintuation with the provided data -->
<xsl:text>function(d,c){c(d);}</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text>; </xsl:text>
<!-- return map -->
<xsl:text>rater._retmap = </xsl:text>
<xsl:choose>
<xsl:when test="$retmap">
<xsl:value-of disable-output-escaping="yes" select="$retmap/text()" />
</xsl:when>
<!-- no map available -->
<xsl:otherwise>
<!-- simply invoke the conintuation with the provided data -->
<xsl:text>function(d,c){c(d);}</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text>; </xsl:text>
<!-- we'll export a version that automatically performs the mapping -->
<xsl:text>module.exports = function( args_base ) { </xsl:text>
<xsl:text>var ret; rater.fromMap( args_base, function( args ) {</xsl:text>
<xsl:text>
var rater_result = rater( args );
// perf counter
var start = ( new Date() ).getTime();
rater._retmap( rater_result.vars, function( result )
{
// add the final premium
result.premium = rater_result.premium;
result.__classes = rater_result.classes;
// process the rating worksheet
try
{
result.__worksheet = process_worksheet(
rater.worksheet,
rater_result.vars,
rater_result.consts,
rater_result.debug,
rater_result.premium
);
}
catch ( e )
{
result.__worksheet = [ 'Failed: ' + e.message ];
}
ret = result;
} );
// add performance data
var end = ( new Date() ).getTime(),
time = ( ( new Date() ).getTime() - start );
ret.__perf = {
time: {
start: start,
end: end,
total: time
}
};
</xsl:text>
<xsl:text>} );</xsl:text>
<xsl:text>return ret;</xsl:text>
<xsl:text>}; </xsl:text>
<xsl:text>
function process_worksheet( worksheet, vars, consts, debug, premium )
{
var ret = {};
for ( var name in worksheet )
{
var data = Array.prototype.slice.call( worksheet[ name ] ),
disp = data[0],
calc = data[1],
always = data[2];
ret[ name ] = [
disp,
process_wdisplay_set( [calc], vars, consts, debug ),
( ( name === 'yield' )
? premium
: ( vars[ name ] || consts[ name ] )
),
( always === 'true' )
];
}
return ret;
}
function process_wdisplay( data, vars, consts, debug )
{
if ( data === null )
{
return null;
}
var name = data[ 0 ],
desc = data[ 1 ],
sub = data[ 2 ],
val = data[ 3 ]; // may not exist
return [
name,
desc,
process_wdisplay_set( sub, vars, consts, debug ),
val || process_wval( name, desc, vars, consts, debug )
];
}
function process_wval( type, desc, vars, consts, debug )
{
if ( desc.runtime )
{
type = 'runtime';
}
switch ( type )
{
case 'apply':
case 'cases':
case 'case':
case 'otherwise':
case 'runtime':
return ( debug[ desc._id ] );
case 'value-of':
return ( vars[ desc.name ] || consts[ desc.name ] );
default:
return '';
}
}
function process_wdisplay_set( sub, vars, consts, debug )
{
var ret = [],
i = sub.length;
while ( i-- )
{
if ( sub[ i ] === undefined )
{
continue;
}
ret[ i ] = process_wdisplay( sub[ i ], vars, consts, debug );
}
return ret;
}
</xsl:text>
<!-- expose the raw, unmapped rater -->
<xsl:text>module.exports.rater = rater;</xsl:text>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,835 @@
/**
* Rater XML summary stylesheet
*/
body {
/* menu width * menu em + 1 */
margin: 0em 0em 0em 13em;
}
.menu
{
position: fixed;
overflow: scroll;
background-color: #ad7fa8;
border-right: 1px solid black;
top: 0px;
left: 0px;
bottom: 0px;
width: 15em;
padding: 0.25em;
font-size: 0.8em;
resize: horizontal;
z-index: 10;
}
.menu h1
{
font-size: 1.4em;
margin-bottom: 0.15em;
border-bottom: 1px solid #75507b;
}
.menu h1:not(:first-child)
{
margin-top: 2em;
}
.menu h2
{
font-size: 1.2em;
margin-left: 0.5em;
margin-bottom: 0.15em;
}
.menu ul
{
margin: 0px;
padding-left: 1.25em;
list-style: none;
}
fieldset,
.container-param,
.tcontent
{
display: none;
}
fieldset:target,
.container-param:target,
.tcontent:target
{
display: block;
}
fieldset fieldset
{
display: block;
}
#xml-raw:not(.show)
{
display: none;
}
table > caption
{
font-weight: bold;
}
dt
{
font-size: 1.1em;
font-weight: bold;
margin-top: 0.5em;
}
dt.param.classifies {
color: green;
}
dt.param > .type
{
font-size: 0.8em;
font-weight: normal;
}
dl.params > .default
{
font-size: 0.8em;
}
table
{
border: 1px solid #888a87;
margin: 1em 0px 0px 0px;
}
table th
{
border-bottom: 2px solid #888a87;
padding: 0.5em;
}
table tr > td
{
padding: 0.5em;
}
table tr:not(:last-child) > td
{
border-bottom: 1px solid #888a87;
}
fieldset
{
border: 0px;
margin: 0px;
}
.class .requires,
.class .name,
.class .usedby,
.typedef .type,
.typedef .name,
.params .usedby,
.rate .yields,
.rate .param,
.func .param,
.func .usedby
{
font-size: 0.8em;
}
.usedby a
{
text-decoration: none;
}
.calc-order,
.classifier-calcs
{
max-height: 20em;
overflow-y: scroll;
}
#workstatus
{
position: fixed;
display: none;
background-color: #eeeeec;
left: 0px;
top: 0px;
right: auto;
padding: 0.25em;
font-size: 0.9em;
border-bottom: 1px solid #babdb6;
border-right: 1px solid #babdb6;
/* entry-form is 2000 */
z-index: 1999;
}
#workstatus.show
{
display: block;
}
.package
{
background-color: #eeeeec;
border: 1px solid #babdb6;
border-radius: 1em;
padding: 1em;
margin-bottom: 3em;
clear: both;
}
.package.devsummary
{
background-color: #ad7fa8;
border-color: #75507b;
}
.package > .title
{
position: relative;
background-color: #d3d7cf;
border-bottom: 1px solid #babdb6;
border-radius: 1em 1em 0px 0px;
margin: -1em -1em 0px -1em;
padding: 1em;
}
.package > .title > h2
{
margin: 0;
padding: 0;
}
.package > h3,
.package > div > h3
{
border-width: 0px 0px 2px 0px;
border-style: solid;
border-color: #babdb6;
margin: 1em -0.9em;
padding: 0.1em 0.5em;
}
.package.us > .title,
.package.devsummary > .title
{
background-color: #ad7fa8;
}
.package.devsummary > .title,
.package.devsummary > h3,
.package.devsummary > div > h3
{
border-bottom-color: #75507b;
}
.package h4.rate-group
{
border-bottom: 1px dashed #babdb6;
margin-top: 3em;
margin-left: -0.5em;
}
.rate-groups .generates
{
font-size: 0.9em;
}
.package > .title > .imports
{
position: absolute;
top: 0px;
right: 0px;
margin: 1.25em;
}
.package > .title > .imports::before
{
content: 'Imports: ';
font-weight: bold;
}
body > fieldset
{
position: relative;
padding: 5em 0em 0em 0em;
}
body > fieldset > legend
{
position: absolute;
background-color: #eeeeec;
border-top: 1px solid #babdb6;
border-bottom: 1px solid #babdb6;
padding: 0.5em;
left: -0.5em;
top: 0em;
right: 0px;
font-size: 1.5em;
letter-spacing: 0.1em;
}
body > fieldset > legend > a.pkg
{
float: right;
font-size: 0.5em;
line-height: 2.5em;
color: inherit;
border: none;
text-decoration: none;
}
body > fieldset > legend + p.desc
{
margin-top: 0em;
font-size: 1.1em;
}
fieldset.rate > legend > a
{
text-decoration: none;
border-bottom: 1px dotted black;
}
fieldset.rate .classes
{
display: inline-block;
margin: -1em 0em 2em 0em;
padding-bottom: 0.25em;
font-size: 0.9em;
border-bottom: 1px dotted black;
}
fieldset.rate .classes > .prefix
{
font-variant: small-caps;
}
/* more than just .rate */
fieldset .calc-yields
{
font-variant: small-caps;
}
ul.let
{
list-style: none;
margin: 0;
padding: 0;
}
ul.let li.letequ
{
margin-top: 1em;
}
ul.let .letdesc
{
font-size: 0.9em;
margin-left: 1em;
}
.rate .body,
.func .body,
.yield .body
{
float: left;
}
.rate .right,
.func .right,
.yield .right
{
float: right;
padding: 0;
margin: 0;
max-width: 40%;
}
.rate .body
{
/** give room for accumulators, depends, etc **/
margin-bottom: 5em;
}
.right h4
{
border-bottom: 1px solid black;
margin-top: 0;
margin-bottom: 0.5em;
}
.right > div:not(:first-child)
{
margin-top: 1em;
}
.right > .parts.many
{
-webkit-columns: 2;
-moz-columns: 2;
}
.right > .parts > .part,
.right > .generators > .generator
{
text-align: center;
margin-top: 2em;
}
.right > .parts > .part:first-child
{
margin-top: 0em;
}
.right > .parts.many > .part
{
/** FF, Webkit and W3C respectively */
display: table;
-webkit-column-break-inside: avoid;
break-inside: avoid-column;
}
.right > .parts > .part > .label,
.right > .generators > .generator > .desc
{
font-size: 0.9em;
}
.accum
{
position: absolute;
bottom: 1em;
font-size: 0.9em;
max-width: 60%;
}
.error
{
font-weight: bold;
color: red;
}
/** entry form **/
#test-data
{
position: relative;
}
#test-data:not(:target)
{
display: none;
}
form.entry-form
{
}
form.entry-form.focus
}
form.entry-form > dl
}
form.entry-form.focus > dl
{
}
form.entry-form dt
{
clear: left;
}
form.entry-form .matrix
{
display: inline-block;
border: 1px inset;
padding: 0.25em;
float: left;
clear: left;
}
form.entry-form .entry-add
{
float: left;
clear: left;
}
form.entry-form > .foot
{
}
form.entry-form > .foot > .ratemsg
{
display: inline-block;
font-weight: bold;
font-size: 1.2em;
}
form.entry-form .final-accept:not(.show),
form.entry-form .final-premium:not(.show)
{
display: none;
}
form.entry-form .final-premium
{
margin: 0.25em;
font-size: 3em;
font-weight: bold;
color: green;
text-shadow: 1px 1px 1px black;
}
form.entry-form .final-premium:before
{
content: '$';
min-height: 0em;
white-space: nowrap;
}
form.entry-form input[type="reset"]
{
/* help protect against accidental clicks */
margin-left: 1em;
}
form .final-comments
{
background-color: rgba( 255, 255, 255, 0.90 );
display: none;
text-align: left;
padding: 1em;
}
form .final-comments.show
{
position: fixed;
display: block;
border: 0.25em solid black;
top: 0.25em;
right: 0.25em;
bottom: 0.25em;
left: 12.7em;
}
form .final-comments textarea
{
width: 95%;
height: 20em;
}
form .final-comments button
{
padding: 0.5em 1em;
margin-right: 1em;
}
form .final-comments #final-submit
{
font-weight: bold;
}
/** load dialog **/
#prior:not(:target)
{
display: none;
}
.load-dialog td
{
cursor: pointer;
}
.load-dialog > button
{
margin-top: 0.5em;
padding: 0.5em 1em;
font-size: 1.1em;
}
.load-dialog tr > td:first-child,
.load-dialog tr > td.premium
{
white-space: nowrap;
}
.load-dialog tr > td.premium,
.load-dialog tr > td.expected
{
text-align: right;
}
.load-dialog tr.good
{
background-color: #c0ffc0;
}
.load-dialog tr.bad
{
background-color: #ffc0c0;
}
.load-dialog tr.good.waiting
{
background-color: #edd400;
}
.load-dialog tr.bad.waiting
{
background-color: #f57900;
}
.load-dialog tr.changed:not(.skipped)
{
font-weight: bold;
font-style: normal !important;
}
.load-dialog tr.premchanged
{
font-style: italic;
}
.load-dialog tr.skipped
{
background-color: gray;
}
.load-dialog.show
{
display: block;
}
.entry-value,
.entry-value-prior
{
background-color: yellow;
padding: 0.1em;
margin-left: 1em;
font-weight: bold;
color: green;
}
.entry-value-prior
{
background-color: purple;
font-weight: normal;
font-size: 0.9em;
color: white;
}
body:not(.prior) .entry-value-prior
{
display: none;
}
/** validation errors **/
.validation-errors
{
position: fixed;
background-color: #FFAFAF;
border: 0.5em solid rgba( 255, 0, 0, 0.75 );
border-bottom: none;
bottom: -30.5em;
left: 0px;
right: 0px;
padding: 0px 0px 0px 0.5em;
z-index: 9000;
}
.validation-errors:hover
{
background-color: white;
bottom: 0px;
}
.validation-errors ol
{
height: 30em;
margin-top: 0;
margin-bottom: 0;
padding-top: 0;
padding-bottom: 0;
overflow-y: scroll;
}
.validation-errors li > .content
{
font-weight: normal;
}
.nb
{
border-top: 1px solid #babdb6;
padding-top: 1em;
}
.ultra-breakdown
{
display: block;
font-size: 0.9em;
clear: both;
}
.ultra-breakdown > h2
{
margin-top: 3em;
}
.ultra-breakdown + .yields
{
margin-top: 2em;
}
.ultra-breakdown legend > .uid
{
font-size: 0.8em;
}
.ultra-breakdown fieldset
{
margin-top: 1em;
border: 1px solid #babdb6;
border-radius: 0.5em;
}
.ultra-breakdown fieldset:hover
{
border-color: black;
}
.test-summary
{
float: right;
}
.test-summary table
{
float: right;
clear: both;
}
#voi-container,
#class-overview
{
display: none;
margin: 0em 0.5em 2em 0em;
}
#class-overview td.prior
{
font-size: 0.9em;
color: purple;
}
body:not(.prior) #class-overview td.prior
{
display: none;
}
#voi-container.show,
#class-overview.show
{
display: inline;
}
#voi-container td.prior
{
font-size: 0.9em;
color: purple;
}
body:not(.prior) #voi-container td.prior
{
display: none;
}
.sel0
{
background-color: #ccccff;
}
.sel1
{
background-color: #ccffcc;
}
.sel2
{
background-color: #ffcccc;
}
#voi-painter
{
position: fixed;
display: block;
background-color: white;
border: 3px solid #eeeeec;
border-radius: 0.25em;
top: -3px;
left: 30em;
font-weight: bold;
font-size: 1.3em;
padding: 0.25em;
}
#prior-message
{
background-color: #c0ffc0;
display: none;
border: 0.25em solid #4e9a06;
width: 50%;
font-family: monospace;
padding: 0.5em;
}
#prior-message.bad
{
background-color: #ffc0c0;
border-color: #c00000;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,110 @@
#!/usr/bin/awk -f
#
# Compiles the given CSV into a table definition
function columngen( header )
{
# output a field constant for each field in the header
i = 0
while ( field = header[ ++i ] )
{
printf " <t:table-column name=\"%s\" " \
"index=\"%d\" seq=\"%s\" />\n",
field,
( i - 1 ),
( seq[ i ] ) ? "true" : "false"
}
}
function seqchk( last )
{
# if there's no last row, then do not bother
i = 0
while ( i++ < NF )
{
if ( seq[ i ] == "" ) seq[ i ] = 1
# this field is sequential if it is greater than or equal to the last field
# (we don't check for descending [yet]); note that on the first check, last
# will be empty and therefore this check will succeed (properly
# initializing seq[i] to 1)
seq[ i ] = seq[ i ] && ( $(i) >= last[ i ] )
}
}
# header
BEGIN {
rootpath = "../../../"
file = ARGV[1]
# grab only the filename (remove all preceding directories and the file ext)
name = gensub( /^.*\/|\.[^.]+$/, "", "g", file )
# output package header
printf \
"<?xml-stylesheet type=\"text/xsl\" href=\"%1$srater/summary.xsl\"?>\n" \
"<package\n" \
" xmlns=\"http://www.lovullo.com/rater\"\n" \
" xmlns:c=\"http://www.lovullo.com/calc\"\n" \
" xmlns:t=\"http://www.lovullo.com/rater/apply-template\"\n" \
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" \
" xsi:schemaLocation=\"http://www.lovullo.com/rater %1$srater/rater.xsd\"\n\n" \
" name=\"suppliers/rates/tables/%2$s\"\n" \
" desc=\"%2$s rate table\">\n\n" \
" <!--\n" \
" WARNING: This file was generated by csv2xml; do not modify!\n" \
" -->\n\n" \
" <import package=\"/rater/core\" />\n" \
" <import package=\"/rater/core/vector/table\" />\n\n", \
rootpath, name
# the first row of the CSV is the header representing the column identifiers
getline
split( $0, header, /,/ )
# table constant identifier
tconst = toupper( gensub( /-/, "_", "g", name ) ) "_RATE_TABLE"
# generate the header for the table constant
printf " <t:create-table name=\"%s\">\n", name
printf "%s", " <t:table-rows data=\"\n"
# delimit fields by commas (the field separator for CSVs); note that this
# won't work properly if strings contain commas
FS = ","
}
# each row of the CSV
{
# generate value string for each field
i = 0
while ( i++ < NF )
{
printf "%s", ( ( i > 1 ) ? "," : "" ) $(i)
}
print ";"
seqchk( last )
split( $0, last )
}
# footer
END {
# end of table-rows node
print "\" />"
# columns can't be generated until after we know which ones represent
# sequential data
columngen( header )
print " </t:create-table>"
print "</package>"
}

View File

@ -0,0 +1,124 @@
#!/usr/bin/awk -f
#
# Performs interpolation for columns in a CSV and outputs the result
#
# Configurable values (use -vname=value from command line):
# step - use predeterminated step instead of calculating from first two rows
#
# #
function storeline()
{
for ( i = 1; i <= hlen; i++ ) {
prev[i] = $i
}
}
function clearline()
{
for ( i = 1; i <= hlen; i++ ) {
prev[i] = 0
}
}
function getprev()
{
for ( i = 1; i <= hlen; i++ ) {
$i = prev[i]
}
}
function interpolate()
{
lastval = prev[1]
curval = $1
diff = curval - lastval
# does this value fall in line with the requested step?
if ( diff == step )
{
storeline()
# we're good; continue
print
next
}
# if we do not yet have a value large enough to reach our step, then continue
# until we do (do not store this line)
n = int( diff / step )
if ( n <= 0 ) {
next
}
# determine interpolation values
for ( i = 2; i <= hlen; i++ ) {
ival[i] = ( ( $i - prev[i] ) / n )
}
getprev()
# let us interpolate values that are divisible by the step
do
{
# increase the last value by our step
$1 += step
# interpolate each column value (notice that we skip the first column, which
# was handled directly above)
for ( i = 2; i <= hlen; i++ ) {
$i += ival[i]
}
# print the new line
print
} while ( ( diff -= step ) > 0 )
# anything remaining does not fit into our step and will be ignored; we'll
# continue with our next step at the next line
# consider this to be our last line
storeline()
}
BEGIN {
# the first row of the CSV is the header representing the column identifiers
getline
hlen = split( $0, header, /,/ )
# output the header
print $0
# delimit fields by commas (the field separator for CSVs); note that this
# won't work properly if strings contain commas
FS = OFS = ","
clearline()
getline
# if no step was provided, then calculate one based on the first two rows
if ( step == 0 ) {
# output the first row, which does not need to be interpolated
print
# compute the step
vala = $1
getline
valb = $1
step = valb - vala
# since the second line is used to determine the step, then it must match the
# step and therefore is good to output
print
# begin.
storeline()
}
}
# for each row
{ interpolate() }

View File

@ -0,0 +1,112 @@
#!/usr/bin/awk -f
#
# Compiles a "magic" CSV file into a normal CSV
#
# "Magic" CSVs simply exist to make life easier: they permit comments, blank
# lines, variables, sub-delimiter expansion, and any number of ranges per line.
# Ranges will be expanded in every combination, making rate tables highly
# maintainable.
#
# Variables are also supported when defined using :var=val. Variables may expand
# into ranges, 'cause they're awesome. Multiple variables may be delimited by
# semi-colons, as may multiple values.
#
# For example:
# :foo=1--3
# $foo;7;9--10:$foo, 5--10
#
# Would generate:
# 1, 5
# 1, 6
# ...
# 5, 10
# 2, 5
# ...
# 9, 5
# ...
# 1, 5
# 1, 6
# ...
function rangeout( i, m, j, me, orig )
{
if ( i > NF )
{
print
return
}
orig = $i
# check first for delimiters
if ( match( $i, /^([^;]+);(.*)$/, m ) )
{
# give it a shot with the first value
$i = m[1]
rangeout( i )
# strip off the first value and process with following value(s)
$i = m[2]
rangeout( i )
# we've delegated; we're done
$i = orig
return
}
# attempt to parse variable (may expand into a range)
if ( match( $i, /^\$([a-zA-Z_-]+)$/, m ) )
{
$i = vars[ m[1] ];
}
# parse range
if ( match( $i, /^([0-9]+)--([0-9]+)$/, m ) )
{
j = m[1]
me = m[2]
do
{
$i = j
rangeout( i + 1 )
} while ( j++ < me )
}
else
{
rangeout( i + 1 );
}
# restore to original value
$i = orig
}
BEGIN {
# we're parsing CSVs
FS = " *, *"
OFS = ","
}
# skip all lines that begin with `#', which denotes a comment, or are empty
/^#|^$/ { next; }
# lines that begin with a colon are variable definitions
/^:/ {
match( $0, /^:([a-zA-Z_-]+)=(.*?)$/, m )
vars[ m[1] ] = m[2]
next
}
# lines containing ranges (denoted by `--', the en dash, which is a typesetting
# convetion for ranges), sub-delimiters, or variables must be expanded
/--|;|\$[a-zA-Z_-]/ { rangeout( 1 ); next; }
# all other lines are normal; simply output them verbatim
{
# this assignment will ensure that awk processes the output, ensuring that
# extra spaces between commas are stripped
$1=$1
print
}

View File

@ -0,0 +1,96 @@
#!/bin/bash
#
# Generates Makefile containing dependencies for each package
# #
# windows machines may not have the tools to resolve a path, so let's do so
# ourselves (TODO: there's better (and more performant) ways of doing this than
# repeated string replacements); TODO: ./
resolv-path()
{
# no need to do anything if the string does not contain a parent dir reference
# (we use this convoluted string replacement check for woe32/64 to prevent
# additional spawns (e.g. sed) that would slow us down and because =~ is not
# properly supported in msys
[[ "$1" != "${1/..\//}"a ]] || {
echo "$1"
return
}
local path=
while read name; do
if [ "$name" == .. ]; then
[ -n "$path" ] || {
echo "warning: will not resolve $1" >&2
return 5
}
path="${path%/*}"
continue
fi
path="$path/$name"
done <<< "${1//\//$'\n'}"
# echo path without leading /
echo -n "${path:1}"
}
# rule for building
[ -z "$GEN_MAKE" ] && {
echo "%.xmlo:: %.tmp"
echo -e "\t@rm -f \$@ \$<"
[ -n "$xmlo_cmd" ] \
&& echo -e "\t$xmlo_cmd" \
|| echo -e "\ttouch \$@"
echo "%.xmlo:: %.xml | prexmlo"
[ -n "$xmlo_cmd" ] \
&& echo -e "\t$xmlo_cmd" \
|| echo -e "\ttouch \$@"
export GEN_MAKE="$( pwd )/$0"
exec "$GEN_MAKE" "$@"
}
until [ $# -eq 0 ]; do (
path="${1%%/}"
echo "[gen-make] scanning $path" >&2
cd "$( basename $path )/" || exit $?
deps=$( find -maxdepth 1 -iname '*.dep' )
for dpath in $deps; do
# equivalent to basename command; use this since spawning processes on
# windoze is slow as shit (originally we did find -exec bashename)
d="${dpath##*/}"
echo "[gen-make] found $path/$d" >&2
echo -n "$path/${d%.*}.xmlo:"
# output deps
while read dep; do
# if the first character is a slash, then it's relative to the project
# root---the resolution has already been done for us!
if [ "${dep:0:1}" == '/' ]; then
echo -n " ${dep:1}.xmlo"
continue
fi
echo -n ' '
resolv-path "$path/$dep.xmlo"
done < "$d"
echo
done
# recurse on every subdirectory
for p in */; do
[ "$p" == ./ -o "$p" == ../ ] && continue
[ ! -d "$p" ] || "$GEN_MAKE" "$path/$p" || {
echo "fatal: failed to recurse on $( pwd )/$path/$p" >&2
exit 1
}
done
); shift; done

View File

@ -0,0 +1,124 @@
<?php
function gen_re_quick( $data )
{
$re = ( '^' . gen_re( $data, 0 ) );
// attempt to simplify the regex (we're not going to put a lot of effort into
// this)
return re_simplify( $re );
}
function gen_re( $data, $offset )
{
// if we've reached the end of the zip length, or if there's no more zips to
// look at, then stop
if ( ( count( $data ) === 0 )
|| ( $offset === 5 )
)
{
return '';
}
$out = '(';
// loop through each digit at the current offset
$last = '';
foreach ( $data as $zip )
{
if ( !( isset( $zip[ $offset ] ) ) )
{
continue;
}
$digit = $zip[ $offset ];
// if we've already seen this digit in the current position, then
// continue
if ( $digit === $last )
{
continue;
}
// we're going to recurse now, delimiting allowable digits with pipes
// (for 'OR'); we'll recurse on a sublist that matches the zip up to
// (and including) the current digit (to do this, note that we only need
// to check the current digit, since our current list is already a
// sublist of the parent list up to the current point)
$prefix = substr( $zip, 0, $offset + 1 );
$out .= ( $last === '' ) ? '' : '|';
$out .= $digit . gen_re(
filter_data( $data, $digit, $offset ),
( $offset + 1 )
);
$last = $digit;
}
return $out . ')';
}
function filter_data( $data, $chr, $offset )
{
$ret = array();
foreach ( $data as $val )
{
if ( $val[ $offset] === $chr )
{
$ret[] = $val;
}
}
return $ret;
}
function re_simplify( $re )
{
// the only simplification we currently do is joining sequential digit ORs
// into a character range (e.g. (1|2|3|4) becomes [1-4])
return preg_replace_callback( '/\([0-9](\|[0-9])*\)/', function( $results )
{
$match = $results[ 0 ];
$digits = explode( '|', str_replace( array( '(', ')' ), '', $match ) );
// are the digits sequential (we will only perform this optimization if
// there's more than 3 digits, since otherwise the replacement would
// result in a string of equal or greater length)?
if ( ( count( $digits ) > 3 ) && is_seq( $digits ) )
{
return sprintf( '[%d-%d]',
$digits[ 0 ],
$digits[ count( $digits ) - 1 ]
);
}
elseif ( count( $digits ) === 1 )
{
// if there's only one digit, then that's all we need to return
return $digits[ 0 ];
}
return '[' . implode( '', $digits ) . ']';
}, $re );
}
function is_seq( $digits, $last = '' )
{
// stop recursing once we're out of digits
if ( count( $digits ) === 0 )
{
return true;
}
// grab the current digit and remove it from the list (this has the effect
// of both cons and cdr)
$digit = (int)( array_shift( $digits ) );
// consider this a sequence if this digit is one more than the last (or if
// there is no last digit) and if the following digit is sequential
return ( ( $last === '' ) || ( $digit === ( $last + 1) ) )
&& is_seq( $digits, $digit );
}

View File

@ -0,0 +1,274 @@
#!/usr/bin/env php
<?xml-stylesheet type="text/xsl" href="../../rater/summary.xsl"?>
<lv:package
xmlns:lv="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.lovullo.com/rater ../../rater/rater.xsd"
<?php
include 'lib/zipre.php';
function parse_tdesc( $line )
{
if ( !( preg_match( '/^([0-9A-Z]+) (.+)$/', $line, $match ) ) )
{
throw new Exception( 'Invalid territory descriptor' );
}
return array( $match[ 1 ], $match[ 2 ] );
}
function gen_yields( $id, $name )
{
return sprintf( 'is%sTerr%s', ucfirst( $name ), $id );
}
function gen_classification( $id, $name, $desc, $prev_yields, $queue, $or )
{
$yields = gen_yields( $id, $name );
$prev_value = '';
foreach ( $prev_yields as $prev )
{
if ( !$prev )
{
continue;
}
$prev_value .= ' <lv:match on="' . $prev . '" value="FALSE" />' . "\n";
}
return sprintf(
'<lv:classify as="%s-terr%s" desc="%s" yields="%s">' .
"\n%s" .
" %s\n" .
"\n</lv:classify>\n",
$name,
gen_identifier( $id ),
$desc,
$yields,
$prev_value,
gen_any_block( $queue, $or )
);
}
function gen_any_block( $queue, $or )
{
$any = gen_zip_re( $queue ) .
gen_on_class( $or );
return ( $any )
? '<lv:any>' . $any . '</lv:any>'
: '';
}
function gen_zip_re( $data )
{
if ( count( $data ) === 0 )
{
return '';
}
return sprintf(
'<lv:match on="zip" pattern="%s" />',
gen_re_quick( $data )
);
}
function gen_on_class( $data )
{
if ( count( $data ) === 0 )
{
return '';
}
$cur = array_shift( $data );
return sprintf(
'<lv:match on="%s" value="TRUE" />%s',
$cur,
gen_on_class( $data )
);
}
function gen_identifier( $id )
{
return is_numeric( $id )
? $id
: '-' . strtolower( $id );
}
function gen_identifier_value( $id )
{
// for non-numeric identifiers, return ascii value
// of character to represent our value
return is_numeric( $id )
? $id
: ord( $id );
}
$file = $argv[ 1 ];
$fdat = explode( '.', basename( $file ) );
$name = $fdat[ 0 ];
$cur = '';
$queue = array();
$or = array();
$fh = fopen( $file, 'r' );
echo 'name="rates/territories/', $name, '" ', "\n",
'desc="', ucfirst( $name ), ' territory classifications">' . "\n\n";
echo "<!--\n",
" WARNING: This file was generated by {$argv[0]}; do not modify!\n",
"-->\n\n";
$ids = array();
$params = array();
$imports = array();
$prev_yields = '';
$prev_yields_all = array();
$classes = '';
$param_type = 'terrType' . ucfirst( $name );
while ( true )
{
// read the line within the loop so that we do not terminate until after we
// treat eof as an empty line
$line = str_replace( array( "\n", "\r" ), '', fgets( $fh ) );
if ( !$cur )
{
if ( substr( $line, 0, 12 ) === '@import-pkg ' )
{
$imports[] = substr( $line, 12 );
continue;
}
// we expect this line to be a territory descriptor
try
{
list ( $id, $desc ) = parse_tdesc( $line );
}
catch ( Exception $e )
{
fwrite( STDERR, 'Invalid territory descriptor: ' . $line );
exit( 1 );
}
$ids[] = $id;
$cur = $id;
}
elseif ( ( $line === '' ) || feof( $fh ) )
{
// generate param for typedef
$params[ $id ] = $desc;
// if there's nothing in the queue, then treat this as an 'ROS' (this
// should appear as the *last* territory, or it will not function as
// expected)
if ( count( $queue ) === 0 )
{
$prev = $prev_yields_all;
}
else
{
$prev = array( $prev_yields );
}
// generate the classification
$classes .= gen_classification( $id, $name, $desc, $prev, $queue, $or );
// this accomplishes two things: (1) avoids regexes if there's a
// previous match and (2) ensures that we cannot possibly match multiple
// territories
$prev_yields = gen_yields( $id, $name );
$prev_yields_all[] = $prev_yields;
$cur = '';
$queue = array();
$or = array();
if ( feof( $fh ) )
{
break;
}
}
elseif ( $line[0] === '=' )
{
// =foo means match on classification @yields "foo"
$or[] = substr( $line, 1 );
}
else
{
$queue[] = $line;
}
}
$param_name = 'territory_' . $name;
?>
<?php /* XXX: This is hard-coded! */ ?>
<lv:import package="/rater/core/tdat" />
<?php foreach ( $imports as $pkg ) { ?>
<lv:import package="<?php echo $pkg; ?>" />
<?php } ?>
<lv:extern name="zip" type="param" dtype="integer" dim="1"
missing="this territory package requires an available `zip' parameter; please
import a package that provides it" />
<lv:param name="<?php echo $param_name; ?>" type="<?php echo $param_type; ?>" default="0" set="vector" desc="Territory Override" />
<lv:typedef name="<?php echo $param_type; ?>" desc="<?php echo ucfirst( $name ); ?> Territories">
<lv:enum type="integer">
<?php $item_prefix = 'TERR_' . strtoupper( $name ) . '_'; ?>
<lv:item name="<?php echo $item_prefix; ?>_NONE" value="0" desc="No Override" />
<?php foreach ( $params as $id => $desc ) { ?>
<?php $item_name = $item_prefix . $id; ?>
<lv:item name="<?php echo $item_name; ?>" value="<?php echo gen_identifier_value( $id ); ?>" desc="<?php echo $desc; ?>" />
<?php } ?>
</lv:enum>
</lv:typedef>
<?php echo $classes; ?>
<lv:section title="Territory Determination">
<?php foreach ( $ids as $id ) { ?>
<?php $yields = sprintf( '%sTerr%s', $name, $id ); ?>
<?php $class = sprintf( '%s-terr%s', $name, gen_identifier( $id ) ); ?>
<lv:apply-template name="_terr-code_" class="<?php echo $class; ?>" code="<?php echo gen_identifier_value( $id ); ?>" generates="<?php echo $yields; ?>" />
<?php } ?>
<lv:rate yields="_<?php echo $name; ?>TerrCode">
<c:sum of="zip" index="k" generates="<?php echo $name; ?>TerrCode" desc="Territory code">
<c:cases>
<c:case>
<c:when name="<?php echo $param_name; ?>" index="k">
<c:gt>
<c:const value="0" type="integer" desc="Use territory override if set" />
</c:gt>
</c:when>
<c:value-of name="<?php echo $param_name; ?>" index="k" />
</c:case>
<c:otherwise>
<c:sum label="Determine applicable territory code">
<?php foreach ( $ids as $id ) { ?>
<c:value-of name="<?php echo $name; ?>Terr<?php echo $id; ?>" index="k" />
<?php } ?>
</c:sum>
</c:otherwise>
</c:cases>
</c:sum>
</lv:rate>
</lv:section>
</lv:package>

View File

@ -0,0 +1,23 @@
#!/usr/bin/env php
<?php
/**
* Given a set of sorted zips, generates a regular expression to match only the
* given input
*
* I wanted to write this in Scheme (it's a perfect recursive application), but
* I figured that other developers may get annoyed having to find a Scheme impl
* that works for them...so...PHP it is...
*
*
* THIS SCRIPT EXPECTS THE DATA TO BE SORTED! This can be easily accomplished by
* doing the following:
* sort -d zipfile | ./zipre
*/
include 'lib/zipre.php';
// grab input from stdin (must be sorted!)
$data = explode( "\n", file_get_contents( 'php://stdin' ) );
// build and output
echo gen_re_quick( $data );

View File

@ -0,0 +1,34 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lovullo.com/rater/worksheet"
xmlns="http://www.lovullo.com/rater/worksheet"
xmlns:w="http://www.lovullo.com/rater/worksheet"
elementFormDefault="qualified">
<xs:element name="worksheet">
<xs:annotation>
<xs:documentation xml:lang="en">
Root node for rating worksheets
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax" />
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="package" type="xs:string" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Package for which worksheet is being generated
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>