1908 lines
52 KiB
XML
1908 lines
52 KiB
XML
<?xml version="1.0" encoding="ISO-8859-1"?>
|
|
<!--
|
|
Compiles rater XML into JavaScript
|
|
|
|
Copyright (C) 2016, 2017 R-T Specialty, LLC.
|
|
|
|
This file is part of TAME.
|
|
|
|
TAME is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see
|
|
<http://www.gnu.org/licenses/>.
|
|
|
|
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>
|
|
|
|
<!-- 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>
|
|
|
|
|
|
<!--
|
|
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" />
|
|
|
|
<variable name="sym-on" as="element( preproc:sym )"
|
|
select="root(.)/preproc:symtable/preproc:sym[ @name = $name ]" />
|
|
|
|
<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>
|
|
<choose>
|
|
<when test="$sym-on/@type = 'const'">
|
|
<text>consts</text>
|
|
</when>
|
|
<otherwise>
|
|
<text>args</text>
|
|
</otherwise>
|
|
</choose>
|
|
|
|
<text>['</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>
|
|
<!-- value unavailable (TODO: vector/matrix support) -->
|
|
<when test="$sym and not( $sym/@value )">
|
|
<message>
|
|
<text>[jsc] !!! bad classification match: `</text>
|
|
<value-of select="$value" />
|
|
<text>' is not a scalar constant</text>
|
|
</message>
|
|
</when>
|
|
|
|
<!-- simple constant -->
|
|
<when test="$sym and @value">
|
|
<value-of select="$sym/@value" />
|
|
</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>
|
|
|
|
|
|
|
|
<!--
|
|
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>
|
|
<!-- yield 0 if there are no calculations (rather than a syntax error!) -->
|
|
<if test="empty( c:* )">
|
|
<message>
|
|
<text>[jsc] warning: empty rate block: `</text>
|
|
<value-of select="@yields" />
|
|
<text>'</text>
|
|
</message>
|
|
|
|
<text>0</text>
|
|
</if>
|
|
|
|
<!-- 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>
|