diff options
72 files changed, 25223 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am index 90090f1..e632d54 100644 --- a/Makefile.am +++ b/Makefile.am @@ -22,7 +22,9 @@ path_src = src path_test = test # all source files will be run through hoxsl; see `applies' target -apply_src := $(shell find "$(path_src)" "$(path_test)" -name '*.xsl') +apply_src := $(shell find "$(path_src)" "$(path_test)" \ + -name '*.xsl' \ + -a \! -path "$(path_src)"/current/\* ) apply_dest := $(apply_src:%.xsl=%.xsl.apply) # needed by test runner @@ -37,6 +37,13 @@ TAME's core library, and [hoxsl](https://github.com/lovullo/hoxsl) was developed as a supporting library. +## "Current" +The current state of the project as used in production is found in +`src/current/`. The environment surrounding the development of this +project resulted in a bit of a mess, which is being refactored into +`src/` as it is touched. Documentation is virtually non-existent. + + ## License This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free diff --git a/doc/Makefile.am b/doc/Makefile.am index 6bd1f0c..889824b 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -21,7 +21,9 @@ path_src := ../src path_tools := ../tools -stylesheets := $(shell find "$(path_src)" -name '*.xsl') +stylesheets := $(shell find "$(path_src)" \ + -name '*.xsl' \ + -a \! -path "$(path_src)"/current/\* ) stexi := $(stylesheets:.xsl=.texi) info_TEXINFOS = tame.texi diff --git a/src/current/.gitignore b/src/current/.gitignore new file mode 100644 index 0000000..2460008 --- /dev/null +++ b/src/current/.gitignore @@ -0,0 +1 @@ +!Makefile diff --git a/src/current/Makefile b/src/current/Makefile new file mode 100644 index 0000000..dc6eb54 --- /dev/null +++ b/src/current/Makefile @@ -0,0 +1,8 @@ + +.PHONY: dslc clean + +dslc: + $(MAKE) -C src/ dslc + +clean: + $(MAKE) -C src/ clean diff --git a/src/current/c1map.xsl b/src/current/c1map.xsl new file mode 100644 index 0000000..660fdf7 --- /dev/null +++ b/src/current/c1map.xsl @@ -0,0 +1,383 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Generates PHP code that works with the LoVullo ConceptOne import system + + This map expects that the data are available in the bucket provided by the + quote and therefore validates against a provided Program UI source file. Data + external to the bucket may be provided if it is indicated as such. + + Each map source file is independent; variables and values do not bleed into + one-another, unless explicitly passed. +--> +<xsl:stylesheet version="2.0" + xmlns:c1="http://www.epic-premier.com/XMLSchema" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:lvm="http://www.lovullo.com/rater/map/c1" + xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp"> + +<xsl:output + indent="yes" + omit-xml-declaration="yes" + /> + +<!-- newline --> +<xsl:variable name="lvmp:nl" select="' '" /> + +<xsl:include href="c1map/c1nodes.xsl" /> +<xsl:include href="c1map/valparse.xsl" /> +<xsl:include href="c1map/render.xsl" /> + +<!-- + Represents the root of the source document that processing was initiated upon +--> +<xsl:variable name="orig-root" select="/" /> + + +<!-- + The root node +--> +<xsl:template match="lvm:c1-map" priority="5"> + <!-- populated by includes, if any --> + <xsl:param name="args" /> + + <xsl:message select="$args" /> + + <!-- get the name from the first C1 node --> + <xsl:variable name="name" select=" + if ( @id ) then + @id + else + concat( translate( c1:*[1]/name(), '_', '' ), 'Composer' ) + " /> + + <!-- preprocessed result --> + <xsl:variable name="pp-result"> + <lvmp:root program="{@program}" name="{$name}"> + <!-- introduce outer scope for variables --> + <lvmp:scope id=""> + <xsl:apply-templates /> + </lvmp:scope> + </lvmp:root> + </xsl:variable> + + <!-- final processing --> + <xsl:variable name="result"> + <xsl:apply-templates select="$pp-result/lvmp:root" mode="lvmp:render" /> + </xsl:variable> + + <!-- remove escapes --> + <xsl:value-of disable-output-escaping="yes" select="$result" /> +</xsl:template> + + +<!-- + Include another source map file relative to the path of the original source + file + + The attributes @name and @for-each are special and cannot be used as + arguments to the template. The special @for-each attribute will be copied + into each of the children of the root node in the template as @lvm:for-each. + For example, if the template consists of + + <c1-map> + <Product> + </Product> + </c1-map> + + then <lvm:include for-each="foo" /> would produce + + <c1-map> + <Product lvm:for-each="foo"> + </Product> + </c1-map> +--> +<xsl:template match="lvm:include" priority="5"> + <xsl:message>[c1map] +<xsl:value-of select="@name" /></xsl:message> + + <xsl:variable name="src" select=" + document( concat( @name, '.xml' ), $orig-root )/lvm:c1-map + " /> + + <xsl:if test="not( $src )"> + <xsl:message terminate="yes">fatal: c1-map node not found</xsl:message> + </xsl:if> + + <!-- process the body of the c1-map; we don't want to process the root node, + as that would start processing from scratch, prematurely rendering the result --> + <lvmp:scope id="/{@name}"> + <!-- arguments are included as attributes --> + <xsl:variable name="args" select=" + @*[ not( local-name()='name' or local-name()='for-each' ) ] + " /> + <xsl:variable name="for-each" select="@for-each" /> + + <!-- augment the XML with our own mappings --> + <xsl:variable name="augmented"> + <lvm:c1-map> + <xsl:copy-of select="$src/@*" /> + + <xsl:for-each select="$src/lvm:param"> + <xsl:call-template name="lvmp:param-to-map"> + <xsl:with-param name="args" select="$args" /> + <xsl:with-param name="param" select="." /> + <xsl:with-param name="context" select="/lvm:c1-map" /> + </xsl:call-template> + </xsl:for-each> + + + <xsl:choose> + <!-- if @for-each was provided, then apply to all first-level + children of the included template --> + <xsl:when test="$for-each"> + <!-- we will need to expose the mapping --> + <!-- TODO: need to validate that it actually exists --> + <lvm:external name="{$for-each}" /> + + <xsl:for-each select="$src/*"> + <xsl:copy> + <xsl:copy-of select="@*" /> + <xsl:attribute name="lvm:for-each" select="$for-each" /> + <xsl:copy-of select="*|text()" /> + </xsl:copy> + </xsl:for-each> + </xsl:when> + + <!-- no @for-each; just do a quick copy of all the nodes --> + <xsl:otherwise> + <xsl:copy-of select="$src/*" /> + </xsl:otherwise> + </xsl:choose> + </lvm:c1-map> + </xsl:variable> + + <xsl:apply-templates select="$augmented/lvm:c1-map/*" /> + </lvmp:scope> + + <xsl:message>[c1map] -<xsl:value-of select="@name" /></xsl:message> +</xsl:template> + + +<!-- + Processes a template param into mappings + + This will generate the mappings necessary to process the template as though + it was hard-coded with the imported mappings. + + The {} brace syntax denotes a variable, but mixing values and inline + variables are not supported. +--> +<xsl:template name="lvmp:param-to-map"> + <xsl:param name="args" /> + <xsl:param name="param" /> + <xsl:param name="context" /> + + <xsl:variable name="name" select="$param/@name" /> + <xsl:variable name="arg" select="$args[ local-name()=$name ]" /> + <xsl:variable name="argvar" select="substring-after( $arg, '{' )" /> + + <xsl:if test="$argvar and not( $argvar='' )"> + <xsl:variable name="varname" select="substring-before( $argvar, '}' )" /> + + <lvmp:translate name="{$name}" to="{$varname}" /> + + <xsl:variable name="predot" select="substring-before( $varname, '.' )" /> + + <xsl:choose> + <!-- no dot; output the entire thing --> + <xsl:when test="$predot = ''"> + <lvm:external name="{$varname}" /> + </xsl:when> + + <!-- multi-level var --> + <xsl:otherwise> + <lvm:external name="{$predot}" dict="true" lvmp:no-validate="true" /> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + + <!-- TODO: no need to do this if the above conditional matches --> + <lvm:map to="{$name}" lvmp:allow-default="true"> + <xsl:copy-of select="$param/@dict" /> + <xsl:copy-of select="$param/@default" /> + + <xsl:choose> + <xsl:when test="$arg"> + <!-- determines if we have a variable --> + + <xsl:choose> + <xsl:when test="$argvar and not( $argvar='' )"> + <xsl:attribute name="from" + select="substring-before( $argvar, '}' )" /> + </xsl:when> + + <!-- static value --> + <xsl:otherwise> + <xsl:attribute name="value" select="$arg" /> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + + <!-- required param --> + <xsl:when test="$param/@required"> + <xsl:message terminate="yes"> + <xsl:text>error: missing required template argument `</xsl:text> + <xsl:value-of select="$name" /> + <xsl:text>'</xsl:text> + </xsl:message> + </xsl:when> + + <!-- otherwise, we have no value --> + <xsl:otherwise> + <xsl:attribute name="value" select="''" /> + </xsl:otherwise> + </xsl:choose> + </lvm:map> +</xsl:template> + + +<!-- + Common actions performed by nearly every mapping +--> +<xsl:template name="lvmp:map-common"> + <!-- may or may not be set --> + <xsl:copy-of select="@dict" /> + <xsl:copy-of select="@link" /> + <xsl:copy-of select="@transform" /> + + <xsl:apply-templates select="@default" /> +</xsl:template> + + +<xsl:template match="lvm:param" priority="4"> + <!-- processed above on import; no longer needed --> +</xsl:template> + +<xsl:template match="lvm:map[@from]" priority="4"> + <lvmp:var name="{@to}" from="{@from}" src="map"> + <xsl:call-template name="lvmp:map-common" /> + </lvmp:var> +</xsl:template> + +<xsl:template match="lvm:map[lvm:from]" priority="4"> + <lvmp:var name="{@to}" from="{lvm:from/@name}"> + <xsl:call-template name="lvmp:map-common" /> + </lvmp:var> +</xsl:template> + +<xsl:template match="lvm:map[@value]" priority="4"> + <!-- it does not make sense to have a string value be a dictionary --> + <xsl:if test="@dict"> + <xsl:message terminate="yes"> + <xsl:text>error: cannot have @dict on static mapping `</xsl:text> + <xsl:value-of select="@to" /> + <xsl:text>'</xsl:text> + </xsl:message> + </xsl:if> + + <!-- nor does a default make sense --> + <xsl:if test="@default and not( @lvmp:allow-default='true' )"> + <xsl:message terminate="yes"> + <xsl:text>error: cannot have @default on static mapping `</xsl:text> + <xsl:value-of select="@to" /> + <xsl:text>'</xsl:text> + </xsl:message> + </xsl:if> + + <lvmp:var name="{@to}" value="{@value}"> + <!-- we may use defaults internally --> + <xsl:call-template name="lvmp:map-common" /> + </lvmp:var> +</xsl:template> + +<xsl:template match="lvm:pass" priority="4"> + <lvmp:var name="{@name}" from="{@name}" src="map"> + <xsl:call-template name="lvmp:map-common" /> + </lvmp:var> +</xsl:template> + +<xsl:template match="lvm:external" priority="4"> + <lvmp:var name="{@name}" from="{@name}" src="external"> + <xsl:call-template name="lvmp:map-common" /> + </lvmp:var> +</xsl:template> + +<xsl:template match="lvm:*/@default"> + <lvmp:default> + <xsl:apply-templates select="." mode="lvm:valparse" /> + </lvmp:default> +</xsl:template> + + +<xsl:template match="lvmp:translate" priority="4"> + <!-- added by pre-processor during include; ignore --> +</xsl:template> + + +<!-- + Override default behavior of c1 nodes when iteration is requested +--> +<xsl:template match="c1:*[ @lvm:for-each ]" + mode="lvmp:c1-node-result" priority="5"> + + <lvmp:for-each name="{@lvm:for-each}"> + <!-- proceed with processing as normal --> + <xsl:apply-templates select="@*|*" /> + </lvmp:for-each> +</xsl:template> + + +<xsl:template match="lvm:if" priority="4"> + <lvmp:condition> + <lvmp:when> + <xsl:call-template name="lvmp:gen-val"> + <xsl:with-param name="name" select="@name" /> + </xsl:call-template> + </lvmp:when> + + <xsl:apply-templates /> + </lvmp:condition> +</xsl:template> + + +<!-- + Unhandled node character data + + Note that, if a node contains newlines, then there will be text preceding and + following its children. For example: + + <foo> + <bar> + </foo> + + In the above, the `foo' node has the text "\n ", followed by the node `bar', + followed by the text "\n" (assuming that `foo' starts in column 1). +--> +<xsl:template match="text()" priority="1"> + <!-- do not output whitespace from source files --> +</xsl:template> + + +<!-- + Bail out on unhandled nodes. +--> +<xsl:template match="*" priority="1"> + <xsl:message> + <xsl:text>[c1map] fatal: unexpected node </xsl:text> + <xsl:apply-templates select="." mode="lvmp:node-out" /> + <xsl:text>:</xsl:text> + </xsl:message> + + <xsl:message terminate="yes" select="." /> +</xsl:template> + +<xsl:template match="*" mode="lvmp:node-out"> + <xsl:variable name="parent" select="parent::*" /> + <xsl:if test="$parent"> + <xsl:apply-templates select="$parent" mode="lvmp:node-out" /> + </xsl:if> + + <xsl:text>/</xsl:text> + <xsl:value-of select="name()" /> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/c1map/c1nodes.xsl b/src/current/c1map/c1nodes.xsl new file mode 100644 index 0000000..da4470f --- /dev/null +++ b/src/current/c1map/c1nodes.xsl @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Describes how ConceptOne nodes are handled in the output. + + Only nodes in the C1 XML namespace will be included in the output; all other + nodes will be in error, except for nodes as part of the c1 map namespace, + which are processed and will not be included in the output. + + The output is an array format used to generate the final XML at runtime; this + format was not developed in conjunction with this project and is separate, so + be sure that this compiler is updated if the format changes. +--> +<xsl:stylesheet version="2.0" + xmlns:c1="http://www.epic-premier.com/XMLSchema" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:lvm="http://www.lovullo.com/rater/map/c1" + xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp"> + +<!-- + Nodes with attributes or children are recursively processed and have the + form: + '>Name' => array( <recurse> ) +--> +<xsl:template match="c1:*[*|@*]" priority="5"> + <!-- make the output a little bit sane --> + <xsl:value-of select="$lvmp:nl" /> + + <!-- defer node rendering; allows us to easily determine if there are + siblings of the same name within a node boundary --> + <lvmp:node name="{name()}" /> + <xsl:text> => </xsl:text> + + <lvmp:node-boundary> + <xsl:apply-templates select="." mode="lvmp:c1-node-result" /> + </lvmp:node-boundary> +</xsl:template> + + +<!-- + The default behavior of c1 nodes is to simply output the nodes as-is, with + variable substitutions. +--> +<xsl:template match="c1:*" mode="lvmp:c1-node-result" priority="1"> + <xsl:text>array( </xsl:text> + <xsl:apply-templates select="@*|*" /> + <xsl:text>) </xsl:text> +</xsl:template> + + +<!-- + Text-only nodes are of the form: + '>Name' => 'value' +--> +<xsl:template match="c1:*[text()]" priority="4"> + <!-- defer node rendering; allows us to easily determine if there are + siblings of the same name within a node boundary --> + <lvmp:node name="{name()}" /> + <xsl:text> => </xsl:text> + + <xsl:text></xsl:text> + <!-- TODO: escape single quotes --> + <xsl:apply-templates select="text()" mode="lvm:valparse" /> + <xsl:text>, </xsl:text> +</xsl:template> + + +<!-- + Attributes are of the format: + '[Name]' => 'value' +--> +<xsl:template match="c1:*/@*" priority="5"> + <xsl:text>'[</xsl:text> + <xsl:value-of select="name()" /> + <xsl:text>]' => </xsl:text> + <xsl:apply-templates select="." mode="lvm:valparse" /> + <xsl:text>, </xsl:text> +</xsl:template> + + +<!-- alternative attribute format for special situations --> +<xsl:template match="lvm:attribute" priority="5"> + <xsl:text>'[</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>]' => </xsl:text> + <xsl:apply-templates select="@value" mode="lvm:valparse" /> + <xsl:text>, </xsl:text> +</xsl:template> + + +<xsl:template match="c1:*/@lvm:*" priority="6"> + <!-- discard all system attributes --> + <!-- TODO: error once everything is properly implemented --> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/c1map/render.xsl b/src/current/c1map/render.xsl new file mode 100644 index 0000000..c782a35 --- /dev/null +++ b/src/current/c1map/render.xsl @@ -0,0 +1,339 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Renders the final PHP code +--> +<xsl:stylesheet version="2.0" + xmlns:c1="http://www.epic-premier.com/XMLSchema" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:lvm="http://www.lovullo.com/rater/map/c1" + xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp"> + + +<xsl:import href="transform.xsl" /> + + +<xsl:template match="lvmp:root" mode="lvmp:render" priority="5"> + <xsl:text><?php </xsl:text> + <xsl:value-of select="$lvmp:nl" /> + + <!-- TODO: add program id to namespace --> + <xsl:text>namespace lovullo\c1\interfaces\c1\contract\</xsl:text> + <xsl:value-of select="@program" /> + <xsl:text>;</xsl:text> + <xsl:value-of select="$lvmp:nl" /> + + <xsl:text>class </xsl:text> + <xsl:value-of select="@name" /> + <xsl:text> {</xsl:text> + <xsl:value-of select="$lvmp:nl" /> + + <xsl:text>public function compose( $contract ) {</xsl:text> + <xsl:value-of select="$lvmp:nl" /> + <xsl:text> return array(</xsl:text> + <xsl:value-of select="$lvmp:nl" /> + <!-- render the preprocessed content --> + <xsl:apply-templates mode="lvmp:render" /> + <xsl:value-of select="$lvmp:nl" /> + <xsl:text> );</xsl:text> + <xsl:value-of select="$lvmp:nl" /> + <xsl:text> }</xsl:text> + <xsl:value-of select="$lvmp:nl" /> + + <xsl:text>}</xsl:text> + <xsl:value-of select="$lvmp:nl" /> + <xsl:text>?></xsl:text> +</xsl:template> + + +<xsl:template match="text()" mode="lvmp:render" priority="5"> + <xsl:value-of select="." /> +</xsl:template> + + +<xsl:template name="lvmp:value" match="lvmp:value" mode="lvmp:render" priority="5"> + <xsl:param name="name" select="@ref" /> + <xsl:param name="scope" select="ancestor::lvmp:scope[1]" /> + <xsl:param name="var" select="$scope/lvmp:var[ @name=$name ][1]" /> + <xsl:param name="from" select="$var/@from" /> + <xsl:param name="value" select="$var/@value" /> + <xsl:param name="default" select="$var/lvmp:default" /> + + <!-- provide error if the variable could not be found in the current scope --> + <xsl:call-template name="lvmp:check-var"> + <xsl:with-param name="name" select="$name" /> + <xsl:with-param name="scope" select="$scope" /> + </xsl:call-template> + + <xsl:choose> + <!-- mapping was provided --> + <xsl:when test="$from and not( $from='' )"> + <xsl:apply-templates select="$var" mode="lvmp:transform"> + <xsl:with-param name="value"> + <xsl:apply-templates select="." mode="lvmp:render-value"> + <xsl:with-param name="var" select="$var" /> + <xsl:with-param name="from" select="$from" /> + <xsl:with-param name="scope" select="$scope" /> + <xsl:with-param name="default" select="$default" /> + </xsl:apply-templates> + </xsl:with-param> + </xsl:apply-templates> + </xsl:when> + + <!-- static string mapping --> + <xsl:when test="$value and not( $value='' )"> + <!-- TODO: escape single quote --> + <xsl:text>'</xsl:text> + <xsl:value-of select="$value" /> + <xsl:text>'</xsl:text> + </xsl:when> + + <!-- if no @from was provided, then it forces the use of the default + value --> + <xsl:when test="$default"> + <xsl:text>$contract->checkDefaultValue( '</xsl:text> + <xsl:value-of select="$name" /> + <xsl:text>', </xsl:text> + <xsl:apply-templates select="$default" mode="lvmp:render" /> + <xsl:text> )</xsl:text> + </xsl:when> + + <!-- if no default is available, then this node shall never be set --> + <xsl:otherwise> + <xsl:text>null</xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:template mode="lvmp:render" priority="7" + match="lvmp:value[ lvmp:value ]"> + + <xsl:param name="name" select="@ref" /> + <xsl:param name="scope" select="ancestor::lvmp:scope[1]" /> + <xsl:param name="var" select="$scope/lvmp:var[ @name=$name ][1]" /> + <xsl:param name="from" select="$var/@from" /> + <xsl:param name="value" select="$var/@value" /> + <xsl:param name="default" select="$var/lvmp:default" /> + + <xsl:text>$contract->getValueByContext( </xsl:text> + <!-- recursive process contexts --> + <xsl:apply-templates select="lvmp:value" mode="lvmp:render"> + <xsl:with-param name="scope" select="$scope" /> + <xsl:with-param name="default" select="$default" /> + </xsl:apply-templates> + <xsl:text>, '</xsl:text> + <!-- TODO: validate existence of key in dict --> + <xsl:value-of select="$name" /> + <xsl:text>' </xsl:text> + + <xsl:text> )</xsl:text> +</xsl:template> + + +<xsl:template match="lvmp:value" mode="lvmp:render-value" priority="1"> + <xsl:param name="var" /> + <xsl:param name="scope" /> + <xsl:param name="default" /> + <xsl:param name="from" /> + <!-- optional --> + <xsl:param name="value-node" select="." /> + <xsl:param name="context" /> + + <!-- the name of the index we reference for our value defaults to our own + name, but can be overridden in the mapping --> + <xsl:param name="index-name"> + <xsl:choose> + <!-- link provided; override --> + <xsl:when test="$var/@link"> + <!-- parse the link and replace it with the var it maps to --> + <xsl:call-template name="lvmp:var-from"> + <xsl:with-param name="name" select="$var/@link" /> + <xsl:with-param name="scope" select="$scope" /> + </xsl:call-template> + </xsl:when> + + <!-- default to our own index --> + <xsl:otherwise> + <xsl:value-of select="$from" /> + </xsl:otherwise> + </xsl:choose> + </xsl:param> + + <xsl:text>$contract->getValue( '</xsl:text> + <xsl:value-of select="$from" /> + <xsl:text>', $contract->getValueIndex( '</xsl:text> + <xsl:value-of select="$index-name" /> + <xsl:text>' )</xsl:text> + + <xsl:if test="$default"> + <xsl:text>, </xsl:text> + <xsl:apply-templates select="$default" mode="lvmp:render" /> + </xsl:if> + + <xsl:text> )</xsl:text> +</xsl:template> + + +<!-- + Generates an error in the event that the given var name cannot be found in + the given scope. +--> +<xsl:template name="lvmp:check-var"> + <xsl:param name="name" /> + <xsl:param name="scope" select="ancestor::lvmp:scope[1]" /> + + <!-- look up variable in parent scope --> + <xsl:variable name="var" select="$scope/lvmp:var[ @name=$name ][1]" /> + + <!-- provide error if the variable could not be found in the current scope --> + <xsl:if test="not( $var )"> + <xsl:message terminate="yes"> + <xsl:text>error: variable not within scope: </xsl:text> + <xsl:value-of select="$scope/@id" /> + <xsl:text>/</xsl:text> + <xsl:value-of select="$name" /> + </xsl:message> + </xsl:if> +</xsl:template> + + +<!-- + Validates and then returns the source mapping of the given variable within + the given (or current) scope +--> +<xsl:template name="lvmp:var-from"> + <xsl:param name="name" /> + <xsl:param name="scope" select="ancestor::lvmp:scope[1]" /> + + <!-- undefined/scoping check --> + <xsl:call-template name="lvmp:check-var"> + <xsl:with-param name="name" select="$name" /> + <xsl:with-param name="scope" select="$scope" /> + </xsl:call-template> + + <xsl:value-of select="$scope/lvmp:var[ @name=$name ][1]/@from" /> +</xsl:template> + + +<xsl:template match="lvmp:translate" mode="lvmp:render" priority="5"> + <!-- no longer needed --> +</xsl:template> + +<xsl:template match="lvmp:var" mode="lvmp:render" priority="5"> + <!-- no longer needed --> +</xsl:template> + +<xsl:template match="lvmp:var/lvmp:default" mode="lvmp:render" priority="5"> + <!-- render contents --> + <xsl:apply-templates mode="lvmp:render" /> +</xsl:template> + + +<!-- + Scope boundary; no longer needed in output +--> +<xsl:template match="lvmp:scope" mode="lvmp:render" priority="2"> + <xsl:apply-templates mode="lvmp:render" /> +</xsl:template> + + +<!-- + Conditional boundary; not needed in output +--> +<xsl:template match="lvmp:condition" mode="lvmp:render" priority="2"> + <xsl:apply-templates mode="lvmp:render" /> +</xsl:template> + +<xsl:template match="lvmp:condition/lvmp:when" mode="lvmp:render" priority="2"> + <!-- will be processed as part of sibling output --> +</xsl:template> + + +<!-- + Iteration boundary +--> +<xsl:template match="lvmp:for-each" mode="lvmp:render" priority="5"> + <xsl:variable name="from"> + <xsl:call-template name="lvmp:var-from"> + <xsl:with-param name="name" select="@name" /> + </xsl:call-template> + </xsl:variable> + + <xsl:text>$contract->iterateValues( '</xsl:text> + <xsl:value-of select="$from" /> + <xsl:text>', </xsl:text> + <xsl:text>function( $contract ) {</xsl:text> + <xsl:text> return array(</xsl:text> + <xsl:apply-templates mode="lvmp:render" /> + <xsl:text>);</xsl:text> + <xsl:text>}</xsl:text> + <xsl:text>)</xsl:text> +</xsl:template> + + +<!-- + The C1 import system recognizes the following formats: + + '>Node' + 'N>Node' + + where N is any number; the latter is used for creating unique indexes for + multiple siblings of the same name. +--> +<xsl:template match="lvmp:node" mode="lvmp:render" priority="5"> + <xsl:variable name="name" select="@name" /> + + <xsl:text>'</xsl:text> + + <!-- generate a unique key to avoid conflicts --> + <xsl:value-of select="generate-id(.)" /> + + <xsl:text>></xsl:text> + <xsl:value-of select="@name" /> + + <xsl:text>'</xsl:text> +</xsl:template> + + +<!-- + Node boundary; not needed in output + + The boundary dictates the context of a conditional. This template has a lower + priority and will be the default if there is no parent conditional. +--> +<xsl:template match="lvmp:node-boundary" mode="lvmp:render" priority="2"> + <xsl:apply-templates mode="lvmp:render" /> + + <!-- unconditional delimiter; nothing funky going on --> + <xsl:text>, </xsl:text> +</xsl:template> + +<xsl:template mode="lvmp:render" priority="5" match=" + lvmp:node-boundary[ + parent::lvmp:condition + or parent::lvmp:scope/parent::lvmp:condition + ] +"> + <!-- select the nearest condition --> + <xsl:variable name="cond" select="ancestor::lvmp:condition[1]" /> + + <xsl:text>( ( </xsl:text> + <xsl:text>$contract->isTruthy( </xsl:text> + <xsl:apply-templates select="$cond/lvmp:when/lvmp:*" mode="lvmp:render" /> + <xsl:text>)</xsl:text> + <xsl:text> ) ? </xsl:text> + <xsl:apply-templates mode="lvmp:render"> + <xsl:with-param name="no-trailing-sep" select="true()" /> + </xsl:apply-templates> + <xsl:text> : null ), </xsl:text> +</xsl:template> + + +<xsl:template match="*" mode="lvmp:render" priority="1"> + <xsl:message terminate="yes"> + <xsl:text>[c1map] fatal: unexpected node during render: </xsl:text> + <xsl:apply-templates select="." mode="lvmp:node-out" /> + </xsl:message> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/c1map/transform.xsl b/src/current/c1map/transform.xsl new file mode 100644 index 0000000..8793648 --- /dev/null +++ b/src/current/c1map/transform.xsl @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Transforms values +--> +<xsl:stylesheet version="2.0" + xmlns:c1="http://www.epic-premier.com/XMLSchema" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:lvm="http://www.lovullo.com/rater/map/c1" + xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp"> + + +<xsl:template match="lvmp:var[ @transform='proper-case' ]" priority="5" + mode="lvmp:transform"> + + <xsl:param name="value" /> + + <xsl:text>ucwords(strtolower(</xsl:text> + <xsl:copy-of select="$value" /> + <xsl:text>))</xsl:text> +</xsl:template> + + + +<xsl:template match="lvmp:var[ not( @transform ) or @transform='' ]" + mode="lvmp:transform" priority="3"> + + <xsl:param name="value" /> + + <!-- no transformation; do nothing --> + <xsl:copy-of select="$value" /> +</xsl:template> + + +<xsl:template match="lvmp:var" mode="lvmp:transform" priority="2"> + <xsl:message terminate="yes"> + <xsl:text>error: unknown transformation `</xsl:text> + <xsl:value-of select="@transform" /> + <xsl:text>' for `</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>'</xsl:text> + </xsl:message> +</xsl:template> + + +<xsl:template match="lvmp:*" mode="lvmp:transform" priority="1"> + <xsl:message terminate="yes"> + <xsl:text>internal error: unexpected node for transformation: </xsl:text> + <xsl:copy-of select="." /> + </xsl:message> +</xsl:template> + +</xsl:stylesheet> + diff --git a/src/current/c1map/valparse.xsl b/src/current/c1map/valparse.xsl new file mode 100644 index 0000000..8c4ab1f --- /dev/null +++ b/src/current/c1map/valparse.xsl @@ -0,0 +1,130 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Inline value parser + + Will parse all attributes and text of the form "a{b}c", where `b' is some + variable. +--> +<xsl:stylesheet version="2.0" + xmlns:c1="http://www.epic-premier.com/XMLSchema" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:lvm="http://www.lovullo.com/rater/map/c1" + xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp"> + + +<xsl:template match="@*|text()" mode="lvm:valparse"> + <xsl:call-template name="lvm:valparse"> + <xsl:with-param name="str" select="." /> + </xsl:call-template> +</xsl:template> + + +<xsl:template name="lvm:valparse"> + <xsl:param name="str" /> + + <xsl:variable name="pre" select="substring-before( $str, '{' )" /> + <xsl:variable name="post" select="substring-after( $str, '}' )" /> + + <!-- get stuff between the two --> + <xsl:variable name="postpre" select="substring-after( $str, '{' )" /> + <xsl:variable name="val" select="substring-before( $postpre, '}' )" /> + + + <xsl:if test="$pre"> + <xsl:text>'</xsl:text> + <!-- TODO: escape --> + <xsl:value-of select="$pre" /> + <xsl:text>' . </xsl:text> + </xsl:if> + + <xsl:choose> + <!-- variable reference --> + <xsl:when test="$val"> + <xsl:call-template name="lvmp:gen-val"> + <xsl:with-param name="name" select="$val" /> + </xsl:call-template> + </xsl:when> + + <!-- static value; no variable --> + <xsl:otherwise> + <xsl:text>'</xsl:text> + <xsl:value-of select="$str" /> + <xsl:text>'</xsl:text> + </xsl:otherwise> + </xsl:choose> + + <xsl:if test="$post"> + <xsl:text> . '</xsl:text> + <!-- TODO: escape --> + <xsl:value-of select="$post" /> + <xsl:text>'</xsl:text> + </xsl:if> +</xsl:template> + + +<!-- + Generate a variable reference for later rendering + + If a variable of the form x.y is found, it is recursively processed with `y' + as the variable and `x' as the context: a dictionary lookup. +--> +<xsl:template name="lvmp:gen-val"> + <xsl:param name="name" /> + + <xsl:variable name="trans" select=" + ancestor::lvm:c1-map/lvmp:translate[ @name=$name ] + " /> + + <xsl:choose> + <!-- name translation requested --> + <xsl:when test="$trans"> + <xsl:call-template name="lvmp:do-gen-val"> + <xsl:with-param name="name" select="$trans/@to" /> + </xsl:call-template> + </xsl:when> + + <!-- no translation; use name as-is --> + <xsl:otherwise> + <xsl:call-template name="lvmp:do-gen-val"> + <xsl:with-param name="name" select="$name" /> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:template name="lvmp:do-gen-val"> + <xsl:param name="name" /> + + <!-- we may have a variable of the form `x.y', in which case we should + process `y' withint he context of `x' --> + <xsl:variable name="rightmost" select=" + substring-after( $name, '.' ) + " /> + + <xsl:choose> + <!-- no more key references --> + <xsl:when test="$rightmost = ''"> + <lvmp:value ref="{$name}" /> + </xsl:when> + + <!-- recursively process key reference --> + <xsl:otherwise> + <!-- determine the key context, which is the entire base sans the + rightmost variable name --> + <xsl:variable name="context" select=" + substring( $name, 1, ( + string-length( $name ) - string-length( $rightmost ) - 1 + ) ) + " /> + + <lvmp:value ref="{$rightmost}" index-key="{$context}"> + <xsl:call-template name="lvmp:gen-val"> + <xsl:with-param name="name" select="$context" /> + </xsl:call-template> + </lvmp:value> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/calc.xsd b/src/current/calc.xsd new file mode 100644 index 0000000..0030aa7 --- /dev/null +++ b/src/current/calc.xsd @@ -0,0 +1,883 @@ +<?xml version="1.0"?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" + targetNamespace="http://www.lovullo.com/calc" + xmlns="http://www.lovullo.com/calc" + elementFormDefault="qualified"> + + +<!--basicTypes--> + <xs:simpleType name="nameType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Generic name reference restriction, intended to be generic enough to + work with most systems (supporting both C-style and Lisp-style + identifiers). + + The systems that implement this schema should perform their own, more + strict, type checks. + </xs:documentation> + </xs:annotation> + + <!-- we need to allow '@' since that's used in macros --> + <xs:restriction base="xs:string"> + <xs:pattern value="[a-zA-Z_@{-][a-zA-Z0-9_@{}-]*" /> + <xs:maxLength value="50" /> + </xs:restriction> + </xs:simpleType> + + + <xs:simpleType name="constValueType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Valid value for constants. + </xs:documentation> + </xs:annotation> + + <!-- we need to allow '@' since that's used in macros --> + <xs:restriction base="xs:string"> + <xs:pattern value="-?[0-9]+(.[0-9]+)?[mek]?|\{?@[a-z][a-zA-Z0-9_]*@\}?" /> + <xs:maxLength value="50" /> + </xs:restriction> + </xs:simpleType> + + + <xs:simpleType name="descType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Documentation for a specific element. + + The documentation must not be sparse; please provide something + descriptive that will be useful to someone completely new to the code. + </xs:documentation> + </xs:annotation> + + <xs:restriction base="xs:string"> + <xs:minLength value="2" /> + </xs:restriction> + </xs:simpleType> + + + <xs:simpleType name="indexNameType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Single-character index variable + </xs:documentation> + </xs:annotation> + + <xs:restriction base="xs:NCName"> + <xs:pattern value="[a-z]" /> + </xs:restriction> + </xs:simpleType> + + + <xs:simpleType name="symbolType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Symbol used to represent an entity when rendered. + + The string should consist of TeX/LaTeX commands and should produce a + single symbol. + </xs:documentation> + </xs:annotation> + + <xs:restriction base="xs:string" /> + </xs:simpleType> +<!--/basicTypes--> + + +<!--calculations--> + <!--operators--> + <xs:element name="operator" type="operatorBaseType" abstract="true"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Abstract operator type used to classify elements as operators. + + Operators are portions of the calculation that perform a portion of + the calculation. For example, a summation would classify as an + operation (operator), but value-of would not (as it is only + representing a value, but not performing a calculation). + </xs:documentation> + </xs:annotation> + </xs:element> + + + <xs:complexType name="operatorBaseType" abstract="true"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Elements/attributes common to all operator types (see the operator + element for a description on what qualifies as an operator). + + All operators may include child calculations. Furthermore, they may + be labeled and may have an identifier associated with their result. + </xs:documentation> + </xs:annotation> + + <xs:sequence> + <xs:element name="when" type="whenType" minOccurs="0" maxOccurs="unbounded" /> + <xs:group ref="calculation" minOccurs="0" maxOccurs="unbounded" /> + <xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded" processContents="lax" /> + </xs:sequence> + + <xs:attribute name="yields" type="nameType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Optional identifier with which the result of the calculation may be + referenced. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="label" type="xs:string"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Optional description of a calculation. @label is used in place of + @desc to ensure that it is not confused with the otherwise required + @desc attribute. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="desc" type="xs:string"> + <xs:annotation> + <xs:documentation xml:lang="en"> + @desc should be used to describe a value that is generated by a + calculation and should not otherwise be used (e.g. with + lv:sum/@generate) + + This is different from @label, which provides a high-level + description of what the equation is doing. @desc describes what the + generated value is. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="index" type="indexNameType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Allows for the definition of an index variable which will be + defined for all children of the operation. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + + + <xs:element name="sum" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Represents a summation (addition) of a set of values. + + If @of is used, the equation defined by child elements will be + iterated using an index on the set provided by @of. If no equation is + given, all elements in the set identified by @of will be summed. + + If @of is omitted, the result of each child element will be summed. + + This operator should also be used for subtraction by summing negative + values. + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType"> + <xs:attribute name="of" type="nameType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Iterate over all values of this set. Must be a set. + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="generates" type="nameType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Optional name of a set to generate from this expressions. + + Generators are compatible only with @of and a sub-expression. + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="sym" type="symbolType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Symbol to use when typesetting the generator + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="precision" type="constValueType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Yield precision + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:complexContent> + </xs:complexType> + </xs:element> + + + <xs:element name="product" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Represents the product (multiplication) of a set of values. + + If @of is used, the equation defined by child elements will be + iterated using an index on the set provided by @of. If no equation is + given, all elements in the set identified by @of will be multiplied. + + If @of is omitted, the result of each child element will be + multiplied. + + While this operator may also be used for division by multiplying by + the inverse of a number (n^-1), a limited quotient operator is + provided for clarity and convenience. + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType"> + <xs:attribute name="of" type="nameType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Iterate over all values of this set. Must be a set. + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="generates" type="nameType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Optional name of a set to generate from this expressions. + + Generators are compatible only with @of and a sub-expression. + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="sym" type="symbolType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Symbol to use when typesetting the generator + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="dot" type="xs:boolean"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Dot product of any number of vectors + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="precision" type="constValueType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Yield precision + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:complexContent> + </xs:complexType> + </xs:element> + + + <xs:element name="quotient" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Represents the quotient (division) of two values. + + This operator is to be thought of as a fraction: the numerator is + identified by the first child element and the denominator the second. + + While the summation operator may also be used for division by + multiplying by the inverse of a number (n^-1), this limited operator + is provided for clarity and convenience. + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType" /> + </xs:complexContent> + </xs:complexType> + </xs:element> + + + <!-- named after Scheme's expt; same arg order --> + <xs:element name="expt" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Exponent; the first child node is the base, the second is the order. + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType" /> + </xs:complexContent> + </xs:complexType> + </xs:element> + + + <xs:element name="cons" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Construct a list using the given element and an existing list + + This is analogous to lisp's cons; the first argument is the car and + the second is the cdr. + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType"> + <xs:sequence> + <!-- both car and cdr required --> + <xs:group ref="calculation" minOccurs="2" maxOccurs="2" /> + </xs:sequence> + </xs:extension> + </xs:complexContent> + </xs:complexType> + </xs:element> + + + <xs:element name="car" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Retrieve the first element of a list + + Analogous to lisp's car. + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType"> + <xs:sequence> + <!-- we accept only a set --> + <xs:group ref="calculation" minOccurs="1" maxOccurs="1" /> + </xs:sequence> + </xs:extension> + </xs:complexContent> + </xs:complexType> + </xs:element> + + + <xs:element name="cdr" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Return the list without its first element + + If the list contains only one element, then an empty list will be + returned. Analogous to lisp's cdr. + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType"> + <xs:sequence> + <!-- we accept only a set --> + <xs:group ref="calculation" minOccurs="1" maxOccurs="1" /> + </xs:sequence> + </xs:extension> + </xs:complexContent> + </xs:complexType> + </xs:element> + + + <xs:element name="length-of" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Retrieves the length of a vector + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType"> + <xs:sequence> + <!-- we accept only a set --> + <xs:group ref="calculation" minOccurs="1" maxOccurs="1" /> + </xs:sequence> + </xs:extension> + </xs:complexContent> + </xs:complexType> + </xs:element> + + + <!--apply--> + <xs:element name="apply" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Denotes a function application. + + The result of the function identified by @name will be used in + place of the call. The application may optionally accept an + argument list (if the function accepts arguments). + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType"> + <xs:sequence> + <xs:element name="arg" type="applyArgumentType" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + + <xs:attribute name="name" type="nameType" use="required"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Name of the function to apply + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <!-- argument shorthand --> + <xs:anyAttribute namespace="##any" processContents="lax" /> + </xs:extension> + </xs:complexContent> + </xs:complexType> + </xs:element> + + + <xs:complexType name="applyArgumentType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Represents an argument to be applied to a function. + </xs:documentation> + </xs:annotation> + + <xs:sequence> + <xs:group ref="calculation" minOccurs="1" maxOccurs="1" /> + </xs:sequence> + + <xs:attribute name="name" type="nameType" use="required"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Name of the parameter to apply to the function + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + + + <xs:element name="recurse" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Recursively applies the current function. + + All arguments are copied to the argument list of the function application unless overridden. + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType"> + <xs:sequence> + <xs:element name="arg" type="applyArgumentType" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + + <!-- argument shorthand --> + <xs:anyAttribute namespace="##any" processContents="lax" /> + </xs:extension> + </xs:complexContent> + </xs:complexType> + </xs:element> + <!--/apply--> + + + <xs:element name="ceil" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Converts the given value to an integer, rounding up if necessary (e.g. 0.1 => 1) + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType"> + <xs:attribute name="precision" type="constValueType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Value precision to consider for rounding. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:complexContent> + </xs:complexType> + </xs:element> + + + <xs:element name="floor" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Converts the given value to an integer, rounding down if necessary (e.g. 0.9 => 0) + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType"> + <xs:attribute name="precision" type="constValueType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Value precision to consider for rounding. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:complexContent> + </xs:complexType> + </xs:element> + + <xs:element name="set" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Creates a set out of its siblings + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType" /> + </xs:complexContent> + </xs:complexType> + </xs:element> + + + <!-- + Debug + --> + <xs:element name="debug-to-console" substitutionGroup="operator"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Outputs the result of the contained expression to the console and + returns its value. + </xs:documentation> + </xs:annotation> + + <xs:complexType> + <xs:complexContent> + <xs:extension base="operatorBaseType" /> + </xs:complexContent> + </xs:complexType> + </xs:element> + <!--/operators--> + + + <!--when--> + <xs:complexType name="whenType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Defines a condition under which the expression will yield one if + true, otherwise will be <em>strongly</em> zero (zero even if + undefined) + </xs:documentation> + </xs:annotation> + + <xs:sequence> + <xs:group ref="conditions" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + + <xs:attribute name="name" type="nameType" use="required" /> + <xs:attribute name="index" type="nameType" /> + <xs:attribute name="label" type="xs:string" /> + </xs:complexType> + + + <xs:group name="conditions"> + <xs:choice> + <xs:element name="eq" type="conditionType" /> + <xs:element name="ne" type="conditionType" /> + <xs:element name="lt" type="conditionType" /> + <xs:element name="gt" type="conditionType" /> + <xs:element name="lte" type="conditionType" /> + <xs:element name="gte" type="conditionType" /> + + <xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" /> + </xs:choice> + </xs:group> + + + <xs:complexType name="conditionType"> + <xs:sequence> + <xs:group ref="calculation" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + <!--/when--> + + + <xs:complexType name="indexType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + More flexible alternative to the @index attribute on elements; supports + calculations for determining the index + </xs:documentation> + </xs:annotation> + + <xs:sequence> + <xs:group ref="calculation" minOccurs="1" maxOccurs="1" /> + </xs:sequence> + </xs:complexType> + + + <xs:complexType name="valueType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Returns the value associated with the given identifier. + </xs:documentation> + </xs:annotation> + + <xs:sequence> + <xs:element name="when" type="whenType" minOccurs="0" maxOccurs="unbounded" /> + <xs:element name="index" type="indexType" minOccurs="0" maxOccurs="2" /> + </xs:sequence> + + <xs:attribute name="name" type="nameType" use="required"> + <xs:annotation> + <xs:documentation xml:lang="en"> + An identifier for which the value should be retrieved + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="index" type="nameType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Allows for the definition of an index variable which will be + defined for all children of the operation. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="label" type="xs:string"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Optional description of a calculation. @label is used in place of + @desc to ensure that it is not confused with the otherwise required + @desc attribute. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + + + <xs:complexType name="constType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Returns @value. This element simply enforces documentation and prevents + the use of magic values. + </xs:documentation> + </xs:annotation> + + <xs:sequence> + <xs:element name="when" type="whenType" minOccurs="0" maxOccurs="unbounded" /> + <xs:element name="index" type="indexType" minOccurs="0" maxOccurs="2" /> + </xs:sequence> + + <xs:attribute name="value" type="constValueType" use="required"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Value of constant (must be within the domain of its @type) + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="type" type="nameType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Type (domain) of the constant (inferred when not provided) + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="desc" type="descType" use="required"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Description of the value explaining its intent + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="label" type="xs:string"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Optional description of a calculation. @label is used in place of + @desc to ensure that it is not confused with the otherwise required + @desc attribute. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + + + <xs:complexType name="casesType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Represents a list of cases for a calculation. + </xs:documentation> + </xs:annotation> + + <xs:sequence> + <xs:element name="case" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:sequence> + <xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" /> + + <xs:element name="when" type="whenType" minOccurs="0" maxOccurs="unbounded" /> + <xs:group ref="calculation" minOccurs="1" maxOccurs="1" /> + </xs:sequence> + + <xs:attribute name="label" type="xs:string"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Optional description of the case + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + + <xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" /> + + <xs:element name="otherwise" minOccurs="0" maxOccurs="1"> + <xs:complexType> + <xs:sequence> + <xs:group ref="calculation" /> + </xs:sequence> + + <xs:attribute name="label" type="xs:string"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Optional description of the case + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + </xs:sequence> + + <xs:attribute name="label" type="xs:string"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Optional description of the case set + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + + + <xs:complexType name="letType"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Declares variables visible within the let block. + + This exists as a means of assignment for re-using values. + </xs:documentation> + </xs:annotation> + + <xs:sequence> + <xs:element name="values" minOccurs="1" maxOccurs="1"> + <xs:complexType> + <xs:annotation> + <xs:documentation xml:lang="en"> + Declares a list of values to define for this block + </xs:documentation> + </xs:annotation> + + <xs:sequence> + <xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded" processContents="lax" /> + <xs:element name="value" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:annotation> + <xs:documentation xml:lang="en"> + Declares a value that will remain in scope for the let block + </xs:documentation> + </xs:annotation> + + <xs:sequence> + <xs:group ref="calculation" minOccurs="1" maxOccurs="1" /> + </xs:sequence> + + <xs:attribute name="name" type="nameType" use="required"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Variable name + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="type" type="nameType" use="required"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Variable datatype + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <!-- TODO: enum --> + <xs:attribute name="set" type="xs:string"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Vector/matrix; if omitted, implicitly scalar. + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute name="desc" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Describe the purpose of the value + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + + <xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded" processContents="lax" /> + </xs:sequence> + </xs:complexType> + </xs:element> + + <xs:group ref="calculation" minOccurs="1" maxOccurs="1" /> + </xs:sequence> + </xs:complexType> + + + <xs:group name="calculation"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Represents every element that can be used to perform a calculation. + Implementors may use this group to accept any type of calculation. + </xs:documentation> + </xs:annotation> + + <xs:choice> + <xs:element ref="operator" /> + <xs:element name="value-of" type="valueType" /> + <xs:element name="const" type="constType" /> + <xs:element name="cases" type="casesType" /> + <xs:element name="let" type="letType" /> + <xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded" processContents="lax" /> + </xs:choice> + </xs:group> + + + <xs:group name="calculationRoot"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Root calculation node; this should be the entry point for any type of + calculation. This helps to ensure that certain nodes are only used at + the beginning of a calculation (such as cases). + </xs:documentation> + </xs:annotation> + + <xs:choice> + <xs:group ref="calculation" /> + </xs:choice> + </xs:group> +<!--/calculations--> + +</xs:schema> diff --git a/src/current/compile.xsl b/src/current/compile.xsl new file mode 100644 index 0000000..2e8d561 --- /dev/null +++ b/src/current/compile.xsl @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Writes preprocessor output to disk to eliminate the overhead of reprocessing + for each task that requires such output. + + Also performs validation. + + N.B.: Requires XSLT >=2.0 +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:lvm="http://www.lovullo.com/rater/map" + xmlns:lvmc="http://www.lovullo.com/rater/map/compiler" + xmlns:c="http://www.lovullo.com/calc" + xmlns:w="http://www.lovullo.com/rater/worksheet" + xmlns:util="http://www.lovullo.com/util" + xmlns:preproc="http://www.lovullo.com/rater/preproc" + xmlns:compiler="http://www.lovullo.com/rater/compiler" + xmlns:calc-compiler="http://www.lovullo.com/calc/compiler" + xmlns:ext="http://www.lovullo.com/ext"> + +<xsl:output + indent="yes" + omit-xml-declaration="yes" + /> + +<!-- processing utilities --> +<xsl:include href="include/util.xsl" /> + +<!-- compiler -> JS --> +<xsl:include href="include/preprocess.xsl" /> +<xsl:include href="compiler/validate.xsl" /> +<xsl:include href="compiler/js.xsl" /> +<xsl:include href="compiler/map.xsl" /> + +<!-- contains get-symbol-map --> +<xsl:include href="include/display.xsl" /> + +<!-- allows disabling of time-consuming validation --> +<xsl:param name="preproc-cache-validate" select="'true'" /> + + +<!-- + Simply copy the preprocessor output; the caller is responsible for outputting + this to a document. +--> +<xsl:template match="/lv:rater|/lv:package" priority="5"> + <!-- XXX: Duplicate code; see summary --> + <xsl:variable name="processed"> + <xsl:apply-templates select="." mode="preproc:compile" /> + </xsl:variable> + + <!-- fail on preprocessor errors --> + <xsl:call-template name="preproc:err-chk"> + <xsl:with-param name="processed" select="$processed" /> + </xsl:call-template> + + <!-- validation must have passed; output the nodes --> + <xsl:copy-of select="$processed" /> +</xsl:template> + + +<xsl:template name="preproc:err-chk"> + <xsl:param name="processed" /> + + <xsl:for-each select="$processed//preproc:error"> + <xsl:message terminate="yes"> + <xsl:text>!!! preprocessor error: </xsl:text> + <xsl:value-of select="." /> + </xsl:message> + </xsl:for-each> +</xsl:template> + + +<xsl:template match="*" mode="preproc:handle-errors" priority="1"> + <!-- do nothing --> +</xsl:template> + + +<xsl:template match="lvm:program-map|lvm:return-map" priority="5"> + <xsl:apply-templates select="." mode="lvmc:compile" /> +</xsl:template> + + +<xsl:template match="w:worksheet" priority="5"> + <xsl:apply-templates select="." mode="w:compile" /> +</xsl:template> + + +<!-- any unhandled nodes should be an error --> +<xsl:template match="*" priority="1"> + <xsl:message terminate="yes"> + <xsl:text>fatal: source file is invalid: unexpected node </xsl:text> + <xsl:value-of select="name()" /> + </xsl:message> +</xsl:template> + +</xsl:stylesheet> 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> diff --git a/src/current/doc/.gitignore b/src/current/doc/.gitignore new file mode 100644 index 0000000..2fa45d0 --- /dev/null +++ b/src/current/doc/.gitignore @@ -0,0 +1,4 @@ +*.aux +*.pdf +*.log +*.toc diff --git a/src/current/doc/chapters/class.tex b/src/current/doc/chapters/class.tex new file mode 100644 index 0000000..82a2f62 --- /dev/null +++ b/src/current/doc/chapters/class.tex @@ -0,0 +1,372 @@ +\chapter{Classification System} +The classification system is one of the most powerful features of \lang, +allowing precise control over the classification and conditional processing of +large sets of data, whether it be external input or values generated from within +\lang\ itself. Virtually every conditional calculation is best represented +through use of the classification system. + + +\section{Classification Matcher} +Data classification is performed by the classification matcher (sometimes +referred to simply as the ``matcher''). Put simply, it is a function (defined by +\aref{cmatch}) that, given a vector of inputs, produces a boolean vector (which +may itself contain boolean vectors) determining if the given input conforms to a +set of stated rules. A set of rules operating on a set input vectors is +collectively known as a \term{classification}. The system that performs matching +based on classifications is referred to as a \term{classifier}. + +A single classification can be separated into a set of rules, often referred to +as \term{matches} within the context of \lang. A single rule attempts to match +on a vector of inputs.\footnote{Scalar inputs are a special condition defined in +\sref{cmatch-scalar}.} A simple example of such a match is shown in +\fref{cmatch-ex-single}. + +\begin{figure}[h] + $$ + I = \left[ + \begin{array}{c} + 1 \\ 3 \\ 4 \\ 1 + \end{array} + \right] + \qquad + M = \left[ + \begin{array}{c} + 1 \\ 4 + \end{array} + \right] + \quad + \to + \quad + R = \left[ + \begin{array}{c} + \top \\ \bot \\ \top \\ \top + \end{array} + \right]. + $$ + + \caption{A simple classification match $M$ on input $I$ and its result vector + $R$.} + \label{f:cmatch-ex-single} +\end{figure} + +In \fref{cmatch-ex-single}, the input vector $I$ is \term{matched} against the +rule $M$. The output is a boolean result vector $R$ which can be summarized with +the following rule: + +$$ + R_n = \exists m\in M(m = I_n). +$$ +\noindent +In other words, $R_n$ is $\top$ if $I_n\in M$ and is $\bot$ if $I_n\notin M$. +Under this definition, $M$ can be considered to be the \term{domain} under which +a given input $I_n$ is considered to be valid (a \term{match}). + +We say that a classification rule \term{matches} if \emph{any} input matches. +That is: + +$$ + \left[\textrm{The rule $M$ matches input $I$}\right] + \iff + \top\in R +$$ +\noindent +Another way to think of this concept is the reduction of the result vector $R$ +using a logical OR. Alternatively, one could assert that: + +$$ + \left[\textrm{The rule $M$ matches input $I$}\right] + \iff + \sum\limits_n R_n \geq 1, \qquad R \in \set{0,1}, +$$ +\noindent +if an implementation were willing to use the sets \boolset and \set{1,0} +interchangeably.\footnote{See \sref{cmatch-int}.} + +The following sections, however, serve to demonstrate that such a simple view of +the classification system, while useful for an introductory demonstration, is +not sufficient when considering the level of flexibility that is necessary to +handle more complicated data (in particular, when $I$ is a +matrix).\footnote{See $\Omega$-reductions, introduced in +\asref{cmatch}{omega-reduce}.} + +%TODO: More example sections + + +\subsection{Classification Match (cmatch) Algorithm} +\label{a:cmatch} + +The classification match (``cmatch'') algorithm is used to determine if a given +set of data matches a given set of classification criteria. + +Let $I$ be the vector of input values.\footnote{$I$ may be a matrix (a vector +of vectors).} Let $M$ be the vector of predicates to match against $I$ such +that a match will be considered successful if \emph{any} predicate is true. +Since $I$ shall always be a vector of values---even if the vector contains only +one element (see algorithm below for comments on scalar values)---$M$ should be +a vector of one element if the desire is to match against a scalar value (rather +than a vector of values). Let $c$ (clear) be a boolean value\footnote{$1$ or $0$ +when used within an integer context within the algorithm.} representing whether +the results of this operation should be logically AND'd together with the +prior cmatch result ($R'$ in the algorithm below); otherwise, the results will +be OR'd (see step \ref{a:cmatch-c} below). + +Let $A\!\left(M,I,c,R'\right)$ (the ``algorithm'') be defined as: + +\begin{enumerate} + \item + Let $R$ be the result vector. + + \item\label{a:cmatch-scalar} + If the given input vector $I$ is a scalar, it should be converted to a vector + of length 1 with the value of the single element being the original scalar + value of $I$---that is, let $s$ be the original scalar value of $I$; then: $I + = \left[ s \right]$. If $s$ is undefined, then an empty result vector should + be returned. + + \item\label{a:cmatch:input-vectorize} + Step \ref{a:cmatch-scalar} should also be done to the match vector $M$, + yielding $M = \left[ s \right]$ where $s$ is the original scalar $M$. If $s$ + is undefined, then it should be treated as if it were the integer + $0$.\footnote{Consistent with the behavior of the remainder of the DSL.} + + \item + Step \ref{a:cmatch-scalar} should also be done to the prior result vector + $R'$, yielding $R = \left[ s \right]$ where $s$ is the original scalar $R'$. + This situation may result from recursing at step \ref{a:cmatch-mrecurse} when + $R'_k$ is a scalar. If $s$ is undefined, then $R'$ should be initialized to an + empty vector, implying a fresh match (no prior results). + \goodbreak + + \item\label{a:cmatch-iter} + The length of the result vector $R$~($\#R$) shall be the larger of the length + of the input vector $I$~($\#I$) or the prior result vector $R'$~($\#R'$). + For each $I_k \in I$: + + \begin{enumerate} + \item\label{a:cmatch-mrecurse} + If $I_k$ is a vector, recurse, beginning at step 1. Let $r = + A(M_k,I_k,c,R'_k)$. + + \begin{align*} + u &= \left\{ + \begin{array}{ll} + \bot & \textrm{if }\#R' > 0, \\ + c & \textrm{otherwise.} + \end{array} + \right. \\ + % + R_k &= \left\{ + \begin{array}{ll} + r & \textrm{if $R'_k$ is a vector or undefined}, \\ + \Omega(r,u) & \textrm{otherwise}.\footnotemark + \end{array} + \right. + \end{align*} + + \footnotetext{\label{a:cmatch-order} If $R'_k$ is a scalar, we must ensure + consistency with step \ref{a:cmatch-c} to ensure that the algorithm is not + dependent on input or execution order. Note the use of $u$ in place of + $c$---this ensures that, if there are any $R'$, we are consistent with the + effects of step \ref{a:cmatch:fill} (but in reverse).} + + Continue with the next $I$ at step \ref{a:cmatch-iter}. + + \item + \label{a:cmatch:omega-reduce} + Otherwise, $I_k$ is a scalar. Let $t$ be a temporary (intermediate) scalar + such that $t = \exists m \in M m(I_k)$. + + \item\label{a:cmatch-c} + Let $v = \Omega\left(R'_k,c\right)$ and let + $$ + R_k = \left\{ + \begin{array}{ll} + v \wedge t & c = \top, \\ + v \vee t & c = \bot. + \end{array} + \right., + $$ + + where\footnote{$\Omega$ is simply the recursive reduction of a vector using + a logical OR. This function exists to resolve the situation where $R'_k$ is + a vector of values when $I_k$ is a scalar, which will occur when $M_k$ is + scalar for any $k$ during one application of the cmatch algorithm and $M_k$ + is a vector for another iteration, where $R'$ is the previous match using + scalars. Note also that $X$, according to the recursion rule, may only be + undefined on the first iteration (in effect initializing the value).} + + $$ + \Omega\left(X,u\right) = \left\{ + \begin{array}{ll} + u & \textrm{if X is undefined,} \\ + X & \textrm{if X is a scalar,} \\ + \exists x\in X \Omega(x,u) & \textrm{otherwise.} + \end{array} + \right. \> + \mbox{ + $X \in \left\{\textrm{undefined},\top,\bot\right\}$ + or a vector. + } + $$ + \end{enumerate} + + \item\label{a:cmatch:fill} + Let $v = \Omega\left(R'_k,c\right) \wedge \neg c$. If $\#R' > \#I$, + $$ + R_k = \left\{ + \begin{array}{ll} + v & \exists n\in I(n\textrm{ is a scalar}), \\ + \left[v\right] & \textrm{otherwise.}\footnotemark + \end{array} + \right. + k \in \left\{j : \#I \leq j < \#R' \right\}. + $$ + + \footnotetext{Note that step \ref{a:cmatch:fill} will produce results + inconsistent with the recursive step \ref{a:cmatch-mrecurse} if there exists + an $I_n$ that is a matrix; this algorithm is not designed to handle such + scenarios.} +\end{enumerate} + +Given a set of classification criteria $C$ such that $C_k = M$ for some integer +$k$ and some application of $A$, and a vectorized clear flag $c$ such that $c_k$ +is associated with $C_k$, the final result $F(\#C-1)$ shall be defined as + +$$ + F(k) = \left\{ + \begin{array}{ll} + A\left(C_k,I_k,c_k\right) & \textrm{k = 0,} \\ + A\bigl(C_k,I_k,c_k,F\!\left(k-1\right)\bigr) & \textrm{otherwise.} + \end{array} + \right. +$$ + +The order of recursion on $F$ need not be right-to-left; $A$ is defined such +that it will produce the same result when applied in any order. This is +necessary since the input may be provided in any order.\footnote{Ibid, +\ref{a:cmatch-order}.} + +\subsubsection{Boolean Classification Match} +\label{s:cmatch-boolean} +A scalar boolean classification match $b$ may be obtained simply as $b = +\Omega\left(F,\bot\right)$, where $F$ and $\Omega$ are defined in the algorithm +above. Consequently, note that an empty result set $F$ will be treated as +$\bot$, since index $0$ will be undefined. + +\subsubsection{Match Vector} +$M$ is defined to be a vector of predicates which serve to {\sl match} against a +vector of input values. Most frequently, predicates will likely be against scalar +values. In such a case, an implementation may choose to forego function +application for performance reasons and instead match directly against the +scalar value. However, this document will consider scalar matches in the context +of predicates as functions. As such, if $M$ is a matrix, then the results are +implementation-defined (since the value does not make sense within the algorithm +as defined). + +\subsubsection{Integer Results} +\label{s:cmatch-int} +$A$ defines $R$ to be a vector/matrix of boolean values. However, it may be +useful to use the cmatch results in calculations; as such, implementations that +make use of or produce cmatch results are required to do one or both of the +following where $b$ is a boolean scalar: + +\begin{enumerate} + \item + Implicitly consider $b$ to be $\textrm{int}\!\left(b\right)$ when used in + calculations, and/or + + \item + Perform the implicit conversion before $R$ is returned from $A$, +\end{enumerate} + +where the function {\sl int} is defined as + +$$ + \textrm{int}(b) = \left\{ + \begin{array}{ll} + 1 & \textrm{if }b = \top, \\ + 0 & \textrm{if }b = \bot. + \end{array} + \right.\qquad + b \in \left\{\top,\bot\right\}. +$$ + + +\subsection{Scalar Classification Matches} +\label{s:cmatch-scalar} +Implementations may find it convenient to support scalar inputs and scalar +classification matches to represent matching ``all'' indexes of a vector. +\aref{cmatch} defines both a classification match ($R$, and consequently $F$) +and an input ($I$) to be a vector, which is generally sufficient. However, in +the case where the number of indexes of the inputs and results of other matches +may be arbitrary, it may be useful to apply a certain classification across all +indexes, which cannot be done when $c = \top$ using \aref{cmatch}. + +The onus of such a feature is on the implementation---it should flag such input +($I$) as a scalar, which is necessary since $I$ is unconditionally converted to +a vector by step \asref{cmatch}{input-vectorize}. If an implementation decides +to support scalar classification matches, \emph{it must conform to this +section}. Let such a scalar flag be denoted $s_k \inbool$ respective to input +$I_k$. Handling of both $F$ and $I$ is discussed in the sections that follow. + +\subsubsection{Mixing Scalar And Vectorized Inputs} +\label{s:cmatch-scalar-mixed} +Under the condition that $\exists v\in s(v=\top)$, the compiler must: + +\begingroup + % this definition is local to this group + \def\siset{k \in\set{j : s_j = \top}} + + \begin{enumerate} + \item + Reorder inputs $I$ such that each scalar input $I_k, \siset$ be applied + after all non-scalar inputs have been matched using \aref{cmatch}. + \begin{enumerate} + \item + Consequently (and contrary to what was mentioned in \aref{cmatch}), + application order of $A$ with respect to inputs $I$ \emph{does} in fact + matter and implementations should ensure that this restriction holds + during runtime. + \end{enumerate} + + \item + Before application of a scalar input, the scalar $I_k$ should be vectorized + according to the following rule: + + $$ + I'_{k,l} = I_k, + \qquad \siset, + \; 0 \leq l < \#R', + $$ + + where $R'$ is the value immediately before the application of $I_k$ as + defined in \aref{cmatch}. + + \item + Application of \aref{cmatch} should then proceed as normal, using $I'$ in + place of $I$. + \end{enumerate} +\endgroup + +\subsubsection{Converting Vectorized Match To Scalar} +As defined by \aref{cmatch}, the result $R$ will always be a vector. An +implementation may \emph{only} convert a vectorized match to a scalar using the +method defined in this section under the condition that $\forall v\in +s(v=\top)$; otherwise, there will be a loss of data (due to the expansion rules +defined in \sref{cmatch-scalar-mixed}). The implementation also \emph{must not} +reduce the vectorized match to a scalar using $\Omega$. An implementation +\emph{may}, however, $\Omega$-reduce the match result $R$ into an +\emph{separate} value as mentioned in \sref{cmatch-boolean}. + +Under the condition that $\forall v\in s(v=\top)$, the system may post-process +$F$ (as defined in \aref{cmatch}) such that + +$$ + F' = F_0, +$$ + +and return $F'$ in place of $F$. + +Note also that $F'$ may be fed back into \aref{cmatch} as an input and that the +results will be consistent and well-defined according to +\sref{cmatch-scalar-mixed} (and, consequently, this section). diff --git a/src/current/doc/manual.sty b/src/current/doc/manual.sty new file mode 100644 index 0000000..b3524f4 --- /dev/null +++ b/src/current/doc/manual.sty @@ -0,0 +1,30 @@ +% manual style package + +% these margins ensure that the PDF can be easily scrolled vertically without +% worrying about alternating margins (good for viewing on screen, but not on +% paper) +\usepackage[margin=1.25in]{geometry} +\usepackage{amsmath} + +\setcounter{secnumdepth}{3} +\setcounter{tocdepth}{3} + +% no name yet +\def\lang{the DSL} + +\def\sref#1{Section \ref{s:#1}} +\def\fref#1{Figure \ref{f:#1}} +\def\aref#1{Algorithm \ref{a:#1}} +\def\asref#1#2{A\ref{a:#1}(\ref{a:#1:#2})} + +\def\set#1{% + \ifmmode% + \left\{#1\right\}% + \else + $\left\{#1\right\}$% + \fi% +} +\def\boolset{\set{\top,\bot}} +\def\inbool{\in\boolset} + +\def\term#1{{\sl #1}} diff --git a/src/current/doc/manual.tex b/src/current/doc/manual.tex new file mode 100644 index 0000000..9d1d80e --- /dev/null +++ b/src/current/doc/manual.tex @@ -0,0 +1,19 @@ +\documentclass[10pt]{book} + +%%begin preamble + \usepackage{manual} + + \author{Mike Gerwitz\\LoVullo Associates} + \date{\today} +%%end preamble + +\begin{document} + +\title{Calc DSL: Design Specification and Programmer's Manual} +\maketitle + +\tableofcontents + +\include{chapters/class} + +\end{document} diff --git a/src/current/dot.xsl b/src/current/dot.xsl new file mode 100644 index 0000000..ed0bb39 --- /dev/null +++ b/src/current/dot.xsl @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Outputs graph visualization of dependencies in DOT format +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + + xmlns:lv="http://www.lovullo.com/rater" + xmlns:dot="http://www.lovullo.com/calc/dot"> + +<xsl:import href="dot/depout.xsl" /> +<xsl:import href="dot/defnode.xsl" /> + +<!-- supported sources (entry points) --> +<xsl:include href="dot/pkg-obj.xsl" /> +<xsl:include href="dot/pkg-exec.xsl" /> + +<xsl:output method="text" /> + + +<!-- + Newline character +--> +<xsl:variable name="dot:nl" select="' '" /> + + +<!-- + Immediately fails on unrecognized source type +--> +<xsl:template match="lv:package" priority="1"> + <xsl:message terminate="yes"> + <xsl:text>[dot] fatal: this is not an object/executable file: </xsl:text> + <xsl:text>no symbol dependencies found</xsl:text> + </xsl:message> +</xsl:template> + + +<!-- + Beginning of a DOT document +--> +<xsl:template match="lv:package" mode="dot:head"> + <xsl:text>/* dependency graph of </xsl:text> + <xsl:value-of select="@name" /> + <xsl:text> */</xsl:text> + + <xsl:text>digraph "</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>" { </xsl:text> + + <xsl:text>graph [rankdir="LR", ranksep="2"]; </xsl:text> +</xsl:template> + + +<!-- + End of a DOT document +--> +<xsl:template match="lv:package" mode="dot:tail"> + <xsl:text>}</xsl:text> +</xsl:template> + + +</xsl:stylesheet> + diff --git a/src/current/dot/attr-color.xsl b/src/current/dot/attr-color.xsl new file mode 100644 index 0000000..4ef7c01 --- /dev/null +++ b/src/current/dot/attr-color.xsl @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Styles node color based on symbol type +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + + xmlns:dot="http://www.lovullo.com/calc/dot" + xmlns:preproc="http://www.lovullo.com/rater/preproc"> + + +<!-- + Parameter +--> +<xsl:template mode="dot:attr-color" priority="5" + match="*[ @type='param' ]"> + + <dot:attr name="color">#204a87</dot:attr> +</xsl:template> + + +<!-- + Param type +--> +<xsl:template mode="dot:attr-color" priority="5" + match="*[ @type='type' ]"> + + <dot:attr name="color">#729fcf</dot:attr> +</xsl:template> + + +<!-- + Classification +--> +<xsl:template mode="dot:attr-color" priority="5" + match="*[ @type='class' or @type='cgen' ]"> + + <dot:attr name="color">#4e9a06</dot:attr> +</xsl:template> + + +<!-- + Function +--> +<xsl:template mode="dot:attr-color" priority="5" + match="*[ @type='func' ]"> + + <dot:attr name="color">#c4a000</dot:attr> +</xsl:template> + + +<!-- + Map +--> +<xsl:template mode="dot:attr-color" priority="5" + match="*[ @type='map' or @type='retmap' ]"> + + <dot:attr name="color">#888a85</dot:attr> +</xsl:template> + + +<!-- + Default +--> +<xsl:template match="*" mode="dot:attr-color" priority="1"> + <!-- no color --> +</xsl:template> + +</xsl:stylesheet> + diff --git a/src/current/dot/attr-extern.xsl b/src/current/dot/attr-extern.xsl new file mode 100644 index 0000000..318da4a --- /dev/null +++ b/src/current/dot/attr-extern.xsl @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Styles node based on locality +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + + xmlns:dot="http://www.lovullo.com/calc/dot" + xmlns:preproc="http://www.lovullo.com/rater/preproc"> + + +<!-- + External nodes should be styled as such +--> +<xsl:template mode="dot:attr-extern" priority="5" match=" + *[ @src and not( @src='' ) ] + "> + + <dot:attr name="style">dashed</dot:attr> +</xsl:template> + + + +<!-- + Default node attributes +--> +<xsl:template match="preproc:sym" mode="dot:defnode-attr" priority="1"> +</xsl:template> + +</xsl:stylesheet> + diff --git a/src/current/dot/attr-keep.xsl b/src/current/dot/attr-keep.xsl new file mode 100644 index 0000000..3a72af4 --- /dev/null +++ b/src/current/dot/attr-keep.xsl @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Styles node based on keep flag +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + + xmlns:dot="http://www.lovullo.com/calc/dot" + xmlns:preproc="http://www.lovullo.com/rater/preproc"> + + +<!-- + External nodes should be styled as such +--> +<xsl:template mode="dot:attr-keep" priority="5" match=" + *[ @keep='true' ] + "> + + <dot:attr name="fontcolor">red</dot:attr> +</xsl:template> + + + +<!-- + Default node attributes +--> +<xsl:template match="preproc:sym" mode="dot:defnode-keep" priority="1"> +</xsl:template> + +</xsl:stylesheet> + diff --git a/src/current/dot/attr-shape.xsl b/src/current/dot/attr-shape.xsl new file mode 100644 index 0000000..f4bc289 --- /dev/null +++ b/src/current/dot/attr-shape.xsl @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Styles node shape based on symbol type +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + + xmlns:dot="http://www.lovullo.com/calc/dot" + xmlns:preproc="http://www.lovullo.com/rater/preproc"> + + +<!-- + Parameter +--> +<xsl:template mode="dot:attr-shape" priority="5" + match="*[ @type='param' ]"> + + <dot:attr name="shape">box</dot:attr> +</xsl:template> + + +<!-- + Classification +--> +<xsl:template mode="dot:attr-shape" priority="5" + match="*[ @type='class' or @type='cgen' ]"> + + <dot:attr name="shape">octagon</dot:attr> +</xsl:template> + + +<!-- + Function +--> +<xsl:template mode="dot:attr-shape" priority="5" + match="*[ @type='func' ]"> + + <dot:attr name="shape">component</dot:attr> +</xsl:template> + + +<!-- + Map +--> +<xsl:template mode="dot:attr-shape" priority="5" + match="*[ @type='map' or @type='retmap' ]"> + + <dot:attr name="shape">note</dot:attr> +</xsl:template> + + +<!-- + Default +--> +<xsl:template match="*" mode="dot:attr-shape" priority="1"> + <!-- default shape --> +</xsl:template> + +</xsl:stylesheet> + diff --git a/src/current/dot/defnode-attr.xsl b/src/current/dot/defnode-attr.xsl new file mode 100644 index 0000000..778835a --- /dev/null +++ b/src/current/dot/defnode-attr.xsl @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Outputs graph visualization of dependencies in DOT format +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + + xmlns:dot="http://www.lovullo.com/calc/dot" + xmlns:preproc="http://www.lovullo.com/rater/preproc"> + +<xsl:import href="./attr-color.xsl" /> +<xsl:import href="./attr-shape.xsl" /> +<xsl:import href="./attr-extern.xsl" /> +<xsl:import href="./attr-keep.xsl" /> + + +<!-- + External nodes should be styled as such +--> +<xsl:template mode="dot:defnode-attr" priority="5" match=" + preproc:sym[ @src and not( @src='' ) ] + "> + + <dot:attr name="label"> + <xsl:value-of select="@src" /> + <xsl:text>/\n</xsl:text> + <xsl:value-of select="@name" /> + </dot:attr> + + <dot:attr name="tooltip"> + <xsl:value-of select="@src" /> + <xsl:text>/</xsl:text> + <xsl:value-of select="@name" /> + </dot:attr> +</xsl:template> + + + +<!-- + Default node attributes +--> +<xsl:template match="preproc:sym" mode="dot:defnode-attr" priority="1"> +</xsl:template> + + +<!-- + Render an attribute list as a comma-delimited string + + Expects a tree of dot:attr nodes where @name is the name of the attribute, and + its normalized text is the value. The value will be quoted; double quotes must + be manually escaped prior to calling this template. +--> +<xsl:template name="dot:render-attr-list"> + <xsl:param name="attr-list" /> + + <xsl:for-each select="$attr-list/dot:attr"> + <xsl:if test="position() > 1"> + <xsl:text>, </xsl:text> + </xsl:if> + + <xsl:value-of select="@name" /> + <xsl:text>="</xsl:text> + <xsl:value-of select="normalize-space( text() )" /> + <xsl:text>"</xsl:text> + </xsl:for-each> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/dot/defnode.xsl b/src/current/dot/defnode.xsl new file mode 100644 index 0000000..c34c591 --- /dev/null +++ b/src/current/dot/defnode.xsl @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Graph node definitions + + Nodes do not need to be defined (DOT will generate them upon first reference); + this defines nodes that require additional data associated with them. +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + + xmlns:dot="http://www.lovullo.com/calc/dot" + xmlns:preproc="http://www.lovullo.com/rater/preproc"> + +<xsl:import href="./defnode-attr.xsl" /> + + +<!-- + Do not declare constants or generated symbols + + XXX: Duplicated logic from smy-ref! +--> +<xsl:template mode="dot:defnode" priority="9" match=" + preproc:sym[ + @type='const' + or @type='map' or @type='retmap' + or @type='map:head' or @type='map:tail' + or @type='retmap:head' or @type='retmap:tail' + or ( + @type='type' + and ( + @name='integer' + or @name='float' + or @name='boolean' + ) + ) + or @parent + or @preproc:generated='true' + ] + "> +</xsl:template> + + +<!-- + Process parent symbol in place of current symbol + + Symbols with defined parents are generated as part of that parent and will + therefore be treated as a single unit. +--> +<xsl:template match="preproc:sym[ @parent ]" mode="dot:defnode" priority="7"> + <xsl:variable name="parent" select="@parent" /> + + <xsl:apply-templates select=" + parent::preproc:symtable/preproc:sym[ @name=$parent ] + " /> +</xsl:template> + + +<!-- + Default node definition + + If no attributes are generated, then the node will be entirely omitted (it'll + be created automatically by DOT when referenced). +--> +<xsl:template match="preproc:sym" mode="dot:defnode" priority="1"> + <xsl:variable name="attr"> + <xsl:call-template name="dot:render-attr-list"> + <xsl:with-param name="attr-list"> + <!-- this kluge exists because of XSLT limitiations and the confusion + that would result (in this particular situation) from + xsl:apply-imports + --> + <xsl:apply-templates select="." mode="dot:defnode-attr" /> + <xsl:apply-templates select="." mode="dot:attr-extern" /> + <xsl:apply-templates select="." mode="dot:attr-color" /> + <xsl:apply-templates select="." mode="dot:attr-shape" /> + <xsl:apply-templates select="." mode="dot:attr-keep" /> + </xsl:with-param> + </xsl:call-template> + </xsl:variable> + + <xsl:if test="not( $attr = '' )"> + <xsl:text>"</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>" [</xsl:text> + <xsl:value-of select="$attr" /> + + <xsl:if test="@src and not( @src='' )"> + <xsl:if test="$attr"> + <xsl:text>,</xsl:text> + </xsl:if> + + <!-- link to other packages in the graph for navigation --> + <xsl:text>href="</xsl:text> + <xsl:value-of select="concat( @src, '.svg' )" /> + <xsl:text>"</xsl:text> + </xsl:if> + <xsl:text>];</xsl:text> + + <xsl:value-of select="$dot:nl" /> + </xsl:if> +</xsl:template> + + +</xsl:stylesheet> + diff --git a/src/current/dot/depout.xsl b/src/current/dot/depout.xsl new file mode 100644 index 0000000..5b3dbda --- /dev/null +++ b/src/current/dot/depout.xsl @@ -0,0 +1,212 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Outputs dependency relationship to a directed graph +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + + xmlns:lv="http://www.lovullo.com/rater" + xmlns:dot="http://www.lovullo.com/calc/dot" + xmlns:preproc="http://www.lovullo.com/rater/preproc"> + + + +<!-- + Container for all the dependencies +--> +<xsl:template match="preproc:sym-deps" mode="dot:depout" priority="5"> + <xsl:apply-templates mode="dot:depout" /> +</xsl:template> + + +<!-- + Treat the entry point (lv:yield) as the root node +--> +<xsl:template match="preproc:sym-dep[ @name='___yield' ]" mode="dot:depout" priority="6"> + <xsl:value-of select="@name" /> + <xsl:text> [root=ctr,fontsize=24,style=bold,label="Yield"]; </xsl:text> + + <xsl:apply-templates mode="dot:depout" /> +</xsl:template> + + +<!-- + Constant and generated symbols will not be rendered +--> +<xsl:template mode="dot:depout" priority="5" match=" + preproc:sym-dep[ + @name=ancestor::lv:package/preproc:symtable/preproc:sym[ + @type='const' + or @preproc:generated='true' + ]/@name + ] + "> +</xsl:template> + + +<!-- + Container for symbol dependencies + + That is: this node represents a symbol, and its children are its dependencies. +--> +<xsl:template match="preproc:sym-dep" mode="dot:depout" priority="3"> + <xsl:apply-templates mode="dot:depout" /> +</xsl:template> + + +<!-- + Do not output relationships to primitives or constants; or generated +--> +<xsl:template mode="dot:depout" priority="5" match=" + preproc:sym-ref[ + @type='type' + and ( + @name='integer' + or @name='float' + or @name='boolean' + ) + or @type='const' + or @type='lparam' + or @type='map' or @type='retmap' + or @type='map:head' or @type='map:tail' + or @type='retmap:head' or @type='retmap:tail' + ] + "> + + <!-- skip --> +</xsl:template> + + +<!-- + Process generated symbol deps as our own + + Generated symbols are not known by the user, so they should be treated as part + of the unit from which they are generated. +--> +<xsl:template mode="dot:depout" priority="4" match=" + preproc:sym-ref[ + @name=ancestor::lv:package/preproc:symtable/preproc:sym[ + @preproc:generated='true' + ]/@name + ] + "> + + <xsl:param name="usedby" select="parent::preproc:sym-dep/@name" /> + + <xsl:variable name="name" select="@name" /> + + <!-- process the generated symbol's deps as our own --> + <xsl:apply-templates mode="dot:depout" select=" + ancestor::preproc:sym-deps/preproc:sym-dep[ + @name=$name + ]/preproc:sym-ref + "> + <xsl:with-param name="usedby" select="$usedby" /> + </xsl:apply-templates> +</xsl:template> + + +<!-- + Process references with parents as if they were their parent symbol + + Symbols with defined parents are generated as part of that parent and will + therefore be treated as a single unit. +--> +<xsl:template match="preproc:sym-ref[ @parent ]" mode="dot:depout" priority="3"> + <xsl:param name="usedby" select="parent::preproc:sym-dep/@name" /> + + <xsl:variable name="parent" select="@parent" /> + + <xsl:apply-templates mode="dot:depout" select=" + ancestor::lv:package/preproc:symtable/preproc:sym[ @name=$parent ] + "> + <xsl:with-param name="usedby" select="$usedby" /> + </xsl:apply-templates> +</xsl:template> + + +<!-- + Trigger processing of symbol associated with the ref +--> +<xsl:template match="preproc:sym-ref" mode="dot:depout" priority="2"> + <xsl:param name="usedby" select="parent::preproc:sym-dep/@name" /> + + <xsl:variable name="name" select="@name" /> + + <xsl:apply-templates mode="dot:depout" select=" + ancestor::lv:package/preproc:symtable/preproc:sym[ @name=$name ] + "> + <xsl:with-param name="usedby" select="$usedby" /> + </xsl:apply-templates> +</xsl:template> + + +<!-- + Output symbol reference to directed graph + + The symbol (dependency) is referenced as a node adjacent to the node of the + symbol that uses it. The edge is directed toward the dependency and shall be + read as "uses". + + For example: "foo uses bar": + (foo) -> (bar) +--> +<xsl:template match="preproc:sym" mode="dot:depout" priority="5"> + <xsl:param name="usedby" /> + + <xsl:variable name="attr"> + <xsl:call-template name="dot:render-attr-list"> + <xsl:with-param name="attr-list"> + <xsl:apply-templates select="." mode="dot:attr-extern" /> + <xsl:apply-templates select="." mode="dot:attr-color" /> + </xsl:with-param> + </xsl:call-template> + </xsl:variable> + + <xsl:text>"</xsl:text> + <xsl:value-of select="$usedby" /> + <xsl:text disable-output-escaping="yes">" -> "</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>"</xsl:text> + + <xsl:if test="not( $attr='' )"> + <xsl:text> [</xsl:text> + <xsl:value-of select="$attr" /> + <xsl:text>]</xsl:text> + </xsl:if> + + <xsl:text>;</xsl:text> + + <xsl:value-of select="$dot:nl" /> +</xsl:template> + + +<!-- + Bail out if asked to render something unexpected +--> +<xsl:template match="*" mode="dot:depout" priority="1"> + <xsl:message terminate="yes"> + <xsl:text>error: what do I do!?: unexpected </xsl:text> + <xsl:value-of select="name()" /> + </xsl:message> +</xsl:template> + + +<!-- + Extra comments and attributes are ignored + + text() is ignored, otherwise, extra whitespace corresponding to the + indentation of nodes appears in the output. + + Ignoring attributes is just in case of an xpath whoopsie, but probably isn't + necessary, and is probably dangerous (because it may veil bugs). +--> +<xsl:template match="@*|text()" mode="dot:depout" priority="1"> + <!-- ignore --> +</xsl:template> + + +</xsl:stylesheet> + diff --git a/src/current/dot/pkg-exec.xsl b/src/current/dot/pkg-exec.xsl new file mode 100644 index 0000000..1c934bf --- /dev/null +++ b/src/current/dot/pkg-exec.xsl @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Processes executable file dependency graph +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + + xmlns:lv="http://www.lovullo.com/rater" + xmlns:l="http://www.lovullo.com/rater/linker" + xmlns:dot="http://www.lovullo.com/calc/dot" + xmlns:preproc="http://www.lovullo.com/rater/preproc"> + + +<!-- + Entry point for linked executable (.xmle) DOT generation + + We wish to generate a dependency graph for an entire program. This approach is + a little bit different than the approach to processing object files, because + we know that the linker's symbol table contains *only* those symbols that are + used (or kept). We further know that each symbol (unless there's a bug in the + linker) is referenced only a single time in the symbol table. + + This makes our job easy: simply walk the symbol table, look up the + preproc:sym-dep in the source package, and render as we normally would for an + object file. + + Lord Jesus it's a fire. +--> +<xsl:template match="lv:package[ l:dep ]" priority="9"> + <xsl:apply-templates select="." mode="dot:head" /> + + <!-- we know that all symbols in the linker symbol table are used, so we can + immediately generate the node definitions --> + <xsl:apply-templates mode="dot:defnode" + select="l:dep/preproc:sym" /> + + <!-- outputting the dependencies of those symbols is more involved and + requires processing data from each object file --> + <xsl:apply-templates select="l:dep/preproc:sym" mode="dot:ldep-sym-deps"> + <xsl:with-param name="exec-name" select="concat( @__rootpath, @name )" /> + </xsl:apply-templates> + + <xsl:apply-templates select="." mode="dot:tail" /> +</xsl:template> + + +<!-- + Omit symbols with parent references + + Symbols with parents are generated from that parent and will be considered to + be a single unit. Since the parent will also be in the symbol table (it is, + after all, a dependency), we don't have to worry about these at all. +--> +<xsl:template match="preproc:sym[ @parent ]" mode="dot:ldep-sym-deps" priority="5"> + <!-- ignore --> +</xsl:template> + + +<!-- + Process dependencies for each symbol + + The linker symbol table only stores a flattened symbol list; to get the + symbol's dependencies, we must consult the source object file. +--> +<xsl:template match="preproc:sym" mode="dot:ldep-sym-deps" priority="1"> + <xsl:param name="exec-name" /> + + <xsl:variable name="name" select="@name" /> + + <!-- empty @src implies program package --> + <xsl:variable name="pkg" select=" + if ( @src and not( @src='' ) ) then + document( concat( @src, '.xmlo' ), / )/lv:package + else + document( concat( $exec-name, '.xmlo' ), / )/lv:package + " /> + + <xsl:variable name="sym-dep" select=" + $pkg/preproc:sym-deps/preproc:sym-dep[ + @name=$name + ] + " /> + + <xsl:if test="not( $sym-dep )"> + <xsl:message terminate="yes"> + <xsl:text>error: cannot locate symbol dependencies for `</xsl:text> + <xsl:value-of select="concat( @src, '/', @name )" /> + <xsl:text>'</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:apply-templates select="$sym-dep" mode="dot:depout" /> +</xsl:template> + + +</xsl:stylesheet> + diff --git a/src/current/dot/pkg-obj.xsl b/src/current/dot/pkg-obj.xsl new file mode 100644 index 0000000..d281cff --- /dev/null +++ b/src/current/dot/pkg-obj.xsl @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Processes object file dependency graph +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + + xmlns:lv="http://www.lovullo.com/rater" + xmlns:dot="http://www.lovullo.com/calc/dot" + xmlns:preproc="http://www.lovullo.com/rater/preproc"> + + +<!-- + Entry point for object file DOT generation + + To render the graph, we first declare all nodes associated with all referenced + symbols in the symbol tree (it's important to check against preproc:sym-deps, + since it's likely that not all imported symbols are used); this allows setting + attributes for the symbol nodes on the graph without having to worry about + duplicate code later on. + + After that, we simply recurse through the dependency list and neighbor the + nodes. +--> +<xsl:template match="lv:package[ preproc:sym-deps ]" priority="5"> + <xsl:apply-templates select="." mode="dot:head" /> + + <xsl:variable name="sym-deps" select="preproc:sym-deps" /> + + <!-- pre-style all referenced nodes (the symbol table is likely to contain + references to symbols that were imported but not used) --> + <xsl:apply-templates mode="dot:defnode" select=" + preproc:symtable/preproc:sym[ + @name=$sym-deps/preproc:sym-dep/preproc:sym-ref/@name + or @name=$sym-deps/preproc:sym-dep/preproc:sym-ref/@parent + ] + " /> + + <!-- output graph description --> + <xsl:apply-templates select="preproc:sym-deps" mode="dot:depout" /> + + <xsl:apply-templates select="." mode="dot:tail" /> +</xsl:template> + + +</xsl:stylesheet> + diff --git a/src/current/include/calc-display.xsl b/src/current/include/calc-display.xsl new file mode 100644 index 0000000..4f9f0fa --- /dev/null +++ b/src/current/include/calc-display.xsl @@ -0,0 +1,702 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Handles calculation output in LaTeX format for styling by Mathjax +--> + +<xsl:stylesheet version="1.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:c="http://www.lovullo.com/calc" + xmlns:preproc="http://www.lovullo.com/rater/preproc"> + + +<!-- + Recursively apply any child equations and then do the same in calc-after mode + + @return LaTeX equation +--> +<xsl:template match="c:*" mode="calc-recurse"> + <xsl:apply-templates select="./c:*" /> + + <!-- invoke `after' templates, which allows inserting data for display after + the initial equation --> + <xsl:apply-templates select="./c:*" mode="calc-after" /> +</xsl:template> + + +<!-- + Style sum of values as a LaTeX equation + + Note that this does not deal with the summation of a series; that's left to + the handling of the @of attribute. + + @return LaTeX equation +--> +<xsl:template match="c:sum"> + <xsl:apply-templates select="." mode="sum-body" /> +</xsl:template> + +<xsl:template match="c:sum" mode="sum-body"> + <xsl:for-each select="./c:*"> + <!-- get value to display --> + <xsl:variable name="display"> + <xsl:apply-templates select="." /> + </xsl:variable> + + <!-- delimit with +s if not first; if we're adding a negative, omit the + addition symbol as well (unless we're displaying a product, since + multiplying by a negative would otherwise appear to be subtraction) --> + <xsl:if test=" + ( position() > 1 ) + and ( + not( substring( $display, 1, 1 ) = '-' ) + or ( local-name() = 'product' ) + ) + "> + + <xsl:text> + </xsl:text> + </xsl:if> + + <!-- the only reason we would have a sum within a sum (that doesn't use + sigma-notation) is for grouping --> + <xsl:if test="( local-name() = 'sum' ) and not( @of )"> + <xsl:text>\left(</xsl:text> + </xsl:if> + + <xsl:copy-of select="$display" /> + + <xsl:if test="( local-name() = 'sum' ) and not( @of )"> + <xsl:text>\right)</xsl:text> + </xsl:if> + </xsl:for-each> + + <!-- since we looped manually, we must also invoke `after' templates + manually --> + <xsl:apply-templates select="./c:*" mode="calc-after" /> +</xsl:template> + + +<!-- + Style summation of a set as a LaTeX equation + + Note that @of deals witht summation of sets only (rather, using the index of a + set to sum over the provided calculation). See the other template(s) for + summing values without a set. + + An index may optionally be provided via an @index attribute; otherwise, one + will be chosen for you. The index is used for the lower limit; the upper limit + is omitted. The child nodes are then used to generate the equation to be + applied by the summation. + + If no child nodes are provided, then the summation is meant to imply that each + value in the set should be summed. Adding child nodes overrides this behavior. + + @return LaTeX equation +--> +<xsl:template match="c:sum[@of]"> + <xsl:variable name="of" select="@of" /> + + <!-- if no index is provided, simply use its symbol to indicate all values + within its domain --> + <xsl:variable name="index"> + <xsl:choose> + <xsl:when test="@index"> + <xsl:value-of select="@index" /> + </xsl:when> + + <xsl:otherwise> + <!-- TODO: Determine an index that is not in use --> + <xsl:text>k</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="ref"> + <xsl:value-of select="@of" /> + </xsl:variable> + + <!-- retrieve the symbol associated with this value (no index) --> + <xsl:variable name="symbol"> + <xsl:call-template name="get-symbol"> + <xsl:with-param name="name" select="$ref" /> + <xsl:with-param name="search" select="/" /> + </xsl:call-template> + </xsl:variable> + + <!-- also retrieve the symbol without its index --> + + <!-- if an index was provided, set the lower limit to 0 (we do this in a + separate variable so that we can display the symbol on its own elsewhere) + --> + <xsl:variable name="index-limit"> + <xsl:value-of select="$index" /> + + <!-- we only need the explicit notation if we are summing more than the + set --> + <xsl:if test="./c:*"> + <xsl:text>=0</xsl:text> + </xsl:if> + </xsl:variable> + + <xsl:text>\sum \limits_{</xsl:text> + <xsl:value-of select="$index-limit" /> + <xsl:text>}</xsl:text> + + <!-- upper limit is only necessary for clarification if they have provided a + more complex expression; if we're only summing over a single set, then + the extra notation is unnecessary and will just clutter --> + <xsl:if test="./c:*"> + <!-- the upper limit of the summation will be denoted by #S, where S is the + symbol for a given set --> + <xsl:text>^{\grave\#</xsl:text> + <xsl:copy-of select="$symbol" /> + <xsl:text>}</xsl:text> + </xsl:if> + + <!-- if no children are provided, just sum @of --> + <xsl:if test="not(./c:*)"> + <!-- output the symbol followed by its index, only if an index was provided + (and is therefore necessary) --> + <xsl:call-template name="get-symbol"> + <xsl:with-param name="name" select="$ref" /> + <xsl:with-param name="index-symbol" select="$index" /> + <xsl:with-param name="search" select="/" /> + </xsl:call-template> + </xsl:if> + + <!-- output any additional expressions, if any --> + <xsl:apply-templates select="." mode="sum-body" /> +</xsl:template> + + +<!-- + Style product of values as a LaTeX equation + + Note that this does not deal with the product of a series; that's left to + the handling of the @of attribute (TODO: @of not yet implemented for + products). + + @return LaTeX equation +--> +<xsl:template match="c:product"> + <xsl:variable name="enclose" select=" + @dot='true' + and ( + preceding-sibling::c:* + or following-sibling::c:* + ) + " /> + + <xsl:if test="$enclose"> + <xsl:text>(</xsl:text> + </xsl:if> + + <xsl:for-each select="./c:*"> + <!-- Function symbols can have multiple chars, so we'll need to add the + multiplication symbol. Adjacent constants should also be separated by a + dot, otherwise it'll look like one giant number. --> + <xsl:if test=" + ( + local-name() = 'apply' + or ../@dot = 'true' + or ( + ( local-name() = 'const' ) + and ( local-name( preceding-sibling::*[1] ) = 'const' ) + ) + ) + and ( position() > 1 ) + "> + <xsl:text> \,\cdot\, </xsl:text> + </xsl:if> + + <!-- if precedence of this operation is lower, we will need to include + parenthesis --> + <!-- XXX: Relies on hard-coded precedence rules in multiple locations; + refactor! --> + <xsl:if test=" + ( local-name() = 'sum' ) + or ( ( local-name() = 'product' ) and not( @of ) ) + "> + <xsl:text>\left(</xsl:text> + </xsl:if> + + <xsl:apply-templates select="." /> + + <!-- close parenthesis --> + <xsl:if test=" + ( local-name() = 'sum' ) + or ( ( local-name() = 'product' ) and not( @of ) ) + "> + <xsl:text>\right)</xsl:text> + </xsl:if> + </xsl:for-each> + + <xsl:if test="$enclose"> + <xsl:text>)</xsl:text> + </xsl:if> + + <!-- since we looped manually, we must also invoke `after' templates + manually --> + <xsl:apply-templates select="./c:*" mode="calc-after" /> +</xsl:template> + + +<!-- + Style quotient of two values as a LaTeX equation + + This is used to divide two values and will be styled as a fraction. The + numerator should be the first calculation node and the denominator the second; + there should be no additional nodes. + + @return LaTeX equation +--> +<xsl:template match="c:quotient"> + <!-- numerator (first child) --> + <xsl:text>\frac{</xsl:text> + <xsl:apply-templates select="./c:*[1]" /> + + <!-- denominator (second child) --> + <xsl:text>}{</xsl:text> + <xsl:apply-templates select="./c:*[2]" /> + <xsl:text>}</xsl:text> + + <!-- since we processed manually, we must also invoke `after' templates + manually --> + <xsl:apply-templates select="./c:*" mode="calc-after" /> +</xsl:template> + + +<xsl:template match="c:let"> + <!-- process the body of the let expression (the variables should have been + processed separately) --> + <xsl:apply-templates select="c:*" /> +</xsl:template> + + +<!-- + Style a value for display within a LaTeX equation + + Forwards to calc-get-value template. +--> +<xsl:template match="c:value-of"> + <xsl:apply-templates select="." mode="calc-get-value" /> +</xsl:template> + + +<!-- + Values from a c:let must have their names altered before looking up the symbol +--> +<xsl:template match="c:*[ @name=ancestor::c:let/c:values/c:value/@name ]" mode="calc-get-value"> + <xsl:call-template name="calc-get-value"> + <!-- :<let-name>:<our-name> --> + <xsl:with-param name="name" select=" + concat( ':', ancestor::c:let[1]/@name, ':', @name ) + " /> + </xsl:call-template> +</xsl:template> + + +<!-- + Style a value for display within a LaTeX equation + + By default, the symbol for the given value (variable) will be rendered in + place of this node. + + This element is not expected to have any children. + + XXX: Refactor me; there are more clear and less convoluted ways to accomplish + this. + + @return LaTeX equation +--> +<xsl:template name="calc-get-value" match="c:*" mode="calc-get-value"> + <xsl:param name="name" select="@name" /> + + <xsl:variable name="index-symbol"> + <xsl:if test="./c:index"> + <xsl:for-each select="./c:index"> + <!-- separate multiple indexes with commas --> + <xsl:if test="position() > 1"> + <xsl:text>,</xsl:text> + </xsl:if> + + <xsl:apply-templates select="./c:*[1]" /> + </xsl:for-each> + </xsl:if> + </xsl:variable> + + <xsl:variable name="sym" select=" + /lv:*/preproc:symtable/preproc:sym[ + @name=$name + ] + " /> + + + <xsl:variable name="value"> + <xsl:choose> + <!-- for scalar constants that do not have a symbol, simply inline their + value (if they have a symbol, then it is assumed that their symbolic + meaning is more meaningful than its value) --> + <xsl:when test=" + $sym[ + @type='const' + and @dim='0' + and ( not( @text ) or @tex='' ) + ] + "> + <xsl:value-of select="$sym/@value" /> + </xsl:when> + + <!-- local index (generated with @of) --> + <xsl:when test="ancestor::c:*[ @of and @index=$name ]"> + <xsl:value-of select="$name" /> + </xsl:when> + + <xsl:otherwise> + <xsl:call-template name="get-symbol"> + <xsl:with-param name="name" select="$name" /> + <xsl:with-param name="index" select="@index" /> + <xsl:with-param name="index-symbol" select="$index-symbol" /> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:copy-of select="$value" /> + + <!-- yes, a value still may have things to appear after it --> + <xsl:apply-templates select="./c:*" mode="calc-after" /> +</xsl:template> + + +<!-- + Style a constant value for use in a LaTeX equation + + Constant values are not treated as variables; instead, their value (rather + than their symbol) is immediately rendered. + + Use this only if the value itself makes more sense (and is more clear) than a + variable. + + This element is not expected to have any children. + + @return LaTeX equation +--> +<xsl:template match="c:const"> + <!-- a constant value of 1 with Iverson's brackets is redundant --> + <xsl:if test="not( ( @value = '1' ) and ./c:when )"> + <!-- display constant value --> + <xsl:value-of select="@value" /> + </xsl:if> + + <!-- a constant may still have things to appear after it --> + <xsl:apply-templates select="./c:*" mode="calc-after" /> +</xsl:template> + + +<xsl:template match="c:ceil|c:floor"> + <xsl:text>\left\l</xsl:text> + <xsl:value-of select="local-name()" /> + <xsl:text> </xsl:text> + <xsl:apply-templates select="." mode="calc-recurse" /> + <xsl:text>\right\r</xsl:text> + <xsl:value-of select="local-name()" /> +</xsl:template> + + +<!-- + Styles a function application for display in a LaTeX equation + + Indicates a function application. A call to the function, with each of its + arguments in parenthesis, will be rendered. + + @return LaTeX equation +--> +<xsl:template match="c:apply"> + <xsl:variable name="self" select="." /> + <xsl:variable name="name" select="@name" /> + <xsl:variable name="fsym" select="//lv:function[@name=$name]/@sym" /> + + <xsl:call-template name="get-symbol"> + <xsl:with-param name="name" select="@name" /> + <xsl:with-param name="search" select="/" /> + </xsl:call-template> + + <!-- if a symbol is provided, then omit the parenthesis --> + <xsl:if test="not( $fsym )"> + <xsl:text>\left(</xsl:text> + </xsl:if> + + <!-- output the symbol associated with the value of each argument --> + <xsl:for-each select="./c:arg"> + <!-- delimit params with a comma --> + <xsl:if test="position() > 1"> + <xsl:text>,</xsl:text> + </xsl:if> + + <xsl:variable name="valname" select="./c:*[1]/@name" /> + + <xsl:choose> + <!-- if this variable has been defined as an index, then simply output + it --> + <xsl:when test="ancestor::c:*[ @of and @index=$valname ]"> + <xsl:value-of select="$valname" /> + </xsl:when> + + <!-- display the value of constants --> + <xsl:when test="local-name( ./c:*[1] ) = 'const'"> + <xsl:value-of select="./c:*[1]/@value" /> + </xsl:when> + + <!-- otherwise, it's some other variable and we must look up its + symbol --> + <xsl:otherwise> + <xsl:apply-templates select="./c:*[1]" /> + </xsl:otherwise> + </xsl:choose> + + <!-- we may have c:when, etc (args are their own sub-equations) --> + <xsl:apply-templates select="./c:*[1]/c:*" mode="calc-after" /> + </xsl:for-each> + + <xsl:if test="not( $fsym )"> + <xsl:text>\right)</xsl:text> + </xsl:if> + + <xsl:apply-templates select="./c:*" mode="calc-after" /> +</xsl:template> + + +<!-- + Outputs Iverson's brackets only if forced; see calc-after mode + + This is hidden by default until calc-after to ensure that this is display + *after* the equation is output. + + @param boolean force-show optionally force the display of the notation + + @return LaTeX equation +--> +<xsl:template match="c:when"> + <xsl:param name="force-show" select="false()" /> + <xsl:param name="standalone" select="false()" /> + + <!-- by default, we want to defer showing this until after the equation has + been output; however, the caller can force it to be displayed if needed --> + <xsl:if test="$force-show = true()"> + <xsl:apply-templates select="." mode="calc-after"> + <xsl:with-param name="standalone" select="$standalone" /> + </xsl:apply-templates> + </xsl:if> +</xsl:template> + + +<!-- + Outputs Iverson's brackets + + This is called *after* the equation is output + + @return LaTeX equation +--> +<xsl:template match="c:when" mode="calc-after" priority="5"> + <xsl:param name="brackets" select="true()" /> + <xsl:param name="standalone" select="false()" /> + + <xsl:variable name="preceding" select="preceding-sibling::c:when" /> + + <!-- output bracket only if (a) requested and (b) first in set --> + <xsl:if test="$brackets and ( $standalone or not( $preceding ) )"> + <xsl:text>\left[</xsl:text> + </xsl:if> + + <!-- if we do have a preceding sibling, prefix with "and" --> + <xsl:if test="not( $standalone ) and $preceding"> + <xsl:text>\text{ and }</xsl:text> + </xsl:if> + + <!-- output the symbol for the variable we are comparing against --> + <xsl:apply-templates select="." mode="calc-get-value" /> + + <xsl:text> </xsl:text> + <xsl:apply-templates select="./c:*" mode="calc-iversons" /> + + <!-- output bracket only if (a) requested and (b) last in set --> + <xsl:if test=" + $brackets and ( $standalone or not( following-sibling::c:when ) ) + "> + + <xsl:text>\right]</xsl:text> + </xsl:if> +</xsl:template> + + +<xsl:template match="c:cases"> + <xsl:text>\begin{cases}</xsl:text> + <xsl:apply-templates select="./c:case|./c:otherwise" /> + <xsl:text>\end{cases}</xsl:text> + + <!-- if any equations immediately follow, add some extra space so as not to + confuse the reader --> + <xsl:if test="following-sibling::c:*"> + <xsl:text>\;\;\;</xsl:text> + </xsl:if> +</xsl:template> + + +<!-- + Generate a case + + When $force-show is provided, as it is when displaying only small portions of + an equation, it will display the line using Iverson's brackets. +--> +<xsl:template match="c:cases/c:case|c:cases/c:otherwise"> + <xsl:param name="force-show" select="false()" /> + + <!-- generate value --> + <xsl:apply-templates select="./c:*[ not( local-name() = 'when' ) ][1]" /> + + <xsl:choose> + <xsl:when test="not( $force-show )"> + <xsl:text> & </xsl:text> + </xsl:when> + + <xsl:otherwise> + <xsl:text> [</xsl:text> + </xsl:otherwise> + </xsl:choose> + + <xsl:choose> + <xsl:when test="local-name() != 'otherwise'"> + <xsl:if test="not( $force-show )"> + <xsl:text>\text{if } </xsl:text> + </xsl:if> + + <!-- generate condition under which this value will apply --> + <xsl:apply-templates select="./c:when" mode="calc-after"> + <xsl:with-param name="brackets" select="false()" /> + </xsl:apply-templates> + </xsl:when> + + <xsl:otherwise> + <xsl:text>\text{otherwise}</xsl:text> + </xsl:otherwise> + </xsl:choose> + + <!-- determine how we end the line (more cases or end?) --> + <xsl:choose> + <xsl:when test="$force-show"> + <xsl:text>]</xsl:text> + </xsl:when> + + <xsl:when test="following-sibling::c:case|following-sibling::c:otherwise"> + <xsl:text>; \\</xsl:text> + </xsl:when> + + <xsl:otherwise> + <xsl:text>.</xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<!-- + Do nothing with calc-after for any unmatched calculations +--> +<xsl:template match="c:*" mode="calc-after" priority="1"> + <!-- make sure nothing is done for all other nodes with calc-after --> +</xsl:template> + + +<!-- + Display a notation intended for use within Iverson's brackets + + @return LaTeX equation +--> +<xsl:template match="c:eq|c:ne|c:gt|c:lt|c:gte|c:lte" mode="calc-iversons"> + <xsl:variable name="name" select="local-name()" /> + + <!-- map to LaTeX equivalent --> + <xsl:variable name="map"> + <c id="eq">=</c> + <c id="ne">\not=\;</c> + <c id="gt">\gt</c> + <c id="lt">\lt</c> + <c id="gte">\geq</c> + <c id="lte">\leq</c> + </xsl:variable> + + <xsl:value-of select="$map/*[ @id=$name ]" /> + <xsl:text> </xsl:text> + + <xsl:apply-templates select="." mode="calc-recurse" /> +</xsl:template> + + +<!-- + TODO: Technically this is incorrect; sets cannot have duplicate values. This + would be best styled as a vector/matrix/etc. +--> +<xsl:template match="c:set" priority="1"> + <xsl:text>\left[</xsl:text> + <xsl:for-each select="./c:*"> + <xsl:if test="position() > 1"> + <xsl:text>,</xsl:text> + </xsl:if> + + <xsl:apply-templates select="." /> + </xsl:for-each> + <xsl:text>\right]^T</xsl:text> +</xsl:template> + +<!-- style subsets as matrices (easier to read) --> +<xsl:template match="c:set[ ./c:set ]" priority="5"> + <xsl:text>\left[\begin{array}\\</xsl:text> + + <xsl:for-each select="./c:set"> + <xsl:if test="position() > 1"> + <xsl:text>\\</xsl:text> + </xsl:if> + + <xsl:for-each select="./c:*"> + <xsl:if test="position() > 1"> + <xsl:text disable-output-escaping="yes"> & </xsl:text> + </xsl:if> + + <xsl:text>{</xsl:text> + <xsl:apply-templates select="." /> + <xsl:text>}</xsl:text> + </xsl:for-each> + </xsl:for-each> + + <xsl:text>\end{array}\right]</xsl:text> +</xsl:template> + + +<xsl:template match="c:length-of"> + <xsl:text>\#\left(</xsl:text> + <xsl:apply-templates select="./c:*[1]" /> + <xsl:text>\right)</xsl:text> +</xsl:template> + +<xsl:template match="c:cons"> + <xsl:text>\textrm{cons}\left(</xsl:text> + <xsl:apply-templates select="./c:*[1]" /> + <xsl:text>,</xsl:text> + <xsl:apply-templates select="./c:*[2]" /> + <xsl:text>\right)</xsl:text> +</xsl:template> + +<xsl:template match="c:car"> + <xsl:text>\left(</xsl:text> + <xsl:apply-templates select="./c:*[1]" /> + <xsl:text>\right)_0</xsl:text> +</xsl:template> + +<xsl:template match="c:cdr"> + <xsl:text>\textrm{cdr}\left(</xsl:text> + <xsl:apply-templates select="./c:*[1]" /> + <xsl:text>\right)</xsl:text> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/include/depgen.xsl b/src/current/include/depgen.xsl new file mode 100644 index 0000000..d897c27 --- /dev/null +++ b/src/current/include/depgen.xsl @@ -0,0 +1,526 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + TODO: we can combine this dependency discovery with the symbol table + generation, eliminating extra passes + + TODO: dependency symbols should not duplicate metadata +--> + +<xsl:stylesheet version="1.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:preproc="http://www.lovullo.com/rater/preproc" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:t="http://www.lovullo.com/rater/apply-template" + xmlns:c="http://www.lovullo.com/calc" + xmlns:ext="http://www.lovullo.com/ext" + xmlns:util="http://www.lovullo.com/util"> + + +<xsl:variable name="tex-defaults"> + <preproc:syms> + <preproc:sym value="\alpha" vec="A" /> + <preproc:sym value="\beta" vec="B" /> + <preproc:sym value="\gamma" vec="\Gamma" /> + <preproc:sym value="x" vec="X" /> + <preproc:sym value="y" vec="Y" /> + <preproc:sym value="z" vec="Z" /> + </preproc:syms> +</xsl:variable> + + +<!-- simply allows invoking the template with dynamic input --> +<xsl:template name="preproc:gen-deps"> + <xsl:param name="pkg" as="element( lv:package )" /> + <xsl:apply-templates select="$pkg" mode="preproc:gen-deps" /> +</xsl:template> + + +<xsl:template match="*" mode="preproc:gen-deps"> + <xsl:copy> + <xsl:sequence select="@*" /> + + <xsl:message> + <xsl:text>[depgen] *determining symbol dependencies...</xsl:text> + </xsl:message> + + <xsl:apply-templates select="preproc:symtable" mode="preproc:depgen" /> + + <xsl:sequence select="*" /> + </xsl:copy> +</xsl:template> + + +<xsl:template match="preproc:symtable" mode="preproc:depgen" priority="9"> + <xsl:variable name="symtable" select="." /> + + <preproc:sym-deps> + <!-- process dependencies for all non-imported symbols --> + <xsl:for-each select="preproc:sym[ not( @src ) ]"> + <xsl:variable name="cursym" select="." /> + + <xsl:variable name="deps"> + <preproc:deps> + <xsl:apply-templates select="." mode="preproc:depgen" /> + </preproc:deps> + </xsl:variable> + + <!-- do not output duplicates (we used to not output references + to ourselves, but we are now retaining them, since those + data are useful) --> + <xsl:variable name="uniq" select=" + $deps/preproc:deps/preproc:sym-ref[ + not( @name=preceding-sibling::preproc:sym-ref/@name ) + ] + " /> + + <!-- symbols must not have themselves as their own dependency --> + <xsl:if test="$uniq[ not( $cursym/@allow-circular = 'true' ) + and ( @name = $cursym/@name + or @parent = $cursym/@name ) ]"> + <xsl:message terminate="yes" + select="concat( '[preproc] !!! fatal: symbol ', + $cursym/@name, + ' references itself ', + '(circular dependency)' )" /> + </xsl:if> + + <!-- grab the original source symbol for these references and augment them + with any additional dependency metadata --> + <xsl:variable name="syms-rtf"> + <xsl:for-each select="$uniq"> + <xsl:variable name="name" select="@name" /> + <xsl:variable name="sym" select=" + $symtable/preproc:sym[ @name=$name ] + " /> + + <!-- we should never have this problem. --> + <xsl:if test="not( $sym ) and not( @lax='true' )"> + <xsl:message terminate="yes"> + <xsl:text>[depgen] internal error: </xsl:text> + <xsl:text>could not locate dependency symbol `</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>' in local symbol table; needed by </xsl:text> + <xsl:value-of select="$cursym/@name" /> + </xsl:message> + </xsl:if> + + <!-- copy and augment (we set @name because $sym/@name may not exist + if @lax) --> + <preproc:sym name="{@name}"> + <xsl:if test="$sym"> + <xsl:sequence select="$sym/@*" /> + </xsl:if> + + <preproc:meta> + <!-- retain type --> + <xsl:sequence select="$sym/@type" /> + <xsl:sequence select="$sym/@dim" /> + + <!-- copy any additional metadata --> + <xsl:sequence select="@*[ not( local-name() = 'name' ) ]" /> + </preproc:meta> + </preproc:sym> + </xsl:for-each> + </xsl:variable> + <xsl:variable name="syms" select="$syms-rtf/preproc:sym" /> + + <!-- only applicable if the symbol is @lax and the symbol was not + found in the local symbol table --> + <xsl:variable name="lax" select=" + $uniq[ + @lax='true' + and not( @name=$syms/@name ) + ] + " /> + + <preproc:sym-dep name="{@name}"> + <!-- process symbols that have not been found in the local symbol + table (only applicable when cursym is @lax) --> + <xsl:for-each select="$lax"> + <!-- the @lax flag here is simply to denote that this symbol may not + actually exist and that ignoring the check was explicitly + requested (and not a bug in the depgen process) --> + <preproc:sym-ref name="{@name}" lax="true"> + <xsl:sequence select="preproc:meta/@*" /> + </preproc:sym-ref> + </xsl:for-each> + + <!-- @tex provided an non-empty, or function --> + <xsl:for-each select=" + $syms[ + ( @tex and not( @tex='' ) ) + or @type='func' + ]"> + + <xsl:choose> + <!-- even if function, @tex overrides symbol --> + <xsl:when test="@tex and not( @tex='' )"> + <preproc:sym-ref tex="{@tex}"> + <xsl:sequence select="@*" /> + <xsl:sequence select="preproc:meta/@*" /> + </preproc:sym-ref> + </xsl:when> + + <!-- must be a function; use its name --> + <xsl:otherwise> + <preproc:sym-ref> + <xsl:sequence select="@*" /> + <xsl:sequence select="preproc:meta/@*" /> + + <xsl:attribute name="tex"> + <xsl:text>\textrm{</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>}</xsl:text> + </xsl:attribute> + </preproc:sym-ref> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + + <!-- no @tex, @tex empty, no function --> + <xsl:for-each select=" + $syms[ + ( not( @tex ) or @tex='' ) + and not( @type='func' ) + ]"> + + <xsl:variable name="name" select="@name" /> + <xsl:variable name="sym" select="." /> + + <preproc:sym-ref> + <!-- minimal attribute copy (avoid data duplication as much as + possible to reduce modification headaches later on) --> + <xsl:sequence select="@name, @parent" /> + <xsl:sequence select="preproc:meta/@*" /> + + <!-- assign a symbol --> + <xsl:variable name="pos" select="position()" /> + <xsl:attribute name="tex"> + <xsl:variable name="texsym" select=" + $tex-defaults/preproc:syms/preproc:sym[ + position() = $pos + ] + " /> + + <xsl:choose> + <xsl:when test="$sym/@tex and not( $sym/@tex='' )"> + <xsl:value-of select="$sym/@tex" /> + </xsl:when> + + <!-- scalar/vector default --> + <xsl:when test="$texsym and number( $sym/@dim ) lt 2"> + <xsl:value-of select="$texsym/@value" /> + </xsl:when> + + <!-- matrix default --> + <xsl:when test="$texsym"> + <xsl:value-of select="$texsym/@vec" /> + </xsl:when> + + <!-- no default available; generate one --> + <xsl:otherwise> + <xsl:value-of select=" + if ( number( $sym/@dim ) lt 2 ) then '\theta' + else '\Theta' + " /> + <xsl:text>_{</xsl:text> + <xsl:value-of select="$pos" /> + <xsl:text>}</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:attribute> + </preproc:sym-ref> + </xsl:for-each> + </preproc:sym-dep> + </xsl:for-each> + </preproc:sym-deps> +</xsl:template> + + +<xsl:template match="preproc:sym[ @extern='true' ]" mode="preproc:depgen" priority="9"> + <!-- externs will be processed once they are resolved in another package --> +</xsl:template> + + +<!-- all symbols with a @parent (e.g. generators) should depend on the parent + itself (which of course introduces the parent's dependencies into the tree) --> +<xsl:template match="preproc:sym[ @parent ]" mode="preproc:depgen" priority="7"> + <preproc:sym-ref name="{@parent}" /> +</xsl:template> + + +<xsl:template match="preproc:sym[ @type='rate' ]" mode="preproc:depgen" priority="5"> + <xsl:variable name="name" select="@name" /> + <xsl:variable name="pkg" as="element( lv:package )" + select="root(.)" /> + + <xsl:variable name="rate" as="element( lv:rate )" + select="$pkg/lv:rate[ @yields=$name ]" /> + + <xsl:apply-templates mode="preproc:depgen" + select="$rate" /> +</xsl:template> + + +<xsl:template match="preproc:sym[ @type='class' ]" mode="preproc:depgen" priority="5"> + <!-- all class symbol names are prefixed with ":class:" --> + <xsl:variable name="as" select="substring-after( @name, ':class:' )" /> + <xsl:variable name="pkg" as="element( lv:package )" + select="root(.)" /> + + <xsl:apply-templates + select="$pkg/lv:classify[ @as=$as ]" + mode="preproc:depgen" /> +</xsl:template> + + +<xsl:template match="preproc:sym[ @type='param' ]" mode="preproc:depgen" priority="5"> + <xsl:variable name="name" select="@name" /> + <xsl:apply-templates + select="root(.)/lv:param[ @name=$name ]" + mode="preproc:depgen" /> +</xsl:template> + + +<xsl:template match="preproc:sym[ @type='func' ]" mode="preproc:depgen" priority="5"> + <xsl:variable name="name" select="@name" /> + <xsl:variable name="pkg" as="element( lv:package )" + select="root(.)" /> + + <xsl:apply-templates + select="$pkg/lv:function[ @name=$name ]" + mode="preproc:depgen" /> +</xsl:template> + + +<xsl:template match="preproc:sym[ @type='type' ]" mode="preproc:depgen" priority="5"> + <xsl:variable name="name" select="@name" /> + <xsl:variable name="pkg" as="element( lv:package )" + select="root(.)" /> + + <!-- a typedef could optionally be contained within another typedef --> + <xsl:apply-templates mode="preproc:depgen" select=" + $pkg/lv:typedef[ @name=$name ] + , $pkg/lv:typedef//lv:typedef[ @name=$name ] + " /> +</xsl:template> + +<xsl:template match="preproc:sym[ @type='lparam' ]" mode="preproc:depgen" priority="5"> + <!-- do nothing --> +</xsl:template> + +<xsl:template match="preproc:sym[ @type='const' ]" mode="preproc:depgen" priority="5"> + <!-- do nothing --> +</xsl:template> + +<xsl:template match="preproc:sym[ @type='tpl' ]" mode="preproc:depgen" priority="5"> + <!-- do nothing --> +</xsl:template> + +<xsl:template match="preproc:sym[ @type='meta' ]" mode="preproc:depgen" priority="5"> + <!-- do nothing --> +</xsl:template> + + +<xsl:template match="preproc:sym" mode="preproc:depgen" priority="1"> + <xsl:message terminate="yes"> + <xsl:text>[depgen] error: unexpected symbol </xsl:text> + <xsl:sequence select="." /> + </xsl:message> +</xsl:template> + + +<xsl:template name="preproc:depgen-c-normal" match="c:value-of|c:when" mode="preproc:depgen" priority="5"> + <xsl:param name="name" select="@name" /> + <xsl:variable name="pkg" as="element( lv:package )" + select="root(.)" /> + + + <xsl:variable name="sym" + select="$pkg/preproc:symtable/preproc:sym[ @name=$name ]" /> + + <!-- see if there is a c:let associated with this name --> + <xsl:variable name="let" select=" + ancestor::c:let[ c:values/c:value/@name=$name ] + " /> + + <xsl:choose> + <!-- c:let reference --> + <xsl:when test="$let"> + <preproc:sym-ref name=":{$let/@name}:{$name}" /> + </xsl:when> + + <!-- scalar constant --> + <xsl:when test="( $sym/@type='const' ) and ( $sym/@dim='0' )"> + <!-- while these are optimized away, they are still useful for evaluating + dependency trees and generating code --> + <preproc:sym-ref name="{$sym/@name}" /> + </xsl:when> + + <!-- function param reference --> + <xsl:when test="$name=ancestor::lv:function/lv:param/@name"> + <xsl:variable name="fname" as="xs:string" + select="ancestor::lv:function/@name" /> + + <preproc:sym-ref name=":{$fname}:{$name}" + varname="{$name}"/> + </xsl:when> + + <!-- index reference --> + <xsl:when test="$name=ancestor::c:*[ @of ]/@index" /> + + <!-- unknown symbol (it is important to do this after the above checks) --> + <xsl:when test="not( $sym )"> + <!-- do not terminate; validator can provide additional information --> + <xsl:message> + <xsl:text>[depgen] warning: unknown symbol `</xsl:text> + <xsl:value-of select="$name" /> + <xsl:text>'</xsl:text> + </xsl:message> + </xsl:when> + + <xsl:when test="$sym/@parent"> + <preproc:sym-ref name="{$sym/@name}" parent="{$sym/@parent}" /> + </xsl:when> + + <!-- just an average 'ol symbol --> + <xsl:otherwise> + <preproc:sym-ref name="{$name}" /> + </xsl:otherwise> + </xsl:choose> + + <xsl:apply-templates mode="preproc:depgen" /> +</xsl:template> + + +<xsl:template match="c:sum[@of]|c:product[@of]" mode="preproc:depgen" priority="5"> + <!-- process using @of --> + <xsl:call-template name="preproc:depgen-c-normal"> + <xsl:with-param name="name" select="@of" /> + </xsl:call-template> +</xsl:template> + + +<xsl:template match="c:apply" mode="preproc:depgen" priority="5"> + <!-- no special treatment yet --> + <xsl:call-template name="preproc:depgen-c-normal" /> +</xsl:template> + +<xsl:template match="c:apply/c:arg" mode="preproc:depgen" priority="5"> + <!-- arguments may have calculations, so we must recurse --> + <xsl:apply-templates mode="preproc:depgen" /> +</xsl:template> + + +<xsl:template match="c:let/c:values/c:value" mode="preproc:depgen" priority="5"> + <!-- do not consider the c:value name --> + <xsl:apply-templates mode="preproc:depgen" /> +</xsl:template> + + +<xsl:template name="preproc:depgen-match"> + <xsl:param name="on" select="@on" /> + + <xsl:variable name="class" select="ancestor::lv:classify" /> + <xsl:variable name="sym" + select="root(.)/preproc:symtable/preproc:sym[ @name=$on ]" /> + + <!-- are we depending on another classification? --> + <xsl:if test="$sym/@type='cgen'"> + <xsl:variable name="cname" select="substring-after( $sym/@parent, ':class:' )" /> + + <!-- check if one of our dependencies wants to be external to the classifier, + but we're trying to pull them in...tug-of-war --> + <xsl:if test="$sym/@extclass='true' and not( $class/@external='true' )"> + <xsl:message terminate="yes"> + <xsl:text>[preproc] !!! fatal: internal classification `</xsl:text> + <xsl:value-of select="$class/@as" /> + <xsl:text>' cannot pull in external classification `</xsl:text> + <xsl:value-of select="$cname" /> + <xsl:text>'</xsl:text> + </xsl:message> + </xsl:if> + </xsl:if> + + <!-- process the @on --> + <xsl:call-template name="preproc:depgen-c-normal"> + <xsl:with-param name="name" select="$on" /> + </xsl:call-template> +</xsl:template> + + +<xsl:template match="lv:match[ @value ]" mode="preproc:depgen" priority="5"> + <!-- process the @value --> + <xsl:call-template name="preproc:depgen-c-normal"> + <xsl:with-param name="name" select="@value" /> + </xsl:call-template> + + <xsl:call-template name="preproc:depgen-match" /> +</xsl:template> + + +<xsl:template match="lv:match[ @anyOf ]" mode="preproc:depgen" priority="6"> + <!-- process the "normal" match --> + <xsl:call-template name="preproc:depgen-match" /> + + <!-- we depend on the type --> + <preproc:sym-ref name="{@anyOf}" /> + <xsl:call-template name="preproc:depgen-match" /> +</xsl:template> + + +<xsl:template match="lv:match[ @pattern ]" mode="preproc:depgen" priority="5"> + <!-- there are no pattern dependencies; process @on --> + <xsl:call-template name="preproc:depgen-match" /> +</xsl:template> + + +<!-- match on calculated value --> +<xsl:template match="lv:match[ c:* ]" mode="preproc:depgen" priority="6"> + <!-- process the "normal" match --> + <xsl:call-template name="preproc:depgen-match" /> + + <!-- process the calculation dependencies --> + <xsl:apply-templates select="c:*" mode="preproc:depgen" /> +</xsl:template> + + +<xsl:template match="lv:template/lv:param" mode="preproc:depgen" priority="9"> + <!-- ignore --> +</xsl:template> + + +<xsl:template match="lv:param" mode="preproc:depgen" priority="5"> + <!-- while the type is reduced to a primitive, let's still include the + dependency symbol --> + <preproc:sym-ref name="{@type}" /> +</xsl:template> + + +<xsl:template match="lv:typedef" mode="preproc:depgen" priority="5"> + <!-- we depend on any types that we create a union of --> + <xsl:for-each select="lv:union/lv:typedef"> + <preproc:sym-ref name="{@name}" /> + </xsl:for-each> +</xsl:template> + + +<!-- @class deps --> +<xsl:template match="lv:class" mode="preproc:depgen" priority="5"> + <preproc:sym-ref name=":class:{@ref}" class-no="{@no}" /> +</xsl:template> + + +<xsl:template match="c:*|lv:*" mode="preproc:depgen" priority="3"> + <!-- ignore --> + <xsl:apply-templates mode="preproc:depgen" /> +</xsl:template> + + +<xsl:template match="text()" mode="preproc:depgen" priority="2"> + <!-- not interested. nope. --> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/include/display.xsl b/src/current/include/display.xsl new file mode 100644 index 0000000..7296b56 --- /dev/null +++ b/src/current/include/display.xsl @@ -0,0 +1,551 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Display-related tasks +--> + +<xsl:stylesheet version="1.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:c="http://www.lovullo.com/calc" + xmlns:sym="http://www.lovullo.com/rater/symbol-map" + xmlns:preproc="http://www.lovullo.com/rater/preproc" + xmlns:summary="http://www.lovullo.com/rater/summary" + + xmlns:exsl="http://exslt.org/common" + extension-element-prefixes="exsl"> + + +<!-- maps certain elements to their default symbols --> +<xsl:variable name="symbol-map" select="document( 'symbol-map.xml' )/sym:symbol-map/*" /> + +<!-- easy-to-reference linked dependency list --> +<xsl:variable name="edeps" select="/lv:*/preproc:deps/preproc:sym" /> + +<xsl:template name="get-symbol-map"> + <xsl:copy-of select="$symbol-map" /> +</xsl:template> + + +<xsl:template match="preproc:sym[ @type='rate' ]" mode="summary:desc" priority="5"> + <span class="letlist-{@name}"> + <a href="#{@name}"> + <xsl:value-of select="@name" /> + </a> + <xsl:text> scalar</xsl:text> + </span> +</xsl:template> + + +<xsl:template match="preproc:sym[ @type='gen' ]" mode="summary:desc" priority="5"> + <span class="letlist-{@parent}"> + <a href="#{@parent}"> + <xsl:value-of select="@name" /> + </a> + <xsl:text> generator; vector</xsl:text> + + <span class="param"> + <xsl:text> (</xsl:text> + <a href="#{@parent}"> + <xsl:value-of select="@parent" /> + </a> + <xsl:text>)</xsl:text> + </span> + </span> +</xsl:template> + + +<xsl:template match="preproc:sym[ @type='cgen' ]" mode="summary:desc" priority="5"> + <xsl:variable name="parent" select="@parent" /> + <xsl:variable name="sym" select=" + ancestor::preproc:symtable/preproc:sym[ @name=$parent ] + " /> + + <xsl:apply-templates select="$sym" mode="summary:desc" /> +</xsl:template> + + +<xsl:template match="preproc:sym[ @type='class' ]" mode="summary:desc" priority="5"> + <xsl:variable name="name" select="@name" /> + <xsl:variable name="document" select=" + if ( @src ) then + document( concat( @src, '.xmlo' ), . )/lv:* + else + /lv:* + " /> + <xsl:variable name="class" select=" + $document/lv:classify[ + @as=substring-after( $name, ':class:' ) + ] + " /> + + <span class="letlist-{$class/@as}"> + <xsl:text>"</xsl:text> + <xsl:value-of select="$class/@desc" /> + <xsl:text>"</xsl:text> + <xsl:text> classification </xsl:text> + + <xsl:choose> + <xsl:when test="@dim = '0'"> + <xsl:text>scalar</xsl:text> + </xsl:when> + + <xsl:when test="@dim = '1'"> + <xsl:text>vector</xsl:text> + </xsl:when> + + <xsl:when test="@dim = '2'"> + <xsl:text>matrix</xsl:text> + </xsl:when> + + <xsl:otherwise> + <xsl:text> [dim </xsl:text> + <xsl:value-of select="@dim" /> + <xsl:text>]</xsl:text> + </xsl:otherwise> + </xsl:choose> + + <!-- TODO: use generator in letlist-* --> + <span class="param"> + <xsl:text> (</xsl:text> + <a href="#:class:{$class/@as}"> + <xsl:value-of select="$class/@as" /> + </a> + <xsl:text>)</xsl:text> + </span> + </span> +</xsl:template> + + +<xsl:template match="preproc:sym[ @type='const' ]" mode="summary:desc" priority="5"> + <xsl:value-of select="@name" /> +</xsl:template> + + +<xsl:template match="preproc:sym[ @type='param' ]" mode="summary:desc" priority="5"> + <xsl:variable name="name" select="@name" /> + <xsl:variable name="document" select=" + if ( @src ) then + document( concat( @src, '.xmlo' ), . )/lv:* + else + /lv:* + " /> + <xsl:variable name="param" select=" + $document/lv:param[ + @name=$name + ] + " /> + + <xsl:value-of select="$param/@desc" /> + + <span class="param letlist-{$param/@name}"> + <xsl:text> (</xsl:text> + <a href="#{$param/@name}"> + <xsl:value-of select="$param/@name" /> + </a> + <xsl:text>)</xsl:text> + </span> +</xsl:template> + + +<xsl:template match="preproc:sym" mode="summary:desc" priority="1"> + <xsl:value-of select="@name" /> + <xsl:text> (!)</xsl:text> +</xsl:template> + + +<xsl:template name="get-symbol"> + <xsl:param name="name" select="@name" /> + <xsl:param name="index" /> + <xsl:param name="index-symbol" /> + <xsl:param name="default" /> + + <preproc:sym-ref name="{$name}"> + <!-- might be an empty string (if provided) --> + <xsl:if test="$default"> + <xsl:attribute name="default" select="$default" /> + </xsl:if> + </preproc:sym-ref> + + <xsl:choose> + <xsl:when test="$index-symbol != ''"> + <xsl:text>_{</xsl:text> + <xsl:value-of select="$index-symbol" /> + <xsl:text>}</xsl:text> + </xsl:when> + + <xsl:when test="$index"> + <xsl:text>_{</xsl:text> + <preproc:sym-ref name="{$index}" default="{$index}" /> + <xsl:text>}</xsl:text> + </xsl:when> + </xsl:choose> +</xsl:template> + + +<xsl:template name="_get-index-symbol"> + <xsl:param name="element" /> + <xsl:param name="index" /> + <xsl:param name="search" /> + + <xsl:call-template name="get-symbol"> + <xsl:with-param name="name" select="$index" /> + <xsl:with-param name="search" select="$search" /> + <xsl:with-param name="default" select="$index" /> + </xsl:call-template> +</xsl:template> + + +<!-- + Retrieve the default symbol for the given type (in LaTeX) + + If the type is "function", the given name will be used for its default symbol. + + @param Node element node to retrieve symbol for + @param NodeSet search all document nodes + + @return default symbol (LaTeX) +--> +<xsl:template name="_get-default-symbol"> + <xsl:param name="element" /> + <xsl:param name="name" /> + <xsl:param name="index" /> + <xsl:param name="search" /> + + <xsl:variable name="type"> + <xsl:choose> + <xsl:when test=" + ( local-name( $element ) = 'param' ) + and ( local-name( $element/.. ) = 'function' )"> + + <!-- this is a function parameter; make a distinction between a global + parameter --> + <xsl:text>fparam</xsl:text> + </xsl:when> + + <!-- if matching lv:classify/@as, then it represents an accumulator --> + <xsl:when test=" + ( local-name( $element ) = 'classify' ) + and ( $element/@as = $name ) + "> + + <xsl:text>class</xsl:text> + </xsl:when> + + <xsl:when test="$element/@generates = $name"> + <xsl:text>generator</xsl:text> + </xsl:when> + + <xsl:otherwise> + <xsl:value-of select="local-name( $element )" /> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="symbol" select="$symbol-map[@type=$type]" /> + + <!-- output the symbol default --> + <xsl:choose> + <!-- certain types use their own name for a default (e.g. functions) --> + <xsl:when test="$symbol/sym:name"> + <xsl:text>\textrm{</xsl:text> + <xsl:value-of select="$name" /> + <xsl:text>}</xsl:text> + </xsl:when> + + <xsl:when test="$symbol/sym:nothing"> + <!-- do nothing; no symbol is desired --> + </xsl:when> + + <xsl:otherwise> + <xsl:if test="$index and ( $index != '' )"> + <xsl:text>(</xsl:text> + </xsl:if> + + <xsl:value-of select="$symbol" /> + + <!-- determine if our default index should be subscript or superscript --> + <xsl:variable name="subsup"> + <xsl:choose> + <xsl:when test="$symbol/@index-pos"> + <xsl:value-of select="$symbol/@index-pos" /> + </xsl:when> + + <!-- default to subscript --> + <xsl:otherwise> + <xsl:text>_</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <!-- in addition to the symbol itself, which alone is not likely to be + unique, we will add a subscript to uniquely identify it by number --> + <xsl:if test="$search"> + <xsl:value-of select="$subsup" /> + <xsl:text>{</xsl:text> + + <xsl:call-template name="_get-name-index"> + <xsl:with-param name="element" select="$element" /> + <xsl:with-param name="name" select="$name" /> + <xsl:with-param name="search" select="$search" /> + </xsl:call-template> + + <xsl:text>}</xsl:text> + </xsl:if> + + <xsl:if test="$index and ( $index != '' )"> + <xsl:text>)</xsl:text> + </xsl:if> + + <!-- if an index was given, and our default index was *not* a subscript, + then we can dedicate the subscript to the index --> + <xsl:if test="$index and ( $index != '' )"> + <xsl:text>_{</xsl:text> + <xsl:value-of select="$index" /> + <xsl:text>}</xsl:text> + </xsl:if> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<!-- + Retrieve index of the element associated with the given name across all named + elements of the same type and parent type in all of $search + + TODO: surely there's a more performant manner...not that speed is an issue + right now + + @param Node element node to retrieve symbol for + @param NodeSet search all document nodes + + @return index +--> +<xsl:template name="_get-name-index"> + <xsl:param name="element" /> + <xsl:param name="name" /> + <xsl:param name="search" /> + + <xsl:choose> + <!-- functions are handled slightly differently, as they introduce scope --> + <xsl:when test="local-name( $element/.. ) = 'function'"> + <xsl:for-each select="$element/../lv:param"> + <xsl:if test="@name = $name"> + <xsl:value-of select="position()" /> + </xsl:if> + </xsl:for-each> + </xsl:when> + + <!-- non-function --> + <xsl:otherwise> + <xsl:value-of select=" + $search//summary:default-indexes/summary:index[ @name=$name ]/@value" + /> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<!-- + Retrieve description for the given element by name + + $vardesc, for those who support it, is useful if the description describes the + node, not a variable generated from it. For example, lv:classify's description + is a short description of the classification, but if documenting @yields, we + want to describe what it is yielding, which would not be immediately clear + from the description. + + @param string name name of element + @param NodeSet search all documents to search + + @return element description +--> +<xsl:template name="get-desc"> + <xsl:param name="name" /> + <xsl:param name="search" /> + + <!-- XXX: Have to maintain this list! --> + <xsl:variable name="desc" + select="$search//summary:descs/summary:desc[ @name=$name ]/@desc" /> + + <xsl:choose> + <xsl:when test="$desc"> + <xsl:copy-of select="$desc" /> + </xsl:when> + + <!-- if we cannot find the element, then display an error --> + <xsl:otherwise> + <span class="error"> + <xsl:text>Unknown @name reference: </xsl:text> + <xsl:value-of select="$name" /> + </span> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + + + + +<!-- + Retrieve processed name for the given element by name + + @param string name name of element + @param NodeSet search all documents to search + + @return element description +--> +<xsl:template name="get-name"> + <xsl:param name="name" /> + <xsl:param name="search" /> + + <xsl:value-of select=" + $search//summary:descs/summary:desc[ @name=$name ]/@display + " /> +</xsl:template> + + +<xsl:template match="lv:rate" mode="gen-let-list" priority="5"> + <xsl:param name="deps" /> + <xsl:param name="context" /> + + <xsl:call-template name="do-gen-let-list"> + <xsl:with-param name="symname" select="@yields" /> + <xsl:with-param name="context" select="$context" /> + </xsl:call-template> +</xsl:template> + + +<xsl:template match="lv:function" mode="gen-let-list" priority="5"> + <xsl:param name="deps" /> + + <xsl:call-template name="do-gen-let-list"> + <xsl:with-param name="symname" select="@name" /> + </xsl:call-template> +</xsl:template> + + +<xsl:template match="*" mode="gen-let-list" priority="1"> + <xsl:message terminate="yes"> + <xsl:text>[summary] !!! unknown let-list type </xsl:text> + <xsl:value-of select="name()" /> + </xsl:message> +</xsl:template> + + +<!-- + Generate list of let statements describing each variable in the given node set + + Variables come from various sources depending on the operation being + performed. +--> +<xsl:template name="do-gen-let-list"> + <xsl:param name="context" /> + <xsl:param name="symname" /> + + <xsl:variable name="deps" select=" + /lv:*/preproc:sym-deps/preproc:sym-dep[ + @name=$symname + ] + " /> + + <ul class="let"> + <!-- output a description for each dependency --> + <xsl:variable name="result"> + <xsl:for-each select=" + /lv:*/preproc:symtable/preproc:sym[ + not( @type='lparam' ) + and @name=$deps/preproc:sym-ref/@name + ] + "> + + <xsl:call-template name="_gen-let-list-item"> + <xsl:with-param name="context" select="$context" /> + </xsl:call-template> + </xsl:for-each> + + + <!-- handle c:let formatting separately --> + <xsl:for-each select=" + /lv:*/preproc:symtable/preproc:sym[ + @type='lparam' + and @name=$deps/preproc:sym-ref/@name + ] + "> + + <xsl:call-template name="_gen-let-list-item"> + <xsl:with-param name="context" select="$context" /> + <xsl:with-param name="class" select="'letequ'" /> + </xsl:call-template> + </xsl:for-each> + </xsl:variable> + + <xsl:apply-templates select="$result" mode="typeset-final"> + <xsl:with-param name="deps" select="$deps" /> + </xsl:apply-templates> + </ul> +</xsl:template> + + +<xsl:template name="_gen-let-list-item"> + <xsl:param name="context" /> + <xsl:param name="class" /> + + <li> + <xsl:if test="$class"> + <xsl:attribute name="class" select="$class" /> + </xsl:if> + + <xsl:choose> + <xsl:when test="@type='lparam' and $context"> + <xsl:text>\(</xsl:text> + <preproc:sym-ref name="{@name}" /> + <xsl:text> = </xsl:text> + + <xsl:variable name="varname" select="@varname" /> + + <xsl:apply-templates select=" + $context//c:let/c:values/c:value[ + @name=$varname + ]/c:* + " /> + <xsl:text>\) </xsl:text> + + <span class="letdesc"> + <xsl:text>(</xsl:text> + <xsl:value-of select="@desc" /> + <xsl:text>)</xsl:text> + </span> + </xsl:when> + + <xsl:otherwise> + <xsl:text>let \(</xsl:text> + <preproc:sym-ref name="{@name}" /> + <xsl:text>\) = </xsl:text> + + <xsl:apply-templates select="." mode="summary:desc" /> + </xsl:otherwise> + </xsl:choose> + + <!-- + <xsl:variable name="param-name"> + <xsl:call-template name="get-name"> + <xsl:with-param name="name" select="$param" /> + <xsl:with-param name="search" select="/" /> + </xsl:call-template> + </xsl:variable> + + <xsl:if test="$param-name != ''"> + <span class="param letlist-{$param-name}"> + <xsl:text> (</xsl:text> + <a href="#{$param-name}"> + <xsl:value-of select="$param-name" /> + </a> + <xsl:text>)</xsl:text> + </span> + </xsl:if> + --> + </li> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/include/dslc-base.xsl b/src/current/include/dslc-base.xsl new file mode 100644 index 0000000..53a3195 --- /dev/null +++ b/src/current/include/dslc-base.xsl @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Additional functionality provided by dslc + + XSL does not provide every feature suitable for compilation (which is no + suprise, since this was not its intended use case). As such, dslc provides + additional features that are defined/abstracted within this file; every + template that is intended for use with dslc should include this. +--> +<xsl:stylesheet version="1.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + + +<!-- + Package source path, stripped of its extension + + XSL does not provide a means of exposing the file path (nor should it, + really). This param will hold the source path of the package, sans its + extension, relative to the project root that was used during compilation. + + I.e., given this source path: + suppliers/common/foo.xml + we would expect this value for __srcpkg: + suppliers/common/foo + + By stripping the extension, we have the benefit of being void of any semantics + that may be associated with it (e.g. xml vs xmlo vs xmle); rather, that + information should be derived from the structe of the document itself and the + path can be used as an identifier to describe the document as a whole, + regardless of what form it is in. + + Consequently, no two files are able to have the same __srcpkg string; this + value may therefore be used for disambiguation. +--> +<xsl:param name="__srcpkg" /> + + +<!-- + Relative path to project root + + The project root is determined entirely by __srcpath by repeating the string + "../" for the number of occurrances of "/". +--> +<xsl:param name="__relroot" /> + + +<!-- + Random value that may be used to seed random values + + XSLT is deterministic and does not offer support for generating random values; + its generate-id() function is not sufficient for cross-package generation. +--> +<xsl:param name="__rseed" /> + + +<!-- + Root node of template on which stylesheet was invoked + + This points to the original, unprocessed document. This is especially + important for `document' function calls, which use nodes as a reference + point for resolving relative paths. +--> +<xsl:variable name="__entry-root" select="/" /> + + + +<!-- + Apply relative root to PATH + + If PATH is an absolute path, it will be prefixed with the relative root + with the leading path delimiter stripped; otherwise, it will be echoed + as-is. +--> +<xsl:template name="__apply-relroot"> + <xsl:param name="path" /> + + <xsl:choose> + <xsl:when test="starts-with( $path, '/' )"> + <xsl:value-of select="$__relroot" /> + <xsl:value-of select="substring-after( $path, '/' )" /> + </xsl:when> + + <xsl:otherwise> + <xsl:value-of select="$path" /> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/include/entry-form.xsl b/src/current/include/entry-form.xsl new file mode 100644 index 0000000..3d6b3c8 --- /dev/null +++ b/src/current/include/entry-form.xsl @@ -0,0 +1,323 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Outputs HTML form that can be used to feed values to the rater for testing +--> + +<xsl:stylesheet version="1.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:c="http://www.lovullo.com/calc" + xmlns:l="http://www.lovullo.com/rater/linker" + xmlns:util="http://www.lovullo.com/util" + xmlns:ext="http://www.lovullo.com/ext" + xmlns:preproc="http://www.lovullo.com/rater/preproc" + + xmlns:exsl="http://exslt.org/common" + xmlns:str="http://exslt.org/strings" + extension-element-prefixes="exsl str"> + + +<!-- + Generate HTML entry form for testing + + Allows for collection of data to feed to the rater. + + The entry form will only be generated for raters, not other packages (since + actual rating will need to be performed). + + @return form HTML +--> +<xsl:template match="lv:package" mode="entry-form"> + <xsl:param name="root-pkg" /> + + <form class="entry-form"> + <h1>Rating Test Case</h1> + + <div class="foot"> + <p id="prior-message"></p> + + <div> + <input type="submit" value="Calculate Premium" /> + <input type="reset" value="Reset" /> + </div> + + <div class="final-premium"></div> + + <div class="final-accept"> + <button id="final-accept-good">Looks Good!</button> + <button id="final-accept-bad">Incorrect</button> + </div> + + <div class="final-comments"> + <h1>Submit Test Case</h1> + + <p>Submission comments (please describe what you were testing, the + desired result and, if the premium was incorrect, what went wrong):</p> + + <textarea id="final-comments"></textarea> + + <div id="final-expect-container"> + <p>Expected premium (if known; must be exact); this will allow us to + automatically re-run this test when we believe that the problem has been + fixed. <strong>Otherwise, you must re-test manually:</strong></p> + <input type="text" id="final-expected" value="" /> + <em>(Only fill out if it does not hit the minimum premium.)</em> + </div> + + <br /> + <label><input type="checkbox" id="final-waiting"> Requires Testing</input></label> + + <br /> + <button id="final-submit">Submit</button> + <button id="final-submit-new">Submit As New Test Case</button> + <button id="final-cancel">Nevermind. Cancel.</button> + </div> + </div> + + <dl> + <!-- generate HTML elements for each *global* parameter, *but only if it + is used in the rater* --> + <xsl:apply-templates + select="/lv:package/l:dep/preproc:sym[ @type='param' ]" + mode="entry-form"> + + <xsl:with-param name="root-pkg" select="$root-pkg" /> + </xsl:apply-templates> + </dl> + </form> + + <script type="text/javascript" src="{$fw-path}/rater/scripts/entry-form.js"></script> +</xsl:template> + + +<!-- + Generate text and input for a global parameter + + @return parameter HTML +--> +<xsl:template match="preproc:sym" mode="entry-form"> + <xsl:param name="root-pkg" /> + + <xsl:variable name="self" select="." /> + <xsl:variable name="package" select=" + if ( @src and not( @src='' ) ) then + document( concat( @src, '.xmlo' ), . )/lv:* + else + $root-pkg + " /> + + <xsl:variable name="name"> + <xsl:value-of select="@name" /> + + <!-- if this is a set, then we will need to generate an array of + elements --> + <xsl:if test="number(@dim) gt 0"> + <xsl:text>[]</xsl:text> + </xsl:if> + </xsl:variable> + + <xsl:variable name="param" + select="$package/lv:param[ @name=$self/@name ]" /> + + <dt id="param-{@name}"> + <xsl:value-of select="@desc" /> + </dt> + + <xsl:variable name="matrix"> + <xsl:if test="number(@dim) gt 1"> + <xsl:text> matrix</xsl:text> + </xsl:if> + </xsl:variable> + + <!-- generate field itself --> + <dd id="param-input-{@name}"> + <div class="entry-row{$matrix}"> + <div class="entry-field"> + <xsl:apply-templates select="$param" mode="entry-form-field"> + <xsl:with-param name="name" select="$name" /> + <xsl:with-param name="sym" select="$self" /> + <xsl:with-param name="pkg" select="$package" /> + </xsl:apply-templates> + + <!-- if this is a set, add the ability to remove values --> + <xsl:if test="number(@dim) gt 0"> + <button class="entry-rm">-</button> + </xsl:if> + </div> + + <xsl:if test="number(@dim) gt 1"> + <button class="entry-add-matrix">+</button> + </xsl:if> + </div> + + <!-- if this is a set, add ability to add values --> + <xsl:if test="number(@dim) gt 0"> + <button class="entry-add">+</button> + </xsl:if> + </dd> +</xsl:template> + + +<!-- + Generate input field for integer parameters + + @return parameter HTML +--> +<xsl:template match="lv:param[@type='integer']" mode="entry-form-field"> + <xsl:param name="name" select="@name" /> + <input type="text" name="{$name}" value="{@default}" /> +</xsl:template> + + +<!-- + Generate input field for float parameters + + @return parameter HTML +--> +<xsl:template match="lv:param[@type='float']" mode="entry-form-field"> + <xsl:param name="name" select="@name" /> + <input type="text" name="{$name}" value="{@default}" /> +</xsl:template> + + +<!-- + Generate radio fields for boolean parameters + + @return parameter HTML +--> +<xsl:template match="lv:param[@type='boolean']" mode="entry-form-field"> + <xsl:param name="name" select="@name" /> + + <xsl:variable name="default-y"> + <xsl:if test="@default = '1'"> + <xsl:text>selected</xsl:text> + </xsl:if> + </xsl:variable> + + <xsl:variable name="default-n"> + <xsl:if test="@default = '0'"> + <xsl:text>selected</xsl:text> + </xsl:if> + </xsl:variable> + + <select name="{$name}"> + <option selected="{$default-y}" value="1">Yes</option> + <option selected="{$default-n}" value="0">No</option> + </select> +</xsl:template> + + +<!-- + Handle parameters that are either of unknown or user-defined types + + @return parameter HTML +--> +<xsl:template match="lv:param" mode="entry-form-field"> + <xsl:param name="name" select="@name" /> + <xsl:param name="sym" /> + <xsl:param name="pkg" /> + + <xsl:variable name="type" select="@type" /> + + <!-- the typedef may or may not be in the same package as the param --> + <xsl:variable name="typesym" select=" + $pkg/preproc:symtable/preproc:sym[ + @type='type' + and @name=$type + ] + " /> + + <!-- if the @src attribute is empty, then it resides within the same package + --> + <xsl:variable name="typesrc"> + <xsl:choose> + <xsl:when test="@src"> + <xsl:value-of select="@src" /> + </xsl:when> + + <xsl:otherwise> + <xsl:value-of select="$sym/@src" /> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <!-- load the typedef from the appropriate package --> + <xsl:variable name="typepkg" select=" + if ( @src and not( @src='' ) ) then + document( concat( $typesrc, '.xmlo' ), $sym )/lv:* + else + $pkg + " /> + <!-- this makes maintinance more difficult, but speeds up searching large + trees --> + <xsl:variable name="typedef" select=" + $typepkg/lv:typedef[ @name=$type ] + |$typepkg/lv:typedef/lv:union/lv:typedef[ @name=$type ] + " /> + + <xsl:choose> + <xsl:when test="$typedef/lv:enum|$typedef/lv:union"> + <xsl:apply-templates select="." mode="entry-form-field-enum"> + <xsl:with-param name="name" select="$name" /> + <xsl:with-param name="typedef" select="$typedef" /> + </xsl:apply-templates> + </xsl:when> + + <xsl:otherwise> + <xsl:message> + <xsl:text>[summary] warning: unknown param type `</xsl:text> + <xsl:value-of select="$typesym/@src" /> + <xsl:text>/</xsl:text> + <xsl:value-of select="@type" /> + <xsl:text>'</xsl:text> + </xsl:message> + + <span class="error"> + <xsl:text>Unknown type: </xsl:text> + <xsl:value-of select="@type" /> + </span> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<!-- + Generate HTML for enumerated lists + + @return parameter HTML +--> +<xsl:template match="lv:param" mode="entry-form-field-enum"> + <xsl:param name="name" select="@name" /> + <xsl:param name="typedef" /> + + <xsl:variable name="type" select="@type" /> + + <!-- get all fields, even if they're within a union --> + <xsl:variable name="fields" select="$typedef//lv:enum/lv:item" /> + + <select name="{$name}" value="{@default}"> + <option value=""></option> + + <xsl:for-each select="$fields"> + <xsl:variable name="value"> + <xsl:choose> + <xsl:when test="@value"> + <xsl:value-of select="@value" /> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="@name" /> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <option value="{$value}"> + <xsl:value-of select="@name" /> + <xsl:text>: </xsl:text> + <xsl:value-of select="@desc" /> + </option> + </xsl:for-each> + </select> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/include/exslt/str.tokenize.template.xsl b/src/current/include/exslt/str.tokenize.template.xsl new file mode 100644 index 0000000..6da270b --- /dev/null +++ b/src/current/include/exslt/str.tokenize.template.xsl @@ -0,0 +1,65 @@ +<?xml version="1.0"?> +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:str="http://exslt.org/strings" + extension-element-prefixes="str"> + +<xsl:template name="str:tokenize"> + <xsl:param name="string" select="''" /> + <xsl:param name="delimiters" select="' 	
'" /> + <xsl:choose> + <xsl:when test="not($string)" /> + <xsl:when test="not($delimiters)"> + <xsl:call-template name="str:_tokenize-characters"> + <xsl:with-param name="string" select="$string" /> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="str:_tokenize-delimiters"> + <xsl:with-param name="string" select="$string" /> + <xsl:with-param name="delimiters" select="$delimiters" /> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template name="str:_tokenize-characters"> + <xsl:param name="string" /> + <xsl:if test="$string"> + <token><xsl:value-of select="substring($string, 1, 1)" /></token> + <xsl:call-template name="str:_tokenize-characters"> + <xsl:with-param name="string" select="substring($string, 2)" /> + </xsl:call-template> + </xsl:if> +</xsl:template> + +<xsl:template name="str:_tokenize-delimiters"> + <xsl:param name="string" /> + <xsl:param name="delimiters" /> + <xsl:variable name="delimiter" select="substring($delimiters, 1, 1)" /> + <xsl:choose> + <xsl:when test="not($delimiter)"> + <token><xsl:value-of select="$string" /></token> + </xsl:when> + <xsl:when test="contains($string, $delimiter)"> + <xsl:if test="not(starts-with($string, $delimiter))"> + <xsl:call-template name="str:_tokenize-delimiters"> + <xsl:with-param name="string" select="substring-before($string, $delimiter)" /> + <xsl:with-param name="delimiters" select="substring($delimiters, 2)" /> + </xsl:call-template> + </xsl:if> + <xsl:call-template name="str:_tokenize-delimiters"> + <xsl:with-param name="string" select="substring-after($string, $delimiter)" /> + <xsl:with-param name="delimiters" select="$delimiters" /> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="str:_tokenize-delimiters"> + <xsl:with-param name="string" select="$string" /> + <xsl:with-param name="delimiters" select="substring($delimiters, 2)" /> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/include/orderizer.xsl b/src/current/include/orderizer.xsl new file mode 100644 index 0000000..170bd2b --- /dev/null +++ b/src/current/include/orderizer.xsl @@ -0,0 +1,268 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + No, not "odorizer". + + This is a powerful system that takes an immensely complex (and insurmountable) + task out of the programmer's hands. In particular, the system: + + - Uses the map to recognize the order in which params appear in a UI + - Using that order, determines which fields in the UI could potentially + cause fields that appear previous to them to become invalid (e.g. hide) + due to classification criteria. + - Based on those conflicts, constructs a custom classification that ignores + only the conflicting portions, allowing a great deal of precision in + controlling field validity up to that particular point in the UI. + + The result is highly-refined, custom-generated classifications per-question + for only the criteria that are needed to ensure that fields cannot hide fields + previous to them: A task that would otherwise be prohibitively complicated (or + would otherwise have to be far too coarse) if done manually in systems with + highly sophisticated classification schemes. Furthermore, the result is wholly + deterministic. + + It should be noted that futher benefit can be realized when the map is altered + or questions in the UI are reordered; the system will simply re-calculate new + classifications that yield desirable results. +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:lvp="http://www.lovullo.com" + xmlns:lvm="http://www.lovullo.com/rater/map" + xmlns:lvmc="http://www.lovullo.com/rater/map/compiler" + xmlns:gc="http://www.lovullo.com/calc/global-classifier" + xmlns:c="http://www.lovullo.com/calc" + xmlns:o="http://www.lovullo.com/rater/compiler/orderizer" + xmlns:preproc="http://www.lovullo.com/rater/preproc"> + + + +<!-- + Determines param conflicts based on UI ordering +--> +<xsl:template name="o:analyze"> + <xsl:param name="class-deps" /> + <xsl:param name="ui" /> + <xsl:param name="sdmap" /> + <xsl:param name="param-classes" /> + + <xsl:variable name="rater" select="." /> + + <!-- put fields in the order that they appear in the UI --> + <!-- TODO: duplicate check (error) --> + <xsl:variable name="ordered"> + <o:ordered> + <xsl:for-each select="$ui//lvp:question"> + <xsl:variable name="id" select="@id" /> + <xsl:copy-of select="$sdmap/lvmc:map[ @from=$id ]" /> + </xsl:for-each> + </o:ordered> + </xsl:variable> + + <!-- the param-class list gives us the classes that are directly used; let's + get a list of all classes that are used by those classes as well, which + will simplify the queries to come --> + <xsl:message>[orderizer] recursively discovering param classes...</xsl:message> + <xsl:variable name="param-classes-expanded"> + <xsl:apply-templates select="$param-classes" mode="o:gen-param-reflist"> + <xsl:with-param name="class-deps" select="$class-deps" /> + </xsl:apply-templates> + </xsl:variable> + + <xsl:variable name="initial"> + <o:analysis> + <xsl:for-each select="$ordered//lvmc:*"> + <xsl:variable name="cur" select="." /> + <xsl:variable name="pref" select="@to" /> + + <!-- progress indicator --> + <xsl:message> + <xsl:text>[orderizer] checking previous UI siblings: </xsl:text> + <xsl:value-of select="@to" /> + </xsl:message> + + <!-- preceding --> + <xsl:for-each select=" + $param-classes/gc:param[ + @ref=$cur/preceding-sibling::lvmc:map/@to + ]"> + + <!-- immediate conflicts (we check these separately rather than in the + xpath above, which would be cleaner, so that they can be + processed --> + <xsl:variable name="conflicts" select=" + .//gc:class[ + @ref=$param-classes-expanded/o:param-refs/o:param[ + @ref=$pref + ]/o:class/@ref + ] + " /> + + <xsl:if test="$conflicts"> + <o:conflict ref="{@ref}" relative-to="{$pref}"> + <xsl:for-each select="$conflicts"> + <!-- record the immediate problem --> + <o:class ref="{@ref}" yields="{@yields}" /> + </xsl:for-each> + </o:conflict> + </xsl:if> + </xsl:for-each> + </xsl:for-each> + </o:analysis> + </xsl:variable> + + <xsl:apply-templates select="$initial/o:analysis" mode="o:reduce-analysis" /> +</xsl:template> + + +<xsl:template match="gc:param-classes" mode="o:gen-param-reflist" priority="5"> + <xsl:param name="class-deps" /> + + <o:param-refs> + <xsl:apply-templates select="gc:param" mode="o:gen-param-reflist"> + <xsl:with-param name="class-deps" select="$class-deps" /> + </xsl:apply-templates> + </o:param-refs> +</xsl:template> + +<xsl:template match="gc:param" mode="o:gen-param-reflist" priority="5"> + <xsl:param name="class-deps" /> + + <o:param ref="{@ref}"> + <xsl:apply-templates select="gc:class" mode="o:gen-param-reflist"> + <xsl:with-param name="class-deps" select="$class-deps" /> + </xsl:apply-templates> + </o:param> +</xsl:template> + +<xsl:template match="gc:class" mode="o:gen-param-reflist" priority="5"> + <xsl:param name="class-deps" /> + + <!-- well, this class is certainly used --> + <o:class ref="{@ref}" /> + + <xsl:variable name="ref" select="@ref" /> + + <!-- but how about things that use this class? --> + <xsl:apply-templates select="$class-deps/preproc:class[ @ref=$ref ]" + mode="o:gen-param-reflist"> + <xsl:with-param name="class-deps" select="$class-deps" /> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="preproc:class" mode="o:gen-param-reflist" priority="5"> + <xsl:param name="class-deps" /> + + <o:class ref="{@ref}" /> + + <xsl:apply-templates mode="o:gen-param-reflist"> + <xsl:with-param name="class-deps" select="$class-deps" /> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="*" mode="o:gen-param-reflist" priority="1"> + <!-- do nothing --> +</xsl:template> + + +<xsl:template match="preproc:class" mode="o:get-indirect-params" priority="5"> + <xsl:apply-templates mode="o:get-indirect-params" /> +</xsl:template> + +<xsl:template match="preproc:class-dep" mode="o:get-indirect-params" priority="5"> + <xsl:variable name="ref" select="@ref" /> + + <xsl:apply-templates + select="ancestor::preproc:class-deps/preproc:class[ @ref=$ref ]" + mode="o:get-indirect-params" /> +</xsl:template> + +<xsl:template match="preproc:rate-dep" mode="o:get-indirect-params" priority="5"> + <xsl:variable name="ref" select="@ref" /> + + <xsl:apply-templates + select="//preproc:rate-deps/preproc:rate[ @ref=$ref ]" + mode="o:get-indirect-params" /> +</xsl:template> + +<xsl:template match="preproc:param" mode="o:get-indirect-params" priority="5"> + <xsl:copy-of select="." /> +</xsl:template> + +<xsl:template match="*" mode="o:get-indirect-params" priority="1"> + <!-- do nothing --> +</xsl:template> + + +<!-- + Combines initial analysis results, merging common refs and de-duplicating + conflicts. +--> +<xsl:template match="o:analysis" mode="o:reduce-analysis"> + <!-- start by combining the contents of all unique refs --> + <xsl:variable name="combined"> + <xsl:for-each select=" + o:conflict[ + not( @ref=preceding-sibling::o:conflict/@ref ) + ] + "> + + <xsl:variable name="ref" select="@ref" /> + <xsl:variable name="dups" select=" + following-sibling::o:conflict[ @ref=$ref ] + " /> + + <o:conflict ref="{@ref}"> + <!-- add relativity nodes --> + <xsl:for-each select="$dups|."> + <o:relative-to ref="{@relative-to}" /> + </xsl:for-each> + + <!-- add class deps --> + <o:reasons> + <xsl:copy-of select="($dups|.)/o:class" /> + </o:reasons> + </o:conflict> + </xsl:for-each> + </xsl:variable> + + <!-- now, remove duplicates resulting from the above operation --> + <xsl:copy> + <xsl:copy-of select="@*" /> + + <xsl:apply-templates select="$combined/o:conflict" + mode="o:reduce-analysis" /> + </xsl:copy> +</xsl:template> + +<xsl:template match="o:conflict" mode="o:reduce-analysis"> + <xsl:copy> + <xsl:copy-of select="@*" /> + + <!-- unique relative-to nodes --> + <xsl:copy-of select=" + o:relative-to[ + not( @ref=preceding-sibling::o:relative-to/@ref ) + ] + " /> + + <xsl:apply-templates select="o:reasons" mode="o:reduce-analysis" /> + </xsl:copy> +</xsl:template> + +<xsl:template match="o:reasons" mode="o:reduce-analysis"> + <xsl:copy> + <xsl:copy-of select="@*" /> + + <!-- unique reasons --> + <xsl:copy-of select=" + o:class[ + not( @ref=preceding-sibling::o:class/@ref ) + ] + " /> + </xsl:copy> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/include/preproc/domain.xsl b/src/current/include/preproc/domain.xsl new file mode 100644 index 0000000..6dc436d --- /dev/null +++ b/src/current/include/preproc/domain.xsl @@ -0,0 +1,226 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Compiles domains from typedefs + + The ultime goal is to implement typedefs as macros and move to a generic + domain system that is much more powerful. +--> + +<xsl:stylesheet version="1.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + + xmlns:preproc="http://www.lovullo.com/rater/preproc" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:c="http://www.lovullo.com/calc"> + + +<!-- + Base typedefs (internal) are declarations only + + TODO: We still want a domain generated. +--> +<xsl:template match="lv:typedef[ lv:base-type ]" + mode="preproc:mkdomain" priority="9"> +</xsl:template> + + +<!-- + Union typedefs + + This generates not only the contained typedefs, but also a denormalized + domain containing the union of all the subdomain elements. This is + intended to improve lookup performance and reduce algorithmic complexity. +--> +<xsl:template match=" + lv:typedef[ + lv:union + ] + " + mode="preproc:mkdomain" priority="5"> + + <!-- generate all contained domains --> + <xsl:variable name="subdomains"> + <xsl:variable name="union-types" select=" + lv:union/lv:typedef" /> + + <!-- if a union is empty, then somebody probably made an oopsie or wrote + a defective/deficient template --> + <xsl:if test="count( $union-types ) = 0"> + <xsl:message> + <xsl:text>warning: union `</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>' has no subdomains; something is probably wrong</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:apply-templates mode="preproc:mkdomain" + select="$union-types" /> + </xsl:variable> + + <!-- provide a denormalized domain for performance and to reduce + algorithmic complexity--> + <xsl:call-template name="preproc:mkdomain-union"> + <xsl:with-param name="name" select="@name" /> + <xsl:with-param name="subdomains" select="$subdomains" /> + </xsl:call-template> + + <xsl:copy-of select="$subdomains" /> +</xsl:template> + + +<!-- + Enumerated typedefs +--> +<xsl:template match=" + lv:typedef[ + lv:enum + ] + " + mode="preproc:mkdomain" priority="5"> + + <lv:domain name="{@name}"> + <xsl:variable name="items" select="lv:enum/lv:item" /> + + <!-- if a typedef is empty, then somebody probably made an oopsie or + wrote a defective/deficient template --> + <xsl:if test="count( $items ) = 0"> + <xsl:message> + <xsl:text>warning: typedef `</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>' is empty; something is probably wrong</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:apply-templates mode="preproc:mkdomain" + select="$items" /> + </lv:domain> +</xsl:template> + + +<!-- + Prohibit mixing explicit and auto-generated values + + For the time being at least. +--> +<xsl:template match=" + lv:typedef[ + lv:enum/lv:item[ @value ] + and lv:enum/lv:item[ not( @value ) ] + ]" + mode="preproc:mkdomain" priority="2"> + + <xsl:message terminate="yes"> + <xsl:text>error: typedef `</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>' must not contain both @value and non-@value items</xsl:text> + </xsl:message> +</xsl:template> + + +<!-- + Unsupported typedef + + Well, we know that it is a typedef, but its format is unknown. This + wouldn't be surprising, since it is presently very limited. +--> +<xsl:template match="lv:typedef" + mode="preproc:mkdomain" priority="2"> + + <xsl:message terminate="yes"> + <xsl:text>error: malformed typedef `</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>'</xsl:text> + </xsl:message> +</xsl:template> + + + +<!-- + Generate a denormalized domain consisting of the union of its subdomains' + elements + + As this is a union, duplicate elements will be removed; the user will not + be notified of this fact, as this allows domains to overlap in order to + interpret the same data in different manners. +--> +<xsl:template name="preproc:mkdomain-union"> + <xsl:param name="name" /> + <xsl:param name="subdomains" /> + + <xsl:variable name="union"> + <preproc:elements> + <xsl:copy-of select="$subdomains/lv:domain/lv:element" /> + </preproc:elements> + </xsl:variable> + + <!-- remove duplicate values (yes, this will take the first description if + there are duplicates; whatever, for now) --> + <lv:domain name="{@name}"> + <xsl:copy-of select=" + $union/preproc:elements/lv:element[ + not( @value = preceding-sibling::lv:element/@value ) + ]" /> + </lv:domain> +</xsl:template> + + +<!-- + Enumerated items without values require calculation + + Note the above validation that ensures that our value generation is + sufficient. +--> +<xsl:template match="lv:enum/lv:item[ not( @value ) ]" + mode="preproc:mkdomain" priority="5"> + + <xsl:variable name="augmented"> + <xsl:copy> + <xsl:attribute name="value" select="position()" /> + </xsl:copy> + </xsl:variable> + + <!-- re-process using an augmented item with the value calculated --> + <xsl:apply-templates mode="preproc:mkdomain" + select="$augmented/lv:item" /> +</xsl:template> + + +<!-- + Convert typedef item into a domain element + + This is a straightforward rename with sanity checking. Note that the + element may have an empty description. + + We do not care about the name, since we use that to generate constants + elsewhere. +--> +<xsl:template match="lv:item" + mode="preproc:mkdomain" priority="4"> + + <!-- previous templates should have prevented this, but just in case --> + <xsl:if test="not( @value )"> + <xsl:message terminate="yes"> + <xsl:text>internal error: preproc:mkdomain on non-value item: </xsl:text> + <xsl:copy-of select="." /> + </xsl:message> + </xsl:if> + + <lv:element value="{@value}" desc="{@desc}" /> +</xsl:template> + + +<!-- + Unexpected node; terminate +--> +<xsl:template match="*" + mode="preproc:mkdomain" priority="1"> + + <xsl:message terminate="yes"> + <xsl:text>internal error: unknown domain source: </xsl:text> + <xsl:copy-of select="." /> + </xsl:message> +</xsl:template> + +</xsl:stylesheet> + diff --git a/src/current/include/preproc/eligclass.xsl b/src/current/include/preproc/eligclass.xsl new file mode 100644 index 0000000..8e78c09 --- /dev/null +++ b/src/current/include/preproc/eligclass.xsl @@ -0,0 +1,261 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Package eligibility class generation + + Here, the term "eligibility" means whether the package is eligible to be used + in a result set basead on the values of its params within their respective + domains and other factors such as the results of terminating classifications + and the eligibility of imported packages. + + The goal of the eligibility classification is to create a cascading failure in + the event of bad data. +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:preproc="http://www.lovullo.com/rater/preproc" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:c="http://www.lovullo.com/calc"> + + + +<!-- + Trigger eligibility class generation +--> +<xsl:template match="lv:package[ not( @preproc:elig-class-yields ) ]" + as="element( lv:package )" + priority="5" + mode="preproc:expand-elig-class"> + <xsl:param name="orig-root" as="element( lv:package )" /> + + <xsl:variable name="elig-class" as="element( lv:classify )"> + <xsl:apply-templates select="." mode="preproc:gen-elig-class"> + <xsl:with-param name="orig-root" select="$orig-root" /> + </xsl:apply-templates> + </xsl:variable> + + <xsl:copy> + <xsl:sequence select="@*" /> + + <xsl:attribute name="preproc:elig-class" + select="$elig-class/@as" /> + + <xsl:attribute name="preproc:elig-class-yields" + select="$elig-class/@yields" /> + + <xsl:sequence select="$elig-class" /> + <xsl:apply-templates mode="preproc:macros" /> + </xsl:copy> +</xsl:template> + + +<xsl:template match="lv:package" as="element( lv:package )" + priority="1" + mode="preproc:expand-elig-class"> + + <!-- already processed --> + <xsl:sequence select="." /> +</xsl:template> + + + +<!-- + Generate eligibility classification asserting all data integrity aspects of a + package + + The eligibility classification will yield a scalar. +--> +<xsl:template match="lv:package" as="element( lv:classify )" + mode="preproc:gen-elig-class"> + <xsl:param name="orig-root" as="element( lv:package )" /> + + <xsl:message>[preproc/eligclass] generating eligibility class</xsl:message> + + + <!-- class-ify name --> + <xsl:variable name="as" as="xs:string" + select="preproc:gen-elig-class-name( @name )" /> + <xsl:variable name="yields" as="xs:string" + select="preproc:gen-elig-class-yields( @name )" /> + + + <lv:classify as="{$as}" yields="{$yields}" + desc="{@name} package is eligible"> + + <!-- TODO: this should really be a compile-time value --> + <xsl:if test="@keep-elig-class = 'true'"> + <xsl:attribute name="keep" select="'true'" /> + </xsl:if> + + <!-- each of our imported packages' elig classes must be truthful --> + <xsl:apply-templates mode="preproc:gen-elig-class-matches" + select="lv:import"> + + <xsl:with-param name="orig-root" select="$orig-root" /> + </xsl:apply-templates> + + <!-- param values must be within their domain --> + <!-- XXX: does not work when param is undefined due to no mapping + <xsl:apply-templates mode="preproc:gen-elig-param-class" + select="lv:param" /> + --> + + <!-- terminating classifications must not have matched --> + <xsl:apply-templates mode="preproc:gen-elig-term-class" + select="preproc:symtable/preproc:sym[ @type='class' ]" /> + </lv:classify> +</xsl:template> + + +<!-- + Generate eligibility classification name for package +--> +<xsl:function name="preproc:gen-elig-class-name" + as="xs:string"> + <xsl:param name="name" /> + + <xsl:sequence select=" + concat( '--elig-', + translate( + translate( $name, '.', '' ), + '/', '-' + ) + ) + " /> +</xsl:function> + + +<!-- + Generate eligibility result scalar name for package +--> +<xsl:function name="preproc:gen-elig-class-yields" + as="xs:string"> + <xsl:param name="name" /> + + <xsl:sequence select=" + concat( + 'isElig', + translate( + translate( $name, '.', '' ), + '/-', '' ) )" /> +</xsl:function> + + +<!-- + Generate matches on eligibility of imported packages + + For each imported package, its eligibility classification must be true. +--> +<xsl:template match="lv:import[ @package ]" + as="element( lv:match )?" + priority="5" + mode="preproc:gen-elig-class-matches"> + <xsl:param name="orig-root" as="element( lv:package )" /> + + <!-- FIXME: path may not yet be resolved due to preprocessing order --> + <xsl:variable name="pkg-path" as="xs:string"> + <xsl:call-template name="__apply-relroot"> + <xsl:with-param name="path" select="@package" /> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="pkg" as="element( lv:package )" + select="document( concat( $pkg-path, '.xmlo' ), + $__entry-root ) + /lv:package" /> + + <xsl:if test="not( $pkg )"> + <xsl:message terminate="yes"> + <xsl:text>[preproc/eligclass] error: could not load `</xsl:text> + <xsl:value-of select="$pkg-path" /> + <xsl:text>' object file</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:variable name="chk" as="xs:string?" + select="$pkg/@preproc:elig-class-yields" /> + + <xsl:choose> + <xsl:when test="not( $chk ) or ( $chk = '' )"> + <!-- TODO: make this an error once we make maps part of the + conventional build process --> + <xsl:message> + <xsl:text>[preproc/eligclass] internal: empty eligibility </xsl:text> + <xsl:text>class for `</xsl:text> + <xsl:value-of select="$pkg/@name" /> + <xsl:text>'; skipping</xsl:text> + </xsl:message> + </xsl:when> + + <xsl:otherwise> + <!-- use eligibility class as stated by the package --> + <lv:match on="{$chk}" value="TRUE" /> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:template match="lv:import" priority="1" + mode="preproc:gen-elig-class-matches"> + + <!-- do nothing --> +</xsl:template> + + +<!-- + Param values must be within their domain + + This is a trivial operation. +--> +<xsl:template match="lv:param" + as="element( lv:any )" + mode="preproc:gen-elig-param-class" priority="5"> + + <lv:any> + <lv:match on="{@name}" anyOf="{@type}" /> + + <!-- TODO: defaults should always be within the domain! --> + <xsl:if test="@default"> + <lv:match on="{@name}" anyOf="empty" /> + </xsl:if> + </lv:any> +</xsl:template> + + + +<!-- + Terminating classification dependencies + + All terminiating classifications defined in the package must yield false + for the package to be eligible. + + N.B. This checks to ensure @extclass is not set; this prevents errors when + the eligibility classification attempts to pull in a terminating + classification marked as external to the classifier. There may or may not + be something we want to do about this in the future. +--> +<xsl:template match="preproc:sym[ + not( @src ) + and not( @pollute='true' ) + and @type='class' + and @terminate='true' + and not( @extclass='true' ) + ]" + as="element( lv:match )" + priority="5" + mode="preproc:gen-elig-term-class"> + + <lv:match on="{@yields}" value="FALSE" /> +</xsl:template> + + +<xsl:template match="preproc:sym" priority="1" + mode="preproc:gen-elig-term-class"> + + <!-- do nothing --> +</xsl:template> + +</xsl:stylesheet> + diff --git a/src/current/include/preproc/expand.xsl b/src/current/include/preproc/expand.xsl new file mode 100644 index 0000000..57c6856 --- /dev/null +++ b/src/current/include/preproc/expand.xsl @@ -0,0 +1,691 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Handles node expansion + + This process is responsible for expanding shorthand and various other data + into a consistent format for the compiler and other processes. +--> + +<xsl:stylesheet + version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:preproc="http://www.lovullo.com/rater/preproc" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:t="http://www.lovullo.com/rater/apply-template" + xmlns:c="http://www.lovullo.com/calc" + xmlns:w="http://www.lovullo.com/rater/worksheet"> + + +<xsl:include href="domain.xsl" /> + + +<xsl:template match="lv:package[ not( @preproc:name ) ]" + mode="preproc:expand" priority="5" + as="element( lv:package )"> + <xsl:copy> + <xsl:sequence select="@*" /> + + <!-- generate name from source package identifier --> + <xsl:attribute name="name" select="$__srcpkg" /> + + <!-- relative path to root src directory (for resolving absolute include + paths) --> + <xsl:attribute name="__rootpath" select="$__relroot" /> + + <!-- TODO: temporary; remove --> + <xsl:attribute name="preproc:name" select="$__srcpkg" /> + + <xsl:apply-templates mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + + + +<xsl:template match="*" mode="preproc:expand" priority="1"> + <xsl:copy> + <xsl:sequence select="@*" /> + + <xsl:apply-templates mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + + +<!-- imports relative to project root --> +<xsl:template match="lv:import[ starts-with( @package, '/' ) ]" mode="preproc:expand" priority="5"> + <xsl:copy> + <xsl:sequence select="@*" /> + + <!-- resolve path into path relative to project root --> + <xsl:attribute name="package"> + <xsl:call-template name="__apply-relroot"> + <xsl:with-param name="path" select="@package" /> + </xsl:call-template> + </xsl:attribute> + </xsl:copy> +</xsl:template> + + +<!-- + Domain data are extracted from typedefs + + Eventually, the typedefs will be converted into templates and removed entirely. +--> +<xsl:template match="lv:typedef" + mode="preproc:expand" priority="5"> + + <xsl:apply-templates select="." mode="preproc:mkdomain" /> + + <xsl:copy> + <xsl:sequence select="@*" /> + <xsl:apply-templates mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + + +<!-- + Infer primitive type if not provided. +--> +<xsl:template mode="preproc:expand" + match="c:const[ not( @type ) ] + |lv:const[ not( @type ) ]" + priority="5"> + <xsl:copy> + <xsl:sequence select="@*" /> + + <xsl:attribute name="type" + select="if ( substring-before( @value, '.' ) ) then + 'float' + else + 'integer'" /> + + <xsl:sequence select="*" /> + </xsl:copy> +</xsl:template> + + +<!-- + Give let's a name so that they may be easily referenced uniquely +--> +<xsl:template match="c:let[ not( @name ) ]" mode="preproc:expand" priority="5"> + <xsl:copy> + <xsl:sequence select="@*" /> + <xsl:attribute name="name" select="generate-id(.)" /> + + <xsl:apply-templates select="*" mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + + +<!-- + Default label of c:let value expressions to description if no label is provided + + This is useful for breakdown display. + + TODO: play well with others; if we change the priority back to 5, we introduce + match ambiguities +--> +<xsl:template match="c:let/c:values/c:value/c:*[1][ not( @label ) ]" mode="preproc:expand" priority="4"> + <xsl:copy> + <xsl:sequence select="@*" /> + + <!-- default the label to the description of the parent c:value --> + <xsl:attribute name="label" select="../@desc" /> + + <xsl:apply-templates select="*" mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + + +<!-- + c:when with no children is shorthand for > 0 + + Note: we check for any children because we may have things like + template applications that we do not want wiped out. +--> +<xsl:template match="c:when[ not( * ) ]" mode="preproc:expand" priority="5"> + <xsl:copy> + <xsl:sequence select="@*" /> + + <c:gt> + <c:value-of name="FALSE" /> + </c:gt> + </xsl:copy> +</xsl:template> + + +<!-- + c:when with multiple children +--> +<xsl:template match="c:when[ count( c:* ) gt 1 ]" mode="preproc:expand" priority="5"> + <xsl:variable name="when" select="." /> + + <!-- expand into adjacent c:when's --> + <xsl:for-each select="c:*"> + <c:when> + <xsl:sequence select="$when/@*" /> + <xsl:apply-templates select="." mode="preproc:expand" /> + </c:when> + </xsl:for-each> +</xsl:template> + + +<!-- + Recursion shorthand + + This exists simply because function application is so verbose and, when + recursing, generally only a small fraction of the arguments actually change. +--> +<xsl:template match="c:recurse" mode="preproc:expand" priority="5"> + <xsl:variable name="self" select="." /> + <xsl:variable name="fname" select="ancestor::lv:function/@name" /> + <xsl:variable name="overrides" select="./c:arg" /> + + <c:apply name="{$fname}"> + <!-- every non-@name attribute should be converted into an argument --> + <xsl:call-template name="preproc:arg-short-expand" /> + + <!-- include all non-overridden args --> + <xsl:for-each select=" + ancestor::lv:function/lv:param[ + not( + @name=$overrides/@name + or @name=$self/@*/local-name() + ) + ] + "> + + <!-- copy the arg value --> + <c:arg name="{@name}"> + <c:value-of name="{@name}" /> + </c:arg> + </xsl:for-each> + + <!-- copy in the overrides --> + <xsl:apply-templates select="$overrides" mode="preproc:expand" /> + </c:apply> +</xsl:template> + + +<!-- metadata constants have different semantics --> +<!-- TODO: maybe ignore single-quoted? --> +<xsl:template mode="preproc:expand" priority="6" + match="lv:meta/lv:prop/lv:const"> + <xsl:sequence select="." /> +</xsl:template> + + +<!-- constants that contain 'e' (scientific notation) should be expanded; allows + for avoiding constants with many zeroes, which is hard to read --> +<xsl:template mode="preproc:expand" priority="5" + match="c:const[ substring-before( @value, 'e' ) ] + |lv:const[ substring-before( @value, 'e' ) ]"> + <xsl:copy> + <xsl:sequence select="@*" /> + + <xsl:attribute name="value"> + <xsl:call-template name="preproc:expand-e"> + <xsl:with-param name="number" select="@value" /> + </xsl:call-template> + </xsl:attribute> + + <xsl:apply-templates mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + +<xsl:template mode="preproc:expand" priority="5" + match="c:const[ substring-before( @value, 'm' ) ] + |lv:const[ substring-before( @value, 'm' ) ]"> + <xsl:copy> + <xsl:sequence select="@*" /> + + <xsl:attribute name="value"> + <xsl:call-template name="preproc:expand-e"> + <xsl:with-param name="number" + select="concat( substring-before( @value, 'm' ), 'e6' )" /> + </xsl:call-template> + </xsl:attribute> + + <xsl:apply-templates mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + +<xsl:template mode="preproc:expand" priority="5" + match="c:const[ substring-before( @value, 'k' ) ] + |lv:const[ substring-before( @value, 'k' ) ]"> + <xsl:copy> + <xsl:sequence select="@*" /> + + <xsl:attribute name="value"> + <xsl:call-template name="preproc:expand-e"> + <xsl:with-param name="number" + select="concat( substring-before( @value, 'k' ), 'e3' )" /> + </xsl:call-template> + </xsl:attribute> + + <xsl:apply-templates mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + + +<!-- expand scientific notation --> +<!-- XXX: negatives not currently supported --> +<xsl:template name="preproc:expand-e"> + <xsl:param name="number" /> + <xsl:param name="whole" select="substring-before( $number, '.' )" /> + <xsl:param name="dec" select="substring-before( substring-after( $number, '.' ), 'e' )" /> + <xsl:param name="count" as="xs:double" + select="number( substring-after( $number, 'e' ) )" /> + + <!-- output the whole number portion --> + <xsl:choose> + <xsl:when test="$whole and not( $whole = '' )"> + <xsl:value-of select="$whole" /> + </xsl:when> + + <!-- if no decimal was provided, then use the entire number before 'e' --> + <xsl:when test="$number and not( $number = '' ) and ( $whole = '' )"> + <xsl:value-of select="substring-before( $number, 'e' )" /> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="$count > 0"> + <xsl:choose> + <!-- if we have a decimal, then use the first digit (as if we moved one + place to the right) --> + <xsl:when test="$dec and not( $dec = '' )"> + <xsl:value-of select="substring( $dec, 1, 1 )" /> + </xsl:when> + + <!-- no decimal portion remaining; fill with 0 --> + <xsl:otherwise> + <xsl:text>0</xsl:text> + </xsl:otherwise> + </xsl:choose> + + <!-- recursively expand --> + <xsl:call-template name="preproc:expand-e"> + <!-- already processed the whole --> + <xsl:with-param name="whole" select="''" /> + + <xsl:with-param name="dec"> + <!-- move to the right one decimal place; otherwise, no decimal --> + <xsl:if test="$dec"> + <xsl:value-of select="substring( $dec, 2 )" /> + </xsl:if> + </xsl:with-param> + + <xsl:with-param name="count" select="$count - 1" /> + </xsl:call-template> + </xsl:when> + + <!-- output the remaining decimal, if any --> + <xsl:otherwise> + <xsl:if test="$dec and not( $dec = '' )"> + <xsl:text>.</xsl:text> + <xsl:value-of select="$dec" /> + </xsl:if> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<!-- + Optimize away c:cases if they contain only c:otherwise + + This is useful primarily for templates that may create a case statement for + conditional operations (using lv:if/lv:unless) and ensures that there is no + penalty for doing so if none of the template conditions result in a c:case. + + Note that we should *not* perform these optimizations if there are templates + awaiting application or any other lv:* nodes that have not been expanded. +--> +<xsl:template mode="preproc:expand" priority="5" match=" + c:cases[ + not( lv:* or t:* ) + and c:otherwise[ + not( preceding-sibling::c:* or following-sibling::c:* ) + ] + ] + "> + + <!-- just replace with the content of the otherwise block (do not explicitly + process c:*, since there may be templates) --> + <xsl:apply-templates select="c:otherwise/*" mode="preproc:expand" /> +</xsl:template> + + +<!-- + Optimize away c:sum/c:product blocks that contain one or zero elements, so + long as they do not contain a generator (since that would remove a ref) or @of + (since that will actually loop through multiple). + + Note that we should *not* perform these optimizations if there are templates + awaiting application or any other lv:* nodes that have not been expanded. +--> +<xsl:template match="c:sum[ lv:* or t:* ]|c:product[ lv:* or t:* ]" mode="preproc:expand" priority="7"> + <xsl:copy> + <xsl:sequence select="@*" /> + <xsl:apply-templates mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + +<xsl:template match="c:sum[ not( @of or @generates ) and count( c:* ) < 2 ]" mode="preproc:expand" priority="5"> + <xsl:apply-templates select="c:*" mode="preproc:expand" /> +</xsl:template> +<xsl:template match="c:product[ not( @of or @generates ) and count( c:* ) < 2 ]" mode="preproc:expand" priority="5"> + <xsl:apply-templates select="c:*" mode="preproc:expand" /> +</xsl:template> + + +<!-- TODO: We could add shorthand for indexes too, e.g. name[i] or name[0] --> +<xsl:template match=" + c:apply[ + @*[ + not( + local-name() = 'name' + or local-name() = 'label' + ) + ] + ] + " + mode="preproc:expand" priority="5"> + + <xsl:copy> + <!-- keep the name attribute, which specifies what function to apply --> + <xsl:sequence select="@name, @label" /> + + <!-- every other attribute should be converted into an argument --> + <xsl:call-template name="preproc:arg-short-expand" /> + + <xsl:apply-templates select="c:arg" mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + + +<xsl:template name="preproc:arg-short-expand"> + <xsl:for-each select="@*[ + not( + local-name() = 'name' + or local-name() = 'label' + ) + ]"> + + <c:arg name="{local-name()}"> + <c:value-of name="{.}" /> + </c:arg> + </xsl:for-each> +</xsl:template> + + +<xsl:template match="lv:rate[ lv:class ]|lv:function[ lv:class ]|lv:yield[ lv:class ]" + mode="preproc:expand" priority="9"> + <!-- already processed --> + <xsl:copy> + <xsl:sequence select="@*" /> + <xsl:apply-templates mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + +<!-- + Add lv:class nodes containing the values of each individual class + + This eliminates the need to tokenize later and drastically simplifies xpath + queries. +--> +<xsl:template match="lv:rate|lv:function|lv:yield" mode="preproc:expand" priority="5"> + <xsl:variable name="self" select="." /> + + <xsl:variable name="classes" select="tokenize( @class, ' ' )" /> + <xsl:variable name="no-classes" select="tokenize( @no, ' ' )" /> + + <xsl:copy> + <xsl:sequence select="@*" /> + + <!-- convert classes into nodes to make life easier down the road (if any) --> + <xsl:for-each select="$classes"> + <xsl:if test="."> + <lv:class ref="{.}" no="false" /> + </xsl:if> + </xsl:for-each> + + <xsl:for-each select="$no-classes"> + <xsl:if test="."> + <lv:class ref="{.}" no="true" /> + </xsl:if> + </xsl:for-each> + + <xsl:apply-templates mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + + + +<!-- + To make life a bit easier, calculate the set type of a classification @yields + and add it to the node as a @set attribute +--> +<xsl:template match="lv:classify" mode="preproc:expand" priority="5"> + <xsl:variable name="self" select="." /> + + <xsl:copy> + <!-- we just want to add an attribute that allows easy referencing of this + @yields set type, which will be a matrix if any matches match on a + matrix of values, otherwise it will be a vector --> + <xsl:attribute name="set"> + <xsl:variable name="params" + select="root(.)//lv:param[ @name=$self//lv:match/@on ]" /> + + <xsl:choose> + <!-- XXX: This does not work properly with classes depending on other + classes --> + <xsl:when test="$params/@set = 'matrix'"> + <xsl:text>matrix</xsl:text> + </xsl:when> + + <!-- XXX: remove this when the above is fixed...note also that we have + to check for lv:join since it hasn't necessarily been preprocessed + yet...what a mess. Also note that, since templates and other things + may not have been expanded, we also fail this test if the + classification does not match on either a param or another + classification (since then things will get more complicated)--> + <xsl:when test=" + not( + $self//lv:match + or $self//lv:join + ) + or ( + not( $params/@set ) + and not( + .//lv:match[ @on=root(.)/lv:classify/@yields ] + ) + and not( .//lv:join ) + and ( + $params + or .//lv:match[ @on=root(.)/lv:classify ] + ) + ) + "> + <!-- output nothing; it's just a scalar --> + </xsl:when> + + <xsl:otherwise> + <xsl:text>vector</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:attribute> + + <!-- if there is no @yields attribute, then generate one --> + <xsl:if test="not( @yields )"> + <xsl:attribute name="yields"> + <xsl:text>__is</xsl:text> + <!-- certain characters are not valid for @yields --> + <xsl:value-of select="translate( @as, '-', '' )" /> + </xsl:attribute> + </xsl:if> + + <xsl:sequence select="@*" /> + + <!-- force @keep on @terminate --> + <xsl:if test="@terminate='true'"> + <xsl:attribute name="keep" select="'true'" /> + </xsl:if> + + <!-- copy everything else --> + <xsl:apply-templates mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + + +<!-- default lv:match/@on short-hand to assert on a value of TRUE --> +<xsl:template match="lv:match[ not( @value + or @anyOf + or @pattern + or * ) ]" + mode="preproc:expand" priority="7"> + + <xsl:copy> + <xsl:copy-of select="@*" /> + <xsl:attribute name="value" + select="'TRUE'" /> + </xsl:copy> +</xsl:template> + + +<xsl:template mode="preproc:expand" + match="lv:join[ @all='true' ]" + priority="8"> + <xsl:call-template name="preproc:mk-class-join-contents" /> +</xsl:template> + + +<xsl:template mode="preproc:expand" + match="lv:join" + priority="7"> + <lv:any> + <xsl:call-template name="preproc:mk-class-join-contents" /> + </lv:any> +</xsl:template> + + +<xsl:template name="preproc:mk-class-join-contents"> + <xsl:variable name="prefix" select="@prefix" /> + + <!-- TODO: remove lv:template nodes in a pass before this so that this + check is not necessary --> + <xsl:for-each select="root(.)/lv:classify[ + starts-with( @as, $prefix ) + and not( ancestor::lv:template ) + ]"> + <lv:match value="TRUE"> + <xsl:attribute name="on"> + <xsl:choose> + <xsl:when test="@yields"> + <xsl:value-of select="@yields" /> + </xsl:when> + + <xsl:otherwise> + <xsl:text>__is</xsl:text> + <xsl:value-of select="@as" /> + </xsl:otherwise> + </xsl:choose> + </xsl:attribute> + </lv:match> + </xsl:for-each> +</xsl:template> + + +<!-- enums have implicit values (as they are, well, enumerated; @value overrides) --> +<!-- TODO: should @value set the next implicit index? --> +<xsl:template match="lv:item[ not( @value ) ]" mode="preproc:expand" priority="5"> + <xsl:copy> + <xsl:sequence select="@*" /> + <xsl:attribute name="value" select="count( preceding-sibling::* )" /> + <xsl:apply-templates mode="preproc:expand" /> + </xsl:copy> +</xsl:template> + + +<xsl:template match="w:display[ @prefix ]" mode="preproc:expand" priority="5"> + <xsl:variable name="prefix" select="@prefix" /> + <xsl:variable name="children" select="w:*" /> + + <xsl:for-each select="root(.)//lv:rate[ starts-with( @yields, $prefix ) ]"> + <w:display name="{@yields}"> + <xsl:sequence select="$children" /> + </w:display> + </xsl:for-each> +</xsl:template> + + +<!-- remove templates that have been copied from an external source for + processing --> +<xsl:template match="lv:template[ + @name=root() + /preproc:symtable/preproc:sym[ @src ]/@name ]" + mode="preproc:expand" priority="5"> +</xsl:template> + +<!-- IMPORTANT: do not process unexpanded templates --> +<xsl:template match="lv:template" mode="preproc:expand" priority="4"> + <xsl:sequence select="." /> +</xsl:template> + + +<xsl:template match="preproc:symtable" mode="preproc:expand" priority="5"> + <!-- ignore --> + <xsl:sequence select="." /> +</xsl:template> + + +<xsl:template match="lv:__external-data" mode="preproc:expand" priority="5"> + <!-- intended for use by code generators; data is not retained in object file + unless some other process overrides this template --> +</xsl:template> + +</xsl:stylesheet> + +<!-- +Footnotes + +What!? We need footnotes for this project!? + +[0] This was a complicated issue to begin dealing with due to the information we + need and the information that is available. In particular, at this point, we + would like to exclude any non-external objects from appearing in the + rate-only output, but we cannot do that, since the preprocessor has not yet + reached that block! We cannot reorder, because the class order block depends + on the rate block! + + (See the commit that introduced this footnote: In the past, the template at + this position passed an `external-only' flag to the template.) + + Originally, the method was to ``guess'' based on the how the system was + currently being used (dangerous, but was meant to be temporary...though we + know how that goes...): implicit externals were ignored, meaning it may not + function as we would like. Now, this could be resolved in the short-term by + actually addinging explicit external attributes to everything that needed it + so that this code would run smoothly. Worked great! Well, so I had thought. + + Then it came down to a certain classification that used the `frame' + classification. This classification was only used by an external + classification in scottsdale, but other companies used it for rating, so the + @external classifier was inappropriate. Of course, the system could easily + figure out if it was to be marked as external or not (it already does), but + we do not yet have access to that information. Therefore, what ended up + happening, was the frame classification was excluded from the classifier, + excluded from one of the iterations that this footnote references (because + it was not explicitly external) and included elsewhere where we didn't care + if it was external or not. When the dependency tree was flattened to + determine compilation order, the classification that uses `frame' was + compiled *before* the `frame' classification itself, due to that exclusion! + Since `frame' was excluded from the classifier, therefore, it was always + false! That is the situation I was trying to avoid with the explicit + @external attributes, but here, that solution would not work. + + Therefore, checking for external-only here will not work; we must output + everything and work on post-processing the data once everything is said and + done, to remove the duplicates that are also present in the classifier. +--> diff --git a/src/current/include/preproc/macros.xsl b/src/current/include/preproc/macros.xsl new file mode 100644 index 0000000..4c4f8e6 --- /dev/null +++ b/src/current/include/preproc/macros.xsl @@ -0,0 +1,456 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Handles macro preprocessing +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:preproc="http://www.lovullo.com/rater/preproc" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:t="http://www.lovullo.com/rater/apply-template" + xmlns:c="http://www.lovullo.com/calc" + xmlns:ext="http://www.lovullo.com/ext"> + + +<xsl:include href="template.xsl" /> +<xsl:include href="eligclass.xsl" /> + + +<!-- + Perform a macro expansion pass + + This will continue to recurse until no preproc:repass nodes are found; this + allos macros to expand into macros for further processing. +--> +<xsl:template match="*" mode="preproc:macropass" priority="1" + as="node()+"> + <xsl:variable name="result" as="node()+"> + <xsl:apply-templates select="." mode="preproc:macros" /> + </xsl:variable> + + <xsl:variable name="nodeset" select="$result" /> + + <xsl:variable name="repass" + select="$nodeset//preproc:repass" /> + + <!-- halt if we are in error --> + <xsl:for-each select="$nodeset//preproc:error"> + <xsl:message terminate="yes"> + <xsl:text>!!! [preproc] error: </xsl:text> + <xsl:value-of select="." /> + </xsl:message> + </xsl:for-each> + + <xsl:choose> + <!-- if it was indicated that we must do so, recurse --> + <xsl:when test="$repass and not( $repass[ @need-sym ] )"> + + <!-- record the repass to keep a count --> + <!-- TODO: reintroduce + <preproc:repass-record /> + --> + + <xsl:message>[preproc] *REPASS*</xsl:message> + + <!-- perform the repass --> + <xsl:apply-templates select="$nodeset" mode="preproc:macropass"> + <xsl:with-param name="clear-tpl-step" + tunnel="yes" + select="false()" /> + </xsl:apply-templates> + </xsl:when> + + <!-- no more passes needed; macro expansion complete --> + <xsl:otherwise> + <xsl:sequence select="$nodeset" /> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:template match="*" mode="preproc:macros" priority="1"> + <xsl:copy> + <xsl:sequence select="@*" /> + + <xsl:apply-templates mode="preproc:macros" /> + </xsl:copy> +</xsl:template> + + +<!-- + Remove repass nodes left over from the previous pass + + Otherwise, we would recurse indefinately. +--> +<xsl:template match="preproc:repass" mode="preproc:macros" priority="5"> + <!-- remove; no longer needed --> +</xsl:template> + + +<xsl:template match="preproc:tpl-step" mode="preproc:macros" priority="5"> + <xsl:param name="clear-tpl-step" + tunnel="yes" + select="true()" /> + + <xsl:choose> + <xsl:when test="$clear-tpl-step"> + <!-- strip --> + </xsl:when> + + <xsl:otherwise> + <xsl:copy-of select="." /> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<!-- + lv:rater is just a special type of package +--> +<xsl:template match="lv:rater" mode="preproc:macros" priority="9"> + <lv:package program="true"> + <xsl:sequence select="@*, *" /> + </lv:package> + + <preproc:repass src="lv:rater" /> +</xsl:template> + + + +<!-- + FOR PERFORMANCE ONLY: + + These nodes (usually) contain nothing that can be processed on the macro pass, + so recursion is unnecessary; note the low priority. +--> +<xsl:template match="lv:typedef" + mode="preproc:macros" priority="2"> + + <xsl:sequence select="." /> +</xsl:template> + + +<!-- + Expand values beginning with `#' into constants + + It is a nuisance to have separate params (e.g. templates) for constants + and values. +--> +<xsl:template mode="preproc:macros" + match="c:value-of[ starts-with( @name, '#' ) ]" + priority="7"> + <c:const value="{substring-after( @name, '#' )}" + type="float" + desc="Generated short-hand constant" /> +</xsl:template> + + +<!-- + Expand index values beginning with `#' into constants + + It is a nuisance to have separate params (e.g. templates) for constants + and values. +--> +<xsl:template mode="preproc:macros" + match="c:value-of[ starts-with( @index, '#' ) ]" + priority="7"> + <xsl:copy> + <xsl:copy-of select="@*[ not( name() = 'index' ) ]" /> + + <c:index> + <c:const value="{substring-after( @index, '#' )}" + type="float" + desc="Generated short-hand constant" /> + </c:index> + </xsl:copy> +</xsl:template> + + +<!-- + It does not make sense to try to take an index of a scalar +--> +<xsl:template mode="preproc:macros" + match="c:value-of[ + @index + and starts-with( @name, '#' ) ]" + priority="9"> + <preproc:error> + <xsl:text>Cannot take index of scalar value: </xsl:text> + <xsl:value-of select="@name" /> + </preproc:error> +</xsl:template> + + +<!-- + Classifications containing only an lv:any child node can be converted into + existential classifications +--> +<xsl:template match="lv:classify[ lv:any and count(*) = 1 ]" mode="preproc:macros" priority="8"> + <xsl:copy> + <xsl:sequence select="@*" /> + <xsl:attribute name="any" select="'true'" /> + + <xsl:sequence select="lv:any/*" /> + </xsl:copy> + + <preproc:repass src="lv:classify any" /> +</xsl:template> + + +<xsl:template match="lv:classify[ .//lv:any|.//lv:all ]" mode="preproc:macros" priority="6"> + <xsl:variable name="result"> + <xsl:apply-templates select="." mode="preproc:class-groupgen" /> + </xsl:variable> + + <xsl:apply-templates select="$result/lv:classify" mode="preproc:class-extract" /> + + <preproc:repass src="lv:classify any|all" /> +</xsl:template> + + +<xsl:template match="lv:classify" mode="preproc:class-groupgen" priority="5"> + <xsl:copy> + <xsl:sequence select="@*" /> + <xsl:apply-templates mode="preproc:class-groupgen" /> + </xsl:copy> +</xsl:template> + + +<xsl:template mode="preproc:class-groupgen" priority="9" + match="lv:any[ not( element() ) ] + |lv:all[ not( element() ) ]"> + <!-- useless; remove --> +</xsl:template> + + +<xsl:template match="lv:any|lv:all" mode="preproc:class-groupgen" priority="5"> + <!-- this needs to be unique enough that there is unlikely to be a conflict + between generated ids in various packages; generate-id is not enough for + cross-package guarantees (indeed, I did witness conflicts), so there is + a random seed passed into the stylesheet externally --> + <xsl:variable name="id" select="concat( $__rseed, generate-id(.) )" /> + + <xsl:variable name="parent-name" select="ancestor::lv:classify/@as" /> + <xsl:variable name="yields" select="concat( 'is', $id )" /> + + <xsl:variable name="external" as="xs:string?" + select="ancestor::lv:classify/@external" /> + + <!-- this will be raised outside of the parent classification during + post-processing --> + <lv:classify as="{$id}" yields="{$yields}" + preproc:generated="true" + preproc:generated-from="{$parent-name}" + external="{$external}" + desc="(generated from predicate group of {$parent-name}"> + <xsl:if test="local-name() = 'any'"> + <xsl:attribute name="any" select="'true'" /> + </xsl:if> + + <xsl:apply-templates mode="preproc:class-groupgen" /> + </lv:classify> + + <!-- this will remain in its place --> + <lv:match on="{$yields}" value="TRUE" preproc:generated="true" /> +</xsl:template> + + +<!-- retain everything else --> +<xsl:template match="*" mode="preproc:class-groupgen" priority="1"> + <xsl:sequence select="." /> +</xsl:template> + + +<xsl:template match="lv:classify" mode="preproc:class-extract" priority="5"> + <xsl:apply-templates select="lv:classify" mode="preproc:class-extract" /> + + <xsl:copy> + <xsl:sequence select="@*" /> + <xsl:apply-templates mode="preproc:class-filter" /> + </xsl:copy> +</xsl:template> + + +<xsl:template match="*" mode="preproc:class-extract" priority="1"> + <!-- ignore non-class --> +</xsl:template> + + +<xsl:template match="lv:classify" mode="preproc:class-filter" priority="5"> + <!-- remove --> +</xsl:template> + + +<xsl:template match="*" mode="preproc:class-filter" priority="1"> + <xsl:sequence select="." /> +</xsl:template> + + +<!-- + Sections exist purely for organization and documentation. Move all + nodes out of it, so that we do not complicate parsing. +--> +<xsl:template mode="preproc:macros" priority="2" + match="lv:section"> + <xsl:apply-templates select="*" mode="preproc:macros" /> +</xsl:template> + + +<!-- + lv:yield is simply another rate block with a special name that is recognized + by the linker +--> +<xsl:template match="lv:yield" mode="preproc:macros" priority="5"> + <lv:rate yields="___yield" local="true"> + <xsl:apply-templates mode="preproc:macros" /> + </lv:rate> +</xsl:template> + + +<!-- this situation may occur both manually and from lv:rate-each-template --> +<xsl:template match="lv:rate-each[ lv:apply-template ]" mode="preproc:macros" priority="9"> + <xsl:variable name="apply"> + <preproc:apply> + <xsl:apply-templates select="lv:apply-template" mode="preproc:macros" /> + </preproc:apply> + </xsl:variable> + + <xsl:choose> + <!-- did the template apply? (note that we only check for a single one, + since that's all that we should have) --> + <xsl:when test="$apply/preproc:apply/lv:apply-template"> + <xsl:sequence select="." /> + + <xsl:message> + <xsl:text>[preproc] waiting to expand rate-each </xsl:text> + <xsl:value-of select="@yields" /> + <xsl:text> (immediate template(s) need expansion)...</xsl:text> + </xsl:message> + </xsl:when> + + <xsl:otherwise> + <!-- it applied! --> + <xsl:copy> + <xsl:sequence select="@*, *[ not( local-name()='apply-template' ) ]" /> + <xsl:sequence select="$apply/preproc:apply/*" /> + </xsl:copy> + + <!-- we'll process this block next time around --> + <preproc:repass src="lv:rate-each lv:apply-template" /> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<!-- + Convenience macro that expands to a lv:rate block summing over the magic + _CMATCH_ set with the product of its value + + The intent here is to reduce highly repetitive code. +--> +<xsl:template match="lv:rate-each" mode="preproc:macros" priority="5"> + <!-- TODO: debug flag + <xsl:message> + <xsl:text>[preproc] expanding rate-each </xsl:text> + <xsl:value-of select="@yields" /> + <xsl:text>...</xsl:text> + </xsl:message> + --> + + <lv:rate> + <xsl:sequence select="@*[ + not( local-name() = 'index' ) + and not( local-name() = 'generates' ) + ]" /> + + <xsl:if test="not( @yields )"> + <!-- if @generates is not supplied either, then we cannot continue --> + <xsl:choose> + <xsl:when test="not( @generates )"> + <!-- TODO: some means of identifying this...the error isn't terribly + helpful... :x --> + <preproc:error> + <xsl:text>rate-each must provide either @yields or @generates</xsl:text> + </preproc:error> + </xsl:when> + + <xsl:otherwise> + <xsl:attribute name="yields" + select="concat( '_', @generates )" /> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + + <xsl:sequence select="./lv:class" /> + + <c:sum of="_CMATCH_" index="{@index}" sym="{@gensym}"> + <!-- copy @generates, if it exists (has the benefit of copying nothing + if it does not exist) --> + <xsl:sequence select="@generates" /> + + <xsl:attribute name="desc"> + <xsl:text>Set of individual </xsl:text> + <xsl:value-of select="@yields" /> + <xsl:text> premiums</xsl:text> + </xsl:attribute> + + <c:product> + <c:value-of name="_CMATCH_" index="{@index}"> + <xsl:attribute name="label"> + <xsl:text>Zero if not </xsl:text> + <xsl:value-of select="@class" /> + <xsl:text>, otherwise one</xsl:text> + </xsl:attribute> + </c:value-of> + + <xsl:apply-templates + select="*[ + not( + local-name() = 'class' + ) + ]" + mode="preproc:macros" /> + </c:product> + </c:sum> + </lv:rate> +</xsl:template> + + +<!-- + Generates a classifier for each boolean param + + This is for convenience; a boolean can esssentially be considered its own + classifier, so let's generate one to cut down on the amount of code. + + Technically not a macro, but needs to be done before preproc:expand. + + XXX: Get rid of me! Now unused! +--> +<xsl:template match="lv:params[ not( @preproc:processed ) ]" + mode="preproc:macros" priority="5"> + + <xsl:copy> + <xsl:attribute name="preproc:processed" select="'true'" /> + <xsl:sequence select="@*" /> + + <xsl:apply-templates mode="preproc:macros" /> + </xsl:copy> + + <xsl:for-each select="lv:param[ @type='boolean' ]"> + <xsl:variable name="as" select="translate( @name, '_', '-' )" /> + <xsl:variable name="genas" select="concat( 'is-', $as )" /> + + <!-- ensure that this name does not already exist --> + <xsl:if test="not( /lv:*/lv:classify[ @as=$genas ] )"> + <!-- TODO: We're flagging as @keep for now due to gclass needs, but this + should be removed later --> + <lv:classify as="{$genas}" desc="{@desc}" keep="true"> + <lv:match on="{@name}" value="TRUE" /> + </lv:classify> + </xsl:if> + </xsl:for-each> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/include/preproc/package.xsl b/src/current/include/preproc/package.xsl new file mode 100644 index 0000000..48536af --- /dev/null +++ b/src/current/include/preproc/package.xsl @@ -0,0 +1,817 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Preprocesses package XML + + This will preprocess a package XML suitable for compilation into an object + file. +--> + +<xsl:stylesheet version="1.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + + xmlns:preproc="http://www.lovullo.com/rater/preproc" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:t="http://www.lovullo.com/rater/apply-template" + xmlns:c="http://www.lovullo.com/calc" + xmlns:w="http://www.lovullo.com/rater/worksheet" + xmlns:lvv="http://www.lovullo.com/rater/validate" + xmlns:ext="http://www.lovullo.com/ext" + xmlns:util="http://www.lovullo.com/util"> + + +<xsl:include href="../dslc-base.xsl" /> + +<!-- phases --> +<xsl:include href="macros.xsl" /> +<xsl:include href="expand.xsl" /> +<xsl:include href="symtable.xsl" /> + +<xsl:include href="../../compiler/fragments.xsl" /> + + +<!-- begin preprocessing from an arbitrary node --> +<xsl:template name="preproc:pkg-compile" as="element( lv:package )" + match="*" mode="preproc:compile" priority="1"> + <xsl:param name="orig-root" as="element()" + select="root(.)/lv:package" /> + + <xsl:param name="stopshort" /> + + <!-- should be provided externally --> + <xsl:if test="not( $__rseed ) or ( $__rseed = '' )"> + <xsl:message terminate="yes"> + <xsl:text>[preproc] error: missing random seed `__rseed'</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:message> + <xsl:text>[preproc] *beginning macro expansion...</xsl:text> + </xsl:message> + + <!-- these can contain repass nodes, thus the element()+ --> + + <!-- macro expansion --> + <xsl:variable name="stage1" as="element()+"> + <xsl:apply-templates select="." mode="preproc:macropass" /> + + <xsl:message> + <xsl:text>[preproc] *macro pass complete; expanding...</xsl:text> + </xsl:message> + </xsl:variable> + + <!-- expand shorthands, etc --> + <xsl:variable name="stage2" as="element()+"> + <xsl:apply-templates select="$stage1" + mode="preproc:expand" /> + + <xsl:message> + <xsl:text>[preproc] *expansion complete; generating symbol table...</xsl:text> + </xsl:message> + </xsl:variable> + + <xsl:variable name="stage3" as="element()+"> + <xsl:apply-templates select="$stage2" + mode="preproc:sym-discover"> + + <xsl:with-param name="orig-root" select="$orig-root" /> + </xsl:apply-templates> + + <xsl:message> + <xsl:text>[preproc] *symbol table generated; checking for </xsl:text> + <xsl:text>unprocessed templates...</xsl:text> + </xsl:message> + </xsl:variable> + + + <!-- TODO: resolve this mess --> + <xsl:variable name="stage3-pkg" as="element( lv:package )" + select="$stage3/preproc:symtable/parent::*" /> + + <!-- TODO: move me somewhere more appropriate and create an error + system that is _guaranteed_ to catch everything --> + <xsl:for-each select="$stage3-pkg/preproc:symtable/preproc:error"> + <xsl:message terminate="yes"> + <xsl:text>!!! [preproc] error: </xsl:text> + <xsl:value-of select="." /> + </xsl:message> + </xsl:for-each> + + + <!-- determine if we should finish or simply return for further processing --> + <xsl:choose> + <xsl:when test="not( $stopshort )"> + <!-- template expansions may have been deferred until their symbols were made + available --> + <xsl:variable name="final" as="element( lv:package )"> + <xsl:call-template name="preproc:tpl-sym-recurse"> + <xsl:with-param name="package" select="$stage3-pkg" /> + <xsl:with-param name="orig-root" select="$orig-root" /> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="extern-chk" as="element( preproc:error )*" + select="preproc:final-extern-check( + $final/preproc:symtable )" /> + + + <xsl:if test="$extern-chk"> + <xsl:for-each select="$extern-chk"> + <xsl:message> + <xsl:text>!!! [preproc] error: </xsl:text> + <xsl:value-of select="." /> + </xsl:message> + </xsl:for-each> + + <xsl:message>~~~~[begin document dump]~~~~</xsl:message> + <xsl:message select="$final" /> + <xsl:message>~~~~[end document dump]~~~~</xsl:message> + + <xsl:message select="'Aborting due to unresolved externs'" /> + + <xsl:message terminate="yes" + select="'Document dumped.'" /> + </xsl:if> + + <!-- ensure that all template parameters have been expanded --> + <xsl:apply-templates select="$final" mode="preproc:tpl-check" /> + + <!-- perform validation before dependency generation to ensure that all + dependencies are available --> + <xsl:apply-templates select="$final" mode="preproc:pkg-validate" /> + + <!-- determine how many passes have been made --> + <!-- TODO: reintroduce + <xsl:variable name="repass-count" + select="count( $final//preproc:repass-record )" /> + --> + + <!-- assign unique ids to each node --> + <xsl:variable name="idized" as="element( lv:package )"> + <xsl:apply-templates select="$final" mode="preproc:idize" /> + </xsl:variable> + + <!-- generate deps --> + <xsl:variable name="depd" as="element( lv:package )"> + <xsl:apply-templates select="$idized" mode="preproc:gen-deps" /> + </xsl:variable> + + <!-- post-process symbol table to resolve any unknowns that require a + dependency tree --> + <xsl:variable name="resolvd" as="element( lv:package )"> + <xsl:apply-templates select="$depd" mode="preproc:resolv-syms"> + <xsl:with-param name="orig-root" select="$orig-root" /> + </xsl:apply-templates> + </xsl:variable> + + + <!-- compile fragments --> + <xsl:message> + <xsl:text>[preproc] compiling fragments...</xsl:text> + </xsl:message> + + + <xsl:apply-templates select="$resolvd" + mode="preproc:compile-fragments" /> + + + <!-- output a repass count, which could be a strong indicator of performance + issues --> + <!-- TODO: reintroduce + <xsl:message> + <xsl:text>[Preprocessor repass count: </xsl:text> + <xsl:value-of select="$repass-count" /> + <xsl:text>] [Node count: </xsl:text> + <xsl:value-of select="count( $final//* )" /> + <xsl:text>]</xsl:text> + </xsl:message> + --> + </xsl:when> + + <!-- return for further processing --> + <xsl:otherwise> + <xsl:sequence select="$stage3" /> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<!-- + A very primitive guard against unexpanded template parameters. +--> +<xsl:template match="*[ starts-with( @*, '@' ) ]" mode="preproc:tpl-check" priority="5"> + <xsl:message> + <xsl:text>[preproc] fatal: unexpanded template parameter: </xsl:text> + <xsl:sequence select="@*[ starts-with( ., '@' ) ]" /> + </xsl:message> + + <xsl:message> + <xsl:text>[preproc] fatal: reference node: </xsl:text> + <xsl:sequence select="." /> + </xsl:message> + + <xsl:message>~~~~[begin document dump]~~~~</xsl:message> + <xsl:message select="root(.)" /> + <xsl:message>~~~~[end document dump]~~~~</xsl:message> + + <xsl:message terminate="yes">[preproc] notice: Document dumped.</xsl:message> +</xsl:template> + + +<!-- this should never happen; but is has, so here's a failsafe --> +<xsl:template match="lv:apply-template" mode="preproc:tpl-check" priority="5"> + <xsl:message> + <xsl:text>[preproc] fatal: unexpanded template: </xsl:text> + <xsl:sequence select="." /> + </xsl:message> + + <xsl:message> + <xsl:text>[preproc] fatal: reference node: </xsl:text> + <xsl:sequence select="." /> + </xsl:message> + + <xsl:message select="'[preproc] internal error: there is a bug in the', + 'preprocessor; this should never happen!'" /> + + <xsl:message>~~~~[begin document dump]~~~~</xsl:message> + <xsl:message select="root(.)" /> + <xsl:message>~~~~[end document dump]~~~~</xsl:message> + + <xsl:message terminate="yes">[preproc] notice: Document dumped.</xsl:message> +</xsl:template> + + +<!-- skip things that cannot contain template applications --> +<xsl:template match="lv:template|lv:const|lv:typedef" + mode="preproc:tpl-check" priority="9"> +</xsl:template> + +<xsl:template match="*" mode="preproc:tpl-check" priority="1"> + <xsl:apply-templates select="*" mode="preproc:tpl-check" /> +</xsl:template> + + + +<!-- + TODO: This needs to go away in the form of a more performant system + that marks a repass as needed *without* scanning the entire tree. +--> +<xsl:template name="preproc:tpl-sym-recurse" as="element( lv:package )"> + <xsl:param name="package" as="element( lv:package )" /> + <xsl:param name="orig-root" as="element()" /> + + <!-- number of iterations where no template applications have + happened --> + <xsl:param name="tpl-stall-count" + tunnel="yes" + select="0" /> + + <!-- get a list of needed template applications (ignoring applications that + are within templates and nested applications) --> + <xsl:variable name="apply" as="element( lv:apply-template )*" + select="$package//lv:apply-template[ + not( + @name=$package/lv:template/@name + or ancestor::lv:template + or ancestor::lv:apply-template + ) + ]" /> + <xsl:variable name="napply" select="count( $apply )" /> + + <xsl:choose> + <xsl:when test="$apply"> + <!-- get a list of required templates --> + <xsl:variable name="req"> + <tpl> + <xsl:for-each select="$apply"> + <xsl:copy> + <xsl:sequence select="@name" /> + </xsl:copy> + </xsl:for-each> + </tpl> + </xsl:variable> + + <xsl:variable name="requniq" as="element( lv:apply-template )*" select=" + $req//lv:apply-template[ + not( @name=preceding-sibling::lv:apply-template/@name + or @name=$package//lv:template/@name ) ] + " /> + + <xsl:message> + <xsl:text>[preproc] </xsl:text> + <xsl:value-of select="count( $apply )" /> + <xsl:text> template(s) still need application: </xsl:text> + + <xsl:for-each select="$apply"> + <xsl:if test="position() gt 1"> + <xsl:text>, </xsl:text> + </xsl:if> + + <xsl:value-of select="@name" /> + </xsl:for-each> + </xsl:message> + + <!-- load each of the requested templates (but only once) --> + <xsl:variable name="tpls"> + <!-- TODO: we no longer need to load the templates; the + template replacement looks up the template on-demand from + the symbol table; we're no longer injecting them --> + <xsl:apply-templates mode="preproc:tpl-from-sym" select="$requniq"> + <xsl:with-param name="orig-root" select="$orig-root" /> + <xsl:with-param name="symtable" select="$package/preproc:symtable" /> + </xsl:apply-templates> + </xsl:variable> + + <!-- if we have recursed and have not decreased the application count at all, + then we have a problem --> + <xsl:if test="$requniq + and ( $package//preproc:sym-available ) + and not( $package//preproc:repass[ @tpl-applied ] )"> + <xsl:message terminate="yes"> + <xsl:text>!!! [preproc] fatal: unable to locate symbols for </xsl:text> + <xsl:text>remaining templates: </xsl:text> + + <xsl:for-each select="$requniq"> + <xsl:if test="position() > 1"> + <xsl:text>; </xsl:text> + </xsl:if> + + <xsl:value-of select="@name" /> + </xsl:for-each> + </xsl:message> + </xsl:if> + + <!-- if there was an error during this part of the process, halt --> + <xsl:if test="$tpls//preproc:error"> + <xsl:message terminate="yes"> + <xsl:text>!!! [preproc] fatal: terminating due to errors</xsl:text> + </xsl:message> + </xsl:if> + + <!-- perform expansion on the new package with the needed templates --> + <xsl:variable name="result" as="element( lv:package )"> + <xsl:apply-templates select="$package" mode="preproc:compile"> + <xsl:with-param name="orig-root" select="$orig-root" /> + <xsl:with-param name="stopshort" select="true()" /> + </xsl:apply-templates> + + <xsl:message> + <xsl:text>[preproc] *expansion complete (recursive)</xsl:text> + </xsl:message> + </xsl:variable> + + <!-- failsafe to prevent infinite recursion (5 (0-indexed) + should be plenty, since everything in the system results in + a template application in fewer steps --> + <xsl:if test="$requniq and $tpl-stall-count eq 4"> + <xsl:message select="'!!! [preproc] internal: expansion deadlock!'" /> + <xsl:message + select="'!!! [preproc] internal: stalled for 5 iterations'" /> + + <xsl:sequence select="preproc:dump-document( $result )" /> + + <xsl:message terminate="yes"> + <xsl:text></xsl:text> + <xsl:text>!!! [preproc] fatal: expansion of remaining </xsl:text> + <xsl:text>templates aborted (have all been imported?): </xsl:text> + + <xsl:for-each select="$requniq"> + <xsl:if test="position() > 1"> + <xsl:text>; </xsl:text> + </xsl:if> + + <xsl:value-of select="@name" /> + </xsl:for-each> + </xsl:message> + </xsl:if> + + <!-- recurse to continue expanding if need be --> + <xsl:call-template name="preproc:tpl-sym-recurse"> + <xsl:with-param name="orig-root" select="$orig-root" /> + <xsl:with-param name="package" select="$result" /> + <xsl:with-param name="tpl-stall-count" + tunnel="yes" + select="if ( $package//preproc:tpl-step ) then + 0 + else + $tpl-stall-count + 1" /> + </xsl:call-template> + </xsl:when> + + + <!-- expansion sequences and template short-hand expansions that + have not yet taken place, due to one reason or another (too + few passes: bug as far as I'm concerned) --> + <xsl:when test="$package//lv:expand-sequence[ + not( ancestor::lv:template + or ancestor::lv:apply-template ) ] + |$package//t:*[ + not( ancestor::lv:template + or ancestor::lv:apply-template ) ]"> + <xsl:message select="'[preproc] pending expansions still present'" /> + + <xsl:variable name="result" as="element( lv:package )"> + <xsl:apply-templates select="$package" mode="preproc:compile"> + <xsl:with-param name="orig-root" select="$orig-root" /> + <xsl:with-param name="stopshort" select="true()" /> + </xsl:apply-templates> + </xsl:variable> + + <xsl:call-template name="preproc:tpl-sym-recurse"> + <xsl:with-param name="orig-root" select="$orig-root" /> + <xsl:with-param name="package" select="$result" /> + </xsl:call-template> + </xsl:when> + + + <!-- no further applications are necessary --> + <xsl:otherwise> + <!-- apply one final pass for the eligibility class generation --> + <xsl:call-template name="preproc:elig-class-pass"> + <xsl:with-param name="package" select="$package" /> + <xsl:with-param name="orig-root" select="$orig-root" /> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:template name="preproc:elig-class-pass" as="element( lv:package )"> + <xsl:param name="package" as="element( lv:package )" /> + <xsl:param name="orig-root" as="element()" /> + + <xsl:variable name="final-pkg" as="element( lv:package )"> + <xsl:apply-templates select="$package" mode="preproc:expand-elig-class"> + <xsl:with-param name="orig-root" select="$package" /> + </xsl:apply-templates> + </xsl:variable> + + <!-- yield the final pass against the elig-class-augmented package --> + <xsl:apply-templates select="$final-pkg" mode="preproc:compile"> + <xsl:with-param name="orig-root" select="$orig-root" /> + <xsl:with-param name="stopshort" select="true()" /> + </xsl:apply-templates> +</xsl:template> + + +<!-- + This is but a shell of its former self. + + We are no longer injecting templates, so this can be removed or + adapted to do just enough to verify that the template symbols exist. +--> +<xsl:template match="lv:apply-template" mode="preproc:tpl-from-sym"> + <xsl:param name="orig-root" as="element()" /> + + <xsl:param name="symtable" as="element( preproc:symtable )" + select="root(.)/preproc:symtable" /> + + <xsl:variable name="tplname" select="@name" /> + + <xsl:variable name="sym" as="element( preproc:sym )?" select=" + $symtable/preproc:sym[ + @name=$tplname + and @type='tpl' + ] + " /> + + <!-- if we have a symbol table, then attempt to locate it --> + <xsl:choose> + <!-- if we have located the template, then we know its source (@src must be + set, otherwise the template is defined in this package and should have + been available to begin with) --> + <xsl:when test="$sym"> + <preproc:sym-available for="{$tplname}" /> + </xsl:when> + + <!-- nothing we can do yet --> + <xsl:otherwise> + <xsl:message> + <xsl:text>[preproc] template symbol not yet available: </xsl:text> + <xsl:value-of select="$tplname" /> + </xsl:message> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<!-- TODO: we should use an attr besides _id; one more definitive --> +<xsl:template match="*[ @_id ]" mode="preproc:macropass" priority="9"> + <!-- already preprocessed --> + <xsl:sequence select="." /> +</xsl:template> +<xsl:template match="*[ @_id ]" mode="preproc:idize" priority="9"> + <!-- already preprocessed --> + <xsl:sequence select="." /> +</xsl:template> + + + +<xsl:template match="preproc:repass-record" mode="preproc:idize" priority="9"> + <!-- no longer needed; remove --> +</xsl:template> + + +<!-- do not idize preproc nodes (unneeded waste of cycles) --> +<xsl:template match="preproc:*" mode="preproc:idize" priority="5"> + <xsl:sequence select="." /> +</xsl:template> + +<!-- do not idize templates (will cause processing problems) --> +<xsl:template match="lv:template" mode="preproc:idize" priority="5"> + <xsl:sequence select="." /> +</xsl:template> + + +<!-- + Generates a unique id for each element and stores it as @_id + + This allows the node set to be copied and maintain its identity. +--> +<xsl:template match="*" mode="preproc:idize" priority="1"> + <xsl:variable name="id"> + <xsl:value-of select="generate-id(.)" /> + </xsl:variable> + + <xsl:copy> + <xsl:sequence select="@*" /> + + <xsl:attribute name="_id"> + <xsl:value-of select="$id" /> + </xsl:attribute> + + <xsl:apply-templates mode="preproc:idize" /> + </xsl:copy> +</xsl:template> + + +<xsl:template match="lv:package" mode="preproc:resolv-syms" as="element( lv:package )" + priority="9"> + <xsl:param name="orig-root" as="element()" /> + <xsl:param name="rpcount" select="0" /> + + <!-- arbitrary; intended to prevent infinite recursion --> + <!-- TODO: same method as for templates; ensure changes, but do not create + arbitrary limit --> + <xsl:if test="$rpcount = 100"> + <xsl:sequence select="preproc:dump-document( root() )" /> + + <xsl:message terminate="yes"> + <xsl:text>[preproc] !!! recursion limit reached in resolving `</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>' symbols</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:variable name="result" as="element( lv:package )"> + <xsl:copy> + <xsl:sequence select="@*" /> + + <xsl:message> + <xsl:text>[preproc] *resolving symbol attributes...</xsl:text> + </xsl:message> + + <xsl:apply-templates mode="preproc:resolv-syms"> + <xsl:with-param name="orig-root" select="$orig-root" /> + </xsl:apply-templates> + </xsl:copy> + </xsl:variable> + + <xsl:variable name="repass" + select="$result//preproc:symtable/preproc:repass" /> + + <xsl:choose> + <!-- repass scheduled; go for it --> + <xsl:when test="$repass"> + <xsl:message>[preproc] *SYM REPASS*</xsl:message> + <xsl:message> + <xsl:text>[preproc] The following </xsl:text> + <xsl:value-of select="count( $repass )" /> + <xsl:text> symbol(s) are still unresolved:</xsl:text> + </xsl:message> + + <xsl:for-each select="$repass"> + <xsl:message> + <xsl:text>[preproc] - </xsl:text> + <xsl:value-of select="@ref" /> + </xsl:message> + </xsl:for-each> + + <xsl:apply-templates select="$result" mode="preproc:resolv-syms"> + <xsl:with-param name="orig-root" select="$orig-root" /> + <xsl:with-param name="rpcount" select="$rpcount + 1" /> + </xsl:apply-templates> + </xsl:when> + + <!-- no repass needed; done --> + <xsl:otherwise> + <xsl:sequence select="$result" /> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:template match="preproc:symtable" mode="preproc:resolv-syms" priority="5"> + <xsl:param name="orig-root" as="element()" /> + + <xsl:copy> + <xsl:apply-templates mode="preproc:resolv-syms"> + <xsl:with-param name="orig-root" select="$orig-root" /> + </xsl:apply-templates> + </xsl:copy> +</xsl:template> + + +<xsl:template match="preproc:sym[ not( @src ) and @dim='?' ]" mode="preproc:resolv-syms" priority="5"> + <xsl:param name="orig-root" as="element()" /> + + <xsl:variable name="name" select="@name" /> + <xsl:variable name="pkg" as="element( lv:package )" + select="root(.)" /> + + <xsl:variable name="deps" as="element( preproc:sym-dep )*" select=" + $pkg/preproc:sym-deps/preproc:sym-dep[ @name=$name ] + " /> + + <xsl:variable name="depsyms-unresolv" as="element( preproc:sym )*" select=" + $pkg/preproc:symtable/preproc:sym[ + @name=$deps/preproc:sym-ref/@name + ] + " /> + + <xsl:variable name="depsyms-resolv"> + <xsl:for-each select="$depsyms-unresolv"> + <xsl:choose> + <xsl:when test="not( @src )"> + <xsl:sequence select="." /> + </xsl:when> + + <!-- look up complete symbol --> + <xsl:otherwise> + <xsl:variable name="name" select="@name" /> + <xsl:variable name="sym" select=" + document( concat( @src, '.xmlo' ), $orig-root ) + /lv:package/preproc:symtable/preproc:sym[ + @name=$name + ] + " /> + + <xsl:if test="not( $sym )"> + <xsl:message terminate="yes"> + <xsl:text>[preproc] !!! failed to look up symbol `</xsl:text> + <xsl:value-of select="$name" /> + <xsl:text>'</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:sequence select="$sym" /> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </xsl:variable> + + <xsl:variable name="depsyms" select="$depsyms-resolv/preproc:sym" /> + + <xsl:choose> + <!-- unresolved dependency dimensions; defer until next pass --> + <xsl:when test=" + $depsyms/@dim = '?' + "> + <xsl:message> + <xsl:text>[preproc] deferring `</xsl:text> + <xsl:value-of select="$name" /> + <xsl:text>' dimensions with unresolved dependencies</xsl:text> + </xsl:message> + + <!-- schedule repass :x --> + <xsl:sequence select="." /> + <preproc:repass src="preproc:sym resolv-syms" + ref="{$name}" /> + </xsl:when> + + <!-- all dependencies are resolved; calculate dimensions --> + <xsl:otherwise> + <!-- sort dependencies so that the largest dimension is at the top --> + <xsl:variable name="maxset"> + <xsl:for-each select="$depsyms"> + <xsl:sort select="@dim" data-type="number" order="descending" /> + <xsl:sequence select="." /> + </xsl:for-each> + </xsl:variable> + + <xsl:variable name="max"> + <xsl:choose> + <xsl:when test="count( $deps/preproc:sym-ref ) = 0"> + <!-- no dependencies, unknown size, so it's a scalar --> + <xsl:text>0</xsl:text> + </xsl:when> + + <xsl:otherwise> + <!-- largest value --> + <xsl:value-of select="$maxset/preproc:sym[1]/@dim" /> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <!-- failure? --> + <xsl:if test="not( $max ) or $max = ''"> + <xsl:message terminate="yes"> + <xsl:text>[preproc] !!! failed to determine dimensions of `</xsl:text> + <xsl:value-of select="$name" /> + <xsl:text>'</xsl:text> + </xsl:message> + </xsl:if> + + <!-- logging --> + <xsl:message> + <xsl:text>[preproc] resolved `</xsl:text> + <xsl:value-of select="$name" /> + <xsl:text>' dimensions as `</xsl:text> + <xsl:value-of select="$max" /> + <xsl:text>'</xsl:text> + </xsl:message> + + <!-- copy, substituting calculated dimensions --> + <xsl:copy> + <xsl:sequence select="@*" /> + <xsl:attribute name="dim" select="$max" /> + <xsl:sequence select="*" /> + </xsl:copy> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:template match="preproc:repass" mode="preproc:resolv-syms" priority="9"> + <!-- strip --> +</xsl:template> + + +<xsl:template match="*" mode="preproc:resolv-syms" priority="1"> + <xsl:sequence select="." /> +</xsl:template> + + +<xsl:template match="lv:package" mode="preproc:pkg-validate"> + <xsl:variable name="symbol-map"> + <xsl:call-template name="get-symbol-map" /> + </xsl:variable> + + <xsl:variable name="err"> + <xsl:apply-templates select="." mode="lvv:validate"> + <xsl:with-param name="symbol-map" select="$symbol-map" /> + </xsl:apply-templates> + </xsl:variable> + + <xsl:apply-templates select="$err//lvv:error" mode="preproc:handle-lvv-errors"> + <xsl:with-param name="document" select="." /> + </xsl:apply-templates> +</xsl:template> + + +<xsl:template match="*|text()" mode="preproc:pkg-validate"> +</xsl:template> + + +<!-- errors should cause a failure --> +<xsl:template match="lvv:error" mode="preproc:handle-lvv-errors" priority="5"> + <xsl:param name="document" /> + + <!-- output error --> + <xsl:message> + <xsl:text>!!! </xsl:text> + <xsl:value-of select="@desc" /> + + <xsl:if test="@path != ''"> + <xsl:text> (</xsl:text> + <xsl:value-of select="@path" /> + <xsl:text>)</xsl:text> + </xsl:if> + + <xsl:text>: </xsl:text> + <xsl:value-of select="." /> + </xsl:message> + + <!-- terminate after we've output each error --> + <xsl:if test="not( following-sibling::lvv:error )"> + <!-- dump document for debugging --> + <xsl:sequence select="preproc:dump-document( $document )" /> + <xsl:message terminate="yes">Compilation failed due to validation errors.</xsl:message> + </xsl:if> +</xsl:template> + + +<xsl:function name="preproc:dump-document"> + <xsl:param name="document" /> + + <xsl:message>~~~~[begin document dump]~~~~</xsl:message> + <xsl:message select="$document" /> + <xsl:message>~~~~[end document dump]~~~~</xsl:message> + <xsl:message>internal: document dumped.</xsl:message> +</xsl:function> + + +<xsl:template match="node()|text()" mode="preproc:handle-lvv-errors" priority="1"> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/include/preproc/path.xsl b/src/current/include/preproc/path.xsl new file mode 100644 index 0000000..469603d --- /dev/null +++ b/src/current/include/preproc/path.xsl @@ -0,0 +1,230 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Operations on paths +--> +<xsl:stylesheet version="1.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:preproc="http://www.lovullo.com/rater/preproc"> + + +<xsl:template name="preproc:get-path"> + <xsl:param name="path" /> + <xsl:param name="prev" select="''" /> + + <xsl:variable name="first" select="substring-before( $path, '/' )" /> + <xsl:variable name="rest" select="substring-after( $path, '/' )" /> + + <xsl:choose> + <!-- if there's no $first, then there is no path separator, in which case + we're done; if there's no rest, then there is a path separator, but it + resulted in an empty string, meanaing that it ends in a path + separator, in which case we are also done --> + <xsl:when test="not( $first ) or not( $rest )"> + <!-- no more path separators; we're done --> + <xsl:value-of select="$prev" /> + </xsl:when> + + <!-- keep recursing --> + <xsl:otherwise> + <xsl:call-template name="preproc:get-path"> + <xsl:with-param name="path" select="$rest" /> + <xsl:with-param name="prev"> + <xsl:if test="not( $prev = '' )"> + <xsl:value-of select="concat( $prev, '/' )" /> + </xsl:if> + + <xsl:value-of select="$first" /> + </xsl:with-param> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<!-- FIXME: duplicate code with above --> +<xsl:template name="preproc:get-basename"> + <xsl:param name="path" /> + <xsl:param name="prev" select="''" /> + + <xsl:variable name="first" select="substring-before( $path, '/' )" /> + <xsl:variable name="rest" select="substring-after( $path, '/' )" /> + + <xsl:choose> + <!-- if there's no $first, then there is no path separator, in which case + we're done; if there's no rest, then there is a path separator, but it + resulted in an empty string, meanaing that it ends in a path + separator, in which case we are also done --> + <xsl:when test="not( $first ) or not( $rest )"> + <!-- no more path separators; we're done --> + <xsl:value-of select="$path" /> + </xsl:when> + + <!-- keep recursing --> + <xsl:otherwise> + <xsl:call-template name="preproc:get-basename"> + <xsl:with-param name="path" select="$rest" /> + <xsl:with-param name="prev"> + <xsl:if test="not( $prev = '' )"> + <xsl:value-of select="concat( $prev, '/' )" /> + </xsl:if> + + <xsl:value-of select="$first" /> + </xsl:with-param> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:template name="preproc:resolv-path"> + <xsl:param name="path" /> + + <!-- in order: strip //, process ../, strip ./ --> + <xsl:call-template name="preproc:strip-sdot-path"> + <xsl:with-param name="path"> + <xsl:call-template name="preproc:resolv-rel-path"> + <xsl:with-param name="path"> + <xsl:call-template name="preproc:strip-extra-path"> + <xsl:with-param name="path" select="$path" /> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + + +<!-- XXX: warning, this won't like 'foo../' --> +<xsl:template name="preproc:resolv-rel-path"> + <xsl:param name="path" /> + + <!-- relative paths --> + <xsl:variable name="before" select="substring-before( $path, '../' )" /> + <xsl:variable name="after" select="substring-after( $path, '../' )" /> + + <xsl:choose> + <xsl:when test="$before"> + <xsl:call-template name="preproc:resolv-rel-path"> + <xsl:with-param name="path"> + <!-- remove the last directory before the ../ --> + <xsl:variable name="before-path"> + <xsl:call-template name="preproc:get-path"> + <xsl:with-param name="path" select="$before" /> + </xsl:call-template> + </xsl:variable> + + <xsl:value-of select="$before-path" /> + + <!-- the above get-path call will strip the trailing slash --> + <xsl:if test="not( $before-path = '' ) and not( $after = '' )"> + <xsl:text>/</xsl:text> + </xsl:if> + + <xsl:value-of select="$after" /> + </xsl:with-param> + </xsl:call-template> + </xsl:when> + + + <!-- if there's no $before but there is an $after, then we must begin with + '../', which we can do nothing with; output it and continue processing + the remainder of the path --> + <xsl:when test="$after"> + <xsl:text>../</xsl:text> + + <xsl:call-template name="preproc:resolv-rel-path"> + <xsl:with-param name="path" select="$after" /> + </xsl:call-template> + </xsl:when> + + + <!-- no relative paths remaining --> + <xsl:otherwise> + <xsl:value-of select="$path" /> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:template name="preproc:strip-sdot-path"> + <xsl:param name="path" /> + + <xsl:choose> + <!-- the only time this should be called with an unresolved relative path + is if it begins with one, in which case we'll simply output it and + continue processing without it --> + <xsl:when test="starts-with( $path, '../' )"> + <xsl:text>../</xsl:text> + + <!-- continue processing without it --> + <xsl:call-template name="preproc:strip-sdot-path"> + <xsl:with-param name="path" select="substring-after( $path, '../' )" /> + </xsl:call-template> + </xsl:when> + + + <!-- path is safe for processing --> + <xsl:otherwise> + <xsl:variable name="a" select="substring-before( $path, './' )" /> + <xsl:variable name="b" select="substring-after( $path, './' )" /> + + + <xsl:choose> + <!-- if we found one, recurse --> + <xsl:when test="$a or $b"> + <xsl:call-template name="preproc:strip-sdot-path"> + <xsl:with-param name="path"> + <xsl:value-of select="$a" /> + + <xsl:if test="$a and $b"> + <xsl:text>/</xsl:text> + </xsl:if> + + <xsl:value-of select="$b" /> + </xsl:with-param> + </xsl:call-template> + </xsl:when> + + + <!-- done --> + <xsl:otherwise> + <xsl:value-of select="$path" /> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:template name="preproc:strip-extra-path"> + <xsl:param name="path" /> + + <xsl:variable name="a" select="substring-before( $path, '//' )" /> + <xsl:variable name="b" select="substring-after( $path, '//' )" /> + + + <xsl:choose> + <!-- if we found one, recurse --> + <xsl:when test="$a or $b"> + <xsl:call-template name="preproc:strip-extra-path"> + <xsl:with-param name="path"> + <xsl:value-of select="$a" /> + + <xsl:if test="$a and $b"> + <xsl:text>/</xsl:text> + </xsl:if> + + <xsl:value-of select="$b" /> + </xsl:with-param> + </xsl:call-template> + </xsl:when> + + <!-- we're done! --> + <xsl:otherwise> + <xsl:value-of select="$path" /> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/include/preproc/symtable.xsl b/src/current/include/preproc/symtable.xsl new file mode 100644 index 0000000..9210c76 --- /dev/null +++ b/src/current/include/preproc/symtable.xsl @@ -0,0 +1,959 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Generates a symbol table from fully a expanded (preprocessed) package + + It is important that this table be generated after fully expanding all + templates, macros, etc; otherwise, the table may be incomplete. + + The preproc:sym/@tex attribute is a TeX symbol used for typsetting. This + process is not responsible for generating defaults; that should be done by the + linker to ensure that there are no conflicts after all symbols are known. + + Here are the recognized types: + rate - lv:rate block + gen - generator (c:*/@generates) + cgen - class generator (lv:classify/@yields) + class - classification (lv:classify/@as) + param - global param (lv:param) + lparam - local param (lv:function/lv:param) + const - global constant (lv:const; lv:enum/lv:item) + tpl - template (lv:template) + type - datatype (lv:typedef) + func - function + + Dimensions (think 0=point, 1=line, 2=plane, etc): + 0 - scalar + 1 - vector + 2 - matrix + ... + + Symbols from imported packages will be consumed and added to the output, + unless local; this has a similiar effect to including a C header file. + External symbols are denoted by preproc:sym/@src, which specifies the name of + the package from which it was imported. If an imported symbol conflicts with + another symbol (imported or otherwise), an error will be produced. Symbols + that are imported are implicitly marked as local. + + Certain symbols will "polute" the symbol table of every package that imports + it, every package that imports that one, etc; this is for compatibility with + the old system and will hopefully be phased out eventually. Pollution will + reserve the symbol, but will not provide enough information about that symbol + to be useful, which will ensure that a package has to import the symbol + explicitly in order to actually make use of it. +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:symtable="http://www.lovullo.com/tame/symtable" + xmlns:preproc="http://www.lovullo.com/rater/preproc" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:c="http://www.lovullo.com/calc"> + + +<xsl:include href="path.xsl" /> +<xsl:include href="../../tame/src/symtable.xsl" /> + + +<!-- we will recurse through the entire tree rather than performing a series of + xpaths that are likely to hit the same nodes many times --> +<xsl:template match="*" mode="preproc:symtable" priority="1"> + <xsl:apply-templates mode="preproc:symtable" /> +</xsl:template> + +<xsl:template match="text()" mode="preproc:symtable" priority="9"> + <!-- output nothing --> +</xsl:template> + + +<!-- an alternative way to invoke the preproc:sym-discover template; useful for + use on variables --> +<xsl:template match="*" mode="preproc:sym-discover" as="element()"> + <xsl:param name="orig-root" /> + + <xsl:call-template name="preproc:sym-discover"> + <xsl:with-param name="orig-root" select="$orig-root" /> + </xsl:call-template> +</xsl:template> + + +<xsl:template match="preproc:*" mode="preproc:sym-discover" + as="element()" priority="9"> + <xsl:sequence select="." /> +</xsl:template> + + +<!-- + Other systems may contribute to a symbol table by invoking this template and + supplying the necessary preproc:symtable templates + + TODO: This guy needs some refactoring +--> +<xsl:template name="preproc:sym-discover" as="element()"> + <xsl:param name="orig-root" /> + + <xsl:variable name="this-pkg" as="element( lv:package )" + select="." /> + + <xsl:copy> + <xsl:sequence select="@*" /> + + <xsl:variable name="new"> + <xsl:message> + <xsl:text>[preproc/symtable] discovering symbols...</xsl:text> + </xsl:message> + + <preproc:syms> + <xsl:apply-templates mode="preproc:symtable"> + <!-- we only need this param for the root children, so this is the only + template application that passes this --> + <xsl:with-param name="orig-root" select="$orig-root" /> + </xsl:apply-templates> + </preproc:syms> + </xsl:variable> + + <!-- gather a list of overrides --> + <xsl:variable name="overrides" select=" + $new/preproc:syms/preproc:sym[ @override='true' ] + " /> + + <!-- check for duplicates --> + <xsl:variable name="symdup" as="element( preproc:sym )*" + select="symtable:find-duplicates( $new/preproc:syms )" /> + + <!-- overrides that override nothing may be the sign of a bug (expectation + of something that isn't there) --> + <xsl:for-each select="$overrides[ not( @name=$symdup/@name ) ]"> + <xsl:message> + <xsl:text>[preproc/symtable] warning: symbol /</xsl:text> + <xsl:value-of select="$this-pkg/@name" /> + <xsl:text>/</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text> has @override set, but does not override anything</xsl:text> + </xsl:message> + </xsl:for-each> + + <!-- perform non-override duplicate checks (TODO: @ignore-dup is + intended to be temporary while map __head and __tail + generation conflicts are resolved) --> + <xsl:variable name="symdup-problems" as="element( preproc:sym )*" select=" + $symdup[ + not( @name = $overrides/@name + and @virtual = 'true' ) + and not( @ignore-dup = 'true' ) ] + " /> + + <xsl:for-each select="$symdup-problems"> + <xsl:variable name="dupname" select="@name" /> + + <xsl:choose> + <!-- attempt to override a non-virtual symbol --> + <xsl:when test="@name=$overrides/@name and not( @virtual='true' )"> + <xsl:message> + <xsl:text>[preproc/symtable] error: cannot override non-virtual symbol /</xsl:text> + <xsl:value-of select="$this-pkg/@name" /> + <xsl:text>/</xsl:text> + <xsl:value-of select="@name" /> + </xsl:message> + </xsl:when> + + <!-- just a plain duplicate --> + <xsl:otherwise> + <xsl:message> + <xsl:text>[preproc/symtable] error: duplicate symbol /</xsl:text> + <xsl:value-of select="$this-pkg/@name" /> + <xsl:text>/</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text> (defined in ./</xsl:text> + + <!-- output sources --> + <xsl:for-each select=" + $new/preproc:syms/preproc:sym[ + @name=$dupname + and @src + and not( @extern='true' ) + and not( @name=$overrides/@name and @virtual='true' ) + ] + "> + <xsl:if test="position() gt 1"> + <xsl:text> and ./</xsl:text> + </xsl:if> + + <xsl:value-of select="@src" /> + </xsl:for-each> + <xsl:text>)</xsl:text> + + <!-- if virtual, suggest override as an alternative solution --> + <xsl:if test="@virtual='true'"> + <xsl:text>; did you forget @override?</xsl:text> + </xsl:if> + </xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + + <!-- terminate if any duplicates are found, dumping documents for debugging --> + <xsl:if test="count( $symdup-problems ) gt 0"> + <xsl:message>~~~~[begin document dump]~~~~</xsl:message> + <xsl:message select="$this-pkg" /> + <xsl:message>~~~~[end document dump]~~~~</xsl:message> + + <xsl:message>~~~~[begin symbol dump]~~~~</xsl:message> + <xsl:message select="$new" /> + <xsl:message>~~~~[end symbol dump]~~~~</xsl:message> + + <xsl:message terminate="yes"> + <xsl:text>[preproc/symtable] fatal: aborting due to symbol errors</xsl:text> + </xsl:message> + </xsl:if> + + + <xsl:variable name="result" as="element( preproc:symtable )"> + <preproc:symtable> + <!-- copy any existing symbols table --> + <preproc:syms> + <xsl:sequence select="preproc:symtable/preproc:sym" /> + <xsl:sequence select="$new/preproc:syms/preproc:sym" /> + </preproc:syms> + </preproc:symtable> + </xsl:variable> + + <!-- output the symbols, checking for duplicates --> + <preproc:symtable> + <!-- validate imported externs --> + <xsl:variable name="extresults" as="element( preproc:syms )"> + <xsl:call-template name="preproc:symtable-process-extern"> + <xsl:with-param name="result" select="$result" /> + </xsl:call-template> + </xsl:variable> + + <!-- remove duplicates (if any) --> + <xsl:sequence select=" + $extresults/preproc:sym[ + not( @name=preceding-sibling::preproc:sym/@name ) + ] + , $extresults//preproc:error + " /> + + <!-- process symbols (except imported externs) --> + <xsl:variable name="newresult" as="element( preproc:syms )"> + <xsl:call-template name="preproc:symtable-process-symbols"> + <xsl:with-param name="extresults" select="$extresults" /> + <xsl:with-param name="new" select="$new/preproc:syms" /> + <xsl:with-param name="this-pkg" select="$this-pkg" /> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="dedup" as="element( preproc:sym )*" + select="$newresult/preproc:sym[ + not( + ( + @pollute='true' + and not( @type ) + and ( + @name=preceding-sibling::preproc:sym/@name + or @name=$newresult/preproc:sym[ @type ]/@name + ) + ) + ) + ]" /> + + + <xsl:apply-templates mode="preproc:symtable-complete" + select="$dedup"> + <xsl:with-param name="syms" select="$dedup" /> + </xsl:apply-templates> + </preproc:symtable> + + <xsl:message> + <xsl:text>[preproc/symtable] done.</xsl:text> + </xsl:message> + + <!-- copy all of the original elements after the symbol table; we're not + outputting them as we go, so we need to make sure that we don't get + rid of them; discard any existing symbol table --> + <xsl:apply-templates mode="preproc:symtable-inject" /> + </xsl:copy> +</xsl:template> + + +<xsl:template match="preproc:symtable" mode="preproc:symtable-inject" + priority="5"> + <!-- strip old symbol table --> +</xsl:template> + +<xsl:template match="*" mode="preproc:symtable-inject"> + <xsl:sequence select="." /> +</xsl:template> + + +<xsl:template name="preproc:symtable-process-extern" + as="element( preproc:syms )"> + <xsl:param name="result" as="element( preproc:symtable )" /> + + <xsl:variable name="syms" as="element( preproc:syms )" + select="$result/preproc:syms" /> + + <preproc:syms> + <xsl:for-each select="$syms/preproc:sym[ + @extern='true' + and @src + and not( @held ) ]"> + <xsl:variable name="name" select="@name" /> + + <!-- our value may be concrete or an extern itself; we may also import + a package with a concrete definition --> + <xsl:variable name="ours" select=" + $syms/preproc:sym[ + @name=$name + and ( + not( @src ) + or ( @dtype and @src and not( @extern ) ) + ) + ] + " /> + + <xsl:choose> + <!-- we have our own symbol; ensure the important values match the + expected (unless @dim has not yet been resolved) --> + <!-- XXX: the @dim='?' check leaves room for a dimension mismatch to + slip through; re-order checks (see package.xsl) as necessary to + ensure that this doesn't happen --> + <xsl:when test="$ours"> + <xsl:if test=" + not( + @type=$ours/@type + and @dtype=$ours/@dtype + and ( + @dim=$ours/@dim + or $ours/@dim='?' + ) + ) + "> + + <preproc:error> + <xsl:text>extern mismatch: '</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>' (imported from </xsl:text> + <xsl:value-of select="@src" /> + <xsl:text>)</xsl:text> + + <xsl:for-each select="@type, @dtype, @dim"> + <xsl:variable name="aname" select="local-name()" /> + <xsl:text>; </xsl:text> + + <xsl:value-of select="local-name()" /> + <xsl:text>=</xsl:text> + <xsl:value-of select="$ours/@*[ local-name() = $aname ]" /> + <xsl:text>, </xsl:text> + <xsl:value-of select="." /> + <xsl:text> expected</xsl:text> + </xsl:for-each> + </preproc:error> + </xsl:if> + + <!-- N.B.: there could potentially be multiple matches --> + <!-- TODO: pollution should be removed and l:resolv-extern + in the linker should look up the package that should + include the resolved extern from the processing stack --> + <preproc:sym pollute="true"> + <xsl:sequence select="$ours[1]/@*|*" /> + </preproc:sym> + </xsl:when> + + <!-- we do not have our own symbol matching this extern; + ignore this for now, as it may be handled by a future + template expansion --> + <xsl:otherwise> + <preproc:sym held="true"> + <xsl:sequence select="@*" /> + </preproc:sym> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </preproc:syms> +</xsl:template> + + +<xsl:function name="preproc:final-extern-check" as="element(preproc:error )*"> + <xsl:param name="symtable" as="element( preproc:symtable )" /> + + <!-- any remaining unresolved externs at this point are bad --> + <xsl:for-each select="$symtable/preproc:sym[ + @extern = 'true' + and @src ]"> + + <!-- since @missing may be provided, let's include the actual + symbol in the runlog for debugging --> + <xsl:message select="'[preproc] missing extern: ', @name" /> + + <preproc:error> + <xsl:choose> + <xsl:when test="@missing and not( @missing = '' )"> + <xsl:value-of select="normalize-space( @missing )" /> + </xsl:when> + + <xsl:otherwise> + <xsl:text>unresolved extern '</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>'</xsl:text> + </xsl:otherwise> + </xsl:choose> + + <xsl:text> (required by </xsl:text> + <xsl:value-of select="@src" /> + <xsl:text>)</xsl:text> + </preproc:error> + </xsl:for-each> +</xsl:function> + + +<xsl:template name="preproc:symtable-process-symbols"> + <xsl:param name="extresults" as="element( preproc:syms )" /> + <xsl:param name="new" as="element( preproc:syms )" /> + <xsl:param name="this-pkg" as="element( lv:package )" /> + + <preproc:syms> + <xsl:variable name="cursym" as="element( preproc:sym )*" + select="preproc:symtable/preproc:sym[ + not( @held = 'true' ) ]" /> + + <xsl:sequence select="$cursym" /> + + <xsl:message> + <xsl:text>[preproc/symtable] processing symbol table...</xsl:text> + </xsl:message> + + <xsl:for-each select="$new/preproc:sym[ not( @extern='true' and @src ) ]"> + <xsl:variable name="name" select="@name" /> + <xsl:variable name="src" select="@src" /> + <xsl:variable name="dupall" select=" + ( + preceding-sibling::preproc:sym, + $cursym, + $extresults/preproc:sym + )[ + @name=$name + ] + " /> + <xsl:variable name="dup" select=" + $dupall[ + not( + @src=$src + or ( not( @src ) and not( $src ) ) + ) + ] + " /> + + <xsl:choose> + <xsl:when test="@pollute='true' and not( @type )"> + <!-- we'll strip these out later --> + <xsl:sequence select="." /> + </xsl:when> + + <!-- note that dupall uses preceding-sibling, which will catch + duplicates in that case even if @override is not set --> + <xsl:when test="following-sibling::preproc:sym[ @name=$name and @override='true' ]"> + <!-- overridden; we're obsolete :( --> + </xsl:when> + + <!-- if we've gotten this far, then the override is good; clear it --> + <xsl:when test="@override='true'"> + <xsl:copy> + <xsl:sequence select="@*[ not( name()='override' ) ], *" /> + </xsl:copy> + </xsl:when> + + <xsl:when test="$dupall[ @type ]"> + <!-- there is already a symbol of this name from the same package; + let's not add duplicates --> + </xsl:when> + + <xsl:otherwise> + <!-- this symbol is good; use it --> + <xsl:sequence select="." /> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </preproc:syms> +</xsl:template> + + +<xsl:template match="preproc:symtable" mode="preproc:symtable" priority="9"> + <!-- ignore existing symbol tables (for now at least) --> +</xsl:template> + + +<!-- do not re-import symbol tables that have already been imported --> +<xsl:template match="lv:import[ + @package=root(.)/preproc:symtable/preproc:sym[ + @dtype + ]/@src + ]" + mode="preproc:symtable" priority="9"> +</xsl:template> + + +<xsl:template name="preproc:symimport" match="lv:import[ @package ]" mode="preproc:symtable" priority="8"> + <xsl:param name="orig-root" /> + <xsl:param name="package" select="@package" /> + <xsl:param name="export" select="@export" /> + <xsl:param name="ignore-keep" select="@ignore-keep" /> + <xsl:param name="no-extclass" select="@no-extclass" /> + <xsl:param name="keep-classes" select="@keep-classes" /> + + <xsl:variable name="path" select="concat( $package, '.xmlo' )" /> + <xsl:variable name="syms" + select="document( $path, $orig-root )/lv:*/preproc:symtable" /> + + <xsl:variable name="import-path" select="$package" /> + + <!-- if they're including a program package, do they realize what they're + doing!? --> + <!-- FIXME: @allow-nonpkg is no longer accurate terminology; change to + @allow-nonprg --> + <xsl:if test=" + not( @allow-nonpkg = 'true' ) + and $syms/parent::lv:package[ @program='true' ] + "> + <xsl:message terminate="yes"> + <xsl:text>[preproc/symtable] error: refusing to import non-package </xsl:text> + <xsl:value-of select="$import-path" /> + <xsl:text>; use @allow-nonpkg to force (if you know what you are doing)</xsl:text> + </xsl:message> + </xsl:if> + + <!-- determine our path from our name --> + <xsl:variable name="our-path"> + <xsl:call-template name="preproc:get-path"> + <xsl:with-param name="path" + select="ancestor::lv:package/@name" /> + </xsl:call-template> + </xsl:variable> + + <!-- to keep everything consistent and to simplify package equality + assertions, resolve relative paths --> + <xsl:variable name="import-default-path" select="$import-path" /> + + <xsl:message> + <xsl:text>[preproc/symtable] importing symbol table of </xsl:text> + <xsl:value-of select="$import-path" /> + <xsl:text>...</xsl:text> + </xsl:message> + + <!-- attempt to import symbols from the processed package --> + <xsl:if test="not( $syms )"> + <xsl:message terminate="yes"> + <xsl:text>[preproc/symtable] internal error: </xsl:text> + <xsl:text>failed to locate symbol table: </xsl:text> + <xsl:value-of select="$path" /> + </xsl:message> + </xsl:if> + + <!-- copy directly into symbol table, setting external source; local symbols + will not be imported --> + <xsl:for-each select=" + $syms/preproc:sym[ + ( + not( @local='true' ) + or @pollute='true' + or ( + ( @type='class' or @type='cgen' ) + and $keep-classes='true' + ) + ) + and not( $no-extclass='true' and @extclass='true' ) + ] + "> + <xsl:copy> + <xsl:choose> + <!-- pollution should have only the name and pollution status copied + over, which has the effect of reserving the symbol but not + providing enough information to actually make use of it; only + strip the data if this is a second-hand import --> + <!-- TODO: this list has gotten too large, but reducing it will require + refactoring other compilers and may reduce performance --> + <xsl:when test="@pollute='true' and @local='true'"> + <xsl:sequence select="@name, @src, @pollute, @keep, @parent, @extclass" /> + </xsl:when> + + <!-- copy all the symbol information --> + <xsl:otherwise> + <xsl:sequence select="@*" /> + </xsl:otherwise> + </xsl:choose> + + <!-- all imported symbols are implicitly local (so including one package + will not include symbols down the entire hierarchy), unless the + symbol is explicitly marked global or @export was provided on the + import node --> + <xsl:if test="not( $export='true' )"> + <xsl:attribute name="local" select="'true'" /> + </xsl:if> + + <!-- determine the relative path to the import --> + <xsl:attribute name="src"> + <xsl:choose> + <!-- if no @src is set, then the answer is simple: the relative path is + the import path --> + <xsl:when test="not( @src )"> + <xsl:value-of select="$import-default-path" /> + </xsl:when> + + <!-- otherwise, we need to merge the import path into the existing + relative path by prepending the import path (sans the package name + itself) onto the existing relative path and resolving relative + paths --> + <xsl:otherwise> + <xsl:call-template name="preproc:resolv-path"> + <xsl:with-param name="path"> + <!-- get the path of the import, sans package name --> + <xsl:variable name="path"> + <xsl:call-template name="preproc:get-path"> + <xsl:with-param name="path" select="$import-path" /> + </xsl:call-template> + </xsl:variable> + + <!-- concatenate it with the existing relative path --> + <xsl:if test="not( $path = '' )"> + <xsl:value-of select="concat( $path, '/' )" /> + </xsl:if> + + <xsl:value-of select="@src" /> + </xsl:with-param> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:attribute> + + <!-- keep manipulation: *always* keep classes if requested, even if + @ignore-keep is provided; in the case of the latter, unsets @keep --> + <xsl:choose> + <!-- keep classes when requested --> + <xsl:when test=" + ( @type = 'class' or @type = 'cgen' ) + and $keep-classes = 'true'"> + + <xsl:attribute name="keep" select="'false'" /> + </xsl:when> + + <!-- demolish @keep if requested --> + <xsl:when test="$ignore-keep = 'true'"> + <xsl:attribute name="keep" select="'false'" /> + </xsl:when> + </xsl:choose> + + <!-- children should always be copied, unless poluting --> + <xsl:if test="not( @pollute='true' and @local='true' )"> + <xsl:sequence select="preproc:*" /> + </xsl:if> + </xsl:copy> + </xsl:for-each> +</xsl:template> + + +<xsl:template match="lv:template" mode="preproc:symtable" priority="9"> + <!-- do not process template bodies; we're only interested in the symbols + they produce after expansion --> + <preproc:sym name="{@name}" + type="tpl" dim="0" desc="{@desc}" /> +</xsl:template> + + +<xsl:template match="lv:rate" mode="preproc:symtable" priority="5"> + <xsl:variable name="external" select="boolean( @external='true' )" /> + + <preproc:sym name="{@yields}" type="rate" + extclass="{$external}" keep="{boolean( @keep )}" + local="{@local}" dtype="float" dim="0" tex="{@sym}" /> + + <xsl:apply-templates mode="preproc:symtable" /> +</xsl:template> + + +<xsl:template match="lv:const" mode="preproc:symtable" priority="5"> + <xsl:variable name="dim"> + <xsl:choose> + <!-- TODO: matrix/vector predicate to support either type via + @values --> + <xsl:when test="./lv:set or @values"> + <xsl:text>2</xsl:text> + </xsl:when> + + <xsl:when test="./lv:item"> + <xsl:text>1</xsl:text> + </xsl:when> + + <xsl:otherwise> + <xsl:text>0</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <!-- TODO: remove magic support --> + <preproc:sym name="{@name}" + magic="{boolean( @magic='true' )}" + type="const" dtype="{@type}" dim="{$dim}" desc="{@desc}" tex="{@sym}"> + + <!-- may or may not exist --> + <xsl:sequence select="@value" /> + </preproc:sym> + + <!-- for performance, we will not recurse any further; the rest are simply + data declarations --> +</xsl:template> + + +<xsl:template match="c:*[ @generates ]" mode="preproc:symtable" priority="5"> + <xsl:variable name="parent" select="ancestor::lv:rate" /> + + <preproc:sym name="{@generates}" keep="{boolean( $parent/@keep )}" + parent="{$parent/@yields}" + type="gen" dtype="float" dim="1" desc="{@desc}" tex="{@sym}" /> + + <xsl:apply-templates mode="preproc:symtable" /> +</xsl:template> + + +<!-- note the @dim value; this is determined later from its dependencies --> +<xsl:template match="lv:classify" mode="preproc:symtable" priority="5"> + <xsl:variable name="external" select="boolean( @external='true' )" /> + <xsl:variable name="terminate" select="boolean( @terminate='true' )" /> + <xsl:variable name="keep" select="boolean( @keep='true' )" /> + + <preproc:sym name=":class:{@as}" + extclass="{$external}" terminate="{$terminate}" keep="{$keep}" + type="class" dim="?" desc="{@desc}" yields="{@yields}" + orig-name="{@as}"> + + <!-- copy preprocessor metadata to symbol for easy reference --> + <xsl:sequence select="@preproc:*" /> + </preproc:sym> + + <!-- generator if @yields is provided (note that we also have a @yields above + to avoid scanning separate object files for such common information) + --> + <xsl:if test="@yields"> + <preproc:sym name="{@yields}" keep="{$keep}" + parent=":class:{@as}" + extclass="{$external}" terminate="{$terminate}" + type="cgen" dtype="boolean" dim="?" desc="{@desc}"> + + <xsl:sequence select="@preproc:*" /> + </preproc:sym> + </xsl:if> + + <xsl:apply-templates mode="preproc:symtable" /> +</xsl:template> + + +<!-- Will be completed during post-processing so that typedefs can be + properly resolved --> +<xsl:template match="lv:param" mode="preproc:symtable" priority="5"> + <xsl:variable name="dim"> + <xsl:call-template name="preproc:param-dim" /> + </xsl:variable> + + <!-- we use the primitive data type derived from the typedef to ensure that + the system can still make use of the type even when the typedef is not + exported; indeed, typedefs are simply restrictions that need only be + known for compiling (at least at present). Also note the keep="true" to + ensure that all param symbols are retained after linking --> + <preproc:sym name="{@name}" keep="true" + type="param" dtype="{@type}" dim="{$dim}" desc="{@desc}" tex="{@sym}" /> +</xsl:template> + + +<xsl:template match="lv:typedef" mode="preproc:symtable" priority="5"> + <!-- FIXME: this is a kluge --> + <xsl:variable name="dtype" as="xs:string?" + select="if ( lv:base-type ) then + @name + else + lv:enum/@type + , lv:union/lv:typedef[1]/lv:enum/@type" /> + + <xsl:if test="not( $dtype )"> + <xsl:message terminate="yes"> + <xsl:text>[preproc/symtable] internal error: </xsl:text> + <xsl:text>failed to resolve type primitve of `</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>'</xsl:text> + </xsl:message> + </xsl:if> + + <preproc:sym name="{@name}" dtype="{$dtype}" + type="type" dim="0" desc="{@desc}" /> + + <xsl:apply-templates mode="preproc:symtable" /> +</xsl:template> + + +<xsl:template match="lv:typedef/lv:enum/lv:item" mode="preproc:symtable" priority="5"> + <xsl:variable name="dtype" select="parent::lv:enum/@type" /> + + <preproc:sym name="{@name}" value="{@value}" + type="const" dtype="{$dtype}" dim="0" desc="{@desc}" /> +</xsl:template> + + +<xsl:template match="lv:function" mode="preproc:symtable" priority="5"> + <!-- default TeX symbol to the function name --> + <xsl:variable name="tex"> + <xsl:choose> + <xsl:when test="@sym"> + <xsl:value-of select="@sym" /> + </xsl:when> + + <xsl:otherwise> + <xsl:text>\textrm{</xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>}</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <!-- TODO: determine return data type from tail --> + <!-- TODO: same for dim --> + <!-- functions can have circular dependencies (recursion) --> + <preproc:sym name="{@name}" type="func" dtype="float" dim="0" desc="{@desc}" + tex="{$tex}" allow-circular="true"> + + <!-- we need to include the argument order and symbol refs so that the + compiler knows how to call the function --> + <xsl:variable name="fname" select="@name" /> + <xsl:for-each select="lv:param"> + <preproc:sym-ref name=":{$fname}:{@name}" /> + </xsl:for-each> + </preproc:sym> + + <xsl:apply-templates mode="preproc:symtable" /> +</xsl:template> + + +<!-- + Function parameters are local to the function and are represented differently + in the symbol table than most other symbols. In particular: + - They have type lparam, not param + - Their name begins with a colon, which is normally invalid (ensuring that + there will be no naming conflicts) + - The name following the colon is the concatenation of the function name, + an underscore and the param name +--> +<xsl:template match="lv:function/lv:param" mode="preproc:symtable" priority="6"> + <!-- determine number of dimensions --> + <xsl:variable name="dim"> + <xsl:call-template name="preproc:param-dim" /> + </xsl:variable> + + <xsl:variable name="fname" select="parent::lv:function/@name" /> + + <preproc:sym name=":{$fname}:{@name}" parent="{$fname}" varname="{@name}" + type="lparam" dtype="{@type}" dim="{$dim}" desc="{@desc}" tex="{@sym}"> + + <!-- may or may not be defined --> + <xsl:sequence select="@default" /> + </preproc:sym> +</xsl:template> + + +<!-- + Same concept as function params +--> +<xsl:template match="c:let/c:values/c:value" mode="preproc:symtable" priority="5"> + <xsl:variable name="name" select="@name" /> + + <!-- determine number of dimensions --> + <xsl:variable name="dim"> + <xsl:call-template name="preproc:param-dim" /> + </xsl:variable> + + <!-- the name is generated automatically by the preprocessor; the user cannot + set it --> + <xsl:variable name="lname" select=" + ancestor::c:let[ + c:values/c:value[ @name=$name ] + ]/@name + " /> + + <preproc:sym name=":{$lname}:{@name}" local="true" varname="{@name}" + type="lparam" dtype="{@type}" dim="{$dim}" desc="{@desc}" tex="{@sym}" /> + + <xsl:apply-templates mode="preproc:symtable" /> +</xsl:template> + + +<xsl:template match="lv:extern" mode="preproc:symtable" priority="5"> + <preproc:sym desc="{@name} extern" extern="true" missing="{@missing}"> + <!-- copy all the user-supplied params --> + <xsl:sequence select="@*" /> + </preproc:sym> +</xsl:template> + + +<xsl:template match="lv:meta" mode="preproc:symtable" priority="5"> + <xsl:for-each select="lv:prop"> + <preproc:sym name=":meta:{@name}" type="meta" + keep="true" /> + </xsl:for-each> +</xsl:template> + + +<xsl:template match="preproc:sym[ @type='param' ]" mode="preproc:symtable-complete" priority="5"> + <xsl:param name="syms" as="element( preproc:sym )*" /> + + <!-- attempt to derive type information from a typedef --> + <!-- TODO: also check symbol table after import (post-process) --> + <xsl:variable name="type" select="@dtype" /> + <xsl:variable name="typedef" as="element( preproc:sym )?" + select="$syms[ @type = 'type' + and @name = $type ]" /> + + <xsl:if test="not( $typedef and $typedef/@dtype )"> + <xsl:message terminate="yes"> + <xsl:text>[preproc/symtable] internal error: </xsl:text> + <xsl:text>failed to resolve type: </xsl:text> + <xsl:value-of select="$type" /> + </xsl:message> + </xsl:if> + + <!-- complete datatype with primitive --> + <xsl:copy> + <xsl:sequence select="@*" /> + <xsl:attribute name="dtype" select="$typedef/@dtype" /> + </xsl:copy> +</xsl:template> + + +<xsl:template match="*" mode="preproc:symtable-complete" priority="1"> + <!-- symbol does not need completion --> + <xsl:sequence select="." /> +</xsl:template> + +<!-- + Determines param dimension from its string definition: + vector = 1-dimensional; + matrix = 2-dimensional; + otherwise, scalar = 0-dimensional + + Other dimensions are certainly supported, but @set's syntax does not support + their specification. +--> +<xsl:template name="preproc:param-dim"> + <xsl:choose> + <xsl:when test="@set = 'vector'"> + <xsl:text>1</xsl:text> + </xsl:when> + + <xsl:when test="@set = 'matrix'"> + <xsl:text>2</xsl:text> + </xsl:when> + + <xsl:otherwise> + <xsl:text>0</xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/current/include/preproc/template.xsl b/src/current/include/preproc/template.xsl new file mode 100644 index 0000000..e1e8edb --- /dev/null +++ b/src/current/include/preproc/template.xsl @@ -0,0 +1,1149 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + Performs template processing and expansion +--> + +<xsl:stylesheet version="2.0" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:preproc="http://www.lovullo.com/rater/preproc" + xmlns:lv="http://www.lovullo.com/rater" + xmlns:t="http://www.lovullo.com/rater/apply-template" + xmlns:c="http://www.lovullo.com/calc" + xmlns:ext="http://www.lovullo.com/ext" + xmlns:util="http://www.lovullo.com/util" + xmlns:eseq="http://www.lovullo.com/tame/preproc/expand/eseq" + exclude-result-prefixes="xs ext eseq lv util"> + +<xsl:import href="../../tame/src/preproc/expand/expand-sequence.xsl" /> + + +<!-- + Macro that abstracts away the boilerplate template application code when + dealing with a single root template + + This macro will cause another macro expansion pass. +--> +<xsl:template match="lv:rate-each-template" mode="preproc:macros" priority="5"> + <!-- TODO: debug flag + <xsl:message> + <xsl:text>[preproc] expanding rate-each-template </xsl:text> + <xsl:value-of select="@yields" /> + <xsl:text>...</xsl:text> + </xsl:message> + --> + + <lv:rate-each> + <!-- copy all attributes except for those we will explicitly handle below --> + <xsl:sequence select="@*[ + not( local-name() = 'name' ) + ]" /> + + <!-- since the entire body is composed from a template, we do not need to + accept an index from the user; traditionally, we use 'k' to leave + indexes like 'i' and 'j' open --> + <xsl:variable name="index"> + <xsl:text>k</xsl:text> + </xsl:variable> + + <xsl:attribute name="index"> + <xsl:value-of select="$index" /> + </xsl:attribute> + + <!-- any lv:* nodes should be placed at the top --> + <xsl:sequence select="./lv:class" /> + + <!-- the template is expected to have an @index@ parameter --> + <lv:apply-template name="{@name}"> + <lv:with-param name="@index@" value="{$index}" /> + + <!-- copy any user-provided params into the template application --> + <xsl:sequence select="./lv:with-param" /> + </lv:apply-template> + </lv:rate-each> + + <!-- since we generated another preprocessor macro (lv:rate-each), notify the + system that we will need to make another pass --> + <preproc:repass tpl="{@name}" /> +</xsl:template> + + +<!-- + If the symbol table is not yet available, defer short-hand + applications for now + + It is far less costly to leave the node alone than it is to process + and copy potentially massive trees only to have to repass anyway. +--> +<xsl:template mode="preproc:macros" priority="9" + match="t:*[ not( root(.)/preproc:symtable ) ]"> + <xsl:sequence select="." /> + <preproc:repass need-sym="true" /> +</xsl:template> + + +<!-- + Converts shorthand template applications into full template expansions + + TODO: This causes an extra pass; we'd like to avoid having to do that. +--> +<xsl:template match="t:*" mode="preproc:macros" priority="5"> + <!-- TODO: debug flag + <xsl:message> + <xsl:text>[preproc] expanding template shorthand for </xsl:text> + <xsl:value-of select="local-name()" /> + <xsl:text>...</xsl:text> + </xsl:message> + --> + + <xsl:variable name="name" as="xs:string" + select="concat( '_', local-name(), '_' )" /> + + <xsl:variable name="params" as="element( lv:with-param )*"> + <xsl:for-each select="@*"> + <lv:with-param name="@{local-name()}@" value="{.}" /> + </xsl:for-each> + + <!-- there may be existing lv:with-param nodes --> + <xsl:sequence select="./lv:with-param" /> + + <xsl:variable name="paramnodes" + select="*[ not( local-name() = 'with-param' ) ]" /> + + <!-- if sub-nodes were provided, pass it as the "@values@" param --> + <xsl:if test="$paramnodes"> + <lv:with-param name="@values@"> + <xsl:sequence select="$paramnodes" /> + </lv:with-param> + </xsl:if> + </xsl:variable> + + <!-- XXX: a large chunk of this is duplicate code; factor out --> + <xsl:variable name="tpl" as="element( lv:template )?" + select="preproc:locate-template( $name, root( . ) )" /> + + <xsl:variable name="src-root" as="element( lv:package )" + select="if ( root(.)/lv:package ) then + root(.)/lv:package + else + root(.)" /> + + <xsl:choose> + <xsl:when test="$tpl"> + <!-- avoid a costly repass; apply immediately --> + <xsl:sequence select="preproc:expand-template( + $tpl, $src-root, $params, . )" /> + </xsl:when> + + <xsl:otherwise> + <preproc:error>Undefined template <xsl:value-of select="$name" /></preproc:error> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:function name="preproc:locate-template" + as="element( lv:template )?"> + <xsl:param name="name" as="xs:string" /> + <xsl:param name="root" as="element( lv:package )" /> + + <xsl:variable name="sym" as="element( preproc:sym )?" + select="$root/preproc:symtable/preproc:sym[ + @name = $name ]" /> + + <xsl:variable name="package" as="element( lv:package )?" + select="if ( $sym/@src ) then + document( concat( $sym/@src, '.xmlo' ), $__entry-root ) + /lv:package + else + $root/lv:package" /> + + <!-- legacy lookup for inline-templates; needs to regenerate symbol + list, then this can be removed --> + <xsl:variable name="inline-match" as="element( lv:template )?" + select="$root//lv:template[ @name = $name ]" /> + + <xsl:sequence select="if ( $inline-match ) then + $inline-match + else + $package//lv:template[ @name=$name ]" /> +</xsl:function> + + + +<!-- + Applies a template in place as if it were pasted directly into the XML in that + location + + This works much like a C macro and is intended to be a very simple means of + code re-use that permits free construction of lv:rate blocks. See XSD + documentation. + + It is *very important* that these macros be expanded before the templates + themselves are removed from the XML by further processing. + + Note that if the attribute shorthand is used for params, one extra expansion + will have to occur, which is an additional performance hit. +--> +<xsl:template match="lv:apply-template[ + @name=root(.)/preproc:symtable/preproc:sym/@name ] + |lv:apply-template[ @name=root(.)//lv:template/@name ]" + mode="preproc:macros" priority="6"> + + <xsl:variable name="name" select="@name" /> + <xsl:variable name="attrparams" select="@*[ not( local-name() = 'name' ) ]" /> + + <!-- used for type checking --> + <xsl:variable name="root" as="element( lv:package )" + select="if ( root( . ) instance of document-node() ) then + root( . )/lv:package + else + root( . )" /> + + <xsl:variable name="src-root" as="element( lv:package )" + select="if ( $root/lv:package ) then + $root/lv:package + else + $root" /> + + <xsl:variable name="tpl" as="element( lv:template )?" + select="preproc:locate-template( $name, $root )" /> + + <xsl:choose> + <xsl:when test="exists( $tpl ) and $attrparams"> + <xsl:message> + <xsl:text>[preproc] expanding template parameters for </xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>...</xsl:text> + </xsl:message> + + <xsl:variable name="params" as="element( lv:with-param )*"> + <xsl:for-each select="$attrparams"> + <lv:with-param name="@{local-name()}@" value="{.}" /> + </xsl:for-each> + + <!-- there may also be non-shorthand params --> + <xsl:sequence select="lv:with-param" /> + </xsl:variable> + + <!-- immediately apply without a wasteful repass --> + <xsl:sequence select="preproc:expand-template( + $tpl, $src-root, $params, . )" /> + </xsl:when> + + <xsl:when test="$tpl"> + <xsl:sequence select="preproc:expand-template( + $tpl, $src-root, lv:with-param, . )" /> + </xsl:when> + + <xsl:otherwise> + <preproc:error>Undefined template <xsl:value-of select="$name" /></preproc:error> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:function name="preproc:expand-template"> + <xsl:param name="tpl" as="element( lv:template )" /> + <xsl:param name="src-root" /> + <xsl:param name="params" as="element( lv:with-param )*" /> + <xsl:param name="context" as="node()" /> + + <xsl:variable name="name" as="xs:string" + select="$tpl/@name" /> + + <!-- TODO: debug flag + <xsl:message> + <xsl:text>[preproc] applying template </xsl:text> + <xsl:value-of select="@name" /> + <xsl:text>...</xsl:text> + </xsl:message> + --> + + <!-- check to ensure that each given argument is defined, unless @strict + is false --> + <xsl:for-each select=" + $params[ + not( @name=$tpl/lv:param/@name ) + and not( @strict='false' ) + ] + "> + <preproc:error> + <xsl:text>undefined template param </xsl:text> + <xsl:value-of select="$name" />/<xsl:value-of select="@name" /> + <xsl:text>; available params are </xsl:text> + + <xsl:for-each select="$tpl/lv:param/@name"> + <xsl:if test="position() > 1"> + <xsl:text>; </xsl:text> + </xsl:if> + + <xsl:value-of select="." /> + </xsl:for-each> + </preproc:error> + </xsl:for-each> + + <!-- replace this node with a copy of all the child nodes of the given + template; this inlines it as if it were copied and pasted directly + into the XML, much like a C macro --> + <xsl:apply-templates + select="$tpl[ 1 ]/*" + mode="preproc:apply-template"> + + <xsl:with-param name="apply" select="$context" + tunnel="yes" /> + <xsl:with-param name="apply-tpl-name" select="$name" + tunnel="yes" /> + <xsl:with-param name="params" select="$params" + tunnel="yes" /> + + <xsl:with-param name="first-child" select="true()" /> + <xsl:with-param name="src-root" select="$src-root" + tunnel="yes" /> + </xsl:apply-templates> + + <!-- since templates can include anything, we should perform another pass + to be sure that all macros that this template may contain/generate + are expanded --> + <preproc:repass tpl="{$name}" /> + <preproc:tpl-step name="{$name}" type="apply-template" /> +</xsl:function> + + + +<!-- + An inline template implicitly defines and then immediately applies a template + with an optional looping construct +--> +<xsl:template match="lv:inline-template" mode="preproc:macros" priority="5"> + <xsl:variable name="name" select="concat( '___i', generate-id(.), '___' )" /> + <xsl:variable name="inline" select="." /> + + <xsl:message> + <xsl:text>[preproc] preparing inline template </xsl:text> + <xsl:value-of select="$name" /> + </xsl:message> + + <!-- generate template --> + <lv:template name="{$name}" desc="Inline template" preproc:from-inline="{$name}"> + <!-- generate params (from both our own attrs and any for-each sets) --> + <xsl:variable name="params"> + <params> + <xsl:for-each select="@*, lv:for-each/lv:set/@*"> + <!-- manual lv:param's will override default param generation --> + <xsl:if test="not( local-name() = $inline/lv:param/@name )"> + <lv:param name="@{local-name()}@" desc="Generated param" /> + </xsl:if> + </xsl:for-each> + + <xsl:if test="lv:for-each/lv:sym-set"> + <lv:param name="@sym_name@" desc="Symbol name" /> + <lv:param name="@sym_type@" desc="Symbol type" /> + <lv:param name="@sym_dim@" desc="Symbol degree (dimensions)" /> + <lv:param name="@sym_desc@" desc="Symbol description (if any)" /> + <lv:param name="@sym_yields@" desc="Symbol yield name (if any)" /> + <lv:param name="@sym_parent@" desc="Symbol parent (if any)" /> + <lv:param name="@sym_external@" desc="Symbol external to classifier" /> + </xsl:if> + </params> + </xsl:variable> + + <!-- use only unique params from each attribute source --> + <xsl:sequence select=" + $params//lv:param[ + not( @name=preceding-sibling::lv:param/@name ) + ] + " /> + + <!-- include any params priovided by the user (they may be used to + generate content) --> + <xsl:sequence select="lv:param" /> + + <!-- copy template body --> + <!-- FIXME: do not use local-name here; use instance of --> + <xsl:sequence select="*[ not( local-name() = 'for-each' ) ]" /> + </lv:template> + + <xsl:choose> + <xsl:when test="lv:for-each"> + <xsl:apply-templates select="lv:for-each/lv:*" mode="preproc:inline-apply"> + <xsl:with-param name="name" select="$name" /> + </xsl:apply-templates> + </xsl:when> + + <xsl:otherwise> + <xsl:apply-templates select="." mode="preproc:inline-apply"> + <xsl:with-param name="name" select="$name" /> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> + + <!-- the next pass will perform the template application --> + <preproc:repass tpl="{$name}" /> +</xsl:template> + + +<xsl:template match="lv:inline-template|lv:for-each/lv:set" mode="preproc:inline-apply"> + <xsl:param name="name" /> + + <!-- immediately apply the template --> + <lv:apply-template name="{$name}"> + <!-- each attribute will be considered to be a template param (and any + parent lv:inline-template attributes will be added to each and every + set) --> + <xsl:for-each select="@*|ancestor::lv:inline-template/@*"> + <lv:with-param name="@{local-name()}@" value="{.}" /> + </xsl:for-each> + </lv:apply-template> +</xsl:template> + + +<xsl:template mode="preproc:inline-apply" + match="lv:for-each/lv:sym-set"> + <xsl:param name="name" /> + + <xsl:param name="src-root" as="element( lv:package )" |