Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
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>