diff options
Diffstat (limited to 'src/current/compiler')
-rw-r--r-- | src/current/compiler/fragments.xsl | 137 | ||||
-rw-r--r-- | src/current/compiler/js-calc.xsl | 1141 | ||||
-rw-r--r-- | src/current/compiler/js.xsl | 1908 | ||||
-rw-r--r-- | src/current/compiler/linker.xsl | 1442 | ||||
-rw-r--r-- | src/current/compiler/linker/log.xsl | 95 | ||||
-rw-r--r-- | src/current/compiler/map.xsl | 997 | ||||
-rw-r--r-- | src/current/compiler/validate.xsl | 771 | ||||
-rw-r--r-- | src/current/compiler/validate/domain.xsl | 193 | ||||
-rw-r--r-- | src/current/compiler/validate/param.xsl | 103 | ||||
-rw-r--r-- | src/current/compiler/worksheet.xsl | 543 |
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$ < _$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">></c> + <c id="lt"><</c> + <c id="gte">>=</c> + <c id="lte"><=</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="' '" /> + + +<!-- + 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, "'", '' )" /> + <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 && </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="'&&'" /> + <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, "'", '' )" /> + <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"> && </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">&& </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">&& </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 + '&&'" /> +</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"> && </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> </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> </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> </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> </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> </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> </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<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&&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<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> |