Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Gerwitz <gerwitm@lovullo.com>2016-08-24 09:43:05 -0400
committerMike Gerwitz <gerwitm@lovullo.com>2016-08-24 12:38:00 -0400
commitff01f39c1e8c9b9549d884a0db1f9a74799cf37e (patch)
tree35978db88a8d385250b1b47ad05966e19516373d /src/current/compiler
parent6c0aa54bd1b7b49d736f0db3a8f48b7aa90b3b65 (diff)
downloadtame-ff01f39c1e8c9b9549d884a0db1f9a74799cf37e.tar.gz
tame-ff01f39c1e8c9b9549d884a0db1f9a74799cf37e.tar.bz2
tame-ff01f39c1e8c9b9549d884a0db1f9a74799cf37e.zip
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.)
Diffstat (limited to 'src/current/compiler')
-rw-r--r--src/current/compiler/fragments.xsl137
-rw-r--r--src/current/compiler/js-calc.xsl1141
-rw-r--r--src/current/compiler/js.xsl1908
-rw-r--r--src/current/compiler/linker.xsl1442
-rw-r--r--src/current/compiler/linker/log.xsl95
-rw-r--r--src/current/compiler/map.xsl997
-rw-r--r--src/current/compiler/validate.xsl771
-rw-r--r--src/current/compiler/validate/domain.xsl193
-rw-r--r--src/current/compiler/validate/param.xsl103
-rw-r--r--src/current/compiler/worksheet.xsl543
10 files changed, 7330 insertions, 0 deletions
diff --git a/src/current/compiler/fragments.xsl b/src/current/compiler/fragments.xsl
new file mode 100644
index 0000000..8ea6024
--- /dev/null
+++ b/src/current/compiler/fragments.xsl
@@ -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>
diff --git a/src/current/compiler/js-calc.xsl b/src/current/compiler/js-calc.xsl
new file mode 100644
index 0000000..8594d4f
--- /dev/null
+++ b/src/current/compiler/js-calc.xsl
@@ -0,0 +1,1141 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ Compiles calculation 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).
+
+ It is assumed that validations are performed prior to compiling; otherwise, it
+ is possible to inject JavaScript into the output; only pass correct input to
+ this compiler.
+
+ All undefined values in a set are considered to be zero, resulting in an
+ infinite set; this simplifies sums and compilation.
+
+ The generated code may not be optimal, but it may be processed by another
+ system (e.g. Closure Compiler) to perform additional optimizations.
+-->
+
+<xsl:stylesheet version="1.0"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:c="http://www.lovullo.com/calc"
+ xmlns:lv="http://www.lovullo.com/rater"
+ xmlns:w="http://www.lovullo.com/rater/worksheet"
+ xmlns:preproc="http://www.lovullo.com/rater/preproc"
+ xmlns:calc-compiler="http://www.lovullo.com/calc/compiler">
+
+
+<!-- enable debugging by default -->
+<xsl:param name="calcc-debug" select="'yes'" />
+
+<!-- name to output when reporting problems -->
+<xsl:variable name="calc-compiler:name" select="'js-calc-compiler'" />
+
+<xsl:template name="calc-compiler:error">
+ <xsl:param name="message" select="'unspecified error'" />
+
+ <xsl:message terminate="yes">
+ <xsl:value-of select="$calc-compiler:name" />
+ <xsl:text>: </xsl:text>
+ <xsl:value-of select="$message" />
+ </xsl:message>
+</xsl:template>
+
+
+<xsl:template match="c:*" mode="compile" priority="1">
+ <xsl:variable name="debugval">
+ <xsl:if test="
+ ( $calcc-debug = 'yes' )
+ or ( //w:worksheet//w:display/@name = @generates )
+ or (
+ (
+ local-name() = 'case'
+ or ./c:index
+ )
+ and //w:worksheet//w:display/@name = ancestor::c:*/@generates
+ )
+ ">
+
+ <xsl:text>yes</xsl:text>
+ </xsl:if>
+ </xsl:variable>
+
+ <!-- should we force debugging? TODO: decouple from lv namespace -->
+ <xsl:variable name="debug-force" select="
+ ancestor::lv:*/@yields =
+ root(.)//calc-compiler:force-debug/calc-compiler:ref/@name
+ " />
+
+ <xsl:if test="$debugval = 'yes' or $debug-force">
+ <xsl:text>( function() { var result = </xsl:text>
+ </xsl:if>
+
+ <xsl:apply-templates select="." mode="compile-pre" />
+
+ <xsl:if test="$debugval = 'yes' or $debug-force">
+ <xsl:text>; </xsl:text>
+ <xsl:text>( debug['</xsl:text>
+ <xsl:value-of select="@_id" />
+ <xsl:text>'] || ( debug['</xsl:text>
+ <xsl:value-of select="@_id" />
+ <xsl:text>'] = [] ) ).push( result ); </xsl:text>
+
+ <xsl:text>return result; </xsl:text>
+ <xsl:text>} )() </xsl:text>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ Begins compilation of a calculation
+
+ This is responsible for wrapping the value to enforce precedence rules and
+ convert it to a number (to avoid string concatenation when adding), then
+ delegating the compilation to the appropriate template(s).
+
+ @return generated JS for the given calculation
+-->
+<xsl:template match="c:*" mode="compile-pre" priority="1">
+ <!-- ensure everything is grouped (for precedence) and converted to a
+ number -->
+ <xsl:text>( </xsl:text>
+ <xsl:apply-templates select="." mode="compile-calc" />
+ <xsl:text> )</xsl:text>
+</xsl:template>
+
+
+<xsl:template match="c:const[ ./c:when ]|c:value-of[ ./c:when ]" mode="compile-pre" priority="5">
+ <xsl:text>( </xsl:text>
+ <!-- first, do what we normally would do (compile the value) -->
+ <xsl:text>( </xsl:text>
+ <xsl:apply-templates select="." mode="compile-calc" />
+ <xsl:text> )</xsl:text>
+
+ <!-- then multiply by the c:when result -->
+ <xsl:text> * ( </xsl:text>
+ <xsl:for-each select="./c:when">
+ <xsl:if test="position() > 1">
+ <xsl:text> * </xsl:text>
+ </xsl:if>
+
+ <xsl:apply-templates select="." mode="compile" />
+ </xsl:for-each>
+ <xsl:text> )</xsl:text>
+ <xsl:text> )</xsl:text>
+</xsl:template>
+
+
+
+<!--
+ Generates code for the sum or product of a set
+
+ This applies only with sets provided via the @of attribute, which will be used
+ to denote what set to some over. If no @index is provided, a dummy index will
+ be used. Otherwise, the index provided will be used and may be referenced by
+ children.
+
+ If child nodes exist, the summation/product will be performed over their
+ resulting equation. Otherwise, the summation/product will be performed over
+ the set of values only.
+
+ The generated self-executing function may be used inline with the rest of the
+ expression; the computed value will be returned.
+
+ @return generated summation/product anonymous self-executing function
+-->
+<xsl:template match="c:product[@of]|c:sum[@of]" mode="compile-calc" priority="1">
+ <!-- see c:ceil/c:floor precision comments -->
+ <xsl:variable name="precision">
+ <xsl:choose>
+ <xsl:when test="@precision">
+ <xsl:value-of select="@precision" />
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:text>8</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:variable name="operator">
+ <xsl:apply-templates select="." mode="compile-getop" />
+ </xsl:variable>
+
+ <xsl:variable name="index">
+ <xsl:choose>
+ <xsl:when test="@index">
+ <xsl:value-of select="@index" />
+ </xsl:when>
+
+ <!-- if they don't provide us with an index, then we don't want them using
+ it -->
+ <xsl:otherwise>
+ <xsl:text>_$i$_</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <!-- introduce scope both to encapsulate values and so we can insert this as
+ part of a larger expression (will return a value) -->
+ <xsl:text>( function() {</xsl:text>
+
+ <!-- will store result of the summation/product -->
+ <xsl:text>var sum = 0;</xsl:text>
+
+ <xsl:variable name="of" select="@of" />
+
+ <xsl:variable name="func" select="ancestor::lv:function" />
+
+ <xsl:variable name="value">
+ <xsl:choose>
+ <!-- is @of a function param? -->
+ <xsl:when test="
+ $func
+ and root(.)/preproc:symtable/preproc:sym[
+ @type='lparam'
+ and @name=concat( ':', $func/@name, ':', $of )
+ ]
+ ">
+
+ <xsl:value-of select="@of" />
+ </xsl:when>
+
+ <!-- maybe a constant? -->
+ <xsl:when test="
+ root(.)/preproc:symtable/preproc:sym[
+ @type='const'
+ and @name=$of
+ ]
+ ">
+
+ <xsl:text>consts['</xsl:text>
+ <xsl:value-of select="@of" />
+ <xsl:text>']</xsl:text>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:text>args['</xsl:text>
+ <xsl:value-of select="@of" />
+ <xsl:text>']</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <!-- if we're looking to generate a set, initialize it -->
+ <xsl:if test="@generates">
+ <xsl:text>var G = []; </xsl:text>
+ </xsl:if>
+
+ <!-- loop through each value -->
+ <xsl:text>for ( var </xsl:text>
+ <xsl:value-of select="$index" />
+ <xsl:text> in </xsl:text>
+ <xsl:value-of select="$value" />
+ <xsl:text> ) {</xsl:text>
+
+ <xsl:text>var result = +(+( </xsl:text>
+ <xsl:choose>
+ <!-- if there are child nodes, use that as the summand/expression -->
+ <xsl:when test="./c:*">
+ <xsl:apply-templates select="." mode="compile-calc-sumprod" />
+ </xsl:when>
+
+ <!-- otherwise, sum/multiply ever value in the set identified by @of -->
+ <xsl:otherwise>
+ <xsl:value-of select="$value" />
+ <xsl:text>[</xsl:text>
+ <xsl:value-of select="$index" />
+ <xsl:text>]</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> )).toFixed(</xsl:text>
+ <xsl:value-of select="$precision" />
+ <xsl:text>);</xsl:text>
+
+ <!-- if generating a set, store this result -->
+ <xsl:if test="@generates">
+ <xsl:text>G.push( result ); </xsl:text>
+ </xsl:if>
+
+ <!-- generate summand -->
+ <xsl:text>sum </xsl:text>
+ <xsl:value-of select="$operator" />
+ <xsl:text>= +result;</xsl:text>
+
+ <!-- end of loop -->
+ <xsl:text>}</xsl:text>
+
+ <!-- if a set has been generated, store it -->
+ <xsl:if test="@generates">
+ <xsl:text>args['</xsl:text>
+ <xsl:value-of select="@generates" />
+ <xsl:text>'] = G; </xsl:text>
+ </xsl:if>
+
+ <xsl:text>return sum;</xsl:text>
+ <xsl:text>} )()</xsl:text>
+</xsl:template>
+
+
+<!--
+ Compile dot products
+
+ N.B. This match has a higher priority than other products.
+
+ Dot products should operate only on vectors; if other input passes the
+ validator, then the result is undefined.
+-->
+<xsl:template match="c:product[@dot]" mode="compile-calc" priority="5">
+ <xsl:text>( function() { </xsl:text>
+
+ <!-- we need to determine which vector is the longest to ensure that we
+ properly compute every value (remember, undefined will be equivalent to
+ 0, so the vectors needn't be of equal length *gasp* blasphemy!) -->
+ <xsl:text>var _$dlen$ = longerOf( </xsl:text>
+ <xsl:for-each select=".//c:value-of">
+ <xsl:if test="position() > 1">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+
+ <!-- skip any wrapping; we want the direct reference (so compile-calc, not
+ compile)-->
+ <xsl:apply-templates select="." mode="compile-calc" />
+ </xsl:for-each>
+ <xsl:text> ); </xsl:text>
+
+ <!-- will store the total sum -->
+ <xsl:text>var _$dsum$ = 0;</xsl:text>
+
+ <!-- sum the product of each -->
+ <xsl:text disable-output-escaping="yes">for ( var _$d$ = 0; _$d$ &lt; _$dlen$; _$d$++ ) {</xsl:text>
+ <xsl:text>_$dsum$ += </xsl:text>
+ <!-- product of each -->
+ <xsl:for-each select=".//c:value-of">
+ <xsl:if test="position() > 1">
+ <xsl:text> * </xsl:text>
+ </xsl:if>
+
+ <xsl:text>( ( </xsl:text>
+ <xsl:apply-templates select="." mode="compile" />
+ <xsl:text> || [] )[ _$d$ ] || 0 )</xsl:text>
+ </xsl:for-each>
+ <xsl:text>; </xsl:text>
+ <xsl:text>}</xsl:text>
+
+ <xsl:text>return _$dsum$;</xsl:text>
+
+ <xsl:text> } )()</xsl:text>
+</xsl:template>
+
+
+<!--
+ Generates a sum/product over the expression denoted by its child nodes
+
+ When no @of set is provided, this will add or multiple each child expression.
+
+ @return generated expression
+-->
+<xsl:template match="c:sum|c:product" mode="compile-calc">
+ <xsl:apply-templates select="." mode="compile-calc-sumprod" />
+</xsl:template>
+
+
+<xsl:template match="c:sum|c:product" mode="compile-calc-sumprod">
+ <xsl:variable name="operator">
+ <xsl:apply-templates select="." mode="compile-getop" />
+ </xsl:variable>
+
+ <xsl:for-each select="./c:*">
+ <!-- add operator between each expression -->
+ <xsl:if test="position() > 1">
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="$operator" />
+ <xsl:text> </xsl:text>
+ </xsl:if>
+
+ <xsl:apply-templates select="." mode="compile" />
+ </xsl:for-each>
+</xsl:template>
+
+
+<!--
+ @return addition operator
+-->
+<xsl:template match="c:sum" mode="compile-getop">
+ <xsl:text>+</xsl:text>
+</xsl:template>
+
+<!--
+ @return multiplication operator
+-->
+<xsl:template match="c:product" mode="compile-getop">
+ <xsl:text>*</xsl:text>
+</xsl:template>
+
+
+<!--
+ Generate code for ceiling/floor functions
+
+ @return ceil/floor compiled JS
+-->
+<xsl:template match="c:ceil|c:floor" mode="compile-calc">
+ <!-- determine precision for ceil/floor (floating point precision errors can
+ drastically affect the outcome) -->
+ <xsl:variable name="precision">
+ <xsl:choose>
+ <!-- if a precision was explicitly provided, then use that -->
+ <xsl:when test="@precision">
+ <xsl:value-of select="@precision" />
+ </xsl:when>
+
+ <!-- ECMAScript uses a default precision of 24; by reducing the
+ precision to 8 decimal places, we can drastically reduce the affect
+ of precision errors on the calculations -->
+ <xsl:otherwise>
+ <xsl:text>8</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:text>Math.</xsl:text>
+ <xsl:value-of select="local-name()" />
+ <xsl:text>( +(</xsl:text>
+ <xsl:apply-templates select="./c:*" mode="compile" />
+ <xsl:text> ).toFixed( </xsl:text>
+ <xsl:value-of select="$precision" />
+ <xsl:text> ) )</xsl:text>
+</xsl:template>
+
+
+<!--
+ Replaces a constant with its value in the compiled expression
+
+ Constants are syntatic sugar in the language to prevent magic values; they are
+ not required at runtime. As such, the value of the constant will be placed
+ directly into the compiled code.
+
+ @return quoted constant value
+-->
+<xsl:template match="c:const" mode="compile-calc">
+ <!-- assumed to be numeric -->
+ <xsl:value-of select="@value" />
+</xsl:template>
+
+
+<!--
+ Generate JS representing the value associated with the given name
+
+ @return generated JS representing value or 0
+-->
+<xsl:template match="c:value-of" mode="compile-calc" priority="1">
+ <xsl:apply-templates select="." mode="compile-calc-value" />
+</xsl:template>
+
+<!-- TODO: this should really be decoupled -->
+<!-- TODO: does not properly support matrices -->
+<xsl:template match="c:value-of[ ancestor::lv:match ]" mode="compile-calc" priority="5">
+ <xsl:variable name="name" select="@name" />
+
+ <xsl:choose>
+ <!-- scalar -->
+ <xsl:when test="
+ root(.)/preproc:symtable/preproc:sym[ @name=$name ]
+ /@dim = '0'
+ ">
+ <xsl:apply-templates select="." mode="compile-calc-value" />
+ </xsl:when>
+
+ <!-- non-scalar -->
+ <xsl:otherwise>
+ <xsl:text>(</xsl:text>
+ <xsl:apply-templates select="." mode="compile-calc-value" />
+ <xsl:text>)[__$$i]</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+
+<!--
+ Generate JS representing the value of a function argument
+
+ This will match whenever value-of is used on a name that matches any function
+ parameter.
+
+ XXX: We want to remain decoupled from lv if possible.
+
+ @return generated JS representing argument value or 0
+-->
+<xsl:template mode="compile-calc-value"
+ match="c:*[@name=ancestor::lv:function/lv:param/@name]">
+
+ <!-- use the argument passed to the function -->
+ <xsl:apply-templates select="." mode="compile-calc-index">
+ <xsl:with-param name="value" select="@name" />
+ </xsl:apply-templates>
+
+ <xsl:text> || 0</xsl:text>
+</xsl:template>
+
+
+<!--
+ Using value from let expressions
+-->
+<xsl:template mode="compile-calc-value"
+ match="c:*[ @name=ancestor::c:let/c:values/c:value/@name ]">
+
+ <!-- compile the value with the index (if any) -->
+ <xsl:apply-templates select="." mode="compile-calc-index">
+ <xsl:with-param name="value" select="@name" />
+ </xsl:apply-templates>
+
+ <xsl:text> || 0</xsl:text>
+</xsl:template>
+
+
+<!--
+ Generate JS representing the value of a global constant
+
+ Since constants are intended only to prevent magic values during development
+ (and are not required at runtime), the value of the constant will be placed
+ directly into the compiled code. However, we will *not* do this if the
+ constant is a set, since its index may be determined at runtime.
+
+ Note that "magic" constants' values are not inlined.
+
+ @return quoted constant value
+-->
+<xsl:template mode="compile-calc-value"
+ match="
+ c:*[
+ @name=root(.)/preproc:symtable/preproc:sym[
+ @type='const'
+ and @dim='0'
+ ]/@name
+ ]
+ ">
+
+ <xsl:variable name="name" select="@name" />
+ <xsl:variable name="sym"
+ select="root(.)/preproc:symtable/preproc:sym[ @name=$name ]" />
+
+ <!-- it is expected that validations prior to compiling will prevent JS
+ injection here -->
+ <xsl:choose>
+ <!-- "magic" constants should not have their values inlined -->
+ <xsl:when test="$sym/@magic='true'">
+ <xsl:text>consts['</xsl:text>
+ <xsl:value-of select="@name" />
+ <xsl:text>']</xsl:text>
+ </xsl:when>
+
+ <!-- @value should be defined when @dim=0 -->
+ <xsl:otherwise>
+ <xsl:value-of select="$sym/@value" />
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+
+<!--
+ Generates JS representing the value of a constant as part of a set
+
+ Since the index of constant sets can be determined at runtime, we need to
+ store all possible values. As such, we shouldn't repeat ourselves by inlining
+ all possible values; instead, we'll reference a pre-generated set of values
+ for the particular constant.
+
+ @return generated code representing value of a variable, or 0 if undefined
+-->
+<xsl:template mode="compile-calc-value"
+ match="
+ c:*[
+ @name=root(.)/preproc:symtable/preproc:sym[
+ @type='const'
+ and not( @dim='0' )
+ ]/@name
+ ]
+ ">
+
+ <xsl:variable name="value">
+ <xsl:text>consts['</xsl:text>
+ <xsl:value-of select="@name" />
+ <xsl:text>']</xsl:text>
+ </xsl:variable>
+
+ <xsl:apply-templates select="." mode="compile-calc-index">
+ <xsl:with-param name="value" select="$value" />
+ </xsl:apply-templates>
+
+ <!-- undefined values in sets are considered to be 0 -->
+ <xsl:text> || 0</xsl:text>
+</xsl:template>
+
+
+<!--
+ Generate JS representing the value of a generated index
+
+ @return generated code associated with the value of the generated index
+-->
+<xsl:template mode="compile-calc-value"
+ match="c:*[ @name = ancestor::c:*[ @of ]/@index ]">
+
+ <!-- depending on how the index is generated, it could be a string, so we must
+ cast it -->
+ <xsl:text>+</xsl:text>
+ <xsl:value-of select="@name" />
+</xsl:template>
+
+
+<!--
+ Generates JS representing the value of a variable
+
+ If the variable is undefined, the value will be considered to be 0 (this is
+ especially important for the summation of sets within this implementation).
+ That is: a value will never be considered undefined.
+
+ @return generated code representing value of a variable, or 0 if undefined
+-->
+<xsl:template match="c:*" mode="compile-calc-value">
+ <xsl:variable name="name" select="@name" />
+ <xsl:variable name="pkg" as="element( lv:package )"
+ select="root(.)" />
+
+ <xsl:variable name="dim"
+ select="$pkg/preproc:symtable/preproc:sym[ @name=$name ]/@dim" />
+
+ <!-- retrieve the value, casting to a number (to avoid potentially nasty
+ string concatenation bugs instead of integer/floating point arithmetic)
+ as long as we're either not a set, or provide an index for the set -->
+ <xsl:variable name="value">
+ <xsl:text>args['</xsl:text>
+ <xsl:value-of select="@name" />
+ <xsl:text>']</xsl:text>
+ </xsl:variable>
+
+ <!-- the awkward double-negatives are intentional, since @index may not
+ exist; here's what we're doing:
+
+ - If it's not a set, then indexes are irrelevant; always cast scalars
+ - Otherwise
+ - If an index was provided and it is not a matrix, cast
+ - Otherwise
+ - If two indexes were provided and it is a matrix, cast
+ -->
+ <!-- N.B. it is important to do this outside the value variable, otherwise the
+ cast may be done at the incorrect time -->
+ <xsl:if test="
+ (
+ $dim='0'
+ or (
+ (
+ ( @index and not( @index = '' ) )
+ or ( ./c:index )
+ )
+ and not( $dim='2' )
+ )
+ or (
+ ( $dim='2' )
+ and ./c:index[2]
+ )
+ )
+ and not(
+ parent::c:arg
+ and not( @index )
+ )
+ ">
+
+ <xsl:text>+</xsl:text>
+ </xsl:if>
+
+ <xsl:apply-templates select="." mode="compile-calc-index">
+ <xsl:with-param name="value" select="$value" />
+ </xsl:apply-templates>
+
+ <!-- default to 0 if nothing is set (see notes on bottom of summary page; we
+ assume all undefined values in a set to be implicitly 0, which greatly
+ simplifies things) -->
+ <xsl:text> || 0</xsl:text>
+</xsl:template>
+
+
+<!--
+ Include the index if one was provided
+
+ The index is represented as a var in the compiled JS, so treat it as an
+ identifier not a string.
+
+ An @index attribute or node may be provided. In the former case, the
+ identifier simply represents a variable. In the latter, a more complicate
+ expression may be used to generate the index.
+
+ Note that the compiled variable to which the index will be applied is
+ included, as a string, to this template. This allows it to be wrapped if
+ necessary (using multiple indexes) so that the default value can be properly
+ applied, avoiding undefined[N].
+
+ @param string value compiled variable to which index will be applied
+
+ @return index (including brackets), if one was provided
+-->
+<xsl:template match="c:*" mode="compile-calc-index">
+ <xsl:param name="value" />
+ <xsl:variable name="index" select="@index" />
+
+ <xsl:choose>
+ <xsl:when test="@index">
+ <!-- output the value, falling back on an empty array to prevent errors
+ when attempting to access undefined values -->
+ <xsl:text>(</xsl:text>
+ <xsl:value-of select="$value" />
+ <xsl:text>||[])</xsl:text>
+
+ <xsl:text>[</xsl:text>
+
+ <xsl:choose>
+ <!-- if this index was explicitly defined as such, just output the name
+ (since it will have been defined as a variable in the compiled code)
+ -->
+ <xsl:when test="ancestor::c:*[ @of and @index=$index ]">
+ <xsl:value-of select="@index" />
+ </xsl:when>
+
+ <!-- if the index references a parameter, simply output the identifier -->
+ <xsl:when test="@index=ancestor::lv:function/lv:param/@name">
+ <xsl:value-of select="@index" />
+ </xsl:when>
+
+ <!-- scalar constant -->
+ <xsl:when test="@index = root(.)/preproc:symtable/preproc:sym
+ [ @type='const'
+ and @dim='0' ]
+ /@name">
+ <xsl:value-of select="root(.)/preproc:symtable
+ /preproc:sym[ @name=$index ]
+ /@value" />
+ </xsl:when>
+
+ <!-- otherwise, it's a variable -->
+ <xsl:otherwise>
+ <xsl:text>args['</xsl:text>
+ <xsl:value-of select="@index" />
+ <xsl:text>']</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:text>]</xsl:text>
+ </xsl:when>
+
+ <!-- if index node(s) were provided, then recursively generate -->
+ <xsl:when test="./c:index">
+ <xsl:apply-templates select="./c:index[1]" mode="compile-calc-index">
+ <xsl:with-param name="wrap" select="$value" />
+ </xsl:apply-templates>
+ </xsl:when>
+
+ <!-- otherwise, we have no index, so just output the compiled variable -->
+ <xsl:otherwise>
+ <xsl:value-of select="$value" />
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template match="c:index" mode="compile-calc-index">
+ <xsl:param name="wrap" />
+
+ <!-- get the next index node, if available -->
+ <xsl:variable name="next" select="following-sibling::c:index" />
+
+ <xsl:variable name="value">
+ <xsl:value-of select="$wrap" />
+
+ <!-- generate index -->
+ <xsl:text>[</xsl:text>
+ <xsl:apply-templates select="./c:*[1]" mode="compile" />
+ <xsl:text>]</xsl:text>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$next">
+ <!-- recurse on any sibling indexes, wrapping with default value -->
+ <xsl:apply-templates select="$next" mode="compile-calc-index">
+ <xsl:with-param name="wrap">
+ <!-- wrap the value in parenthesis so that we can provide a default value if
+ the index lookup fails -->
+ <xsl:text>(</xsl:text>
+
+ <xsl:value-of disable-output-escaping="yes" select="$value" />
+
+ <!-- using 0 as the default is fine, even if we have $next; accessing an index
+ of 0 is perfectly fine, since it will be cast to an object; we just must
+ make sure that it is not undefined -->
+ <xsl:text> || 0 )</xsl:text>
+ </xsl:with-param>
+ </xsl:apply-templates>
+ </xsl:when>
+
+ <!-- if there is no sibling, just output our value with the index, unwrapped
+ (since the caller will handle its default value for us -->
+ <xsl:otherwise>
+ <xsl:value-of disable-output-escaping="yes" select="$value" />
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+
+<!--
+ Generates a quotient between two calculations
+
+ The result is simply division between the two nodes, the first being the
+ numerator and the second being the denominator.
+
+ @return generate quotient
+-->
+<xsl:template match="c:quotient" mode="compile-calc">
+ <!-- we only accept a numerator and a denominator -->
+ <xsl:apply-templates select="./c:*[1]" mode="compile" />
+ <xsl:text> / </xsl:text>
+ <xsl:apply-templates select="./c:*[2]" mode="compile" />
+</xsl:template>
+
+
+<xsl:template match="c:expt" mode="compile-calc">
+ <!-- we only accept a numerator and a denominator -->
+ <xsl:text>Math.pow(</xsl:text>
+ <xsl:apply-templates select="./c:*[1]" mode="compile" />
+ <xsl:text>, </xsl:text>
+ <xsl:apply-templates select="./c:*[2]" mode="compile" />
+ <xsl:text>)</xsl:text>
+</xsl:template>
+
+
+<!--
+ Generates a function application (call)
+
+ Arguments are set by name in the XML, but the generate code uses actual
+ function arguments. Therefore, the generated argument list will be generated
+ in the order that the function is expecting, filling in unset parameters with
+ the constant 0.
+
+ @return generated function application
+-->
+<xsl:template match="c:apply" mode="compile-calc">
+ <xsl:variable name="name" select="@name" />
+ <xsl:variable name="self" select="." />
+
+ <xsl:call-template name="calc-compiler:gen-func-name">
+ <xsl:with-param name="name" select="@name" />
+ </xsl:call-template>
+
+ <xsl:text>( args</xsl:text>
+
+ <xsl:variable name="arg-prefix" select="concat( ':', $name, ':' )" />
+
+ <!-- generate argument list in the order that the arguments are expected (they
+ can be entered in the XML in any order) -->
+ <xsl:for-each select="
+ root(.)/preproc:symtable/preproc:sym[
+ @type='func'
+ and @name=$name
+ ]/preproc:sym-ref
+ ">
+
+ <xsl:text>, </xsl:text>
+
+ <xsl:variable name="pname" select="substring-after( @name, $arg-prefix )" />
+ <xsl:variable name="arg" select="$self/c:arg[@name=$pname]" />
+
+ <xsl:choose>
+ <!-- if the call specified this argument, then use it -->
+ <xsl:when test="$arg">
+ <xsl:apply-templates select="$arg/c:*[1]" mode="compile" />
+ </xsl:when>
+
+ <!-- otherwise, there's no value; pass in 0 -->
+ <xsl:otherwise>
+ <xsl:value-of select="'0'" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+
+ <xsl:text> )</xsl:text>
+
+ <!-- if c:when was provided, compile it in such a way that we retain the
+ function call (we want the result for providing debug information) -->
+ <xsl:if test="./c:when">
+ <xsl:text> * </xsl:text>
+ <xsl:apply-templates select="./c:when" mode="compile" />
+ </xsl:if>
+</xsl:template>
+
+
+<xsl:template match="c:when" mode="compile-calc">
+ <!-- note that if we have multiple c:whens, they'll be multiplied together by
+ whatever calls this, so we're probably fine -->
+
+ <xsl:text>( function() {</xsl:text>
+ <!-- return a 1 or a 0 depending on the result of the expression -->
+ <xsl:text>return ( </xsl:text>
+ <xsl:text>( </xsl:text>
+ <!-- get the value associated with this node -->
+ <xsl:apply-templates select="." mode="compile-calc-value" />
+ <xsl:text> ) </xsl:text>
+
+ <!-- generate remainder of expression -->
+ <xsl:apply-templates select="./c:*[1]" mode="compile-calc-when" />
+ <xsl:text>) ? 1 : 0; </xsl:text>
+ <xsl:text>} )()</xsl:text>
+</xsl:template>
+
+
+<xsl:template match="c:eq|c:ne|c:lt|c:gt|c:lte|c:gte" mode="compile-calc-when">
+ <xsl:variable name="name" select="local-name()" />
+
+ <!-- map to LaTeX equivalent -->
+ <xsl:variable name="map">
+ <c id="eq">==</c>
+ <c id="ne">!=</c>
+ <c id="gt">&gt;</c>
+ <c id="lt">&lt;</c>
+ <c id="gte">&gt;=</c>
+ <c id="lte">&lt;=</c>
+ </xsl:variable>
+
+ <xsl:value-of disable-output-escaping="yes" select="$map/*[ @id=$name ]" />
+ <xsl:text> </xsl:text>
+
+ <xsl:apply-templates select="./c:*[1]" mode="compile" />
+</xsl:template>
+
+
+<xsl:template match="c:*" mode="compile-calc-when">
+ <xsl:apply-templates select="." mode="compile-pre" />
+</xsl:template>
+
+
+<xsl:template match="c:cases" mode="compile-calc">
+ <xsl:text>( function() {</xsl:text>
+
+ <xsl:for-each select="./c:case">
+ <!-- turn "if" into an "else if" if needed -->
+ <xsl:if test="position() > 1">
+ <xsl:text>else </xsl:text>
+ </xsl:if>
+
+ <xsl:text>if (</xsl:text>
+ <xsl:for-each select="./c:when">
+ <xsl:if test="position() > 1">
+ <!-- they all yield integer values, so this should work just fine -->
+ <xsl:text> * </xsl:text>
+ </xsl:if>
+
+ <xsl:apply-templates select="." mode="compile" />
+ </xsl:for-each>
+ <xsl:text> ) { return </xsl:text>
+ <!-- process on its own so that we can debug its final value -->
+ <xsl:apply-templates select="." mode="compile" />
+ <xsl:text>; } </xsl:text>
+ </xsl:for-each>
+
+ <!-- check for the existence of an "otherwise" clause, which should be
+ trivial enough to generate -->
+ <xsl:if test="c:otherwise">
+ <!-- this situation may occur in templates since it's a convenient and
+ easy-to-use template notation (conditional cases, none of which may
+ actually match and be output) -->
+ <xsl:choose>
+ <xsl:when test="c:case">
+ <xsl:text>else</xsl:text>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:text>if ( true )</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:text> { return </xsl:text>
+ <xsl:apply-templates select="c:otherwise" mode="compile" />
+ <xsl:text>; } </xsl:text>
+ </xsl:if>
+
+ <xsl:text> } )() || 0</xsl:text>
+</xsl:template>
+
+<xsl:template match="c:case" mode="compile-calc">
+ <xsl:apply-templates
+ select="./c:*[ not( local-name() = 'when' ) ][1]"
+ mode="compile-calc-when" />
+</xsl:template>
+
+<xsl:template match="c:otherwise" mode="compile-calc">
+ <xsl:apply-templates
+ select="c:*[1]"
+ mode="compile-calc-when" />
+</xsl:template>
+
+
+<xsl:template match="c:set" mode="compile-calc">
+ <xsl:text>[</xsl:text>
+ <xsl:for-each select="./c:*">
+ <xsl:if test="position() > 1">
+ <xsl:text>,</xsl:text>
+ </xsl:if>
+
+ <xsl:apply-templates select="." mode="compile" />
+ </xsl:for-each>
+ <xsl:text>]</xsl:text>
+</xsl:template>
+
+
+<!--
+ Just list the Lisp cons (constructs a list)
+-->
+<xsl:template match="c:cons" mode="compile-calc">
+ <xsl:variable name="car" select="./c:*[1]" />
+ <xsl:variable name="cdr" select="./c:*[2]" />
+
+ <xsl:text>(function(){</xsl:text>
+ <!-- duplicate the array just in case...if we notice a performance impact,
+ then we can determine if such a duplication is necessary -->
+ <xsl:text>var cdr = Array.prototype.slice.call(</xsl:text>
+ <xsl:apply-templates select="$cdr" mode="compile" />
+ <xsl:text>, 0);</xsl:text>
+ <xsl:text>cdr.unshift( </xsl:text>
+ <xsl:apply-templates select="$car" mode="compile" />
+ <xsl:text> ); </xsl:text>
+ <!-- no longer the cdr -->
+ <xsl:text>return cdr; </xsl:text>
+ <xsl:text>})()</xsl:text>
+</xsl:template>
+
+
+<xsl:template match="c:car" mode="compile-calc">
+ <xsl:text>(</xsl:text>
+ <xsl:apply-templates select="c:*[1]" mode="compile" />
+ <xsl:text>[0]||0)</xsl:text>
+</xsl:template>
+
+<xsl:template match="c:cdr" mode="compile-calc">
+ <xsl:apply-templates select="c:*[1]" mode="compile" />
+ <xsl:text>.slice(1)</xsl:text>
+</xsl:template>
+
+
+<!--
+ Returns the length of any type of set (not just a vector)
+-->
+<xsl:template match="c:length-of" mode="compile-calc">
+ <xsl:text>( </xsl:text>
+ <xsl:apply-templates select="./c:*[1]" mode="compile" />
+ <xsl:text>.length || 0 )</xsl:text>
+</xsl:template>
+
+
+<xsl:template match="c:let" mode="compile-calc">
+ <!-- the first node contains all the values -->
+ <xsl:variable name="values" select="./c:values/c:value" />
+
+ <!-- if a @name was provided, then treat it like a named Scheme let
+ expression in that it can be invoked with different arguments -->
+ <xsl:variable name="fname">
+ <xsl:if test="@name">
+ <xsl:call-template name="calc-compiler:gen-func-name">
+ <xsl:with-param name="name" select="@name" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:variable>
+
+ <xsl:text>( </xsl:text>
+ <xsl:if test="@name">
+ <xsl:value-of select="$fname" />
+ <xsl:text> = </xsl:text>
+ </xsl:if>
+ <xsl:text>function( </xsl:text>
+ <!-- generate arguments -->
+ <xsl:for-each select="$values">
+ <xsl:if test="position() > 1">
+ <xsl:text>,</xsl:text>
+ </xsl:if>
+
+ <xsl:value-of select="@name" />
+ </xsl:for-each>
+ <xsl:text> ) { </xsl:text>
+
+ <!-- the second node is the body -->
+ <xsl:text>return </xsl:text>
+ <xsl:apply-templates select="./c:*[2]" mode="compile" />
+ <xsl:text>;</xsl:text>
+ <xsl:text>} )</xsl:text>
+
+ <!-- assign the arguments according to the calculations -->
+ <xsl:text>( </xsl:text>
+ <xsl:for-each select="$values">
+ <xsl:if test="position() > 1">
+ <xsl:text>,</xsl:text>
+ </xsl:if>
+
+ <!-- compile the argument value (the parenthesis are just to make it
+ easier to read the compiled code) -->
+ <xsl:text>(</xsl:text>
+ <xsl:apply-templates select="./c:*[1]" mode="compile" />
+ <xsl:text>)</xsl:text>
+ </xsl:for-each>
+ <xsl:text> ) </xsl:text>
+</xsl:template>
+
+
+<!--
+ Generate a function name for use as the name of a function within the compiled
+ code
+
+ @return generated function name
+-->
+<xsl:template name="calc-compiler:gen-func-name">
+ <xsl:param name="name" />
+
+ <xsl:text>func_</xsl:text>
+ <xsl:value-of select="$name" />
+</xsl:template>
+
+
+<xsl:template match="c:debug-to-console" mode="compile-calc">
+ <xsl:text>(function(){</xsl:text>
+ <xsl:text>var result = </xsl:text>
+ <xsl:apply-templates select="./c:*[1]" mode="compile" />
+ <xsl:text>;</xsl:text>
+
+ <!-- log the result and return it so that we do not inhibit the calculation
+ (allowing it to be inlined anywhere) -->
+ <xsl:text>console.log( </xsl:text>
+ <xsl:if test="@label">
+ <xsl:text>'</xsl:text>
+ <xsl:value-of select="@label" />
+ <xsl:text>', </xsl:text>
+ </xsl:if>
+
+ <xsl:text>result ); </xsl:text>
+ <xsl:text>return result; </xsl:text>
+ <xsl:text>})()</xsl:text>
+</xsl:template>
+
+
+<!--
+ Catch-all for calculations
+
+ If an unmatched calculation element is used, then the generated code will
+ output an error to the console (if a console is available) and return a value
+ that may be used within context of an equation; this allows it to be placed
+ inline.
+
+ @return self-executing anonymous error function
+-->
+<xsl:template match="c:*" mode="compile-calc">
+ <xsl:text>( function () {</xsl:text>
+ <xsl:text>throw Error( "Unknown calculation: </xsl:text>
+ <xsl:value-of select="name()" />
+ <xsl:text>" ); </xsl:text>
+ <xsl:text>} )() </xsl:text>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/current/compiler/js.xsl b/src/current/compiler/js.xsl
new file mode 100644
index 0000000..55ea029
--- /dev/null
+++ b/src/current/compiler/js.xsl
@@ -0,0 +1,1908 @@
+<?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:lvp="http://www.lovullo.com"
+ xmlns:c="http://www.lovullo.com/calc"
+ xmlns:w="http://www.lovullo.com/rater/worksheet"
+ xmlns:wc="http://www.lovullo.com/rater/worksheet/compiler"
+ xmlns:compiler="http://www.lovullo.com/rater/compiler"
+ xmlns:calc-compiler="http://www.lovullo.com/calc/compiler"
+ xmlns:preproc="http://www.lovullo.com/rater/preproc"
+ xmlns:util="http://www.lovullo.com/util"
+ xmlns:ext="http://www.lovullo.com/ext">
+
+
+<!-- calculation compiler -->
+<include href="js-calc.xsl" />
+
+<!-- rating worksheet definition compiler -->
+<include href="worksheet.xsl" />
+
+<!-- newline -->
+<variable name="compiler:nl" select="'&#10;'" />
+
+
+<!--
+ Generates rater function
+
+ The rater is a single function that may be invoked with a key-value argument
+ list and will return a variety of data, including the final premium.
+
+ @param NodeSet pkgs all packages, including rater
+
+ @return compiled JS
+-->
+<template match="lv:package" mode="compiler:entry">
+ <!-- enclose everything in a self-executing function to sandbox our data -->
+ <text>( function() { </text>
+ <!-- to store debug information for equations (we have to put this out here
+ so that functions also have access to it...yes, it's stateful, yes it's
+ bullshit, but oh well) -->
+ <text>var debug = {};</text>
+ <text>var consts = {};</text>
+ <text>var params = {};</text>
+ <text>var types = {};</text>
+ <text>var meta = {};</text>
+ <!--
+ <value-of select="$compiler:nl" />
+ <apply-templates
+ select="root(.)//lv:const[ .//lv:item or preproc:iou ]"
+ mode="compile" />
+ -->
+
+</template>
+
+
+<template match="lv:package" mode="compiler:entry-rater">
+ <!-- the rater itself -->
+ <value-of select="$compiler:nl" />
+ <text>function rater( arglist, _canterm ) {</text>
+ <text>_canterm = ( _canterm == undefined ) ? true : !!_canterm;</text>
+
+ <!-- XXX: fix; clear debug from any previous runs -->
+ <text>debug = {};</text>
+
+ <!-- magic constants (N.B. these ones must be re-calculated with each
+ call, otherwise the data may be incorrect!) -->
+ <value-of select="$compiler:nl" />
+
+ <!-- XXX: Remove this; shouldn't be magic -->
+ <text>consts['__DATE_YEAR__'] = ( new Date() ).getFullYear(); </text>
+
+ <!-- clone the object so as not to modify the one that was passed
+ (ES5 feature); also adds constants -->
+ <text>var args = Object.create( arglist );</text>
+
+ <!-- will store the global params that we ended up requiring -->
+ <text>var req_params = {};</text>
+
+ <!-- handle defaults -->
+ <text>init_defaults( args, params );</text>
+
+ <!-- perform classifications -->
+ <value-of select="$compiler:nl" />
+ <text>var classes = rater.classify( args, _canterm );</text>
+ <!-- for @external generated clases -->
+ <text>var genclasses = {};</text>
+</template>
+
+<template match="lv:package" mode="compiler:entry-classifier">
+ <!-- allow classification of any arbitrary dataset -->
+ <value-of select="$compiler:nl" />
+ <text>rater.classify = function( args, _canterm ) {</text>
+ <text>_canterm = ( _canterm == undefined ) ? true : !!_canterm;</text>
+
+ <!-- XXX: Remove this; shouldn't be magic -->
+ <text>consts['__DATE_YEAR__'] = ( new Date() ).getFullYear(); </text>
+
+ <!-- object into which all classifications will be stored -->
+ <text>var classes = {}, genclasses = {}; </text>
+
+ <!-- TODO: We need to do something with this... -->
+ <text>var req_params = {}; </text>
+</template>
+
+<template match="lv:package" mode="compiler:exit-classifier">
+ <text>return classes;</text>
+ <text> };</text>
+
+ <!-- TODO: make sure fromMap has actually been compiled -->
+ <text>rater.classify.fromMap = function( args_base, _canterm ) { </text>
+ <text>var ret = {}; </text>
+ <text>rater.fromMap( args_base, function( args ) {</text>
+ <text>var classes = rater.classify( args, _canterm ); </text>
+
+ <text>
+ for ( var c in classes )
+ {
+ ret[ c ] = {
+ is: !!classes[ c ],
+ indexes: args[ rater.classify.classmap[ c ] ]
+ };
+ }
+ </text>
+ <text>} );</text>
+ <text>return ret;</text>
+ <text> }; </text>
+</template>
+
+<template match="lv:package" mode="compiler:exit-rater">
+ <param name="symbols" as="element( preproc:sym )*" />
+
+ <value-of select="$compiler:nl" />
+ <text>return { </text>
+ <!-- round the premium (special symbol ___yield) to max of 2 decimal places -->
+ <text>premium: ( Math.round( args.___yield * 100 ) / 100 ), </text>
+ <text>classes: classes, </text>
+ <text>vars: args, </text>
+ <text>consts: consts, </text>
+ <text>reqParams: req_params, </text>
+ <text>debug: debug </text>
+ <text>}; </text>
+ <text>}</text>
+
+ <!-- make the name of the supplier available -->
+ <text>rater.supplier = '</text>
+ <value-of select="substring-after( @name, '/' )" />
+ <text>'; </text>
+
+ <text>rater.meta = meta;</text>
+
+ <!-- provide classification -> yields mapping -->
+ <value-of select="$compiler:nl" />
+ <text>rater.classify.classmap = { </text>
+ <apply-templates select="." mode="compiler:classifier-yields-map">
+ <with-param name="symbols" select="$symbols" />
+ </apply-templates>
+ <text> }; </text>
+
+ <!-- provide classification descriptions -->
+ <value-of select="$compiler:nl" />
+ <text>rater.classify.desc = { </text>
+ <sequence select="
+ compiler:class-desc(
+ $symbols[ @type='class' ] )" />
+ <text> }; </text>
+
+ <variable name="mapfrom" select="
+ preproc:symtable/preproc:sym[
+ @type='map'
+ ]/preproc:from[
+ not(
+ @name = parent::preproc:sym
+ /preceding-sibling::preproc:sym[
+ @type='map'
+ ]/preproc:from/@name
+ )
+ ]
+ " />
+
+ <!-- mapped fields (external names) -->
+ <text>rater.knownFields = {</text>
+ <for-each select="$mapfrom">
+ <if test="position() > 1">
+ <text>, </text>
+ </if>
+
+ <text>'</text>
+ <value-of select="@name" />
+ <text>': true</text>
+ </for-each>
+ <text>}</text>
+
+ <!-- common stuff -->
+ <call-template name="compiler:static" />
+
+ <!-- the rater has been generated; return it -->
+ <text>return rater;</text>
+ <text>} )()</text>
+</template>
+
+
+<template match="lv:package" mode="compiler:classifier-yields-map">
+ <param name="symbols" />
+
+ <!-- for each cgen symbol (which is the classification @yields), map the
+ classification name (the @parent) to the cgen symbol name -->
+ <for-each select="$symbols[ @type='cgen' ]">
+ <if test="position() > 1">
+ <text>,</text>
+ </if>
+
+ <text>'</text>
+ <value-of select="substring-after( @parent, ':class:' )" />
+ <text>':'</text>
+ <value-of select="@name" />
+ <text>'</text>
+ </for-each>
+</template>
+
+
+<function name="compiler:class-desc">
+ <param name="syms" as="element( preproc:sym )*" />
+
+ <for-each select="$syms">
+ <if test="position() > 1">
+ <text>,</text>
+ </if>
+
+ <text>'</text>
+ <value-of select="substring-after( @name, ':class:' )" />
+ <text>':'</text>
+ <!-- todo: escape -->
+ <value-of select="translate( @desc, &quot;'&quot;, '' )" />
+ <text>'</text>
+ </for-each>
+</function>
+
+
+<!--
+ Compile global parameter list into an object literal
+
+ @return key and value for the given parameter
+-->
+<template match="lv:param" mode="compile">
+ <!-- generate key using param name -->
+ <text>params['</text>
+ <value-of select="@name" />
+ <text>'] = {</text>
+
+ <!-- param properties -->
+ <text>type: '</text>
+ <value-of select="@type" />
+ <text>',</text>
+
+ <text>'default': '</text>
+ <value-of select="@default" />
+ <text>',</text>
+
+ <text>required: </text>
+ <!-- param is required if the attribute is present, not non-empty -->
+ <value-of select="string( not( boolean( @default ) ) )" />
+ <text>};</text>
+</template>
+
+
+<!--
+ Generate value table for unions
+
+ The type of the table will be considered the type of the first enum and each
+ enum value table will be combined.
+
+ @return object literal properties containing union data
+-->
+<template match="lv:typedef/lv:union" mode="compile" priority="5">
+ <!-- generate key using type name -->
+ <text>types['</text>
+ <value-of select="../@name" />
+ <text>'] = {</text>
+
+ <!-- its type will be the type of its first enum (all must share the same
+ domain) -->
+ <text>type: '</text>
+ <value-of select=".//lv:enum[1]/@type" />
+ <text>',</text>
+
+ <!-- finally, its table of values should consist of every enum it contains -->
+ <text>values: {</text>
+ <for-each select="./lv:typedef/lv:enum/lv:item">
+ <if test="position() > 1">
+ <text>,</text>
+ </if>
+
+ <apply-templates select="." mode="compile" />
+ </for-each>
+ <text>}</text>
+
+ <text>};</text>
+</template>
+
+
+<!--
+ Generate value table for enums
+
+ @return object literal properties containing enum data
+-->
+<template match="lv:typedef/lv:enum" mode="compile" priority="5">
+ <!-- generate key using type name -->
+ <text>types['</text>
+ <value-of select="../@name" />
+ <text>'] = {</text>
+
+ <!-- domain of all values -->
+ <text>type: '</text>
+ <value-of select="@type" />
+ <text>',</text>
+
+ <!-- table of acceptable values -->
+ <text>values: {</text>
+ <for-each select="./lv:item">
+ <if test="position() > 1">
+ <text>,</text>
+ </if>
+
+ <apply-templates select="." mode="compile" />
+ </for-each>
+ <text>}</text>
+
+ <text>};</text>
+</template>
+
+
+<template match="lv:typedef/lv:base-type" mode="compile" priority="5">
+ <text>types['</text>
+ <value-of select="../@name" />
+ <text>'] = {</text>
+
+ <!-- base types are their own type -->
+ <text>type: '</text>
+ <value-of select="../@name" />
+ <text>',</text>
+
+ <text>values:{}</text>
+
+ <text>};</text>
+</template>
+
+
+<template match="lv:typedef/*" mode="compile" priority="1">
+ <message terminate="yes">
+ <text>[lvc] Unhandled typedef: </text>
+ <value-of select="../@name" />
+ </message>
+</template>
+
+
+<!--
+ Generate enum item value
+
+ @return property representing a specific value
+-->
+<template match="lv:enum/lv:item" mode="compile">
+ <!-- determine enumerated value -->
+ <variable name="value">
+ <choose>
+ <when test="@value">
+ <value-of select="@value" />
+ </when>
+
+ <!-- default to string value equivalent to name -->
+ <otherwise>
+ <value-of select="@name" />
+ </otherwise>
+ </choose>
+ </variable>
+
+ <!-- we are only interest in its value; its constant is an internal value -->
+ <text>'</text>
+ <value-of select="$value" />
+ <text>': true</text>
+</template>
+
+
+<!--
+ Generate an object containing values of constant sets
+
+ This is done instead of inlining constant values as we do with non-sets since
+ the specific index can be determined at runtime.
+
+ @return JS object assignment for constant set values
+-->
+<template mode="compile" priority="1"
+ match="lv:const[ element() or @values ]">
+ <text>consts['</text>
+ <value-of select="@name" />
+ <text>'] = [ </text>
+
+ <!-- matrices -->
+ <for-each select="compiler:const-sets( . )[ not( . = '' ) ]">
+ <if test="position() > 1">
+ <text>, </text>
+ </if>
+
+ <text>[ </text>
+ <for-each select="compiler:set-items( ., true() )">
+ <if test="position() > 1">
+ <text>, </text>
+ </if>
+
+ <value-of select="." />
+ </for-each>
+ <text> ]</text>
+ </for-each>
+
+ <!-- vectors -->
+ <for-each select="compiler:set-items( ., false() )">
+ <if test="position() > 1">
+ <text>, </text>
+ </if>
+
+ <value-of select="." />
+ </for-each>
+
+ <text> ]; </text>
+</template>
+
+
+<!--
+ Produce sequence of sets
+
+ Sets are used to group together items in a matrix. A set can be
+ defined explicitly (using nodes), or via a GNU Octave or
+ MATLAB-style matrix definition.
+-->
+<function name="compiler:const-sets" as="item()*">
+ <param name="const" as="element( lv:const )" />
+
+ <variable name="values-def" as="xs:string?"
+ select="$const/@values" />
+
+ <choose>
+ <when test="$values-def and contains( $values-def, ';' )">
+ <sequence select="tokenize(
+ normalize-space( $values-def ), ';' )" />
+ </when>
+
+ <otherwise>
+ <sequence select="$const/lv:set" />
+ </otherwise>
+ </choose>
+</function>
+
+
+<!--
+ Produce a sequence of items
+
+ Items represent elements of a vector. They may be specified
+ explicitly using nodes, or via a comma-delimited string.
+-->
+<function name="compiler:set-items" as="xs:string*">
+ <param name="set" as="item()*" />
+ <param name="allow-values" as="xs:boolean" />
+
+ <choose>
+ <when test="$set instance of xs:string">
+ <sequence select="tokenize(
+ normalize-space( $set ), ',' )" />
+ </when>
+
+ <when test="$set/@values and $allow-values">
+ <sequence select="tokenize(
+ normalize-space( $set/@values ), ',' )" />
+ </when>
+
+ <otherwise>
+ <sequence select="$set/lv:item/@value" />
+ </otherwise>
+ </choose>
+</function>
+
+
+<!--
+ Generate code to perform a classification
+
+ Based on the criteria provided by the classification, generate and store the
+ result of a boolean expression performing the classification using global
+ arguments.
+
+ TODO: Refactor; both @yields and $result-set checks are unneeded; they can be
+ combined (@yields as the default, which may or may not exist)
+
+ @return generated classification expression
+-->
+<template match="lv:classify" mode="compile">
+ <param name="noclass" />
+ <param name="result-set" />
+ <param name="ignores" />
+
+ <variable name="self" select="." />
+
+ <value-of select="$compiler:nl" />
+
+ <if test="not( $noclass )">
+ <if test="@preproc:generated='true'">
+ <text>gen</text>
+ </if>
+
+ <text>classes['</text>
+ <value-of select="@as" />
+ <text>'] = (function(){var result,tmp; </text>
+ </if>
+
+ <!-- locate classification criteria -->
+ <variable name="criteria" as="element( lv:match )*"
+ select="lv:match[
+ not( $ignores )
+ or not( @on=$ignores/@ref ) ]" />
+
+ <variable name="criteria-syms" as="element( preproc:sym )*"
+ select="root(.)/preproc:symtable/preproc:sym[
+ @name = $criteria/@on ]" />
+
+ <variable name="dest">
+ <choose>
+ <when test="$result-set">
+ <value-of select="$result-set" />
+ </when>
+
+ <otherwise>
+ <text>args['</text>
+ <value-of select="@yields" />
+ <text>']</text>
+ </otherwise>
+ </choose>
+ </variable>
+
+ <!-- generate boolean value from match expressions -->
+ <choose>
+ <!-- if classification criteria were provided, then use them -->
+ <when test="$criteria">
+ <variable name="criteria-scalar" as="element( lv:match )*"
+ select="$criteria[
+ @on = $criteria-syms[
+ @dim = '0' ]/@name ]" />
+
+ <variable name="op" as="xs:string"
+ select="compiler:match-group-op( $self )" />
+
+ <text></text>
+ <!-- first, non-scalar criteria -->
+ <apply-templates mode="compile"
+ select="$criteria[
+ not( @on = $criteria-scalar/@on ) ]">
+ <with-param name="result-set" select="$result-set" />
+ <with-param name="ignores" select="$ignores" />
+ <with-param name="operator" select="$op" />
+ </apply-templates>
+
+ <!-- scalars must be matched last -->
+ <apply-templates mode="compile"
+ select="$criteria-scalar">
+ <with-param name="result-set" select="$result-set" />
+ <with-param name="ignores" select="$ignores" />
+ <with-param name="operator" select="$op" />
+ </apply-templates>
+ </when>
+
+ <!-- if no classification criteria, then always true/false -->
+ <otherwise>
+ <!-- this is only useful if $noclass is *not* set -->
+ <if test="not( $noclass )">
+ <choose>
+ <!-- universal -->
+ <when test="not( @any='true' )">
+ <text>tmp = true; </text>
+ </when>
+
+ <!-- existential -->
+ <otherwise>
+ <text>tmp = false; </text>
+ </otherwise>
+ </choose>
+ </if>
+
+ <!-- if @yields was provided, then store the value in a variable of their
+ choice as well (since cmatch will not be done) -->
+ <if test="@yields or $result-set">
+ <value-of select="$dest" />
+ <choose>
+ <!-- universal -->
+ <when test="not( @any='true' )">
+ <text> = 1;</text>
+ </when>
+
+ <!-- existential -->
+ <otherwise>
+ <text> = 0;</text>
+ </otherwise>
+ </choose>
+ </if>
+ </otherwise>
+ </choose>
+
+ <text> return tmp;})();</text>
+
+ <!-- support termination on certain classifications (useful for eligibility
+ and error conditions) -->
+ <if test="@terminate = 'true'">
+ <text>if ( _canterm &amp;&amp; </text>
+
+ <if test="@preproc:generated='true'">
+ <text>gen</text>
+ </if>
+ <text>classes['</text>
+ <value-of select="@as" />
+ <text>'] ) throw Error( '</text>
+ <value-of select="@desc" />
+ <text>' );</text>
+
+ <value-of select="$compiler:nl" />
+ </if>
+
+ <variable name="sym"
+ select="root(.)/preproc:symtable/preproc:sym[ @name=$self/@yields ]" />
+
+ <!-- if we are not any type of set, then yield the value of the first
+ index (note the $criteria check; see above); note that we do not do
+ not( @set ) here, since that may have ill effects as it implies that
+ the node is not preprocessed -->
+ <!-- TODO: this can be simplified, since @yields is always provided -->
+ <if test="$criteria and ( @yields or $result-set ) and ( $sym/@dim='0' )">
+ <value-of select="$dest" />
+ <text> = </text>
+ <value-of select="$dest" />
+ <text>[0];</text>
+
+ <value-of select="$compiler:nl" />
+ </if>
+</template>
+
+
+<template match="preproc:rate" mode="compile">
+ <variable name="yields" select="@ref" />
+
+ <!-- compile the lv:rate block associated with this name -->
+ <apply-templates
+ select="root(.)//lv:rate[ @yields=$yields ]"
+ mode="compile" />
+</template>
+
+<template match="preproc:class" mode="compile">
+ <variable name="as" select="@ref" />
+
+ <apply-templates
+ select="root(.)//lv:classify[ @as=$as ]"
+ mode="compile" />
+</template>
+
+
+<!--
+ Generate domain checks for require-param nodes
+
+ The resulting code will cause an exception to be thrown if the domain check
+ fails.
+
+ FIXME: remove
+
+ @return generated domain check code
+-->
+<template match="lv:required-param" mode="compile">
+ <!--
+ <variable name="name" select="@ref" />
+
+ <text>vocalDomainCheck( '</text>
+ <value-of select="$name" />
+ <text>', '</text>
+ <value-of select="root(.)//lv:param[ @name=$name ]/@type" />
+ <text>', args['</text>
+ <value-of select="$name" />
+ <text>'] ); </text>
+ -->
+
+ <!-- record that this param was required -->
+ <!--
+ <text>req_params['</text>
+ <value-of select="$name" />
+ <text>'] = true; </text>
+ -->
+</template>
+
+
+<!--
+ Generate code asserting a match
+
+ Siblings are joined by default with ampersands to denote an AND relationship,
+ unless overridden.
+
+ @return generated match code
+-->
+<template match="lv:match" mode="compile" priority="1">
+ <!-- default to all matches being required -->
+ <param name="operator" select="'&amp;&amp;'" />
+ <param name="yields" select="../@yields" />
+ <param name="result-set" />
+
+ <variable name="name" select="@on" />
+
+ <text> tmp = </text>
+
+ <variable name="input-raw">
+ <choose>
+ <!-- if we have assumptions, then we'll be recalculating (rather than
+ referencing) an existing classification -->
+ <when test="lv:assuming">
+ <text>_cassume</text>
+ </when>
+
+ <otherwise>
+ <text>args['</text>
+ <value-of select="translate( @on, &quot;'&quot;, '' )" />
+ <text>']</text>
+ </otherwise>
+ </choose>
+ </variable>
+
+ <!-- yields (if not set, generate one so that cmatches still works properly)
+ -->
+ <variable name="yieldto">
+ <choose>
+ <!-- if we were given a result set to use, then use it -->
+ <when test="$result-set">
+ <value-of select="$result-set" />
+ </when>
+
+ <!-- store directly into the destination result set -->
+ <otherwise>
+ <call-template name="compiler:gen-match-yieldto">
+ <with-param name="yields" select="$yields" />
+ </call-template>
+ </otherwise>
+ </choose>
+ </variable>
+
+ <!-- the input value -->
+ <variable name="input">
+ <choose>
+ <when test="@scalar = 'true'">
+ <text>stov( </text>
+ <value-of select="$input-raw" />
+ <text>, ( ( </text>
+ <value-of select="$yieldto" />
+ <!-- note that we default to 1 so that there is at least a single
+ element (which will be the case of the scalar is the first match)
+ in a given classification; the awkward inner [] is to protect
+ against potentially undefined values and will hopefully never
+ happen, and the length is checked on the inner grouping rather than
+ on the outside of the entire expression to ensure that it will
+ yield the intended result if yieldto.length === 0 -->
+ <text> || [] ).length || 1 ) )</text>
+ </when>
+
+ <otherwise>
+ <value-of select="$input-raw" />
+ </otherwise>
+ </choose>
+ </variable>
+
+ <if test="lv:assuming">
+ <text>(function(){</text>
+ <!-- initialize variable (ensuring that the closure we're about to generate
+ will properly assign the value rather than encapsulate it) -->
+ <text>var </text>
+ <value-of select="$input-raw" />
+ <text>; </text>
+
+ <!-- generate assumptions and recursively generate the referenced
+ classification -->
+ <apply-templates select="." mode="compile-match-assumptions">
+ <with-param name="result-var" select="$input-raw" />
+ </apply-templates>
+ <text>; return </text>
+ </if>
+
+ <!-- invoke the classification matcher on this input -->
+ <text>anyValue( </text>
+ <value-of select="$input" />
+ <text>, </text>
+
+ <!-- TODO: error if multiple; also, refactor -->
+ <choose>
+ <when test="@value">
+ <variable name="value" select="@value" />
+ <variable name="sym"
+ select="root(.)/preproc:symtable/preproc:sym[ @name=$value ]" />
+
+ <choose>
+ <!-- simple constant -->
+ <when test="$sym and @value">
+ <value-of select="$sym/@value" />
+ </when>
+
+ <!-- value unavailable (XXX: this probably should never happen...) -->
+ <when test="$sym and @value">
+ <message>
+ <text>[jsc] !!! TODO: bad classification match: '</text>
+ <value-of select="$value" />
+ </message>
+ </when>
+
+ <otherwise>
+ <text>'</text>
+ <!-- TODO: Should we disallow entirely? -->
+ <message>
+ <text>[jsc] warning: static classification match '</text>
+ <value-of select="$value" />
+ <text>' in </text>
+ <value-of select="ancestor::lv:classify[1]/@as" />
+ <text>; use calculation predicate or constant instead</text>
+ </message>
+
+ <value-of select="$value" />
+ <text>'</text>
+ </otherwise>
+ </choose>
+ </when>
+
+ <when test="@pattern">
+ <text>function( val ) { </text>
+ <text>return /</text>
+ <value-of select="@pattern" />
+ <text>/.test( val );</text>
+ <text> }</text>
+ </when>
+
+ <when test="./c:*">
+ <text>function( val, __$$i ) { </text>
+ <text>return ( </text>
+ <for-each select="./c:*">
+ <if test="position() > 1">
+ <text disable-output-escaping="yes"> &amp;&amp; </text>
+ </if>
+
+ <text>( val </text>
+ <apply-templates select="." mode="compile-calc-when" />
+ <text> ) </text>
+ </for-each>
+ <text>);</text>
+ <text>}</text>
+ </when>
+
+ <otherwise>
+ <apply-templates select="." mode="compiler:match-anyof" />
+ </otherwise>
+ </choose>
+
+ <text>, ( </text>
+ <value-of select="$yieldto" />
+ <text> || ( </text>
+ <value-of select="$yieldto" />
+ <text> = [] ) ), </text>
+
+ <!-- if this match is part of a classification that should yield a matrix,
+ then force a matrix set -->
+ <choose>
+ <when test="ancestor::lv:classify/@set = 'matrix'">
+ <text>true</text>
+ </when>
+ <otherwise>
+ <text>false</text>
+ </otherwise>
+ </choose>
+
+ <text>, </text>
+ <choose>
+ <when test="parent::lv:classify/@any='true'">
+ <text>false</text>
+ </when>
+ <otherwise>
+ <text>true</text>
+ </otherwise>
+ </choose>
+
+ <!-- for debugging -->
+ <text>,"</text>
+ <value-of select="$input" />
+ <text>"</text>
+
+ <!-- end of anyValue() call -->
+ <text> ) </text>
+
+ <!-- end of assuming function call -->
+ <if test="lv:assuming">
+ <text>})()</text>
+ </if>
+
+ <text>;</text>
+
+ <text>( debug['</text>
+ <value-of select="@_id" />
+ <text>'] || ( debug['</text>
+ <value-of select="@_id" />
+ <text>'] = [] ) ).push( tmp ); </text>
+
+
+ <text>result = </text>
+ <choose>
+ <!-- join with operator if not first in set -->
+ <when test="position() > 1">
+ <text>result </text>
+ <value-of select="$operator" />
+ <text> tmp;</text>
+ </when>
+
+ <otherwise>
+ <text>tmp;</text>
+ </otherwise>
+ </choose>
+</template>
+
+<template name="compiler:gen-match-yieldto">
+ <param name="yields" />
+
+ <text>args['</text>
+ <choose>
+ <when test="$yields">
+ <value-of select="$yields" />
+ </when>
+
+ <otherwise>
+ <call-template name="compiler:gen-default-yield">
+ <with-param name="name" select="ancestor::lv:classify/@as" />
+ </call-template>
+ </otherwise>
+ </choose>
+ <text>']</text>
+</template>
+
+<!--
+ Handles the special "float" domain
+
+ Rather than the impossible task of calculating all possible floats and
+ asserting that the given values is within that set, the obvious task is to
+ assert whether or not the value is logically capable of existing in such a
+ set based on a definition of such a set.
+
+ See also "integer"
+-->
+<template match="lv:match[ @anyOf='float' ]" mode="compiler:match-anyof" priority="5">
+ <!-- ceil(x) - floor(x) = [ x is not an integer ] -->
+ <text>function( val ) {</text>
+ <text>return ( typeof +val === 'number' ) </text>
+ <text disable-output-escaping="yes">&amp;&amp; </text>
+ <!-- note: greater than or equal to, since we want to permit integers as
+ well -->
+ <text disable-output-escaping="yes">( Math.ceil( val ) >= Math.floor( val ) )</text>
+ <text>;</text>
+ <text>}</text>
+</template>
+
+<!--
+ Handles the special "float" domain
+
+ Rather than the impossible task of calculating all possible integers and
+ asserting that the given values is within that set, the obvious task is to
+ assert whether or not the value is logically capable of existing in such a
+ set based on a definition of such a set.
+
+ See also "float"
+-->
+<template match="lv:match[ @anyOf='integer' ]" mode="compiler:match-anyof" priority="5">
+ <!-- ceil(x) - floor(x) = [ x is not an integer ] -->
+ <text>function( val ) {</text>
+ <text>return ( typeof +val === 'number' ) </text>
+ <text disable-output-escaping="yes">&amp;&amp; </text>
+ <text>( Math.floor( val ) === Math.ceil( val ) )</text>
+ <text>;</text>
+ <text>}</text>
+</template>
+
+<!--
+ Handles matching on an empty set
+
+ This is useful for asserting against fields that may have default values,
+ since in such a case an empty value would be permitted.
+-->
+<template match="lv:match[ @anyOf='empty' ]" mode="compiler:match-anyof" priority="5">
+ <!-- ceil(x) - floor(x) = [ x is not an integer ] -->
+ <text>function( val ) {</text>
+ <text>return ( val === '' ) </text>
+ <text>|| ( val === undefined ) || ( val === null )</text>
+ <text>;</text>
+ <text>}</text>
+</template>
+
+<!--
+ Uh oh. Hopefully this never happens; will throw an exception if a type is
+ defined as a base type (using typedef), but is not handled by the compiler.
+-->
+<template match="lv:match[ @anyOf=root(.)//lv:typedef[ ./lv:base-type ]/@name ]"
+ mode="compiler:match-anyof" priority="3">
+
+ <text>function( val ) {</text>
+ <text>throw Error( 'CRITICAL: Unhandled base type: </text>
+ <value-of select="@anyOf" />
+ <text>' );</text>
+ <text>}</text>
+</template>
+
+<!--
+ Used for user-defined domains
+-->
+<template match="lv:match[ @anyOf ]" mode="compiler:match-anyof" priority="1">
+ <text>types['</text>
+ <value-of select="@anyOf" />
+ <text>'].values</text>
+</template>
+
+
+<function name="compiler:match-group-op" as="xs:string">
+ <param name="class" as="element( lv:classify )" />
+
+ <sequence select="if ( $class/@any = 'true' ) then
+ '||'
+ else
+ '&amp;&amp;'" />
+</function>
+
+
+<!--
+ Compiles a function
+
+ Parameters will be converted into actual function parameters. The function
+ will return the result of its expression (represented by a calculation in the
+ XML).
+
+ @return generated function
+-->
+<template match="lv:function" mode="compile">
+ <value-of select="$compiler:nl" />
+
+ <text>function </text>
+ <call-template name="calc-compiler:gen-func-name">
+ <with-param name="name" select="@name" />
+ </call-template>
+ <text>( args </text>
+
+ <!-- add parameters -->
+ <for-each select="./lv:param">
+ <text>, </text>
+ <value-of select="@name" />
+ </for-each>
+
+ <text>) {</text>
+
+ <text>return ( </text>
+ <!-- begin calculation generation (there should be only one calculation node
+ as a child, so only it will be considered) -->
+ <apply-templates select="./c:*[1]" mode="compile" />
+ <text> );</text>
+
+ <text>} </text>
+</template>
+
+
+<!--
+ Compile lv:rate's in such an order that dependencies will be compiled first
+
+ This is important to ensure that premium calculations based on other premiums
+ are actually calculated after the premium that they depend on. Having an
+ order-dependent document doesn't make sense with the declarative style and is
+ especially confusing when including packages.
+-->
+<template match="lv:package" mode="compile-rates">
+ <!-- generate the rate blocks, dependencies first; does not compile classifier
+ dependencies, as those will be compiled with the appropriate classifier
+ -->
+ <apply-templates mode="compile" select="
+ ./preproc:rate-deps/preproc:flat/preproc:rate
+ |
+ ./preproc:rate-deps/preproc:flat/preproc:class[ @external='true' ]
+ " />
+
+</template>
+
+
+
+<!--
+ Generates a premium calculation
+
+ The result of the generated expression, as denoted by a calculation in the
+ XML, will be stored in the variable identified by @yields.
+
+ TODO: If another calculation uses the yielded premium in the document before
+ this lv:rate block, then this block needs to be compiled *before* the block
+ that references is. We don't want to depend on order, as that would not be
+ declarative (in this particular scenario, at least).
+
+ @return generated self-executing premium calculation function
+-->
+<template match="lv:rate" mode="compile">
+ <value-of select="$compiler:nl" />
+
+ <!-- see c:ceil/c:floor precision comments in js-calc -->
+ <variable name="precision">
+ <choose>
+ <when test="@precision">
+ <value-of select="@precision" />
+ </when>
+
+ <otherwise>
+ <text>8</text>
+ </otherwise>
+ </choose>
+ </variable>
+
+ <variable name="store">
+ <!-- TODO: escape single quotes (even though there should never be any) -->
+ <text>args['</text>
+ <value-of select="@yields" />
+ <text>']</text>
+ </variable>
+
+ <!-- store the premium -->
+ <value-of select="$store" />
+ <text> = </text>
+
+ <text>( function rate_</text>
+ <!-- dashes, which may end up in generated code from templates, must be
+ removed -->
+ <value-of select="translate( @yields, '-', '_' )" />
+ <text>() {</text>
+
+ <text>var predmatch = ( </text>
+ <apply-templates select="." mode="compile-class-condition" />
+ <text> ); </text>
+
+ <!-- set the magic _CMATCH_ var to represent a list of indexes that meet all
+ the classifications -->
+ <text>consts._CMATCH_ = </text>
+ <apply-templates select="." mode="compile-cmatch" />
+ <text>;</text>
+
+ <!-- return the result of the calculation for this rate block -->
+ <text>return (+( </text>
+ <!-- begin calculation generation (there should be only one calculation
+ node as a child, so only it will be considered) -->
+ <apply-templates select="./c:*[1]" mode="compile" />
+ <text> )).toFixed(</text>
+ <value-of select="$precision" />
+ <text>) * predmatch; } )() </text>
+
+ <text>; </text>
+</template>
+
+<template match="lv:rate" mode="compile-class-condition">
+ <!-- generate expression for class list (leave the @no check to the cmatch
+ algorithm, since we want per-index @no's) -->
+ <text>( </text>
+ <variable name="class-set" select="./lv:class" />
+
+ <choose>
+ <when test="$class-set">
+ <for-each select="$class-set">
+ <!-- join class expressions with AND operator -->
+ <if test="position() > 1">
+ <text disable-output-escaping="yes"> &amp;&amp; </text>
+ </if>
+
+ <!-- negate if @no -->
+ <if test="@no='true'">
+ <text>!</text>
+ </if>
+
+ <variable name="ref" select="@ref" />
+
+ <if test="
+ root(.)/preproc:symtable/preproc:sym[
+ @name=concat( ':class:', $ref )
+ ]/@preproc:generated='true'
+ ">
+ <text>gen</text>
+ </if>
+
+ <text>classes['</text>
+ <value-of select="@ref" />
+ <text>']</text>
+ </for-each>
+ </when>
+
+ <!-- well, we need to output something -->
+ <otherwise>
+ <text>true</text>
+ </otherwise>
+ </choose>
+ <text> )</text>
+</template>
+
+
+<template match="lv:rate" mode="compile-cmatch">
+ <variable name="root" select="root(.)" />
+
+ <!-- generate cmatch call that will generate the cmatch set -->
+ <text>cmatch( [</text>
+ <for-each select="lv:class[ not( @no='true' ) ]">
+ <if test="position() > 1">
+ <text>, </text>
+ </if>
+
+ <text>args['</text>
+ <call-template name="compiler:get-class-yield">
+ <with-param name="name" select="@ref" />
+ <with-param name="search" select="$root" />
+ </call-template>
+ <text>']</text>
+ </for-each>
+ <text>], [</text>
+ <for-each select="lv:class[ @no='true' ]">
+ <if test="position() > 1">
+ <text>, </text>
+ </if>
+
+ <text>args['</text>
+ <call-template name="compiler:get-class-yield">
+ <with-param name="name" select="@ref" />
+ <with-param name="search" select="$root" />
+ </call-template>
+ <text>']</text>
+ </for-each>
+ <text>] )</text>
+</template>
+
+
+<template name="compiler:get-class-yield">
+ <param name="name" />
+ <param name="search" />
+
+ <variable name="yields">
+ <value-of select="
+ root(.)/preproc:symtable/preproc:sym[
+ @name=concat( ':class:', $name )
+ ]/@yields
+ " />
+ </variable>
+
+ <choose>
+ <when test="$yields != ''">
+ <value-of select="$yields" />
+ </when>
+
+ <otherwise>
+ <call-template name="compiler:gen-default-yield">
+ <with-param name="name" select="$name" />
+ </call-template>
+ </otherwise>
+ </choose>
+</template>
+
+
+<template name="compiler:gen-default-yield">
+ <param name="name" />
+
+ <!-- a random name that would be invalid to reference from the XML -->
+ <text>___$$</text>
+ <value-of select="$name" />
+ <text>$$</text>
+</template>
+
+
+<!--
+ Generates calculation used to yield a final premium
+
+ @return generated expression
+-->
+<template match="lv:yield" mode="compile">
+ <!-- compile yield calculation -->
+ <apply-templates select="./c:*[1]" mode="compile" />
+</template>
+
+
+
+<template match="lv:meta/lv:prop" mode="compile">
+ <text>meta['</text>
+ <value-of select="@name" />
+ <text>'] = </text>
+
+ <call-template name="util:json">
+ <with-param name="array">
+ <!-- values -->
+ <for-each select="lv:value">
+ <variable name="name" select="@name" />
+
+ <util:value>
+ <!-- TODO: refactor -->
+ <value-of select="
+ root(.)//lv:const[ @name=$name ]/@value
+ , root(.)//lv:item[ @name=$name ]/@value" />
+ </util:value>
+ </for-each>
+
+ <!-- constants -->
+ <for-each select="lv:const">
+ <util:value>
+ <value-of select="@value" />
+ </util:value>
+ </for-each>
+ </with-param>
+ </call-template>
+
+ <text>;</text>
+</template>
+
+
+
+<template match="lvp:*" mode="compile" priority="1">
+ <!-- do nothing with UI nodes -->
+</template>
+<template match="lvp:*" priority="1">
+ <!-- do nothing with UI nodes -->
+</template>
+
+<!--
+ Static code common to each rater
+
+ This is included here because XSLT cannot, without extension, read plain-text
+ files (the included file must be XML). If we separated it into another file,
+ we would be doing the same thing as we are doing here.
+
+ @return JavaScript code
+-->
+<template name="compiler:static">
+<text>
+<![CDATA[
+ var domains = {
+ 'integer': function( value )
+ {
+ return ( value == +value );
+ },
+
+ 'float': function( value )
+ {
+ return ( value == +value );
+ },
+
+ 'boolean': function( value )
+ {
+ return ( ( +value === 1 ) || ( +value === 0 ) );
+ },
+
+ 'string': function( value )
+ {
+ // well, everything is a string
+ return true;
+ }
+ };
+
+
+ function argCheck( args, params )
+ {
+ var req = {};
+
+ for ( var name in params )
+ {
+ // if the argument is not required, then we do not yet need to deal
+ // with it
+ if ( !( params[ name ].required ) )
+ {
+ continue;
+ }
+
+ // first, ensure that required arguments have been provided (note
+ // the strict check for .length; this is important, since it won't
+ // have a length property if it's not an array, in which case we do
+ // not want to trigger an error)
+ if ( !( args[ name ] ) || ( args[ name ].length === 0 ) )
+ {
+ throw Error( "argument required: " + name );
+ }
+
+ var value = args[ name ];
+
+ // next, ensure that the argument is within the domain of its type
+ vocalDomainCheck( name, params[ name ].type, deepClone( value ) );
+
+ // record that we required this param
+ req[ name ] = true;
+ }
+
+ return req;
+ }
+
+
+ function vocalDomainCheck( name, domain, value )
+ {
+ var default_val = ( rater.params[ name ] || {} )['default'];
+
+ if ( !( domainCheck( domain, value, default_val ) ) )
+ {
+ throw Error(
+ "argument '" + name + "' value outside domain of '" +
+ domain + "': " + JSON.stringify( value )
+ );
+ }
+
+ return true;
+ }
+
+
+ function domainCheck( domain, value, default_val )
+ {
+ var type;
+
+ // if it's an object, then the value is assumed to be an array
+ if ( typeof value === 'object' )
+ {
+ if ( value.length < 1 )
+ {
+ return true;
+ }
+
+ // clone before popping so that we don't wipe out any values that
+ // will be used
+ value = Array.prototype.slice.call( value );
+
+ // check each value recursively
+ return domainCheck( domain, value.pop(), default_val )
+ && domainCheck( domain, value, default_val );
+ }
+
+ if ( ( ( value === undefined ) || ( value === '' ) )
+ && ( default_val !== undefined )
+ )
+ {
+ value = +default_val;
+ }
+
+ if ( domains[ domain ] )
+ {
+ return domains[ domain ]( value );
+ }
+ else if ( type = types[ domain ] ) /** XXX: types global **/
+ {
+ // custom type checks are two-fold: ensure that the value is within
+ // the domain of its base type and that it is within its list of
+ // acceptable values
+ return !!( domainCheck( type.type, value )
+ && type.values[ value ]
+ );
+ }
+
+ // no domain found
+ return false;
+ }
+
+
+ /**
+ * Checks for matches against values for any param value
+ *
+ * A single successful match will result in a successful classification.
+ *
+ * For an explanation and formal definition of this algorithm, please see
+ * the section entitled "Classification Match (cmatch) Algorithm" in the
+ * manual.
+ *
+ * @param {Array|string} param value or set of values to check
+ * @param {Array|string} values or set of values to match against
+ * @param {Object} yield_to object to yield into
+ * @param {boolean} clear when true, AND results; otherwise, OR
+ *
+ * @return {boolean} true if any match is found, otherwise false
+ */
+ function anyValue( param, values, yield_to, ismatrix, clear, _id )
+ {
+ // convert everything to an array if needed (we'll assume all objects to
+ // be arrays; Array.isArray() is ES5-only) to make them easier to work
+ // with
+ if ( ( param === undefined ) || ( param === null ) )
+ {
+ // according to the specification, an undefined input vector should
+ // yield an empty result set, which in turn will be interpreted as
+ // false (yield_to is the result vector)
+ param = [];
+ }
+ else if ( typeof param !== 'object' )
+ {
+ param = [ param ];
+ }
+
+ var values_orig = values;
+ if ( typeof values !== 'object' )
+ {
+ // the || 0 here ensures that non-values are treated as 0, as
+ // mentioned in the specification
+ values = [ values || 0 ];
+ }
+ else
+ {
+ var tmp = [];
+ for ( var v in values )
+ {
+ tmp.push( v );
+ }
+
+ values = tmp;
+ }
+
+ // if no yield var name was provided, we'll just be storing in a
+ // temporary array which will be discarded when it goes out of scope
+ // (this is the result vector in the specification)
+ var store = yield_to || [];
+
+ var i = param.length,
+ found = false,
+ scalar = ( i === 0 ),
+ u = ( store.length === 0 ) ? clear : false;
+
+ while ( i-- )
+ {
+ // these var names are as they appear in the algorithm---temporary,
+ // and value
+ var t,
+ v = returnOrReduceOr( store[ i ], u );
+
+ // recurse on vectors
+ if ( typeof param[ i ] === 'object' )
+ {
+ var r = deepClone( store[ i ] || [] );
+ if ( typeof r !== 'object' )
+ {
+ r = [ r ];
+ }
+
+ var rfound = !!anyValue( param[ i ], values_orig, r, false, clear, _id );
+ found = ( found || rfound );
+
+ if ( ( typeof store[ i ] === 'object' )
+ || ( store[ i ] === undefined )
+ )
+ {
+ // we do not want to reduce; this is the match that we are
+ // interested in
+ store[ i ] = r;
+ continue;
+ }
+ else
+ {
+ t = returnOrReduceOr( r, clear );
+ }
+ }
+ else
+ {
+ // we have a scalar, folks!
+ scalar = true;
+ t = anyPredicate( values, ( param[ i ] || 0 ), i );
+ }
+
+ store[ i ] = +( ( clear )
+ ? ( v && t )
+ : ( v || t )
+ );
+
+ // equivalent of "Boolean Classification Match" section of manual
+ found = ( found || !!store[ i ] );
+ }
+
+ if ( store.length > param.length )
+ {
+ var sval = ( scalar ) ? anyPredicate( values, param[0] ) : null;
+ if ( typeof sval === 'function' )
+ {
+ // pass the scalar value to the function
+ sval = values[0]( param[0] );
+ }
+
+ // XXX: review the algorithm; this is a mess
+ for ( var k = param.length, l = store.length; k < l; k++ )
+ {
+ // note that this has the same effect as initializing (in the
+ // case of a scalar) the scalar to the length of the store
+ var v = +(
+ ( returnOrReduceOr( store[ k ], clear )
+ || ( !clear && ( scalar && sval ) )
+ )
+ && ( !clear || ( scalar && sval ) )
+ );
+
+ store[ k ] = ( scalar )
+ ? v
+ : [ v ];
+
+ found = ( found || !!v );
+ }
+ }
+
+ return found;
+ }
+
+
+ function anyPredicate( preds, value, index )
+ {
+ for ( var i in preds )
+ {
+ var p = preds[ i ];
+
+ if ( ( typeof p === 'function' )
+ && p( value, index )
+ )
+ {
+ return true;
+ }
+ // lazy equality intentional
+ else if ( p == value )
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ function returnOrReduceOr( arr, c )
+ {
+ if ( arr === undefined )
+ {
+ return !!c;
+ }
+ else if ( !( arr.length ) )
+ {
+ return arr;
+ }
+
+ return reduce( arr, function( a, b )
+ {
+ return returnOrReduceOr( a, c ) || returnOrReduceOr( b, c );
+ } );
+ }
+
+
+ function returnOrReduceAnd( arr, c )
+ {
+ if ( arr === undefined )
+ {
+ return !!c;
+ }
+ else if ( !( arr.length ) )
+ {
+ return arr;
+ }
+
+ return reduce( arr, function( a, b )
+ {
+ return returnOrReduceAnd( a, c ) && returnOrReduceAnd( b, c );
+ } );
+ }
+
+
+ function deepClone( obj )
+ {
+ var objnew = [];
+
+ // if we were not given an object, then do nothing
+ if ( typeof obj !== 'object' )
+ {
+ return obj;
+ }
+
+ for ( var i in obj )
+ {
+ // deep-clone for matrices
+ objnew[ i ] = ( typeof obj[ i ] === 'object' )
+ ? deepClone( obj[ i ] )
+ : obj[ i ];
+ }
+
+ return objnew;
+ }
+
+
+ /**
+ * Converts a match set to an integer
+ *
+ * If the given set is an array, then return a sum of each of its boolean
+ * values (if any one is set, then it's 1); otherwise, cast the given
+ * value to a number, just in case it's not.
+ *
+ * This function does not check to ensure that the given set contains valid
+ * data.
+ *
+ * @param {*} match set to convert
+ *
+ * @return {number} 1 or 0
+ */
+ function matchSetToInt( match )
+ {
+ if ( Array.isArray( match ) )
+ {
+ return reduce( match, function( a, b )
+ {
+ return a + b;
+ } );
+ }
+
+ return +match;
+ }
+
+
+ function cmatch( match, nomatch )
+ {
+ var len = 0,
+ cmatch = [];
+
+ // the length of our set should be the length of the largest set (we
+ // will not know this until runtime)
+ for ( var i in match )
+ {
+ // note that this has the consequence of not matching on scalar-only
+ // classifications...is this what we want? If not, we need to
+ // document a proper solution.
+ if ( ( match[ i ] || [] ).length > len )
+ {
+ len = match[ i ].length;
+ }
+ }
+
+ for ( var i in nomatch )
+ {
+ if ( ( nomatch[ i ] || [] ).length > len )
+ {
+ len = nomatch[ i ].length;
+ }
+ }
+
+ while ( len-- )
+ {
+ var fail = false;
+
+ for ( var i in match )
+ {
+ // if we're dealing with a scalar, then it should be used for
+ // every index
+ var mdata = ( ( typeof match[ i ] !== 'object' )
+ ? match[ i ]
+ : ( match[ i ] || [] )[ len ]
+ );
+
+ if ( !( matchSetToInt( mdata ) ) )
+ {
+ fail = true;
+ }
+ }
+
+ // XXX duplicate code
+ for ( var i in nomatch )
+ {
+ // if we're dealing with a scalar, then it should be used for
+ // every index
+ var mdata = ( ( typeof nomatch[ i ] !== 'object' )
+ ? nomatch[ i ]
+ : ( nomatch[ i ] || [] )[ len ]
+ );
+
+ if ( matchSetToInt( mdata ) !== 0 )
+ {
+ fail = true;
+ }
+ }
+
+ cmatch[ len ] = ( fail ) ? 0 : 1;
+ }
+
+ return cmatch;
+ }
+
+
+ /**
+ * Return the length of the longest set
+ *
+ * Provide each set as its own argument.
+ *
+ * @return number length of longest set
+ */
+ function longerOf()
+ {
+ var i = arguments.length,
+ len = 0;
+ while ( i-- )
+ {
+ var thislen = arguments[ i ].length;
+
+ if ( thislen > len )
+ {
+ len = thislen;
+ }
+ }
+
+ return len;
+ }
+
+
+ /**
+ * Some browsers don't support Array.reduce(), and adding to the prototype
+ * causes problems since we cannot make it non-enumerable in those browsers
+ * due to broken Object.defineProperty implementations (IE8).
+ */
+ function reduce( arr, c )
+ {
+ var ret = arr[ 0 ],
+ i = 0, // skip first
+ l = arr.length;
+
+ while ( ++i < l )
+ {
+ ret = c( ret, arr[ i ] );
+ }
+
+ // note that this will have the effet of returning the first element if
+ // there are none/no more than 1
+ return ret;
+ }
+
+
+ /* scalar to vector */
+ function stov( s, n )
+ {
+ // already a vector
+ if ( typeof s === 'object' )
+ {
+ // if the length is only one, then we can pretend that it is a
+ // scalar (unless the requested length is one, in which case it is
+ // utterly pointless to continue)
+ if ( ( n === 1 ) || ( s.length !== 1 ) )
+ {
+ return s;
+ }
+
+ s = s[ 0 ];
+ }
+
+ var v = [];
+ for ( var i = 0; i < n; i++ )
+ {
+ v.push( s );
+ }
+
+ return v;
+ }
+
+
+ function argreplace( orig, value )
+ {
+ if ( !( typeof orig === 'object' ) )
+ {
+ return value;
+ }
+
+ // we have an object; recurse
+ for ( var i in orig )
+ {
+ return argreplace( orig[ i ], value );
+ }
+ }
+
+
+ function init_defaults( args, params )
+ {
+ for ( var param in params )
+ {
+ var val = params[ param ]['default'];
+ if ( !val )
+ {
+ continue;
+ }
+
+ args[ param ] = set_defaults( args[ param ], val );
+ }
+ }
+
+
+ function set_defaults( input, value )
+ {
+ // scalar
+ if ( !( typeof input === 'object' ) )
+ {
+ return ( input === '' || input === undefined ) ? value : input;
+ }
+
+ // otherwise, assume array
+ var i = input.length;
+ var ret = [];
+ while ( i-- ) {
+ ret[i] = ( input[i] === '' ) ? value : input[i];
+ }
+ return ret;
+ }
+]]>
+</text>
+</template>
+
+</stylesheet>
diff --git a/src/current/compiler/linker.xsl b/src/current/compiler/linker.xsl
new file mode 100644
index 0000000..f6a0478
--- /dev/null
+++ b/src/current/compiler/linker.xsl
@@ -0,0 +1,1442 @@
+<?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:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:lv="http://www.lovullo.com/rater"
+ xmlns:c="http://www.lovullo.com/calc"
+ xmlns:l="http://www.lovullo.com/rater/linker"
+ xmlns:log="http://www.lovullo.com/logger"
+ xmlns:compiler="http://www.lovullo.com/rater/compiler"
+ xmlns:preproc="http://www.lovullo.com/rater/preproc">
+
+<xsl:include href="../include/preproc/symtable.xsl" />
+<xsl:include href="../include/util.xsl" />
+<xsl:include href="js.xsl" />
+
+<xsl:include href="linker/log.xsl" />
+
+<!-- indentation makes dep lists easier to mentally process -->
+<xsl:output indent="yes" />
+
+<!-- optional fragments to include in exit block, comma-delimited
+ (e.g. path/to/object/file/_fragment_) -->
+<xsl:param name="rater-exit-fragments" />
+
+<!--
+ Used to output a great deal of linker information for debugging
+
+ THIS WILL HAVE A PERFORMANCE HIT!
+-->
+<xsl:param name="l:aggressive-debug" select="false()" />
+
+<xsl:variable name="l:orig-root" as="document-node( element( lv:package ) )"
+ select="/" />
+
+<xsl:variable name="l:process-empty" as="element( l:pstack )">
+ <l:pstack />
+</xsl:variable>
+
+<xsl:variable name="l:stack-empty" as="element( l:sym-stack )">
+ <l:sym-stack />
+</xsl:variable>
+
+
+<xsl:template match="*" mode="l:link" priority="1">
+ <xsl:call-template name="log:error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>cannot link </xsl:text>
+ <xsl:value-of select="name()" />
+ <xsl:text>; must link program</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+</xsl:template>
+
+
+<!-- entry point (if run directly) -->
+<xsl:template match="/" priority="1">
+ <xsl:apply-templates select="/lv:*" mode="l:link" />
+</xsl:template>
+
+
+<!--
+ We will only link program package
+
+ Raters are similar to shared object files (that is, packages), but explicitly
+ recognize the fact that linking should be done. They also contain definitions
+ for exit points (lv:yields); think of it like defining a main() function.
+-->
+<xsl:template match="lv:package[ @program='true' ]" mode="l:link" priority="5">
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>linking </xsl:text>
+ <xsl:value-of select="@name" />
+ <xsl:text>...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <!-- start by recursively discovering imported shared object files -->
+ <xsl:variable name="pre-deps" as="element( l:dep )">
+ <xsl:call-template name="log:debug">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>building dependency tree...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <l:dep>
+ <!-- empty stack -->
+ <xsl:apply-templates select="preproc:symtable" mode="l:depgen">
+ <xsl:with-param name="stack" select="$l:stack-empty" as="element( l:sym-stack )" />
+ </xsl:apply-templates>
+ </l:dep>
+ </xsl:variable>
+
+
+ <!-- a single-pass post-processing of the deps to resolve any final issues -->
+ <xsl:variable name="deps" as="element( l:dep )">
+ <xsl:call-template name="log:debug">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>resolving dependency tree...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:apply-templates select="$pre-deps" mode="l:resolv-deps" />
+ </xsl:variable>
+
+ <xsl:copy>
+ <xsl:copy-of select="@*" />
+
+ <!-- copy the dependency tree -->
+ <xsl:copy-of select="$deps" />
+
+ <!-- if map data was provided, generate the map -->
+ <xsl:variable name="maplink">
+ <xsl:apply-templates select="." mode="l:map">
+ <xsl:with-param name="deps" select="$deps" />
+ </xsl:apply-templates>
+ </xsl:variable>
+
+ <xsl:if test="$maplink//l:map-error">
+ <xsl:call-template name="log:error">
+ <xsl:with-param name="name" select="'link'" />
+ </xsl:call-template>
+ </xsl:if>
+
+ <!-- all good. -->
+ <xsl:copy-of select="$maplink" />
+
+ <!-- link. -->
+ <l:exec>
+ <xsl:apply-templates select="." mode="l:link-deps">
+ <!-- TODO: remove this exception -->
+ <xsl:with-param name="deps" select="
+ $deps/preproc:sym[
+ not(
+ starts-with( @type, 'map' )
+ or starts-with( @type, 'retmap' )
+ )
+ ]
+ " />
+ </xsl:apply-templates>
+ </l:exec>
+ </xsl:copy>
+</xsl:template>
+
+
+<xsl:template mode="l:depgen" as="element( preproc:sym )*"
+ match="preproc:symtable">
+ <xsl:param name="stack" as="element( l:sym-stack )"
+ select="$l:stack-empty" />
+
+ <!-- we care only of the symbols used by lv:yields, from which all
+ dependencies may be derived (if it's not derivable from the yield
+ symbol, then it must not be used); note that lv:yield actually compiles
+ into a special symbol ___yield -->
+ <xsl:variable name="yields" as="element( preproc:sym )+">
+ <xsl:copy-of select="preproc:sym[ @name='___yield' ]" />
+
+ <!-- also include anything derivable from any @keep symbol, either local
+ or imported -->
+ <xsl:copy-of select="preproc:sym[ @keep='true' ]" />
+
+ <!-- TODO: these should be included as a consequence of the linking
+ process, not as an exception -->
+ <xsl:copy-of select="
+ preproc:sym[
+ @type='map' or @type='map:head' or @type='map:tail'
+ or @type='retmap' or @type='retmap:head' or @type='retmap:tail'
+ ]
+ " />
+
+ <!-- TODO: same as above -->
+ <xsl:copy-of select="preproc:sym[ @name='___worksheet' ]" />
+ </xsl:variable>
+
+ <!-- start at the top of the table and begin processing each symbol
+ individually, generating a dependency tree as we go -->
+ <xsl:variable name="result" as="element()+">
+ <xsl:call-template name="l:depgen-sym">
+ <xsl:with-param name="pending" select="$yields" />
+ <xsl:with-param name="stack" select="$stack" as="element( l:sym-stack )" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <!-- stack will contain additional metadata -->
+ <xsl:sequence select="$result[ . instance of
+ element( preproc:sym ) ]" />
+</xsl:template>
+
+
+<xsl:template mode="l:resolv-deps" as="element( l:dep )"
+ priority="9"
+ match="l:dep">
+ <xsl:copy>
+ <xsl:apply-templates mode="l:resolv-deps" />
+ </xsl:copy>
+</xsl:template>
+
+
+<!-- replace marks with the symbols that they reference (note that the linker
+ will not add duplicates, so we needn't worry about them) -->
+<xsl:template mode="l:resolv-deps" as="element( preproc:sym )"
+ priority="8"
+ match="preproc:sym[ @l:mark-inclass ]">
+ <!-- FIXME: I sometimes return more than one symbol! -->
+ <xsl:variable name="sym" as="element( preproc:sym )*"
+ select=" root(.)/preproc:sym[
+ @name = current()/@name ]" />
+
+ <!-- sanity check; hopefully never necessary -->
+ <xsl:if test="not( $sym )">
+ <xsl:call-template name="log:internal-error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>l:mark-class found for non-existing symbol </xsl:text>
+ <xsl:value-of select="@name" />
+ <xsl:text>; there is a bug in l:depgen-process-sym</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <!-- copy the element and mark as inclass (no need to check @src, since at
+ this point, such conflicts would have already resulted in an error -->
+ <preproc:sym>
+ <xsl:sequence select="$sym/@*" />
+
+ <!-- override attribute -->
+ <xsl:attribute name="inclass" select="'true'" />
+ </preproc:sym>
+</xsl:template>
+
+
+<!-- any now-inclass symbols should be stripped of their original position -->
+<xsl:template mode="l:resolv-deps" priority="7"
+ match="preproc:sym[
+ @name = root(.)/preproc:sym[ @l:mark-inclass ]
+ /@name ]">
+ <!-- bye-bye -->
+</xsl:template>
+
+
+<xsl:template match="*" mode="l:resolv-deps" priority="1">
+ <xsl:sequence select="." />
+</xsl:template>
+
+
+<!-- FIXME: I want element( l:preproc:sym ), but we also have
+ l:mark-inclass -->
+<xsl:template name="l:depgen-sym" as="element()*">
+ <xsl:param name="pending" as="element( preproc:sym )*" />
+ <xsl:param name="stack" as="element( l:sym-stack )" />
+ <xsl:param name="path" as="xs:string"
+ select="''" />
+ <xsl:param name="processing" as="element( l:pstack )"
+ select="$l:process-empty" />
+
+ <xsl:variable name="pend-count" as="xs:integer"
+ select="count( $pending )" />
+ <xsl:variable name="stack-count" as="xs:integer"
+ select="count( $stack/preproc:sym )" />
+ <xsl:variable name="process-count" as="xs:integer"
+ select="count( $processing/* )" />
+
+ <xsl:choose>
+ <!-- if there are no pending symbols left, then we are done; return the
+ stack -->
+ <xsl:when test="$pend-count = 0">
+ <xsl:sequence select="$stack/*" />
+ </xsl:when>
+
+
+ <xsl:otherwise>
+ <!-- take the first item from the pending list -->
+ <xsl:variable name="cur" as="element( preproc:sym )"
+ select="$pending[1]" />
+
+ <!-- aggressive debugging data -->
+ <xsl:if test="$l:aggressive-debug">
+ <xsl:call-template name="log:debug">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>(</xsl:text>
+ <xsl:value-of select="$path" />
+ <xsl:text>) </xsl:text>
+ <xsl:value-of select="$pend-count" />
+ <xsl:text>p - </xsl:text>
+ <xsl:value-of select="$stack-count" />
+ <xsl:text>s - </xsl:text>
+ <xsl:value-of select="$process-count" />
+ <xsl:text>r - </xsl:text>
+ <xsl:value-of select="$cur/@name" />
+ <xsl:text> [s:: </xsl:text>
+ <xsl:value-of select="$stack/preproc:sym/@name" />
+ <xsl:text> ::s] [r:: </xsl:text>
+ <xsl:value-of select="$processing/preproc:sym/@name" />
+ <xsl:text>::r]</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <xsl:variable name="pkg-seen" as="xs:boolean"
+ select="(
+ ( $cur/@src = '' or not( $cur/@src ) )
+ and $stack/preproc:pkg-seen/@src = ''
+ )
+ or $cur/@src = $stack/preproc:pkg-seen/@src" />
+
+ <xsl:variable name="newpending" as="element( l:pending )">
+ <l:pending>
+ <xsl:sequence select="$pending" />
+
+ <!-- if this is the first time seeing this package, then pend its
+ @keep's for processing -->
+ <xsl:if test="not( $pkg-seen )">
+ <xsl:message select="'[link] found package ', $cur/@src" />
+
+ <xsl:variable name="document" as="element( lv:package )"
+ select="if ( not( $cur/@src or $cur/@src = '' ) ) then
+ $l:orig-root/lv:package
+ else
+ document( concat( $cur/@src, '.xmlo' ),
+ $l:orig-root )
+ /lv:package" />
+
+ <xsl:variable name="keeps" as="element( preproc:sym )*" select="
+ $document/preproc:symtable/preproc:sym[
+ (
+ @keep='true'
+ or ( $l:orig-root/lv:package/@auto-keep-imports='true'
+ and ( @type = 'class'
+ or @type = 'cgen' ) )
+ )
+ and not(
+ $l:orig-root/lv:package/@no-extclass-keeps='true'
+ and @extclass='true'
+ )
+ and not( @name=$pending/@name )
+ and not( @name=$stack/preproc:sym/@name )
+ ]
+ " />
+
+ <xsl:variable name="keepdeps" as="element( preproc:sym )*">
+ <xsl:call-template name="l:dep-aug">
+ <xsl:with-param name="cur" select="$cur" />
+ <xsl:with-param name="deps" select="$keeps" />
+ <xsl:with-param name="proc-barrier" select="true()" />
+ <xsl:with-param name="parent-name"
+ select="concat( 'package ', $cur/@src )" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:sequence select="$keepdeps" />
+ </xsl:if>
+ </l:pending>
+ </xsl:variable>
+
+
+ <xsl:variable name="stack-seen" as="element( l:sym-stack )">
+ <l:sym-stack>
+ <xsl:if test="not( $pkg-seen )">
+ <xsl:sequence select="$stack/*" />
+ <preproc:pkg-seen src="{$cur/@src}" />
+ </xsl:if>
+ </l:sym-stack>
+ </xsl:variable>
+
+ <xsl:variable name="newstack" as="element( l:sym-stack )"
+ select="if ( $pkg-seen ) then
+ $stack
+ else
+ $stack-seen" />
+
+ <xsl:apply-templates select="$cur" mode="l:depgen-process-sym">
+ <xsl:with-param name="pending" select="$newpending/*" />
+ <xsl:with-param name="stack" select="$newstack" />
+ <xsl:with-param name="path" select="$path" />
+ <xsl:with-param name="processing" select="
+ if ( $cur/@l:proc-barrier = 'true' )
+ then $l:process-empty
+ else
+ $processing
+ " />
+ </xsl:apply-templates>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+
+<xsl:function name="l:resolv-extern" as="element( preproc:sym )">
+ <xsl:param name="sym" as="element( preproc:sym )" />
+
+ <xsl:variable name="name" as="xs:string"
+ select="$sym/@name" />
+
+ <!-- there is no reason (in the current implementation) that this
+ should _not_ have already been resolved in the package being
+ linked -->
+ <xsl:variable name="pkg" as="element( lv:package )" select="
+ $l:orig-root/lv:package" />
+
+ <xsl:variable name="resolv" as="element( preproc:sym )?"
+ select="$pkg/preproc:symtable/preproc:sym[
+ @name=$name ]" />
+
+ <xsl:choose>
+ <!-- if this symbol is not external, then we have found it -->
+ <xsl:when test="$resolv and not( $resolv/@extern )">
+ <xsl:sequence select="$resolv" />
+ </xsl:when>
+
+ <!-- if there is no more stack to check and we have not found the symbol,
+ then this is a problem (this should never happen) -->
+ <xsl:otherwise>
+ <xsl:call-template name="log:internal-error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>unresolved extern </xsl:text>
+ <xsl:value-of select="$name" />
+ <xsl:text> (declared by `</xsl:text>
+ <xsl:value-of select="$sym/@src" />
+ <xsl:text>')</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:function>
+
+
+<!--
+ Resolves externs before processing them
+
+ External symbols need special treatment; unlike other symbols, which are both
+ declared and defined within the same package, externs are declared but their
+ definitions are deferred to a later package. This allows using a value from
+ another package in a C-style reverse-inheritence kind of way that is
+ generally not a good idea, but sometimes necessary.
+
+ When a package imports another package that declares an extern, the importing
+ package must either declare that symbol as an extern or provide a definition.
+ This is important, as it provides us with an easy means of resolution:
+ traverse up the stack of symbols that are being processed, check their
+ containing packages and see if there is a definition for the symbol. At some
+ point, assuming that the shared object files are properly built, we must
+ arrive at the definition. Since the symbol is defined within that package,
+ the object file will also contain its dependency list.
+-->
+<xsl:template match="preproc:sym[ @extern='true' ]" mode="l:depgen-process-sym" priority="5">
+ <xsl:param name="pending" as="element( preproc:sym )*" />
+ <xsl:param name="stack" as="element( l:sym-stack )" />
+ <xsl:param name="path" as="xs:string" />
+ <xsl:param name="processing" as="element( l:pstack )" />
+
+ <xsl:variable name="cur" select="." />
+
+ <xsl:variable name="eresolv" as="element( preproc:sym )*"
+ select="l:resolv-extern( $cur )" />
+
+ <!-- were we able to resolve the symbol? -->
+ <xsl:if test="empty( $eresolv )">
+ <xsl:call-template name="log:internal-error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>could not resolve external symbol `</xsl:text>
+ <xsl:value-of select="$cur/@name" />
+ <xsl:text>'</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <!-- in the event that we are importing symbols from program packages (which
+ hopefully is a rare case), we may have external symbols that resolve to
+ the same package; filter out duplicates -->
+ <xsl:variable name="eresolv-uniq" as="element( preproc:sym )"
+ select="$eresolv[
+ not( @src = $eresolv[ not( current() ) ]/@src ) ]" />
+
+ <!-- did we find more than one? (that would be very bad and likely represents
+ a symbol table generation bug) -->
+ <xsl:if test="count( $eresolv-uniq ) gt 1">
+ <xsl:call-template name="log:internal-error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>ambiguous external symbol `</xsl:text>
+ <xsl:value-of select="$cur/@name" />
+ <xsl:text>'; resolution failed (found </xsl:text>
+ <xsl:for-each select="$eresolv-uniq">
+ <xsl:if test="position() > 1">
+ <xsl:text>; </xsl:text>
+ </xsl:if>
+ <xsl:value-of select="@src" />
+ </xsl:for-each>
+ <xsl:text>); pulled in by: </xsl:text>
+
+ <!-- help the user figure out how this happened -->
+ <xsl:for-each select="$processing/preproc:sym">
+ <xsl:if test="position() gt 1">
+ <xsl:text> - </xsl:text>
+ </xsl:if>
+ <xsl:value-of select="concat( @src, '/', @name )" />
+ </xsl:for-each>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <xsl:call-template name="log:debug">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg"
+ select="concat(
+ 'external symbol ', $cur/@name,
+ ' resolved to ',
+ ( if ( $eresolv-uniq/@src ) then
+ $eresolv-uniq/@src
+ else '' ),
+ '/',
+ $eresolv-uniq/@name )" />
+ </xsl:call-template>
+
+ <!-- use the resolved symbol in place of the original extern -->
+ <xsl:apply-templates select="$eresolv-uniq" mode="l:depgen-process-sym">
+ <xsl:with-param name="pending" select="$pending" />
+ <xsl:with-param name="stack" select="$stack" as="element( l:sym-stack )" />
+ <xsl:with-param name="path" select="$path" />
+ <xsl:with-param name="processing" select="$processing" />
+ </xsl:apply-templates>
+</xsl:template>
+
+
+<xsl:template mode="l:depgen-process-sym" priority="1"
+ match="preproc:sym">
+ <xsl:param name="pending" as="element( preproc:sym )*" />
+ <xsl:param name="stack" as="element( l:sym-stack )" />
+ <xsl:param name="path" as="xs:string" />
+ <xsl:param name="processing" as="element( l:pstack )" />
+
+ <xsl:variable name="cur" as="element( preproc:sym )"
+ select="." />
+
+ <!-- determines if the compile destination for these dependencies will be
+ within the classifier; this is the case for all class dependencies
+ *unless* the class is external -->
+ <xsl:variable name="inclass" as="xs:boolean"
+ select="( ( $cur/@type='class' )
+ and not( $cur/@extclass='true' ) )
+ or $processing/preproc:sym[
+ @type='class'
+ and not( @extclass='true' ) ]" />
+
+ <!-- perform circular dependency check and blow up if found (we cannot choose
+ a proper linking order without a correct dependency tree); the only
+ exception is if the circular dependency is a function, since that simply
+ implies recursion, which we can handle just fine -->
+ <xsl:variable name="circ" as="element( preproc:sym )*"
+ select="$processing/preproc:sym[
+ @name=$cur/@name
+ and @src=$cur/@src ]" />
+
+ <xsl:choose>
+ <!-- non-function; fatal -->
+ <xsl:when test="$circ
+ and $circ[ not( @type='func' ) ]
+ ">
+ <xsl:call-template name="l:err-circular">
+ <xsl:with-param name="stack" select="$processing" />
+ <xsl:with-param name="cur" select="$cur" />
+ </xsl:call-template>
+ </xsl:when>
+
+ <!-- function; we've done all we need to, so do not re-link
+ (recursive call) -->
+ <xsl:when test="$circ">
+ <!-- continue processing; leave stack unchanged -->
+ <xsl:call-template name="l:depgen-sym">
+ <xsl:with-param name="pending" select="remove( $pending, 1 )" />
+ <xsl:with-param name="processing" select="$processing" />
+ <xsl:with-param name="stack" select="$stack" as="element( l:sym-stack )" />
+ </xsl:call-template>
+ </xsl:when>
+
+
+ <!-- process; TODO: good refactoring point; large template -->
+ <xsl:otherwise>
+ <xsl:variable name="existing" as="element( preproc:sym )*"
+ select="$stack/preproc:sym[
+ @name=$cur/@name ]" />
+
+ <xsl:variable name="src-conflict" as="element( preproc:sym )*"
+ select="if ( not( $cur/@src ) or $cur/@src = '' ) then
+ ()
+ else
+ $existing[ not( @src = $cur/@src ) ]" />
+
+ <xsl:if test="$src-conflict">
+ <xsl:call-template name="log:error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>symbol name is not unique: `</xsl:text>
+ <xsl:value-of select="@name" />
+ <xsl:text>' found in</xsl:text>
+ <xsl:value-of select="$cur/@src" />
+
+ <xsl:for-each select="$src-conflict">
+ <xsl:text> and </xsl:text>
+ <xsl:value-of select="@src" />
+ </xsl:for-each>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <!-- determine if class already exists, but needs to be marked for
+ inclusion within the classifier -->
+ <xsl:variable name="needs-class-mark" as="xs:boolean"
+ select="$existing
+ and $inclass
+ and not( $existing/@inclass='true' )
+ and not( $stack/preproc:sym[
+ @l:mark-inclass
+ and @name=$cur/@name
+ and @src=$cur/@src ] )" />
+
+ <!-- continue with the remainder of the symbol list -->
+ <xsl:call-template name="l:depgen-sym">
+ <xsl:with-param name="pending" select="remove( $pending, 1 )" />
+ <xsl:with-param name="processing" select="$processing" />
+
+ <xsl:with-param name="stack" as="element( l:sym-stack )">
+ <!-- if this symbol already exists on the stack, then there is no use
+ re-adding it (note that we check both the symbol name and its source
+ since symbols could very well share a name due to exporting rules) -->
+ <xsl:choose>
+ <xsl:when test="not( $existing ) or $needs-class-mark">
+ <!-- does this symbol have any dependencies? -->
+ <xsl:variable name="deps" as="element( preproc:sym )*">
+ <xsl:apply-templates select="$cur" mode="l:depgen-sym" />
+ </xsl:variable>
+
+ <!-- determine our path -->
+ <xsl:variable name="mypath">
+ <xsl:call-template name="preproc:get-path">
+ <xsl:with-param name="path" select="$cur/@src" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <!-- augment each of the dep paths with our own (this ultimately
+ creates symbol paths relative to the rater) -->
+ <xsl:variable name="deps-aug" as="element( preproc:sym )*">
+ <xsl:call-template name="l:dep-aug">
+ <xsl:with-param name="cur" select="$cur" />
+ <xsl:with-param name="deps" select="$deps" />
+ <xsl:with-param name="inclass" select="$inclass" />
+ <xsl:with-param name="mypath" select="$mypath" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <l:sym-stack>
+ <!-- process the dependencies (note that this has the effect of
+ outputting the existing stack as well, which is why we have
+ not yet done so) -->
+ <xsl:call-template name="l:depgen-sym">
+ <xsl:with-param name="pending" select="$deps-aug" />
+ <xsl:with-param name="stack" select="$stack" as="element( l:sym-stack )" />
+ <xsl:with-param name="path" select="$mypath" />
+ <xsl:with-param name="processing" as="element( l:pstack )">
+ <l:pstack>
+ <xsl:sequence select="$processing/*" />
+ <xsl:sequence select="$cur" />
+ </l:pstack>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <!-- finally, we can output ourself -->
+ <xsl:choose>
+ <!-- existing symbol needs to be marked -->
+ <xsl:when test="$needs-class-mark">
+ <preproc:sym l:mark-inclass="true" name="{$cur/@name}" src="{$cur/@src}" />
+ </xsl:when>
+
+ <!-- new symbol -->
+ <xsl:otherwise>
+ <preproc:sym>
+ <xsl:sequence select="$cur/@*" />
+ <xsl:attribute name="inclass" select="$inclass" />
+ </preproc:sym>
+ </xsl:otherwise>
+ </xsl:choose>
+ </l:sym-stack>
+ </xsl:when>
+
+
+ <!-- already exists; leave stack unchanged -->
+ <xsl:otherwise>
+ <xsl:sequence select="$stack" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+
+<xsl:template name="l:dep-aug" as="element( preproc:sym )*">
+ <xsl:param name="cur" as="element( preproc:sym )" />
+ <xsl:param name="deps" as="element( preproc:sym )*" />
+ <xsl:param name="inclass" as="xs:boolean"
+ select="false()" />
+ <xsl:param name="proc-barrier" as="xs:boolean"
+ select="false()" />
+ <xsl:param name="parent-name" as="xs:string"
+ select="$cur/@name" />
+ <xsl:param name="mypath">
+ <!-- default -->
+ <xsl:call-template name="preproc:get-path">
+ <xsl:with-param name="path" select="$cur/@src" />
+ </xsl:call-template>
+ </xsl:param>
+
+ <xsl:for-each select="$deps">
+ <xsl:copy>
+ <xsl:copy-of select="@*" />
+
+ <xsl:variable name="newsrc">
+ <xsl:choose>
+ <!-- if a source path is provided, then we must take it
+ relative to the current symbol's package's directory
+ -->
+ <xsl:when test="@src">
+ <xsl:call-template name="preproc:resolv-path">
+ <xsl:with-param name="path">
+ <xsl:value-of select="$mypath" />
+
+ <xsl:if test="$mypath and not( $mypath='' ) and @src and not( @src='' )">
+ <xsl:text>/</xsl:text>
+ </xsl:if>
+
+ <xsl:value-of select="@src" />
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:when>
+
+ <!-- if no source path is set, then it exists in the same
+ package as we do -->
+ <xsl:otherwise>
+ <xsl:value-of select="$cur/@src" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <!-- set new src path -->
+ <xsl:attribute name="src" select="$newsrc" />
+
+ <!-- flag for inclusion into classifier, if necessary -->
+ <xsl:if test="$inclass">
+ <xsl:attribute name="inclass" select="$inclass" />
+ </xsl:if>
+
+ <xsl:if test="$proc-barrier">
+ <xsl:attribute name="l:proc-barrier" select="'true'" />
+ </xsl:if>
+
+
+ <xsl:if test="$l:aggressive-debug">
+ <xsl:call-template name="log:debug">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:value-of select="$parent-name" />
+ <xsl:text> depends upon </xsl:text>
+ <xsl:if test="@extern='true'">
+ <xsl:text>external </xsl:text>
+ </xsl:if>
+ <xsl:value-of select="concat( @type, ' ', $newsrc, '/', @name )" />
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:copy>
+ </xsl:for-each>
+</xsl:template>
+
+
+
+<!-- TODO: some better way. -->
+<xsl:template match="preproc:sym[ starts-with( @type, 'map' ) or starts-with( @type, 'retmap' ) ]"
+ mode="l:depgen-sym" priority="7">
+
+ <!-- do not process deps -->
+</xsl:template>
+
+
+<xsl:template mode="l:depgen-sym" as="element()*"
+ match="preproc:pkg-seen"
+ priority="5">
+ <xsl:sequence select="." />
+</xsl:template>
+
+
+<xsl:template mode="l:depgen-sym" as="element( preproc:sym )*"
+ match="preproc:sym[
+ @type='const' ]"
+ priority="7">
+ <!-- no need to link this; it has no code associated with it -->
+</xsl:template>
+
+<xsl:template mode="l:depgen-sym" as="element( preproc:sym )*"
+ match="preproc:sym"
+ priority="5">
+ <!-- get the source package -->
+ <xsl:variable name="pkg" as="element( lv:package )?" select="
+ if ( @src and not( @src='' ) ) then
+ document( concat( @src, '.xmlo' ), $l:orig-root )/lv:*
+ else
+ $l:orig-root/lv:package
+ " />
+
+ <xsl:variable name="name" as="xs:string"
+ select="@name" />
+ <xsl:variable name="deps" as="element( preproc:sym-dep )?"
+ select="$pkg/preproc:sym-deps
+ /preproc:sym-dep[ @name=$name ]" />
+
+ <!-- if we could not locate the dependencies, then consider this to be an
+ error (even if there are no deps, there should still be a list dfn) -->
+ <xsl:if test="not( $deps )">
+ <xsl:call-template name="log:internal-error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>could not locate dependency list for </xsl:text>
+ <xsl:value-of select="@src" />
+ <xsl:text>/</xsl:text>
+ <xsl:value-of select="@name" />
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <xsl:variable name="symtable" as="element( preproc:symtable )"
+ select="$pkg/preproc:symtable" />
+
+ <xsl:for-each select="
+ $deps/preproc:sym-ref[
+ not( @parent=$deps/preproc:sym-ref/@name )
+ ]
+ ">
+ <xsl:variable name="sym-ref" as="xs:string"
+ select="@name" />
+
+ <xsl:variable name="sym" as="element( preproc:sym )?"
+ select="$symtable/preproc:sym[ @name=$sym-ref ]" />
+
+ <!-- if we cannot locate the referenced symbol, then that too is an error
+ -->
+ <xsl:if test="not( $sym )">
+ <xsl:call-template name="log:internal-error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>failed locating dependency symbol `</xsl:text>
+ <xsl:value-of select="$sym-ref" />
+ <xsl:text>'</xsl:text>
+ <xsl:text> from package </xsl:text>
+ <xsl:value-of select="$pkg/@name" />
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <!-- output the symbol, sans children -->
+ <preproc:sym>
+ <xsl:sequence select="$sym/@*" />
+ </preproc:sym>
+ </xsl:for-each>
+</xsl:template>
+
+
+<xsl:template mode="l:depgen-sym"
+ match="*"
+ priority="1">
+ <xsl:message terminate="yes">
+ <xsl:text>internal error: unknown symbol for l:depgen-sym: </xsl:text>
+ <xsl:sequence select="." />
+ </xsl:message>
+</xsl:template>
+
+
+<xsl:template name="l:err-circular">
+ <xsl:param name="stack" />
+ <xsl:param name="cur" />
+
+ <xsl:call-template name="log:error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>circular dependency </xsl:text>
+
+ <xsl:text>(</xsl:text>
+ <xsl:value-of select="concat( $cur/@src, '/', $cur/@name )" />
+ <xsl:text>): </xsl:text>
+
+ <xsl:for-each select="$stack//preproc:sym">
+ <xsl:if test="position() > 1">
+ <xsl:text> - </xsl:text>
+ </xsl:if>
+
+ <xsl:value-of select="concat( @src, '/', @name )" />
+ </xsl:for-each>
+ </xsl:with-param>
+ </xsl:call-template>
+</xsl:template>
+
+
+
+<!--
+ Links the object code for each dependency
+
+ And here is where the magic happens. This will take the generated dependency
+ list and, in order of the list, combine the object code from its source
+ package's object file. The result is a fully linked executable.
+
+ Not only is this one of the most interesting parts, this is also one of the
+ fastest; all the hard work has already been done.
+
+ Note: though the linked code is suitable for execution, it is up to a
+ particular implementation to decide how it should be wrapped and invoked.
+-->
+<xsl:template match="lv:package" mode="l:link-deps">
+ <xsl:param name="deps" />
+
+ <!-- to make this executable, we must compile an entry point -->
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>compiling entry point...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ <xsl:apply-templates select="." mode="compiler:entry" />
+
+
+ <xsl:apply-templates select="." mode="l:link-meta">
+ <xsl:with-param name="deps" select="$deps" />
+ </xsl:apply-templates>
+ <xsl:apply-templates select="." mode="l:link-worksheet">
+ <xsl:with-param name="deps" select="$deps" />
+ </xsl:apply-templates>
+ <xsl:apply-templates select="." mode="l:link-classifier">
+ <xsl:with-param name="deps" select="$deps" />
+ </xsl:apply-templates>
+ <xsl:apply-templates select="." mode="l:link-params">
+ <xsl:with-param name="deps" select="$deps" />
+ </xsl:apply-templates>
+ <xsl:apply-templates select="." mode="l:link-types">
+ <xsl:with-param name="deps" select="$deps" />
+ </xsl:apply-templates>
+ <xsl:apply-templates select="." mode="l:link-funcs">
+ <xsl:with-param name="deps" select="$deps" />
+ </xsl:apply-templates>
+ <xsl:apply-templates select="." mode="l:link-rater">
+ <xsl:with-param name="deps" select="$deps" />
+ </xsl:apply-templates>
+
+
+ <!-- finally, finish up -->
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>compiling exit...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:value-of select="@name" />
+ <xsl:text> compilation complete.</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+</xsl:template>
+
+
+<xsl:function name="l:link-exit-fragments" as="xs:string*">
+ <xsl:param name="paths" as="xs:string" />
+ <xsl:param name="context" as="node()" />
+
+ <xsl:variable name="split" as="xs:string*"
+ select="tokenize( $paths, ',' )" />
+
+ <xsl:variable name="base-uri" as="xs:anyURI"
+ select="base-uri( $context )" />
+
+ <xsl:for-each select="$split">
+ <xsl:variable name="fragment" as="xs:string?"
+ select="l:get-fragment-by-path( ., $base-uri )" />
+
+ <xsl:if test="empty( $fragment )">
+ <xsl:call-template name="log:error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>fatal: missing exit fragment: </xsl:text>
+ <xsl:value-of select="." />
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <xsl:sequence select="$fragment" />
+ </xsl:for-each>
+</xsl:function>
+
+
+<xsl:template match="lv:package" mode="l:link-meta">
+ <xsl:param name="deps" as="element( preproc:sym )*" />
+
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>** linking metadata...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:apply-templates select="." mode="l:do-link">
+ <xsl:with-param name="symbols" select="
+ $deps[ @type='meta' ]" />
+ </xsl:apply-templates>
+</xsl:template>
+
+
+<xsl:template match="lv:package" mode="l:link-worksheet">
+ <xsl:param name="deps" as="element( preproc:sym )*" />
+
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>** linking worksheet...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:apply-templates select="." mode="l:do-link">
+ <xsl:with-param name="symbols" select="
+ $deps[ @type='worksheet' ]" />
+ </xsl:apply-templates>
+</xsl:template>
+
+
+<xsl:template match="lv:package" mode="l:link-classifier">
+ <xsl:param name="deps" />
+
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>** linking classifier...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <!-- link everything that shall be a part of the classifier -->
+ <xsl:apply-templates select="." mode="compiler:entry-classifier" />
+ <xsl:apply-templates select="." mode="l:do-link">
+ <xsl:with-param name="symbols" select="
+ $deps[
+ @inclass='true'
+ and not( @type='param' )
+ and not( @type='type' )
+ and not( @type='meta' )
+ and not( @type='worksheet' )
+ ]
+ " />
+ </xsl:apply-templates>
+ <xsl:apply-templates select="." mode="compiler:exit-classifier" />
+</xsl:template>
+
+
+<xsl:template match="lv:package" mode="l:link-params">
+ <xsl:param name="deps" />
+
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>** linking global params...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <!-- link all params -->
+ <xsl:apply-templates select="." mode="l:do-link">
+ <xsl:with-param name="symbols" select="
+ $deps[ @type='param' ]
+ " />
+ </xsl:apply-templates>
+</xsl:template>
+
+
+<xsl:template match="lv:package" mode="l:link-types">
+ <xsl:param name="deps" />
+
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>** linking types...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <!-- link all params -->
+ <xsl:apply-templates select="." mode="l:do-link">
+ <xsl:with-param name="symbols" select="
+ $deps[ @type='type' ]
+ " />
+ </xsl:apply-templates>
+</xsl:template>
+
+
+<xsl:template match="lv:package" mode="l:link-funcs">
+ <xsl:param name="deps" />
+
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>** linking functions...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <!-- link all params -->
+ <xsl:apply-templates select="." mode="l:do-link">
+ <xsl:with-param name="symbols" select="
+ $deps[ @type='func' ]
+ " />
+ </xsl:apply-templates>
+</xsl:template>
+
+
+<xsl:template match="lv:package" mode="l:link-rater">
+ <xsl:param name="deps" />
+
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>** linking rater...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:apply-templates select="." mode="compiler:entry-rater" />
+
+ <!-- TODO: this list of exclusions is a mess -->
+ <xsl:apply-templates select="." mode="l:do-link">
+ <xsl:with-param name="symbols" select="
+ $deps[
+ not( @inclass='true' )
+ and not( @type='param' )
+ and not( @type='type' )
+ and not( @type='func' )
+ and not( @type='meta' )
+ and not( @type='worksheet' )
+ ]
+ " />
+ </xsl:apply-templates>
+
+ <xsl:sequence select="l:link-exit-fragments(
+ $rater-exit-fragments,
+ . )" />
+
+ <xsl:apply-templates select="." mode="compiler:exit-rater">
+ <xsl:with-param name="symbols" select="$deps" />
+ </xsl:apply-templates>
+</xsl:template>
+
+
+<xsl:template match="lv:package" mode="l:do-link">
+ <xsl:param name="symbols" />
+
+ <!-- link each of the dependencies -->
+ <xsl:for-each select="$symbols">
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>linking </xsl:text>
+ <xsl:value-of select="concat( @type, ' ', @src, '/', @name )" />
+ <xsl:text>...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:apply-templates select="." mode="l:link-deps" />
+ </xsl:for-each>
+</xsl:template>
+
+
+<xsl:template match="preproc:sym[ @type='lparam' ]" mode="l:link-deps" priority="9">
+ <!-- no object code for local params -->
+</xsl:template>
+
+<!-- priority of 7 because there may otherwise be some ambiguities
+ (e.g. with lparam) -->
+<xsl:template match="preproc:sym[ @parent ]" mode="l:link-deps" priority="7">
+ <!-- if a parent is defined, then its symbol will have been sufficient -->
+</xsl:template>
+
+<xsl:template match="preproc:sym" mode="l:link-deps" priority="5">
+ <!-- consult the source package for the last time... -->
+ <xsl:variable name="pkg" select="
+ if ( @src and not( @src='' ) ) then
+ document( concat( @src, '.xmlo' ), $l:orig-root )/lv:*
+ else
+ $l:orig-root/lv:package
+ " />
+
+ <xsl:variable name="name" select="@name" />
+ <xsl:variable name="objcode" as="xs:string?"
+ select="l:get-fragment( $pkg, $name )" />
+
+ <xsl:if test="empty( $objcode )">
+ <xsl:if test="not( @type='param'
+ or ( @type='const' and @dim='0' )
+ or @type='tpl'
+ or @type='meta'
+ or not( @type='worksheet' ) )">
+ <xsl:call-template name="log:internal-error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>missing object code for symbol </xsl:text>
+ <xsl:value-of select="concat( @src, '/', @name )" />
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:if>
+
+ <xsl:copy-of select="$objcode" />
+</xsl:template>
+
+
+<xsl:function name="l:get-fragment-by-path" as="xs:string?">
+ <xsl:param name="path" as="xs:string" />
+ <xsl:param name="base-uri" as="xs:anyURI" />
+
+ <xsl:variable name="pkg-path" as="xs:string">
+ <xsl:call-template name="preproc:get-path">
+ <xsl:with-param name="path" select="$path" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="fragment-name" as="xs:string">
+ <xsl:call-template name="preproc:get-basename">
+ <xsl:with-param name="path" select="$path" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="package-uri" as="xs:anyURI"
+ select="resolve-uri(
+ concat( $pkg-path, '.xmlo' ),
+ $base-uri )" />
+
+ <xsl:variable name="doc" as="document-node()"
+ select="doc( $package-uri )" />
+
+ <xsl:if test="empty( $doc )">
+ <xsl:call-template name="log:internal-error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>could not locate package for exit-fragment: </xsl:text>
+ <xsl:value-of select="$package-uri" />
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <xsl:variable name="package" as="element()"
+ select="$doc/*" />
+
+ <xsl:sequence select="l:get-fragment(
+ $package,
+ $fragment-name )" />
+</xsl:function>
+
+
+<xsl:function name="l:get-fragment" as="xs:string?">
+ <xsl:param name="package" as="element()" />
+ <xsl:param name="name" as="xs:string" />
+
+ <xsl:variable name="fragment" as="element( preproc:fragment )?"
+ select="$package/preproc:fragments/preproc:fragment[
+ @id = $name ]" />
+
+ <xsl:sequence select="$fragment/text()" />
+</xsl:function>
+
+
+<xsl:template match="lv:package" mode="l:map" priority="5">
+ <!-- it is important that we check against the dependencies actually compiled
+ rather than the list of available symbols -->
+ <xsl:param name="deps" as="element( l:dep )" />
+
+ <xsl:variable name="syms" as="element( preproc:sym )*"
+ select="preproc:symtable/preproc:sym" />
+
+ <xsl:variable name="mapsyms" as="element( preproc:sym )*"
+ select="$syms[ @type='map' ]" />
+ <xsl:variable name="retmapsyms" as="element( preproc:sym )*"
+ select="$syms[ @type='retmap' ]" />
+
+ <!-- get head and tail -->
+ <xsl:variable name="head" select="$syms[ @type='map:head' ]" />
+ <xsl:variable name="tail" select="$syms[ @type='map:tail' ]" />
+ <xsl:variable name="ret-head" select="$syms[ @type='retmap:head' ]" />
+ <xsl:variable name="ret-tail" select="$syms[ @type='retmap:tail' ]" />
+
+ <xsl:if test="count( $mapsyms ) gt 0">
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>generating input map...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:if test="not( $head ) or not( $tail )">
+ <xsl:call-template name="log:internal-error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>missing object code for input map head or tail</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <!-- input map -->
+ <l:map-exec>
+ <xsl:apply-templates select="$head" mode="l:link-deps" />
+ <xsl:text>&#10;</xsl:text>
+ <xsl:apply-templates select="$mapsyms" mode="l:map">
+ <xsl:with-param name="symtable" select="$deps" />
+ <!-- TODO -->
+ <xsl:with-param name="ignore-error" select="true()" />
+ </xsl:apply-templates>
+ <xsl:apply-templates select="$tail" mode="l:link-deps" />
+ </l:map-exec>
+ </xsl:if>
+
+
+ <!-- TODO: very similar to above; refactor -->
+ <xsl:if test="count( $retmapsyms ) gt 0">
+ <xsl:call-template name="log:info">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>generating return map...</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:if test="not( $ret-head ) or not( $ret-tail )">
+ <xsl:call-template name="log:internal-error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>missing object code for return map head or tail</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <!-- return map -->
+ <l:retmap-exec>
+ <xsl:apply-templates select="$ret-head" mode="l:link-deps" />
+ <xsl:text>&#10;</xsl:text>
+ <xsl:apply-templates select="$retmapsyms" mode="l:map">
+ <xsl:with-param name="type" select="'return'" />
+ <xsl:with-param name="from" select="'input'" />
+ <xsl:with-param name="symtable" select="$deps" />
+ </xsl:apply-templates>
+ <xsl:apply-templates select="$ret-tail" mode="l:link-deps" />
+ </l:retmap-exec>
+ </xsl:if>
+</xsl:template>
+
+
+<xsl:template match="preproc:sym" mode="l:map" priority="5">
+ <xsl:param name="symtable" as="element( l:dep )" />
+ <xsl:param name="type" as="xs:string"
+ select="'input'" />
+ <xsl:param name="from" as="xs:string"
+ select="'destination'" />
+ <xsl:param name="ignore-error" as="xs:boolean"
+ select="false()" />
+
+ <xsl:variable name="name" as="xs:string"
+ select="@name" />
+ <xsl:variable name="src" as="xs:string"
+ select="@src" />
+
+ <!-- map symbols must always be remote -->
+ <xsl:variable name="pkg" as="element( lv:package )"
+ select="document( concat( @src, '.xmlo' ), . )
+ /lv:package" />
+
+ <!-- get map symbol dependencies -->
+ <xsl:variable name="deps" as="element( preproc:sym-dep )*"
+ select="$pkg/preproc:sym-deps/
+ preproc:sym-dep[ @name=$name ]" />
+
+ <xsl:if test="not( $deps )">
+ <xsl:call-template name="log:internal-error">
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:text>could not locate symbol dependencies: </xsl:text>
+ <xsl:value-of select="concat( @src, '/', @name )" />
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <!-- FIXME: we should not have to check for @yields here; we may
+ have to require imports in the map to satisfy normalization
+ before-hand -->
+ <xsl:variable name="unknown" as="element( preproc:sym-ref )*"
+ select="$deps/preproc:sym-ref[
+ not( @name=$symtable/preproc:sym/@name
+ or @name=$symtable/preproc:sym/@yields ) ]" />
+
+ <xsl:choose>
+ <!-- ensure that every dependency is known (we only care that the symbol
+ actually exists and is an input) -->
+ <xsl:when test="$unknown and not( $ignore-error )">
+ <xsl:for-each select="$unknown">
+ <xsl:call-template name="log:error">
+ <xsl:with-param name="terminate" select="'no'" />
+ <xsl:with-param name="name" select="'link'" />
+ <xsl:with-param name="msg">
+ <xsl:value-of select="$type" />
+ <xsl:text> map </xsl:text>
+ <xsl:value-of select="$from" />
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@name" />
+ <xsl:text> is not a known </xsl:text>
+ <xsl:value-of select="$type" />
+ <xsl:text> field for </xsl:text>
+ <xsl:value-of select="concat( $src, '/', $name )" />
+ <xsl:text>; ensure that it exists and is either used or has @keep set</xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:for-each>
+
+ <l:map-error />
+ </xsl:when>
+
+ <!-- good to go; link symbol -->
+ <xsl:otherwise>
+ <xsl:apply-templates select="." mode="l:link-deps" />
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/current/compiler/linker/log.xsl b/src/current/compiler/linker/log.xsl
new file mode 100644
index 0000000..9c8db60
--- /dev/null
+++ b/src/current/compiler/linker/log.xsl
@@ -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>
+
diff --git a/src/current/compiler/map.xsl b/src/current/compiler/map.xsl
new file mode 100644
index 0000000..10d8c42
--- /dev/null
+++ b/src/current/compiler/map.xsl
@@ -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>
diff --git a/src/current/compiler/validate.xsl b/src/current/compiler/validate.xsl
new file mode 100644
index 0000000..91cfd03
--- /dev/null
+++ b/src/current/compiler/validate.xsl
@@ -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>
diff --git a/src/current/compiler/validate/domain.xsl b/src/current/compiler/validate/domain.xsl
new file mode 100644
index 0000000..0d84a8c
--- /dev/null
+++ b/src/current/compiler/validate/domain.xsl
@@ -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>
+
diff --git a/src/current/compiler/validate/param.xsl b/src/current/compiler/validate/param.xsl
new file mode 100644
index 0000000..1d18d7a
--- /dev/null
+++ b/src/current/compiler/validate/param.xsl
@@ -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>
+
diff --git a/src/current/compiler/worksheet.xsl b/src/current/compiler/worksheet.xsl
new file mode 100644
index 0000000..9149ddd
--- /dev/null
+++ b/src/current/compiler/worksheet.xsl
@@ -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>