Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore12
-rw-r--r--.gitlab-ci.yml13
-rw-r--r--.rev-xmlo2
-rw-r--r--README.md15
-rw-r--r--RELEASES.md283
-rw-r--r--bin/dslc.in2
-rwxr-xr-xbin/tame49
-rwxr-xr-xbin/tamed13
-rwxr-xr-xbootstrap2
-rw-r--r--build-aux/Makefile.am81
-rwxr-xr-xbuild-aux/gen-make3
-rwxr-xr-xbuild-aux/lsimports2
-rw-r--r--build-aux/m4/calcdsl.m48
-rwxr-xr-xbuild-aux/progtest-runner2
-rwxr-xr-xbuild-aux/suppmk-gen42
-rw-r--r--core/aggregate.xml154
-rw-r--r--core/base.xml161
-rw-r--r--core/tdat.xml2
-rw-r--r--core/test/core/aggregate.xml264
-rw-r--r--core/test/core/class.xml733
-rw-r--r--core/test/core/suite.xml2
-rw-r--r--core/test/spec.xml66
-rw-r--r--core/vector/filter.xml319
-rw-r--r--design/tpl/.gitignore34
-rw-r--r--design/tpl/.latexmkrc12
-rw-r--r--design/tpl/Makefile15
-rw-r--r--design/tpl/README.md19
-rwxr-xr-xdesign/tpl/autogen.sh10
-rw-r--r--design/tpl/conf.tex.in5
-rw-r--r--design/tpl/configure.ac38
-rw-r--r--design/tpl/sec/appendix-typesetting.tex92
-rw-r--r--design/tpl/sec/class.tex784
-rw-r--r--design/tpl/sec/notation.tex642
-rw-r--r--design/tpl/tpl.bib8
-rw-r--r--design/tpl/tpl.sty268
-rw-r--r--design/tpl/tpl.tex77
-rw-r--r--doc/Makefile.am2
-rw-r--r--package-lock.json3
-rw-r--r--progtest/package-lock.json1586
-rw-r--r--progtest/package.json30
-rw-r--r--src/current/Makefile6
-rw-r--r--src/current/compiler/js-calc.xsl425
-rw-r--r--src/current/compiler/js-legacy.xsl508
-rw-r--r--src/current/compiler/js.xsl1622
-rw-r--r--src/current/compiler/map.xsl1
-rw-r--r--src/current/compiler/validate.xsl107
-rw-r--r--src/current/doc/.gitignore4
-rw-r--r--src/current/doc/chapters/class.tex372
-rw-r--r--src/current/doc/manual.sty30
-rw-r--r--src/current/doc/manual.tex19
-rw-r--r--src/current/include/depgen.xsl27
-rw-r--r--src/current/include/preproc/expand.xsl49
-rw-r--r--src/current/include/preproc/macros.xsl168
-rw-r--r--src/current/include/preproc/package.xsl18
-rw-r--r--src/current/include/preproc/symtable.xsl18
-rw-r--r--src/current/include/preproc/template.xsl14
-rw-r--r--src/current/neo4j.xsl328
-rw-r--r--src/current/neo4j/post.neo4j60
-rw-r--r--src/current/neo4j/pre.neo4j8
-rw-r--r--src/current/pkg-dep.xsl4
-rw-r--r--src/current/rater.xsd79
-rw-r--r--src/current/scripts/entry-form.js15
-rw-r--r--src/current/summary.xsl47
-rw-r--r--src/current/util/serialize.xsl651
-rw-r--r--tamer/Cargo.lock246
-rw-r--r--tamer/Cargo.toml1
-rw-r--r--tamer/build-aux/intra_rustdoc_links_check.rs25
-rw-r--r--tamer/configure.ac14
-rw-r--r--tamer/src/bin/tameld.rs104
-rw-r--r--tamer/src/ir/asg/base.rs852
-rw-r--r--tamer/src/ir/asg/graph.rs94
-rw-r--r--tamer/src/ir/asg/ident.rs63
-rw-r--r--tamer/src/ir/asg/mod.rs7
-rw-r--r--tamer/src/ir/asg/object.rs217
-rw-r--r--tamer/src/ir/legacyir.rs4
-rw-r--r--tamer/src/ir/mod.rs4
-rw-r--r--tamer/src/ld/poc.rs64
-rw-r--r--tamer/src/lib.rs3
-rw-r--r--tamer/src/obj/xmle/writer/xmle.rs5
-rw-r--r--tamer/src/obj/xmlo/asg_builder.rs18
-rw-r--r--tamer/tests/tameld.rs13
-rwxr-xr-xtools/pkg-graph73
82 files changed, 9661 insertions, 2581 deletions
diff --git a/.gitignore b/.gitignore
index 7a432f5..4221f3e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,12 +10,12 @@
/bin/dslc
/build-aux/install-sh
/build-aux/missing
-confdefs.h
-Makefile.in
-Makefile
-aclocal.m4
-*.cache/
-configure
+/confdefs.h
+/Makefile.in
+/Makefile
+/aclocal.m4
+/*.cache/
+/configure
/config.*
# should be added using autoreconf -i
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 41cde86..b0ac9f2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -27,12 +27,25 @@ build:
- tamer/target
expire_in: 30 min
+build:doc:tpl:
+ image: $BUILD_IMAGE_TEXLIVE
+ stage: build
+ script:
+ - cd design/tpl/
+ - make
+ artifacts:
+ paths:
+ - design/tpl/tpl.pdf
+ expire_in: 30 min
+
pages:
stage: deploy
script:
- mkdir -p public/doc
- mv doc/tame.html/* doc/tame.pdf doc/tame.info public/
- mv tamer/target/doc public/tamer/
+ - mkdir -p public/design
+ - mv design/tpl/tpl.pdf public/design/
artifacts:
paths:
- public/
diff --git a/.rev-xmlo b/.rev-xmlo
index caaf06c..97d737a 100644
--- a/.rev-xmlo
+++ b/.rev-xmlo
@@ -1,4 +1,4 @@
# This number is incremented for every compiler change to force rebuilding
# of xmlo files.
-2
+3
diff --git a/README.md b/README.md
index a5af58c..0b2b4cd 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
<!---
- Copyright (C) 2015, 2016, 2017 LoVullo Associates, Inc.
+ Copyright (C) 2015-2021 Ryan Specialty Group, Inc.
Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU Free Documentation License,
@@ -28,16 +28,13 @@ TAME consists of a macro processor (implementing a metalanguage), numerous
compilers for various targets (JavaScript, HTML documentation and debugging
environment, LaTeX, and others), linkers, and supporting tools. The input
grammar is XML, and the majority of the project (including the macro processor,
-compilers, and linkers) is written in XSLT. There is a reason for that odd
-choice; until an explanation is provided, know that someone is perverted enough
-to implement a full compiler in XSLT.
+compilers, and linkers) is written in a combination of XSLT and Rust.
-## "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.
+## TAMER
+Due to performance requirements, this project is currently being
+reimplemented in Rust. That project can be found in the [tamer/](./tamer/)
+directory.
## Documentation
diff --git a/RELEASES.md b/RELEASES.md
index 4ee8e0a..9222f5e 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -2,26 +2,295 @@ TAME Release Notes
==================
This file contains notes for each release of TAME since v17.4.0.
-TAME uses [semantic versioning][]. Any major version number change
-represents backwards-incompatible changes. Each such version will be
-accompanied by notes that provide a migration path to resolve
-incompatibilities.
+TAME uses [semantic versioning][]. Any major version number increment
+indicates that backwards-incompatible changes have been introduced in that
+version. Each such version will be accompanied by notes that provide a
+migration path to resolve incompatibilities.
[semantic versioning]: https://semver.org/
TAME developers: Add new changes under a "NEXT" heading as part of the
commits that introduce the changes. To make a new release, run
-=tools/mkrelease=, which will handle updating the heading for you.
+`tools/mkrelease`, which will handle updating the heading for you.
-NEXT
-====
+v18.0.0 (2021-06-23)
+====================
+This release focuses primarily on compiler optimizations that affect runtime
+performance (both CPU and memory). The classification system has undergone
+a rewrite, but the new system is gated behind a template-based feature flag
+`_use-new-classification-system_` (see Core below). Many optimizations
+listed below are _not_ affected by this toggle.
+
+Compiler
+--------
+- Numerous compiler optimizations including (but not limited to):
+ - Classification system rewrite with significant correctness and
+ performance improvements, with significantly less generated code.
+ - There is more work to be done in TAMER.
+ - This change is gated behind a feature toggle (see
+ `_use-new-classification-system_` in Core below).
+ - Significant reduction in byte count of JavaScript target output.
+ - Classifications with single-`TRUE` predicate matches are aliases and now
+ compile more efficiently.
+ - Classifications that are a disjunction of conjunctions with a common
+ predicate will have the common predicate hoisted out, resulting in more
+ efficeint code generation.
+ - Classifications with equality matches entirely on a single param are
+ compiled into a `Set` lookup.
+ - Most self-executing functions in target JavaScript code have been
+ eliminated, resulting in a performance improvement.
+ - Floating point truncation now takes place without using `toFixed` in
+ JavaScript target, eliminating expensive number->string->number
+ conversions.
+ - Code paths are entirely skipped if a calculation predicate indicates
+ that it should not be executed, rather than simply multiplying by 0
+ after performing potentially expensive calculations.
+ - A bunch of wasteful casting has been eliminated, supplanted by proper
+ casting of param inputs.
+ - Unnecessary debug output removed, significantly improving performance in
+ certain cases.
+ - Single-predicate any/all blocks stripped rather than being extracted
+ into separate classifications.
+ - Extracted any/all classifications are inlined at the reference site when
+ the new classification system is enabled, reducing the number of
+ temporaries created at runtime in JavaScript.
+- Summary Page now displays values of `lv:match/@on` instead of debug
+ values.
+ - This provides more useful information and is not subject to the
+ confusing reordering behavior of the compiler that is not reflected on
+ the page.
+ - Changes that have not yet been merged will remove debug values for the
+ classification system.
+
+Core
+----
+- New feature flag template `_use-new-classification-system_`.
+ - This allows selectively enabling code generation for the new
+ classification system, which has BC breaks in certain buggy situations.
+ See `core/test/core/class` package for more information.
+- Remove `core/aggregate`.
+ - This package is not currently utilized and is dangerous---it could
+ easily aggregate unintended values if used carelessly. Those who know
+ what they are doing can use `sym-set` if such a thing is a good thing
+ within the given context, and proper precautions are taken (as many
+ templates already do today).
+
+Rust
+----
+- Version bump from 1.42.0 to 1.48.0 now that intra-doc links has been
+ stabalized.
+
+Miscellaneous
+-------------
+- `build-aux/progtest-runner` will now deterministically concatenate files
+ based on name rather than some unspecified order.
+
+
+v17.9.0 (2021-05-27)
+====================
+This is a documentation/design release, introducing The TAME Programming
+Language in `design/tpl`.
+
+Compiler
+-------
+- Allow the mapping of flag values from `program.xml`.
+
+Design
+------
+- Introduce The TAME Programming Language.
+
+
+v17.8.1 (2021-03-18)
+====================
+This release contains a bufix for recent build changes in v17.8.0 that were
+causing, under some circumstances, builds to fail during dependency
+generation. It also contains minor improvements and cleanup.
+
+Build System
+------------
+- [bugfix] Lookup tables will no longer build `rater/core/vector/table` when
+ geneating the `xml` package.
+ - This was causing problems during `suppliers.mk` dependency generation.
+ The dependency is still in place for the corresponding `xmlo` file.
+ - This was broken by v17.8.0.
+- Minor improvements to `tame` and `tamed` scripts to ensure that certain
+ unlikely failures are not ignored.
+
+
+v17.8.0 (2021-02-23)
+====================
+This release contains changes to the build system to accommodate
+liza-proguic's introduction of step-based packages (in place of a monolithic
+`package-dfns.xml`), as well as miscellaneous improvements.
+
+Compiler
+--------
+- `rater.xsd`, used for certain validations of TAME's grammar, has been
+ updated to an out-of-tree version; it had inadvertently gotten out of
+ date, and the discrepency won't happen again in the future.
+ - Further, limits on the length of `@yields` identifiers have been
+ removed; the lack of namespacing and generation of identifiers from
+ templates can yield longer identifier names.
+
+Build System
+------------
+- Only modify `.version.xml` timestamp when hash changes. This allows
+ its use as a dependency without forcefully rebuilding each and every time.
+- `configure` will no longer immediately generate `suppliers.mk`.
+ - Additionally, `build-aux/suppmk-gen`, which `configure` directly invoked
+ until now, was removed in favor of generic rules in `Makefile.am`.
+- Step-level imports in program definitions are now recognized to
+ accommodate liza-proguic's step-level package generation.
+- Step-level program packages are now properly accounted for as dependencies
+ for builds.
+- `supplier.mk` is now automatically regenerated when source files
+ change. This previously needed to be done manually when imports changed.
+ - `supplier.mk` generation will no longer be verbose (it'll instead be
+ only one line), which makes it more amenable to more frequent
+ regeneration.
+
+
+v17.7.0 (2020-12-09)
+====================
+This release provides tail-call optimizations aimed at the query system in
+core.
+
+Compiler
+--------
+- [bugfix] Recursive calls using TCO will wait to overwrite their function
+ arguments until all expressions calculating the new argument values have
+ completed.
+
+`tame-core`
+-----------
+- `mrange` is now fully tail-recursive and has experimental TCO applied.
+ - It was previously only recursive for non-matching rows.
+
+
+v17.6.5 (2020-12-03)
+====================
+This release improves Summary Page performance when populating the page with
+data loaded from an external source.
+
+Summary Page
+------------
+- Populating the DOM with loaded data now runs in linear time.
+
+
+v17.6.4 (2020-11-23)
+====================
+This release tolerates invalid map inputs in certain circumstances.
+
+Compiler
+--------
+- Tolerate non-string inputs to `uppercase` and `hash` map methods.
+
+
+v17.6.3 (2020-11-03)
+====================
+- Update the CDN used to get MathJax.
+
+
+v17.6.2 (2020-10-01)
+====================
+- Optionally include a "program.mk" file if it is present in the project's root
+ directory. This allows us to move program specific tasks outside of TAME.
+
+
+v17.6.1 (2020-09-23)
+====================
+Compiler
+--------
+- `lv:param-class-to-yields` will now trigger a failure rather than relying
+ on propagating bad values, which may not result in failure if the symbol
+ is represented by another type (non-class) of object.
+
+Miscellaneous
+-------------
+- `package-lock.json` additions.
+
+
+v17.6.0 (2020-08-19)
+====================
+This release provides a new environment variable for JVM tuning. It does
+not provide any new compiler features or performance enhancements in itself,
+though it enables optimizations through JVM tuning.
+
+Compiler
+--------
+- The new environment variable `TAMED_JAVA_OPTS` can now be used to provide
+ arguments to the JVM. This feature was added to support heap ratio
+ tuning.
+
+Miscellaneous
+-------------
+- `build-aux/lsimports` was causing Gawk to complain about the third
+ argument to `gensub`; fixed.
+- `bootstrap` will test explicitly whether `hoxsl` is a symbol link, since
+ `-e` fails if the symlink is broken.
+
+
+v17.5.0 (2020-07-15)
+====================
+This release adds support for experimental human-guided tail call
+optimizations (TCO) to resolve issues of stack exhaustion during runtime for
+tables with a large number of rows after having applied the first
+predicate. This feature should not be used outside of `tame-core`, and will
+be done automatically by TAMER in the future.
+
+`tame-core`
+-----------
+- `vector/filter/mrange`, used by the table lookup system, has had its
+ mutually recursive function inlined and now uses TCO.
+ - This was the source of stack exhaustion on tables whose predicates were
+ unable to filter rows sufficiently.
+
+Compiler
+--------
+- Experimental guided tail call optimizations (TCO) added to XSLT-based
+ compiler, allowing a human to manually indicate recursive calls in tail
+ position.
+ - This is undocumented and should only be used by `tame-core`. The
+ experimental warning will be removed in future releases if the behavior
+ proves to be sound.
+ - TAMER will add support for proper tail calls that will be detected
+ automatically.
+
+
+v17.4.3 (2020-07-02)
+====================
+This release fixes a bug caused by previous refactoring that caused
+unresolved externs to produce an obscure and useless error for the end
+user.
+
+Linker
+------
+- Provide useful error for unresolved identifiers.
+ - This was previously falling through to an `unreachable!` block,
+ producing a very opaque and useless internal error message.
+
+
+v17.4.2 (2020-05-13)
+====================
+This release adds GraphML output for linked objects to allow us to
+inspect the graph.
+
+Linker
+------
+- Add `--emit` oprion to `tamer/src/bin/tameld.rs` that allows us to specify
+ the type of output we want.
+- Minor refactoring.
Miscellaneous
-------------
+- Added `make` target to build linked GraphML files.
+- Updated `make *.xmle` target to explicitly state it is emitting `xmle`.
+- Added Cypher script to use in Neo4J after a GraphML file is imported.
- `RELEASES.md`
- Add missing link to semver.org.
- Fix `tame-core` heading, which was erroneously Org-mode-styled.
+ - Rephrase and correct formatting of an introduction paragraph.
v17.4.1 (2020-04-29)
diff --git a/bin/dslc.in b/bin/dslc.in
index 3598bf3..64f62b0 100644
--- a/bin/dslc.in
+++ b/bin/dslc.in
@@ -37,6 +37,6 @@ rater-path()
CLASSPATH="$CLASSPATH:@DSLC_CLASSPATH@:$dslc_jar" \
- "@JAVA@" @JAVA_OPTS@ \
+ "@JAVA@" @JAVA_OPTS@ $JAVA_OPTS \
com.lovullo.dslc.DslCompiler \
"$( rater-path )"
diff --git a/bin/tame b/bin/tame
index 5788c5f..1a2d7d8 100755
--- a/bin/tame
+++ b/bin/tame
@@ -19,7 +19,8 @@
set -euo pipefail
-declare -r mypath=$( dirname "$( readlink -f "$0" )" )
+declare mypath; mypath=$( dirname "$( readlink -f "$0" )" )
+readonly mypath
declare -ri EX_NOTAMED=1 # tried to start tamed or runner but failed
declare -ri EX_STALLED=2 # runner stalled and could not recover
@@ -36,6 +37,7 @@ declare -ri TAME_CMD_WAITTIME="${TAME_CMD_WAITTIME:-3}"
# propagate to daemon
export TAMED_STALL_SECONDS
export TAMED_SPAWNER_PID
+export TAMED_JAVA_OPTS
# Send a single command to the next available runner and
@@ -73,7 +75,7 @@ command-runner()
local -r base="$root/$id"
local -ri pid=$( cat "$base/pid" )
- verify-runner "$base" "$pid"
+ verify-runner "$base" "$id" "$pid"
# forward signals to runner so that build is actually halted
# (rather than continuing in background after we die)
@@ -100,11 +102,11 @@ command-runner()
}
# output lines from runner until we reach a line stating "DONE"
- while read line; do
+ while read -r line; do
# don't parse words in the initial read because we may be
# dealing with a lot of lines
if [ "${line:0:5}" == "DONE " ]; then
- read _ code _ <<< "$line"
+ read -r _ code _ <<< "$line"
mark-available "$base"
return "$code"
@@ -141,7 +143,7 @@ reserve-runner()
}
# grab the first available or request a new one
- local id=$( get-available-runner-id "$root" )
+ local id; id=$( get-available-runner-id "$root" )
if [ -z "$id" ]; then
id=$( spawn-runner-and-wait "$root" ) || {
echo "error: failed to reserve runner at $root" >&2
@@ -237,7 +239,8 @@ mark-available()
verify-runner()
{
local -r base="${1?Missing base}"
- local -ri pid="${2?Missing pid}"
+ local -ri id="${2?Missing id}"
+ local -ri pid="${3?Missing pid}"
ps "$pid" &>/dev/null || {
echo "error: runner $id ($pid) is offline!" >&2
@@ -377,23 +380,23 @@ kill-tamed()
# output rather than wasting cycles doing this filtering.
saneout()
{
- awk ' \
- /^~~~~\[begin /,/^~~~~\[end / { next } \
- /^rm / { next } \
- /^COMMAND / { next } \
- /^Exception|^\t+at / { \
- if ( /^E/ ) { \
- print; \
- print "Stack trace written to run-*.log"; \
- } \
- next; \
- } \
- /([Ww]arning|[Nn]otice)[: ]/ { printf "\033[0;33m"; w++; out=1; } \
- /[Ff]atal:/ { printf "\033[0;31m"; out=1; } \
- /!|[Ee]rror:/ { printf "\033[0;31m"; e++; out=1; } \
- /internal:/ { printf "\033[0;35m"; out=1; } \
- /internal error:/ { printf "\033[1m"; out=1; } \
- /^[^[]/ || out { print; printf "\033[0;0m"; out=0; } \
+ awk '
+ /^~~~~\[begin /,/^~~~~\[end / { next }
+ /^rm / { next }
+ /^COMMAND / { next }
+ /^Exception|^\t+at / {
+ if ( /^E/ ) {
+ print;
+ print "Stack trace written to run-*.log";
+ }
+ next;
+ }
+ /([Ww]arning|[Nn]otice)[: ]/ { printf "\033[0;33m"; w++; out=1; }
+ /[Ff]atal:/ { printf "\033[0;31m"; out=1; }
+ /!|[Ee]rror:/ { printf "\033[0;31m"; e++; out=1; }
+ /internal:/ { printf "\033[0;35m"; out=1; }
+ /internal error:/ { printf "\033[1m"; out=1; }
+ /^[^[]/ || out { print; printf "\033[0;0m"; out=0; }
'
}
diff --git a/bin/tamed b/bin/tamed
index 8722d66..04cf8c6 100755
--- a/bin/tamed
+++ b/bin/tamed
@@ -19,7 +19,8 @@
set -euo pipefail
-declare -r mypath=$( dirname "$( readlink -f "$0" )" )
+declare mypath; mypath=$( dirname "$( readlink -f "$0" )" )
+readonly mypath
declare -ri EX_RUNNING=1
declare -ri EX_USAGE=64 # incorrect usage; sysexits.h
@@ -32,6 +33,10 @@ declare -ri TAMED_STALL_SECONDS="${TAMED_STALL_SECONDS:-1}"
# id of process that indirectly spawned tamed (default $PPID)
declare -ri TAMED_SPAWNER_PID="${TAMED_SPAWNER_PID:-$PPID}"
+# options to pass to JVM via dslc
+declare -r TAMED_JAVA_OPTS="${TAMED_JAVA_OPTS:-}"
+export JAVA_OPTS="$TAMED_JAVA_OPTS"
+
# set by `main', global for `cleanup'
declare root=
@@ -141,9 +146,10 @@ stall-monitor()
# monitor output FIFO modification time
while true; do
- local -i since=$( date +%s )
+ local -i since last
+ since=$( date +%s )
sleep "$TAMED_STALL_SECONDS"
- local -i last=$( stat -c%Y "$base/1" )
+ last=$( stat -c%Y "$base/1" )
# keep waiting if there has been activity since $since
test "$last" -le "$since" || continue
@@ -258,6 +264,7 @@ Environment Variables:
runner is automatically killed (default 1)
TAMED_SPAWNER_PID inhibit stalling while this process is running
(default PPID)
+ TAMED_JAVA_OPTS opts to pass to dslc, and in turn, the JVM
EOF
exit $EX_USAGE
diff --git a/bootstrap b/bootstrap
index 4f2ecdc..0e3d665 100755
--- a/bootstrap
+++ b/bootstrap
@@ -31,7 +31,7 @@ test "${1:-}" = -n || git submodule update --init --recursive
&& ./autogen.sh && ./configure
) \
&& ( cd tamer && ./bootstrap && ./configure ) \
- && { test -e hoxsl || ln -s ../hoxsl; } \
+ && { test -e hoxsl || test -L hoxsl || ln -s ../hoxsl; } \
&& autoreconf -fvi \
&& ./configure \
&& make all
diff --git a/build-aux/Makefile.am b/build-aux/Makefile.am
index 55672c9..dd8a6bc 100644
--- a/build-aux/Makefile.am
+++ b/build-aux/Makefile.am
@@ -65,12 +65,30 @@ dest_c1map := $(patsubst \
$(path_c1map)/%.php, \
$(src_c1map))
+# Program fragments combined to form one large program.expanded.xml
+# TODO: Move into liza-proguic
program_fragments=$(shell \
find $(path_ui)/program/ -name '*.xml' 2>/dev/null \
| LC_ALL=C sort \
| tr '\n' ' ' \
)
+# Packages associated with each program step.
+# TODO: Move into liza-proguic
+package_dfns_pkgs = $(shell \
+ find $(path_ui)/package/ -name 'progui-pkg-*.xml' 2>/dev/null 2>/dev/null \
+ | LC_ALL=C sort \
+ | tr '\n' ' ' \
+)
+package_dfns_xmlos = $(patsubst %.xml, %.xmlo, $(package_dfns_pkgs))
+
+# Dependencies for suppliers.mk.
+src_suppliersmk = $(src_common) \
+ $(shell find $(path_suppliers) ui/package ui/map rater/core -name '*.xml') \
+ $(program_fragments) \
+ ui/program.xml \
+ ui/package-dfns.xml
+
compiled_suppliers := $(src_suppliers:.xml=.xmlo)
linked_suppliers := $(src_suppliers:.xml=.xmle)
@@ -95,6 +113,7 @@ SHELL = /bin/bash -O extglob -O nullglob
# propagate to tame{,d}
export TAME_CMD_WAITTIME
export TAMED_STALL_SECONDS
+export TAMED_JAVA_OPTS
TAMED_SPAWNER_PID=$(shell echo $$PPID)
export TAMED_SPAWNER_PID
@@ -117,6 +136,10 @@ program-ui: ui/package.strip.js ui/Program.js ui/html/index.phtml
%.xmli: %.xml
$(path_tame)/tamer/target/release/tamec --emit xmlo -o $@ $<
+%.graphml: %.xmlo
+ $(TAME_TS)
+ $(path_tame)/tamer/target/release/tameld --emit graphml -o $@ $<
+
# Individual dependencies appear in suppliers.mk (see below)
%.xmlo: %.xmli $(path_tame)/.rev-xmlo
$(TAME_TS)
@@ -136,7 +159,7 @@ standalones: $(dest_standalone)
strip: $(dest_standalone_strip) ui/package.strip.js
%.xmle: %.xmlo $(path_tame)/.rev-xmle
$(TAME_TS)
- $(path_tame)/tamer/target/release/tameld -o $@ $<
+ $(path_tame)/tamer/target/release/tameld --emit xmle -o $@ $<
%.js: %.xmle
$(TAME_TS)
$(TAME) standalone $< $@
@@ -154,12 +177,20 @@ c1map: $(dest_c1map)
$(TAME_TS)
$(TAME) dot $< $@
+%.neo4j: %.xmlo
+ $(TAME) neo4j $< $@
+%.neo4je: %.xmle
+ $(TAME) neo4j $< $@
+
+
%.svg: %.dote
dot -Tsvg "$<" > "$@"
%.svg: %.dot
dot -Tsvg "$<" > "$@"
-%.xml: %.dat
+# These are deprecated and will be removed in a future version of TAME (in
+# favor of CSVM tables).
+%.xml: %.dat rater/core/tdat.xmlo
rater/tools/tdat2xml $< > $@
%.xml: %.typelist
@@ -172,31 +203,43 @@ c1map: $(dest_c1map)
%.csvo: %.csv
cp $< $@
+%.xml: %.csvo
+ rater/tools/csv2xml $< > $@
+
+# All lookup tables rely on rater/core/vector/package. This rule applies to
+# xmlo files only when there is a corresponding csvo file. Note that this
+# relies on .SECONDARY above to work properly.
+#
# TODO: This is necessary right now because of the current depgen
# process. Once that is eliminated in favor of individual dependency files
# (e.g. the %.d convention), this can go away since dependency generation
# can properly take place for the various file formats.
-%.xml: %.csvo rater/core/vector/table.xmlo
- rater/tools/csv2xml $< > $@
+%.xmlo: %.csvo rater/core/vector/table.xmlo
+# This target is always run, but only update the file (and thus its
+# timestamp) if the hash actually changes, so that we do not rebuild any
+# dependencies unnecessarily.
version: .version.xml
.version.xml: FORCE
- git log HEAD^.. -1 --pretty=format:'<version>%h</version>' > .version.xml
+ git log HEAD^.. -1 --pretty=format:'<version>%h</version>' > $@.new
+ cmp $@ $@.new || mv $@.new $@
+ $(RM) $@.new
-ui/program.expanded.xml: ui/program.xml $(program_fragments)
+ui/program.expanded.xml: ui/program.xml $(program_fragments) .version.xml
$(TAME_TS)
$(TAME) progui-expand $< $@
ui/Program.js: ui/program.expanded.xml ui/package.js
$(TAME_TS)
- $(TAME) progui-class $< $@ include-path=../../../ui/
+ $(TAME) progui-class $< $@ include-path=$$(pwd)/ui/
ui/html/index.phtml: ui/program.expanded.xml
$(TAME_TS)
$(TAME) progui-html $< $@ out-path=./
-ui/package-dfns.xmlo: ui/package-dfns.xml
+ui/package-dfns.xmlo: ui/package-dfns.xml $(package_dfns_xmlos)
ui/package-dfns.xml: ui/program.expanded.xml
$(TAME_TS)
$(TAME) progui-pkg $< $@
-ui/package-map.xmlo: ui/package-map.xml ui/package-dfns.xmlo
+$(package_dfns_pkgs): ui/package-dfns.xml
+ui/package-map.xmlo: ui/package-map.xml ui/package-dfns.xmlo $(package_dfns_xmlos)
ui/package-map.xml: ui/program.expanded.xml ui/package-dfns.xml
$(TAME_TS)
$(TAME) progui-pkg-map $< $@
@@ -228,12 +271,15 @@ clean:
| sed 's/\.csvm$$/\.xml/; s/\.dat$$/\.xml/' \
| xargs rm -fv
-# generates a Makefile that will properly build all package dependencies; note
-# that territory and rate packages also have includes; see top of this file for
-# an explanation
-suppliers.mk:
- $(ant) pkg-dep
- test ! -f $(path_ui)/program.dep || mv $(path_ui)/program.dep $(path_ui)/package-dfns.dep
+# Generates a Makefile that will properly build all package
+# dependencies. The redirect of ant to /dev/null is because it's still too
+# noisy even with -q---the "BUILD SUCCESSFUL" line is confusing, considering
+# it's merely a small part of a broader build.
+suppliers.mk: $(src_suppliersmk)
+ $(ant) -q pkg-dep >/dev/null
+ find $(path_ui)/program/ -name '*.dep' | xargs cat $(path_ui)/program.dep | sort -u \
+ > $(path_ui)/package-dfns.dep
+ $(RM) $(path_ui)/program.dep
$(path_dsl)/tame/build-aux/gen-make $(SRCPATHS) > $@
test ! -d $(path_c1map) || $(path_dsl)/tame/build-aux/gen-c1make $(path_c1map)/*.xml >> $@
@@ -333,3 +379,8 @@ me-a-sandwich:
|| echo 'Make it yourself.'
FORCE: ;
+
+# optionally include a "program.mk" file if it is
+# present in the project's root directory
+-include program.mk
+
diff --git a/build-aux/gen-make b/build-aux/gen-make
index e72dc55..ee9e220 100755
--- a/build-aux/gen-make
+++ b/build-aux/gen-make
@@ -60,7 +60,6 @@ resolv-path()
until [ $# -eq 0 ]; do (
path="${1%%/}"
- echo "[gen-make] scanning $path" >&2
cd "$path" || exit $?
@@ -72,8 +71,6 @@ until [ $# -eq 0 ]; do (
d="${dpath##*/}"
sansext="${d%.*}"
- echo "[gen-make] found $path/$d" >&2
-
# this might be derived from another file
# TODO: handle all cases, not just typelists!
if [ -f "$sansext.typelist" ]; then
diff --git a/build-aux/lsimports b/build-aux/lsimports
index 611a4e9..64a200e 100755
--- a/build-aux/lsimports
+++ b/build-aux/lsimports
@@ -39,7 +39,7 @@ grep -H '<\([a-z]\+:\)\?import \+\(package\|path\)=' "$@" \
# otherwise concatenate import with package directory
{
- dir = gensub( /[^/]+.xml/, "", "", $1 )
+ dir = gensub( /[^/]+.xml/, "", 1, $1 )
path = "/" dir $3
# resolve parent references
diff --git a/build-aux/m4/calcdsl.m4 b/build-aux/m4/calcdsl.m4
index a329df8..c8c2b42 100644
--- a/build-aux/m4/calcdsl.m4
+++ b/build-aux/m4/calcdsl.m4
@@ -88,11 +88,3 @@ AC_CONFIG_FILES(Makefile:m4_defn(`calc_root')/build-aux/Makefile.in)
# Generate configure script
AC_OUTPUT
-
-# we want this to run as part of the configure script, not during M4
-# expansion
-"$CALCROOT/build-aux/suppmk-gen" $SRCPATHS || exit
-
-AC_MSG_NOTICE([complete
-
-You may now run `make` to build.])
diff --git a/build-aux/progtest-runner b/build-aux/progtest-runner
index e58ee28..a4076a3 100755
--- a/build-aux/progtest-runner
+++ b/build-aux/progtest-runner
@@ -32,7 +32,7 @@ shift
for supplier in "$@"; do
base=$( basename "$supplier" .xml )
path_suppliers=$( dirname "$supplier" )
- tests=$( find -L "$path_tests"/"$base"/ -name '*.yml' )
+ tests=$( find -L "$path_tests"/"$base"/ -name '*.yml' | LC_ALL=c sort )
echo
echo "$path_suppliers/$base"
diff --git a/build-aux/suppmk-gen b/build-aux/suppmk-gen
deleted file mode 100755
index 347828b..0000000
--- a/build-aux/suppmk-gen
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/bin/bash
-# Configuration script to be run before `make'
-#
-# Copyright (C) 2014-2020 Ryan Specialty Group, LLC.
-#
-# 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 Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-##
-
-set -euo pipefail
-
-echo "Generating suppliers.mk..."
-# TODO: kluge; do properly.
-rm -f suppliers.mk
-make suppliers.mk
-
-# XXX: paths are hard-coded here!
-while read csv; do
- csvbase="${csv%%.*}"
- echo "$csvbase.xmlo: $csvbase.xml"
- echo "$csvbase.xml: $csvbase.csvo"
-done < <( find "$@" -regex '^.+\.csv.?$' ) \
- >> suppliers.mk
-
-while read tdat; do
- tbase="${tdat%%.*}"
- echo "$tbase.xmlo: $tbase.xml rater/core/tdat.xmlo"
- echo "$tbase.xml: $tdat"
- echo -e "\trater/tools/tdat2xml \$< > \$@"
-done < <( find "$@" -regex '^.+territories/.+\.dat$' ) \
- >> suppliers.mk
-
diff --git a/core/aggregate.xml b/core/aggregate.xml
deleted file mode 100644
index f59db3e..0000000
--- a/core/aggregate.xml
+++ /dev/null
@@ -1,154 +0,0 @@
-<?xml version="1.0"?>
-<!--
- Copyright (C) 2014-2020 Ryan Specialty Group, LLC.
-
- This file is part of tame-core.
-
- tame-core is free software: you can redistribute it and/or modify it
- under the terms of the GNU General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
--->
-<package xmlns="http://www.lovullo.com/rater"
- xmlns:c="http://www.lovullo.com/calc"
- xmlns:t="http://www.lovullo.com/rater/apply-template"
- core="true"
- desc="Aggregating Values">
-
- <import package="base" export="true" />
- <import package="vector/cmatch" export="true" />
-
-
- Aggregate templates simplify aggregating values through various means.
- Unless otherwise specified,
- the default means of aggregation is summation.
-
-
- <section title="Symbol-Based Aggregation">
- For large numbers of values,
- the most convenient way to aggregate is by matching on symbol names.
- Note that symbols must be available for a match to occur.
- All imported symbols are immediately available,
- but \tt{expand-sequence} may need to be used for symbols produced by
- the same package.
-
-
- \ref{_aggregate-rate-each_} aggregates values of generators (usually
- referred to by \tt{rate-each}) through summation.
- A \tt{rate-each} block is generated to perform the summation.
-
- Since \tt{rate-each} multiplies its body by \tt{_CMATCH_},
- zero symbols would normally result in the summation of \tt{_CMATCH_}
- itself, which is not desirable;
- this template always includes \ref{ZERO} in the body to defend
- against this,
- causing a yield of~$0.00$ if there are no symbol matches.
-
- <template name="_aggregate-rate-each_"
- desc="Aggregate generator values by symbol prefix">
- <param name="@class@" desc="Iterator class (omit for scalars)" />
- <param name="@prefix@" desc="Symbol prefix" />
-
- <param name="@yields@" desc="Scalar yield name (optional)">
- <text></text>
- </param>
-
- <param name="@generates@" desc="Generator name (optional)">
- <text></text>
- </param>
-
-
- <rate-each class="@class@" yields="@yields@"
- generates="@generates@" index="k">
- <c:sum>
- <!-- prevent summing _CMATCH_ if there are no symbols (see above
- comments) -->
- <c:value-of name="ZERO"
- label="Guard against zero symbol matches" />
-
- <inline-template>
- <for-each>
- <sym-set name-prefix="@prefix@" type="gen" />
- </for-each>
-
- <c:value-of name="@sym_name@" index="k" />
- </inline-template>
- </c:sum>
- </rate-each>
- </template>
-
-
- \ref{_aggregate-rate_} is analgous to \ref{_aggregate-rate-each_},
- handling only scalar~\tt{@yields@}.
- A \tt{rate} block is generated to aggregate by summation.
-
- To prevent an empty rate block from being generated if there are no
- symbol matches,
- \ref{ZERO} is always included as part of the summation.
-
- <template name="_aggregate-rate_"
- desc="Aggregate scalar results by symbol prefix">
- <param name="@prefix@" desc="Symbol prefix" />
- <param name="@yields@" desc="Scalar yield name" />
-
-
- <rate yields="@yields@">
- <c:sum>
- <!-- prevent completely empty rate block -->
- <c:value-of name="ZERO"
- label="Guard against zero symbol matches" />
-
- <inline-template>
- <for-each>
- <sym-set name-prefix="@prefix@" type="rate" />
- </for-each>
-
- <c:value-of name="@sym_name@" />
- </inline-template>
- </c:sum>
- </rate>
- </template>
-
-
- \ref{_aggregate-classify_} aggregates classifications.
- Keep in mind that classifications act as universal quantifiers by default,
- meaning zero symbol matches will produce a match and a scalar~$1$;
- existential quantifiers (\tt{@any@} set to \tt{true}) will \emph{not}
- match and will produce the scalar~$0$.
-
- <template name="_aggregate-classify_"
- desc="Aggregate classification results by symbol prefix">
- <param name="@prefix@" desc="Symbol prefix" />
- <param name="@as@" desc="Classification name" />
- <param name="@desc@" desc="Generated classification description" />
-
- <param name="@yields@" desc="Vector yield name (optional)">
- <text></text>
- </param>
-
- <param name="@any@"
- desc="Existential classification (default false, universal)">
- <text></text>
- </param>
-
-
- <classify as="@as@" yields="@yields@" desc="@desc@" any="@any@">
- <inline-template>
- <for-each>
- <sym-set name-prefix="@prefix@" type="class" />
- </for-each>
-
- <t:match-class name="@sym_name@" />
- </inline-template>
- </classify>
- </template>
- </section>
-</package>
diff --git a/core/base.xml b/core/base.xml
index 37b757f..435ea8d 100644
--- a/core/base.xml
+++ b/core/base.xml
@@ -217,68 +217,105 @@
</template>
</section>
- <template name="_yield_"
- desc="Final scalar result provided to caller">
- <param name="@values@" desc="Yield calculation" />
-
- <rate yields="___yield" local="true">
- <param-copy name="@values@" />
- </rate>
- </template>
-
- <template name="_rate-each_"
- desc="Convenience template that expands to a lv:rate block summing over
- the magic _CMATCH_ set with the product of its value">
- <param name="@values@"
- desc="Yield calculation" />
-
- <param name="@generates@" desc="Generator name (optional)">
- <text></text>
- </param>
-
- <param name="@yields@" desc="Yield (optional)">
- <text>_</text>
- <param-value name="@generates@" />
- </param>
-
- <!-- at least one of generates or yields is required -->
- <if name="@yields@" eq="">
- <if name="@generates@" eq="">
- <error>must provide at least one of @generates or @yields</error>
+
+ <section title="Calculations">
+ These templates represent calculations that used to be defined as XSLT
+ tempaltes before TAME's template system existed.
+
+ <template name="_yield_"
+ desc="Final scalar result provided to caller">
+ <param name="@values@" desc="Yield calculation" />
+
+ <rate yields="___yield" local="true">
+ <param-copy name="@values@" />
+ </rate>
+ </template>
+
+
+ <template name="_rate-each_"
+ desc="Convenience template that expands to a lv:rate block summing over
+ the magic _CMATCH_ set with the product of its value">
+ <param name="@values@"
+ desc="Yield calculation" />
+
+ <param name="@generates@" desc="Generator name (optional)">
+ <text></text>
+ </param>
+
+ <param name="@yields@" desc="Yield (optional)">
+ <text>_</text>
+ <param-value name="@generates@" />
+ </param>
+
+ <!-- at least one of generates or yields is required -->
+ <if name="@yields@" eq="">
+ <if name="@generates@" eq="">
+ <error>must provide at least one of @generates or @yields</error>
+ </if>
</if>
- </if>
-
- <param name="@class@"
- desc="Space-delimited classifications for predicated iteration" />
- <param name="@no@"
- desc="Space-delimited classifications for predicated iteration to prevent matches">
- <text></text>
- </param>
-
- <param name="@index@"
- desc="Generator index" />
-
- <param name="@dim@" desc="Dim (optional)">
- <text></text>
- </param>
-
- <param name="@gensym@" desc="Generator TeX symbol">
- <text></text>
- </param>
-
- <rate class="@class@" no="@no@" yields="@yields@"
- gentle-no="true"
- desc="Total {@yields@} premium">
- <c:sum of="_CMATCH_" dim="@dim@" sym="@gensym@"
- generates="@generates@" index="@index@"
- desc="Set of individual {@yields@} premiums">
- <c:product>
- <c:value-of name="_CMATCH_" index="@index@"
- label="One if {@class@} and not {@no@} (if provided), otherwise zero" />
- <param-copy name="@values@" />
- </c:product>
- </c:sum>
- </rate>
- </template>
+
+ <param name="@class@"
+ desc="Space-delimited classifications for predicated iteration" />
+ <param name="@no@"
+ desc="Space-delimited classifications for predicated iteration to prevent matches">
+ <text></text>
+ </param>
+
+ <param name="@index@"
+ desc="Generator index" />
+
+ <param name="@dim@" desc="Dim (optional)">
+ <text></text>
+ </param>
+
+ <param name="@gensym@" desc="Generator TeX symbol">
+ <text></text>
+ </param>
+
+ <rate class="@class@" no="@no@" yields="@yields@"
+ gentle-no="true"
+ desc="Total {@yields@} premium">
+ <c:sum of="_CMATCH_" dim="@dim@" sym="@gensym@"
+ generates="@generates@" index="@index@"
+ desc="Set of individual {@yields@} premiums">
+ <c:product>
+ <c:value-of name="_CMATCH_" index="@index@"
+ label="One if {@class@} and not {@no@} (if provided), otherwise zero" />
+ <param-copy name="@values@" />
+ </c:product>
+ </c:sum>
+ </rate>
+ </template>
+ </section>
+
+
+ <section title="Feature Flags">
+ These templates alter the behavior of the TAME compiler or runtime.
+ They will be removed at some point in the future.
+
+
+ <section title="Classification System">
+ The template \tt{_use-new-classification-system_} sets a compile-time
+ flag that will cause all following sibling classifications to be
+ compiled using the new classification system.
+ Once the feature is enabled by default,
+ this template will become a noop and will begin to emit a warning,
+ before eventually being removed.
+
+ It is possible to mix both old and new classifications within the same
+ package,
+ though such behavior may lead to confusion in certain cases.
+ For more information on where the new and old system differ,
+ see the \tt{core/test/core/class} specification.
+
+ <template name="_use-new-classification-system_"
+ desc="Compile following-sibling::lv:classify using the new
+ classification system">
+ <!-- Even though this is a template param-meta, it will only affect
+ following-sibling for performance reasons -->
+ <param-meta name="___feature-newclassify" value="1" />
+ </template>
+ </section>
+ </section>
</package>
diff --git a/core/tdat.xml b/core/tdat.xml
index 2923702..f7c474e 100644
--- a/core/tdat.xml
+++ b/core/tdat.xml
@@ -36,7 +36,7 @@
</param>
- <rate-each class="@class@" accumulate="none" yields="@yields@" generates="@generates@" index="k">
+ <rate-each class="@class@" yields="@yields@" generates="@generates@" index="k">
<c:const value="@code@" type="integer" desc="Territory code" />
</rate-each>
</template>
diff --git a/core/test/core/aggregate.xml b/core/test/core/aggregate.xml
deleted file mode 100644
index 4d0e32c..0000000
--- a/core/test/core/aggregate.xml
+++ /dev/null
@@ -1,264 +0,0 @@
-<?xml version="1.0"?>
-<!--
- Copyright (C) 2014-2020 Ryan Specialty Group, LLC.
-
- This file is part of tame-core.
-
- tame-core is free software: you can redistribute it and/or modify it
- under the terms of the GNU General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
--->
-<package xmlns="http://www.lovullo.com/rater"
- xmlns:c="http://www.lovullo.com/calc"
- xmlns:t="http://www.lovullo.com/rater/apply-template"
- desc="Aggregate Package Specification">
-
- <import package="../../base" />
- <import package="../../test/spec" />
-
- <import package="../../base" />
- <import package="../../vector/cmatch" />
- <import package="../../vector/stub" />
-
- <import package="../../aggregate" />
-
-
- <rate-each class="nclass3"
- generates="aggregateGen1" index="k">
- <c:const value="1" desc="Constant value" />
- </rate-each>
-
- <rate-each class="nclass3"
- generates="aggregateGen2" index="k">
- <c:value-of name="k" />
- </rate-each>
-
-
- <rate yields="aggregateRate1">
- <c:const value="1" desc="Constant value" />
- </rate>
-
- <rate yields="aggregateRate2">
- <c:const value="3" desc="Constant value" />
- </rate>
-
-
- <classify as="agg-class-1"
- desc="Aggregate test 1">
- <match on="AGG_1VEC" />
- </classify>
-
- <classify as="agg-class-2"
- desc="Aggregate test 2">
- <t:match-gt on="AGG_INCVEC" const="0" />
- </classify>
-
-
- <t:n-vector n="3" name="AGG_1VEC" value="1" />
-
- <const name="AGG_INCVEC" desc="Incrementing vector">
- <item value="0" />
- <item value="1" />
- <item value="2" />
- </const>
-
-
-
- <t:describe name="aggregate template">
- <t:describe name="_aggregate-rate-each_">
- <t:aggregate-rate-each class="nclass3" yields="yieldAggReEmpty"
- prefix="doesNotExist"
- generates="genAggReEmpty" />
-
- <t:aggregate-rate-each class="nclass3" yields="yieldAggReNonEmpty"
- prefix="aggregateGen"
- generates="genAggReNonEmpty" />
-
-
- <t:describe name="with no symbols">
- <t:it desc="produces 0">
- <t:given>
- <c:sum>
- <c:value-of name="yieldAggReEmpty" />
- <c:sum of="genAggReEmpty" />
- </c:sum>
- </t:given>
-
- <t:expect>
- <t:match-result eq="0" />
- </t:expect>
- </t:it>
- </t:describe>
-
-
- <t:describe name="with symbols">
- <t:it desc="sums respective index of each symbol">
- <t:given>
- <c:sum of="genAggReNonEmpty" />
- </t:given>
-
- <t:expect>
- <!-- 1 + 2 + 3 -->
- <t:match-result eq="6" />
- </t:expect>
- </t:it>
-
-
- <t:it desc="yields sum of symbols">
- <t:given>
- <c:value-of name="yieldAggReNonEmpty" />
- </t:given>
-
- <t:expect>
- <!-- same as above -->
- <t:match-result eq="6" />
- </t:expect>
- </t:it>
- </t:describe>
- </t:describe>
-
-
- <t:describe name="_aggregate-rate_">
- <t:aggregate-rate prefix="doesNotExist" yields="yieldAggRateEmpty" />
- <t:aggregate-rate prefix="aggregateRate" yields="yieldAggRateNonEmpty" />
-
- <t:describe name="with no symbols">
- <t:it desc="yields 0">
- <t:given>
- <c:value-of name="yieldAggRateEmpty" />
- </t:given>
-
- <t:expect>
- <t:match-result eq="0" />
- </t:expect>
- </t:it>
- </t:describe>
-
-
- <t:describe name="with symbols">
- <t:it desc="yields sum of symbols">
- <t:given>
- <c:value-of name="yieldAggRateNonEmpty" />
- </t:given>
-
- <t:expect>
- <t:match-result eq="4" />
- </t:expect>
- </t:it>
- </t:describe>
- </t:describe>
-
-
- <t:describe name="_aggregate-classify_">
- <t:describe name="as a univiersal quantifier">
- <t:aggregate-classify prefix="does-not-exist" as="class-agg-univ-empty"
- desc="Aggregate universal class empty test"
- yields="classAggUnivEmpty" />
- <t:aggregate-classify prefix="agg-class-" as="class-agg-univ-nonempty"
- desc="Aggregate class nonempty test"
- yields="classAggUnivNonEmpty" />
-
- <t:describe name="with no symbols">
- <t:it desc="produces scalar 1">
- <t:given>
- <c:value-of name="classAggUnivEmpty" />
- </t:given>
-
- <t:expect>
- <t:match-result eq="1" />
- </t:expect>
- </t:it>
- </t:describe>
-
-
- <t:describe name="with symbols">
- <t:it desc="generates matching class">
- <rate-each class="class-agg-univ-nonempty"
- yields="aggUnivNonEmptyCheck"
- index="k">
- <c:const value="1" desc="Truth check" />
- </rate-each>
-
- <t:expect>
- <!-- two non-zero in AGG_INCVEC -->
- <t:match-eq on="aggUnivNonEmptyCheck" const="2" />
- </t:expect>
- </t:it>
-
-
- <t:it desc="produces vector">
- <t:given>
- <c:sum of="classAggUnivNonEmpty" />
- </t:given>
-
- <t:expect>
- <!-- two non-zero in AGG_INCVEC -->
- <t:match-result eq="2" />
- </t:expect>
- </t:it>
- </t:describe>
- </t:describe>
-
-
- <t:describe name="as a existential quantifier">
- <t:aggregate-classify prefix="does-not-exist" as="class-agg-exist-empty"
- desc="Aggregate existersal class empty test"
- yields="classAggExistEmpty"
- any="true" />
- <t:aggregate-classify prefix="agg-class-" as="class-agg-exist-nonempty"
- desc="Aggregate class nonempty test"
- yields="classAggExistNonEmpty"
- any="true" />
-
- <t:describe name="with no symbols">
- <t:it desc="produces scalar 0">
- <t:given>
- <c:value-of name="classAggExistEmpty" />
- </t:given>
-
- <t:expect>
- <t:match-result eq="0" />
- </t:expect>
- </t:it>
- </t:describe>
-
-
- <t:describe name="with symbols">
- <t:it desc="generates matching class">
- <rate-each class="class-agg-exist-nonempty"
- yields="aggExistNonEmptyCheck"
- index="k">
- <c:const value="1" desc="Truth check" />
- </rate-each>
-
- <t:expect>
- <!-- all match in AGG_1VEC -->
- <t:match-eq on="aggExistNonEmptyCheck" const="3" />
- </t:expect>
- </t:it>
-
-
- <t:it desc="produces vector">
- <t:given>
- <c:sum of="classAggExistNonEmpty" />
- </t:given>
-
- <t:expect>
- <!-- all match in AGG_1VEC -->
- <t:match-result eq="3" />
- </t:expect>
- </t:it>
- </t:describe>
- </t:describe>
- </t:describe>
- </t:describe>
-</package>
diff --git a/core/test/core/class.xml b/core/test/core/class.xml
new file mode 100644
index 0000000..4335ea5
--- /dev/null
+++ b/core/test/core/class.xml
@@ -0,0 +1,733 @@
+<?xml version="1.0"?>
+<!--
+ Copyright (C) 2014-2021 Ryan Specialty Group, LLC.
+
+ This file is part of tame-core.
+
+ tame-core is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+-->
+<package xmlns="http://www.lovullo.com/rater"
+ xmlns:c="http://www.lovullo.com/calc"
+ xmlns:t="http://www.lovullo.com/rater/apply-template"
+ desc="Classification System Specs">
+
+ <import package="../../test/spec" />
+ <import package="../../vector/stub" />
+ <import package="../../base" />
+
+ Note that many of these classifications may match on similar values to try
+ to thwart potential optimizations,
+ present or future,
+ but these approaches
+ may need further adjustment to thwart future optimizations (or a way to
+ explicitly inhibit them).
+ These tests are also written a bit lazily,
+ given the difficulties in matching comprehensively;
+ that ought to be fixed in the future.
+
+ <const name="MAT3X3" desc="3x3 Matrix, Ones">
+ <set desc="Row 0">
+ <item value="1" desc="0,0" />
+ <item value="1" desc="0,1" />
+ <item value="1" desc="0,2" />
+ </set>
+ <set desc="Row 1">
+ <item value="1" desc="1,0" />
+ <item value="1" desc="1,1" />
+ <item value="1" desc="1,2" />
+ </set>
+ <set desc="Row 2">
+ <item value="1" desc="2,0" />
+ <item value="1" desc="2,1" />
+ <item value="1" desc="2,2" />
+ </set>
+ </const>
+
+ <const name="MAT3X3Z" desc="3x3 Matrix, Zeroes">
+ <set desc="Row 0">
+ <item value="0" desc="0,0" />
+ <item value="0" desc="0,1" />
+ <item value="0" desc="0,2" />
+ </set>
+ <set desc="Row 1">
+ <item value="0" desc="1,0" />
+ <item value="0" desc="1,1" />
+ <item value="0" desc="1,2" />
+ </set>
+ <set desc="Row 2">
+ <item value="0" desc="2,0" />
+ <item value="0" desc="2,1" />
+ <item value="0" desc="2,2" />
+ </set>
+ </const>
+
+ <const name="MAT3X3OOZ" desc="3x3 Matrix, Columns 1, 1, 0">
+ <set desc="Row 0">
+ <item value="1" desc="0,0" />
+ <item value="1" desc="0,1" />
+ <item value="0" desc="0,2" />
+ </set>
+ <set desc="Row 1">
+ <item value="1" desc="1,0" />
+ <item value="1" desc="1,1" />
+ <item value="0" desc="1,2" />
+ </set>
+ <set desc="Row 2">
+ <item value="1" desc="2,0" />
+ <item value="1" desc="2,1" />
+ <item value="0" desc="2,2" />
+ </set>
+ </const>
+
+ <const name="MAT3X1" desc="3x2 Matrix, Ones">
+ <set desc="Row 0">
+ <item value="1" desc="0,0" />
+ </set>
+ <set desc="Row 1">
+ <item value="1" desc="1,0" />
+ </set>
+ <set desc="Row 2">
+ <item value="1" desc="2,0" />
+ </set>
+ </const>
+
+ <const name="MAT3X1Z" desc="3x2 Matrix, Zeroes">
+ <set desc="Row 0">
+ <item value="0" desc="0,0" />
+ </set>
+ <set desc="Row 1">
+ <item value="0" desc="1,0" />
+ </set>
+ <set desc="Row 2">
+ <item value="0" desc="2,0" />
+ </set>
+ </const>
+
+ <const name="MAT1X3Z" desc="1x3 Matrix, Zeroes">
+ <set desc="Row 0">
+ <item value="0" desc="0,0" />
+ <item value="0" desc="0,1" />
+ <item value="0" desc="0,2" />
+ </set>
+ </const>
+
+ <template name="_class-tests_" desc="Classification system tests">
+ <param name="@system@" desc="SUT (lowercase)" />
+
+ <param name="@systemuc@" desc="SUT (title case)">
+ <param-value name="@system@" ucfirst="true" />
+ </param>
+
+
+ <t:describe name="{@system@} classify">
+ <t:describe name="without predicates">
+ <t:it desc="yields TRUE for conjunction">
+ <classify as="conj-no-pred-{@system@}"
+ yields="conjNoPred{@systemuc@}"
+ desc="No predicate, conjunction" />
+
+ <t:given name="conjNoPred{@systemuc@}" />
+
+ <t:expect>
+ <t:match-result value="TRUE" />
+ </t:expect>
+ </t:it>
+
+
+ <t:it desc="yields FALSE for disjunction">
+ <classify as="disj-no-pred-{@system@}"
+ yields="disjNoPred{@systemuc@}"
+ any="true"
+ desc="No predicate, disjunction" />
+
+ <t:given name="disjNoPred{@systemuc@}" />
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+ </t:describe>
+
+
+ <t:describe name="with scalar predicates">
+ <t:it desc="yields TRUE when scalar value is TRUE">
+ <t:given-classify>
+ <match on="alwaysTrue" />
+ </t:given-classify>
+
+ <t:expect>
+ <t:match-result value="TRUE" />
+ </t:expect>
+ </t:it>
+
+
+ <t:it desc="yields FALSE when scalar value is FALSE">
+ <t:given-classify>
+ <match on="neverTrue" />
+ </t:given-classify>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+
+
+ <t:it desc="yields TRUE for all-true scalar conjunction">
+ <t:given-classify>
+ <match on="alwaysTrue" />
+ <match on="neverTrue" value="FALSE" />
+ </t:given-classify>
+
+ <t:expect>
+ <t:match-result value="TRUE" />
+ </t:expect>
+ </t:it>
+
+
+ <t:it desc="yields TRUE for all-true scalar disjunction">
+ <t:given-classify>
+ <any>
+ <match on="alwaysTrue" />
+ <match on="neverTrue" value="FALSE" />
+ </any>
+ </t:given-classify>
+
+ <t:expect>
+ <t:match-result value="TRUE" />
+ </t:expect>
+ </t:it>
+
+
+ <t:it desc="yields TRUE for single-true scalar disjunction">
+ <t:given-classify>
+ <any>
+ <match on="alwaysTrue" />
+ <match on="neverTrue" />
+ </any>
+ </t:given-classify>
+
+ <t:expect>
+ <t:match-result value="TRUE" />
+ </t:expect>
+ </t:it>
+ </t:describe>
+
+
+ <t:describe name="with vector predicates">
+ <t:it desc="yields TRUE for all-true element-wise conjunction">
+ <t:given-classify-scalar>
+ <match on="NVEC3" value="ZERO" />
+ <match on="nClass3" value="TRUE" />
+ </t:given-classify-scalar>
+
+ <t:expect>
+ <t:match-result value="TRUE" />
+ </t:expect>
+ </t:it>
+
+
+ <t:it desc="yields FALSE for some-true element-wise conjunction">
+ <t:given-classify-scalar>
+ <match on="NVEC3" value="ZERO" />
+ <match on="nClass3" value="FALSE" />
+ </t:given-classify-scalar>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+
+
+ <t:it desc="yields TRUE for some-true element-wise disjunction">
+ <t:given-classify-scalar>
+ <any>
+ <match on="NVEC3" value="ZERO" />
+ <match on="nClass3" value="FALSE" />
+ </any>
+ </t:given-classify-scalar>
+
+ <t:expect>
+ <t:match-result value="TRUE" />
+ </t:expect>
+ </t:it>
+
+
+ <t:it desc="yields FALSE for all-false element-wise disjunction">
+ <t:given-classify-scalar>
+ <any>
+ <match on="NVEC3" value="TRUE" />
+ <match on="nClass3" value="FALSE" />
+ </any>
+ </t:given-classify-scalar>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+
+
+ The old classification system would interpret missing values as $0$,
+ which could potentially trigger a match.
+ The new classification system will always yield \tparam{FALSE}
+ regardless of predicate when values are undefined.
+
+ <t:describe name="of different lengths">
+ <if name="@system@" eq="legacy">
+ <t:describe name="with legacy classification system">
+ <t:it desc="interprets undefined values as zero during match">
+ <classify as="vec-len-mismatch-conj-{@system@}"
+ yields="vecLenMismatchConj{@systemuc@}"
+ desc="Multi vector length mismatch (legacy)">
+ <!-- actually ZERO for all indexes -->
+ <match on="NVEC3" value="ZERO" />
+
+ <!-- legacy system, implicitly zero for match -->
+ <match on="NVEC2" value="ZERO" />
+ </classify>
+
+ <t:given>
+ <c:value-of name="vecLenMismatchConj{@systemuc@}" index="#2" />
+ </t:given>
+
+ <t:expect>
+ <t:match-result value="TRUE" />
+ </t:expect>
+ </t:it>
+ </t:describe>
+ </if>
+
+
+ <if name="@system@" eq="new">
+ <t:describe name="with new classification system">
+ <t:it desc="yields false for conjunction rather than implicit zero">
+ <classify as="vec-len-mismatch-conj-{@system@}"
+ yields="vecLenMismatchConj{@systemuc@}"
+ desc="Multi vector length mismatch (new system)">
+ <!-- actually ZERO for all indexes -->
+ <match on="NVEC3" value="ZERO" />
+
+ <!-- must not be implicitly ZERO for third index -->
+ <match on="NVEC2" value="ZERO" />
+ </classify>
+
+ <t:given>
+ <c:value-of name="vecLenMismatchConj{@systemuc@}" index="#2" />
+ </t:given>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+ </t:describe>
+ </if>
+ </t:describe>
+ </t:describe>
+
+
+ <t:describe name="with matrix predicates">
+ <t:it desc="yields TRUE for all-true element-wise conjunction">
+ <t:given-classify-scalar>
+ <match on="MAT3X3Z" value="FALSE" />
+ <match on="MAT3X3" value="TRUE" />
+ </t:given-classify-scalar>
+
+ <t:expect>
+ <t:match-result value="TRUE" />
+ </t:expect>
+ </t:it>
+
+
+ <t:it desc="yields FALSE for some-true element-wise conjunction">
+ <t:given-classify-scalar>
+ <match on="MAT3X3Z" value="TRUE" />
+ <match on="MAT3X3" value="TRUE" />
+ </t:given-classify-scalar>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+
+
+ <t:it desc="yields TRUE for some-true element-wise disjunction">
+ <t:given-classify-scalar>
+ <any>
+ <match on="MAT3X3Z" value="ZERO" />
+ <match on="MAT3X3" value="ZERO" />
+ </any>
+ </t:given-classify-scalar>
+
+ <t:expect>
+ <t:match-result value="TRUE" />
+ </t:expect>
+ </t:it>
+
+
+ <t:it desc="yields FALSE for all-false element-wise disjunction">
+ <t:given-classify-scalar>
+ <any>
+ <match on="MAT3X3Z" value="TRUE" />
+ <match on="MAT3X3" value="FALSE" />
+ </any>
+ </t:given-classify-scalar>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+
+
+ <t:describe name="of different column lengths">
+ Certain behavior is the same between the old and the new system---%
+ in particular,
+ when the match of lower length is first.
+
+ <classify as="mat-len-mismatch-first-conj-{@system@}"
+ yields="matLenMismatchFirstConj{@systemuc@}"
+ desc="Multi matrix length mismatch when first match">
+ <!-- fallthrough for undefined (note that this is
+ intentionally matching on FALSE to test against an
+ implicit 0 in place of undefined) -->
+ <match on="MAT3X1Z" value="FALSE" />
+
+ <!-- first two columns ones, last column zero -->
+ <match on="MAT3X3OOZ" value="TRUE" />
+ </classify>
+
+ <t:it desc="always yields FALSE when first match (TRUE)">
+ <t:given>
+ <c:value-of name="matLenMismatchFirstConj{@systemuc@}">
+ <c:index>
+ <c:value-of name="#1" />
+ </c:index>
+ <c:index>
+ <c:value-of name="#1" />
+ </c:index>
+ </c:value-of>
+ </t:given>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+
+ <t:it desc="always yields FALSE when first match (FALSE)">
+ <t:given>
+ <c:value-of name="matLenMismatchFirstConj{@systemuc@}">
+ <c:index>
+ <c:value-of name="#2" />
+ </c:index>
+ <c:index>
+ <c:value-of name="#2" />
+ </c:index>
+ </c:value-of>
+ </t:given>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+
+
+ <if name="@system@" eq="legacy">
+ <t:describe name="with legacy classification system">
+ The legacy system is frightenly problematic when the matrix of
+ lesser column length appears after the first match---%
+ the commutative properites of the system are lost,
+ and the value from the previous match falls through!
+
+ <classify as="mat-len-mismatch-conj-{@system@}"
+ yields="matLenMismatchConj{@systemuc@}"
+ desc="Multi matrix length mismatch (legacy)">
+ <!-- first two columns ones, last column zero -->
+ <match on="MAT3X3OOZ" value="TRUE" />
+
+ <!-- fallthrough for undefined (note that this is
+ intentionally matching on FALSE to test against an
+ implicit 0 in place of undefined) -->
+ <match on="MAT3X1Z" value="FALSE" />
+ </classify>
+
+ <!-- which means that it's not cummutatitve! -->
+ <t:it desc="causes values from previous match to fall through
+ into undefined (TRUE)">
+ <t:given>
+ <c:value-of name="matLenMismatchConj{@systemuc@}">
+ <c:index>
+ <c:value-of name="#1" />
+ </c:index>
+ <c:index>
+ <c:value-of name="#1" />
+ </c:index>
+ </c:value-of>
+ </t:given>
+
+ <t:expect>
+ <t:match-result value="TRUE" />
+ </t:expect>
+ </t:it>
+
+ <t:it desc="causes values from previous match to fall through
+ into undefined (FALSE)">
+ <t:given>
+ <c:value-of name="matLenMismatchConj{@systemuc@}">
+ <c:index>
+ <c:value-of name="#2" />
+ </c:index>
+ <c:index>
+ <c:value-of name="#2" />
+ </c:index>
+ </c:value-of>
+ </t:given>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+ </t:describe>
+ </if>
+
+
+ <if name="@system@" eq="new">
+ <t:describe name="with new classification system">
+ <classify as="mat-len-mismatch-conj-{@system@}"
+ yields="matLenMismatchConj{@systemuc@}"
+ desc="Multi matrix length mismatch (new)">
+ <!-- first two columns ones, last column zero -->
+ <match on="MAT3X3OOZ" value="TRUE" />
+
+ <!-- must not fall through like legacy (must always be
+ FALSE; note that this is intentionally matching on
+ FALSE to test against an implicit 0 in place of
+ undefined) -->
+ <match on="MAT3X1Z" value="FALSE" />
+ </classify>
+
+ <!-- which means that it's not cummutatitve! -->
+ <t:it desc="is FALSE regardless of previous match or current
+ value (TRUE)">
+ <t:given>
+ <c:value-of name="matLenMismatchConj{@systemuc@}">
+ <c:index>
+ <c:value-of name="#1" />
+ </c:index>
+ <c:index>
+ <c:value-of name="#1" />
+ </c:index>
+ </c:value-of>
+ </t:given>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+
+ <t:it desc="is FALSE regardless of previous match or current
+ value (FALSE)">
+ <t:given>
+ <c:value-of name="matLenMismatchConj{@systemuc@}">
+ <c:index>
+ <c:value-of name="#2" />
+ </c:index>
+ <c:index>
+ <c:value-of name="#2" />
+ </c:index>
+ </c:value-of>
+ </t:given>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+ </t:describe>
+ </if>
+ </t:describe>
+
+
+ <t:describe name="of different row lengths">
+ <if name="@system@" eq="legacy">
+ <t:describe name="with legacy classification system">
+ The legacy classification system does something terrible when
+ the second match is the shorter---%
+ it discards the indexes entirely!
+
+ <classify as="mat-len-mismatch-rows-conj-{@system@}"
+ yields="matLenMismatchRowsConj{@systemuc@}"
+ desc="Multi matrix row mismatch (legacy)">
+ <!-- we _should_ have a 3x3 result matrix -->
+ <match on="MAT3X3OOZ" value="TRUE" />
+
+ <!-- but instead we get [[1, 1, 1], [0], [0]] because of
+ this match being second! -->
+ <match on="MAT1X3Z" value="FALSE" />
+ </classify>
+
+ <classify as="mat-len-mismatch-rows-first-conj-{@system@}"
+ yields="matLenMismatchRowsFirstConj{@systemuc@}"
+ desc="Multi matrix row mismatch first match (legacy)">
+ <match on="MAT1X3Z" value="TRUE" />
+ <match on="MAT3X3OOZ" value="TRUE" />
+ </classify>
+
+ <!-- note that this is testing buggy behavior; the new system
+ corrects it -->
+ <t:it desc="replaces all inner vectors of other rows">
+ <t:given>
+ <c:length-of>
+ <c:value-of name="matLenMismatchRowsConj{@systemuc@}">
+ <c:index>
+ <c:value-of name="#1" />
+ </c:index>
+ </c:value-of>
+ </c:length-of>
+ </t:given>
+
+ <t:expect>
+ <!-- were it not for this bug, it should be 3 -->
+ <t:match-result value="#1" />
+ </t:expect>
+ </t:it>
+
+ <!-- note that this is testing buggy behavior; the new system
+ corrects it -->
+ <t:it desc="considers only defined rows' values when smaller
+ is first">
+ <t:given>
+ <c:value-of name="matLenMismatchRowsFirstConj{@systemuc@}">
+ <c:index>
+ <c:value-of name="#1" />
+ </c:index>
+ <c:index>
+ <c:value-of name="#0" />
+ </c:index>
+ </c:value-of>
+ </t:given>
+
+ <!-- we get [[0, 0, 0], [1, 1, 0], [1, 1, 0]] -->
+ <!-- ^ -->
+ <t:expect>
+ <t:match-result value="#1" />
+ </t:expect>
+ </t:it>
+ </t:describe>
+ </if>
+
+ <if name="@system@" eq="new">
+ <t:describe name="with new classification system">
+ <classify as="mat-len-mismatch-rows-conj-{@system@}"
+ yields="matLenMismatchRowsConj{@systemuc@}"
+ desc="Multi matrix row mismatch (new)">
+ <match on="MAT3X3OOZ" value="TRUE" />
+
+ <!-- must yield FALSE rather than matching on 0 -->
+ <match on="MAT1X3Z" value="FALSE" />
+ </classify>
+
+ <classify as="mat-len-mismatch-rows-first-conj-{@system@}"
+ yields="matLenMismatchRowsFirstConj{@systemuc@}"
+ desc="Multi matrix row mismatch first match (new)">
+ <match on="MAT1X3Z" value="TRUE" />
+ <match on="MAT3X3OOZ" value="TRUE" />
+ </classify>
+
+
+ <t:it desc="retains shape of larger matrix">
+ <t:given>
+ <c:length-of>
+ <c:value-of name="matLenMismatchRowsConj{@systemuc@}">
+ <c:index>
+ <c:value-of name="#2" />
+ </c:index>
+ </c:value-of>
+ </c:length-of>
+ </t:given>
+
+ <t:expect>
+ <!-- unlike legacy system -->
+ <t:match-result value="#3" />
+ </t:expect>
+ </t:it>
+
+ <t:it desc="always yields FALSE for each undefined element (TRUE)">
+ <t:given>
+ <c:length-of>
+ <c:value-of name="matLenMismatchRowsConj{@systemuc@}">
+ <c:index>
+ <c:value-of name="#2" />
+ </c:index>
+ <c:index>
+ <c:value-of name="#1" />
+ </c:index>
+ </c:value-of>
+ </c:length-of>
+ </t:given>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+
+ <t:it desc="always yields FALSE for each undefined element (FALSE)">
+ <t:given>
+ <c:length-of>
+ <c:value-of name="matLenMismatchRowsConj{@systemuc@}">
+ <c:index>
+ <c:value-of name="#1" />
+ </c:index>
+ <c:index>
+ <c:value-of name="#2" />
+ </c:index>
+ </c:value-of>
+ </c:length-of>
+ </t:given>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+
+ <!-- unlike legacy -->
+ <t:it desc="is commutative with different row lengths">
+ <t:given>
+ <c:value-of name="matLenMismatchRowsFirstConj{@systemuc@}">
+ <c:index>
+ <c:value-of name="#1" />
+ </c:index>
+ <c:index>
+ <c:value-of name="#2" />
+ </c:index>
+ </c:value-of>
+ </t:given>
+
+ <t:expect>
+ <t:match-result value="FALSE" />
+ </t:expect>
+ </t:it>
+ </t:describe>
+ </if>
+ </t:describe>
+ </t:describe>
+ </t:describe>
+ </template>
+
+
+ <section title="Legacy System Tests">
+ <t:class-tests system="legacy" />
+ </section>
+
+ <section title="New System Tests">
+ <t:use-new-classification-system />
+ <t:class-tests system="new" />
+ </section>
+</package>
diff --git a/core/test/core/suite.xml b/core/test/core/suite.xml
index 32aee5b..8df410d 100644
--- a/core/test/core/suite.xml
+++ b/core/test/core/suite.xml
@@ -38,7 +38,7 @@
<import package="vector/minmax" />
<import package="vector/table" />
- <import package="aggregate" />
+ <import package="class" />
<import package="insurance" />
<import package="retry" />
<import package="symbol" />
diff --git a/core/test/spec.xml b/core/test/spec.xml
index 8406db9..818e7ae 100644
--- a/core/test/spec.xml
+++ b/core/test/spec.xml
@@ -256,6 +256,72 @@
<!--
+ Describe a classification-based value for expectation groups
+
+ The defined value is available to adjacent expectations through use of
+ `_match-result_`.
+
+ A `_given_` definition is not required; it exists as a convenient and
+ concise way to represent test data in clear terms.
+
+ Permitted children:
+ - Any match
+ -->
+ <template name="_given-classify_"
+ desc="Describe a classification-based value for expectation groups">
+ <param name="@values@" desc="Classification predicates" />
+
+ <param name="@__id@"
+ desc="Unique identifier to avoid symbol conflicts">
+ <text unique="true">given</text>
+ </param>
+
+
+ <param-meta name="spec-given-id"
+ value="{@__id@}Yield" />
+
+ <classify as="@__id@" yields="{@__id@}Yield"
+ desc="Given value generated via _given-clasify_">
+ <param-copy name="@values@" />
+ </classify>
+ </template>
+
+
+ <template name="_given-classify-scalar_"
+ desc="Describe a classification-based value for expectation groups">
+ <param name="@values@" desc="Classification predicates" />
+
+ <param name="@__id@"
+ desc="Unique identifier to avoid symbol conflicts">
+ <text unique="true">given</text>
+ </param>
+
+
+ <param-meta name="spec-given-id"
+ value="{@__id@}Yield" />
+
+ <classify as="{@__id@}-pre" yields="{@__id@}PreYield"
+ desc="Given value generated via _given-clasify-scalar_ pre-scalar">
+ <param-copy name="@values@" />
+ </classify>
+
+ <rate class="{@__id@}-pre" yields="__{@__id@}ScalarSum">
+ <c:sum of="_CMATCH_" />
+ </rate>
+
+ <classify as="@__id@" yields="{@__id@}Yield"
+ desc="Given value generated via _given-classify_">
+ <match on="__{@__id@}ScalarSum">
+ <c:gt>
+ <c:const value="0" desc="Any match" />
+ </c:gt>
+ </match>
+ </classify>
+ </template>
+
+
+
+ <!--
Describe a feature expectation
An expectation tests the adherence of a feature to its specification. It
diff --git a/core/vector/filter.xml b/core/vector/filter.xml
index a1d4b77..0c71eb2 100644
--- a/core/vector/filter.xml
+++ b/core/vector/filter.xml
@@ -248,6 +248,104 @@
<c:let>
<c:values>
+ <c:value name="matches" type="integer" set="vector"
+ desc="Matching row indexes of matrix">
+ <c:apply name="mrange_accum"
+ matrix="matrix" col="col" val="val"
+ start="start" end="end" seq="seq" op="op">
+ <!-- Matchines indexes will be accumulated into a vector (in
+ reverse) to permit TCO -->
+ <c:arg name="accum">
+ <c:vector />
+ </c:arg>
+ </c:apply>
+ </c:value>
+ </c:values>
+
+ <c:apply name="_mextract_rows" matrix="matrix" indexes="matches" i="#0">
+ <!-- Pre-compute so _mextract_rows doesn't have to -->
+ <c:arg name="length">
+ <c:length-of>
+ <c:value-of name="matches" />
+ </c:length-of>
+ </c:arg>
+
+ <!-- The final matrix will be accumulated to permit TCO (note that
+ this reverse the original reversal mentioned above, so the
+ final matrix is in the right order) -->
+ <c:arg name="accum">
+ <c:vector />
+ </c:arg>
+ </c:apply>
+ </c:let>
+ </function>
+
+
+ <function name="_mextract_rows"
+ desc="Pull rows from a matrix by index">
+ <param name="matrix" type="float" set="matrix" desc="Source matrix" />
+ <param name="indexes" type="integer" set="vector" desc="Indexes to extract" />
+ <param name="length" type="integer" desc="Length of indexes vector" />
+ <param name="i" type="integer" desc="Current index offset" />
+ <param name="accum" type="float" set="matrix" desc="Accumulator (matrix)" />
+
+ <param name="__experimental_guided_tco" type="float" desc="Experimental guided TCO" />
+
+ <c:cases>
+ <!-- When we're done, yield the accumulated value, representign our
+ final matrix -->
+ <c:case>
+ <t:when-eq name="i" value="length" />
+ <c:value-of name="accum" />
+ </c:case>
+
+ <c:otherwise>
+ <c:recurse __experimental_guided_tco="TRUE">
+ <c:arg name="i">
+ <c:sum>
+ <c:value-of name="i" />
+ <c:const value="1" desc="Proceed to next index" />
+ </c:sum>
+ </c:arg>
+
+ <c:arg name="accum">
+ <c:cons>
+ <!-- Add the row identified by the current index to the
+ accumulator. Note that this uses cons, so it adds it
+ to the head, but since the original mrange results are
+ reversed, this is precisely what we want—to reverse the
+ reversal -->
+ <c:value-of name="matrix">
+ <c:index>
+ <c:value-of name="indexes" index="i" />
+ </c:index>
+ </c:value-of>
+
+ <c:value-of name="accum" />
+ </c:cons>
+ </c:arg>
+ </c:recurse>
+ </c:otherwise>
+ </c:cases>
+ </function>
+
+
+ <function name="mrange_accum"
+ desc="Filter matrix rows by column value within a certain
+ range of indexes (inclusive)">
+ <param name="matrix" type="float" set="matrix" desc="Matrix to filter" />
+ <param name="col" type="integer" desc="Column index to filter on" />
+ <param name="val" type="float" desc="Column value to filter on" />
+ <param name="start" type="integer" desc="Starting index (inclusive)" />
+ <param name="end" type="integer" desc="Ending index (inclusive)" />
+ <param name="seq" type="boolean" desc="Is data sequential?" />
+ <param name="op" type="integer" desc="Comparison operator" />
+ <param name="accum" type="integer" set="vector" desc="Accumulator (row indexes)" />
+
+ <param name="__experimental_guided_tco" type="float" desc="Experimental guided TCO" />
+
+ <c:let>
+ <c:values>
<c:value name="curval" type="float" desc="Current value">
<c:value-of name="matrix">
<c:index>
@@ -281,7 +379,7 @@
<c:case>
<t:when-gt name="start" value="end" />
- <c:vector />
+ <c:value-of name="accum" />
</c:case>
<!-- if the data is sequential and the next element is over the
@@ -291,27 +389,112 @@
<t:when-lte name="op" value="CMP_OP_LTE" />
<t:when-eq name="over" value="TRUE" />
- <c:vector />
+ <c:value-of name="accum" />
</c:case>
<c:otherwise>
- <c:apply name="_mrange_cmp" matrix="matrix" col="col" val="val"
- start="start" end="end" seq="seq" op="op">
- <c:arg name="cur">
- <c:value-of name="matrix">
- <!-- current row -->
- <c:index>
- <c:value-of name="start" />
- </c:index>
+ <c:let>
+ <c:values>
+ <c:value name="cur" type="float"
+ desc="Current value">
+ <c:value-of name="matrix">
+ <!-- current row -->
+ <c:index>
+ <c:value-of name="start" />
+ </c:index>
+
+ <!-- requested column -->
+ <c:index>
+ <c:value-of name="col" />
+ </c:index>
+ </c:value-of>
+ </c:value>
+ </c:values>
- <!-- requested column -->
- <c:index>
- <c:value-of name="col" />
- </c:index>
- </c:value-of>
- </c:arg>
- </c:apply>
+ <c:let>
+ <c:values>
+ <c:value name="found" type="boolean"
+ desc="Whether comparison matches">
+ <c:cases>
+ <c:case label="Equal">
+ <t:when-eq name="op" value="CMP_OP_EQ" />
+
+ <c:value-of name="TRUE">
+ <t:when-eq name="cur" value="val" />
+ </c:value-of>
+ </c:case>
+
+ <c:case label="Less than">
+ <t:when-eq name="op" value="CMP_OP_LT" />
+
+ <c:value-of name="TRUE">
+ <t:when-lt name="cur" value="val" />
+ </c:value-of>
+ </c:case>
+
+ <c:case label="Less than or equal to">
+ <t:when-eq name="op" value="CMP_OP_LTE" />
+
+ <c:value-of name="TRUE">
+ <t:when-lte name="cur" value="val" />
+ </c:value-of>
+ </c:case>
+
+ <c:case label="Greater than">
+ <t:when-eq name="op" value="CMP_OP_GT" />
+
+ <c:value-of name="TRUE">
+ <t:when-gt name="cur" value="val" />
+ </c:value-of>
+ </c:case>
+
+ <c:case label="Greater than or equal to">
+ <t:when-eq name="op" value="CMP_OP_GTE" />
+
+ <c:value-of name="TRUE">
+ <t:when-gte name="cur" value="val" />
+ </c:value-of>
+ </c:case>
+ </c:cases>
+ </c:value>
+ </c:values>
+
+ <!-- continue recursion using TCO so that we do not
+ exhaust the stack (this is an undocumented,
+ experimental feature that requires explicitly stating
+ that a recursive call is in tail position) -->
+ <c:recurse __experimental_guided_tco="TRUE">
+ <c:arg name="accum">
+ <c:cases>
+ <!-- If match, add the current row index to the
+ accumulator (cons, so note that it is added
+ in reverse) -->
+ <c:case>
+ <t:when-eq name="found" value="TRUE" />
+
+ <c:cons>
+ <c:value-of name="start" />
+ <c:value-of name="accum" />
+ </c:cons>
+ </c:case>
+
+ <!-- If no match, no change to accumulator -->
+ <c:otherwise>
+ <c:value-of name="accum" />
+ </c:otherwise>
+ </c:cases>
+ </c:arg>
+
+ <c:arg name="start">
+ <c:sum>
+ <c:value-of name="start" />
+ <c:const value="1" desc="Check next element" />
+ </c:sum>
+ </c:arg>
+ </c:recurse>
+ </c:let>
+ </c:let>
</c:otherwise>
</c:cases>
</c:let>
@@ -319,108 +502,6 @@
</function>
- <!-- mutually recursive with _mrange -->
- <function name="_mrange_cmp" desc="mrange helper for value comparison">
- <param name="matrix" type="float" set="matrix" desc="Matrix to filter" />
- <param name="col" type="integer" desc="Column index to filter on" />
- <param name="val" type="float" desc="Column value to filter on" />
- <param name="start" type="integer" desc="Starting index (aka current index)" />
- <param name="end" type="integer" desc="Ending index" />
- <param name="seq" type="integer" desc="Is data sequential?" />
- <param name="op" type="integer" desc="Comparison operator" />
- <param name="cur" type="float" desc="Current value" />
-
-
- <c:let>
- <c:values>
- <c:value name="found" type="boolean"
- desc="Whether comparison matches">
- <c:cases>
- <c:case label="Equal">
- <t:when-eq name="op" value="CMP_OP_EQ" />
-
- <c:value-of name="TRUE">
- <t:when-eq name="cur" value="val" />
- </c:value-of>
- </c:case>
-
- <c:case label="Less than">
- <t:when-eq name="op" value="CMP_OP_LT" />
-
- <c:value-of name="TRUE">
- <t:when-lt name="cur" value="val" />
- </c:value-of>
- </c:case>
-
- <c:case label="Less than or equal to">
- <t:when-eq name="op" value="CMP_OP_LTE" />
-
- <c:value-of name="TRUE">
- <t:when-lte name="cur" value="val" />
- </c:value-of>
- </c:case>
-
- <c:case label="Greater than">
- <t:when-eq name="op" value="CMP_OP_GT" />
-
- <c:value-of name="TRUE">
- <t:when-gt name="cur" value="val" />
- </c:value-of>
- </c:case>
-
- <c:case label="Greater than or equal to">
- <t:when-eq name="op" value="CMP_OP_GTE" />
-
- <c:value-of name="TRUE">
- <t:when-gte name="cur" value="val" />
- </c:value-of>
- </c:case>
- </c:cases>
- </c:value>
- </c:values>
-
- <c:cases>
- <!-- if values matches, cons it -->
- <c:case>
- <t:when-eq name="found" value="TRUE" />
-
- <c:cons>
- <c:value-of name="matrix">
- <c:index>
- <c:value-of name="start" />
- </c:index>
- </c:value-of>
-
- <c:apply name="mrange" matrix="matrix" col="col" val="val"
- end="end" seq="seq" op="op">
- <c:arg name="start">
- <c:sum>
- <c:value-of name="start" />
- <c:const value="1" desc="Check next element" />
- </c:sum>
- </c:arg>
- </c:apply>
- </c:cons>
- </c:case>
-
-
- <!-- no match, continue (mutual) recursion -->
- <c:otherwise>
- <c:apply name="mrange" matrix="matrix" col="col" val="val"
- end="end" seq="seq" op="op">
- <c:arg name="start">
- <c:sum>
- <c:value-of name="start" />
- <c:const value="1" desc="Check next element" />
- </c:sum>
- </c:arg>
- </c:apply>
- </c:otherwise>
- </c:cases>
- </c:let>
- </function>
-
-
<section title="Bisecting">
Perform an~$O(lg n)$ bisect on a data set.
diff --git a/design/tpl/.gitignore b/design/tpl/.gitignore
new file mode 100644
index 0000000..5bca4bd
--- /dev/null
+++ b/design/tpl/.gitignore
@@ -0,0 +1,34 @@
+# Ignored files for The TAME Programming Language Git repository
+
+# Targets
+*.pdf
+*.dvi
+
+# (La)TeX
+*.aux
+*.fls
+*.log
+*.out
+*.toc
+
+# BibLaTeX
+*.bbl
+*.bcf
+*.blg
+*.run.xml
+
+# Index
+*.idx
+*.ilg
+*.ind
+
+# latexmk
+*.fdb_latexmk
+
+# configure
+conf.tex
+aclocal.m4
+autom4te.cache
+config.status
+configure
+
diff --git a/design/tpl/.latexmkrc b/design/tpl/.latexmkrc
new file mode 100644
index 0000000..287faec
--- /dev/null
+++ b/design/tpl/.latexmkrc
@@ -0,0 +1,12 @@
+
+add_cus_dep('glo', 'gls', 0, 'run_makeglossaries');
+add_cus_dep('acn', 'acr', 0, 'run_makeglossaries');
+
+sub run_makeglossaries {
+ if ( $silent ) {
+ system "makeglossaries -q '$_[0]'";
+ }
+ else {
+ system "makeglossaries '$_[0]'";
+ };
+}
diff --git a/design/tpl/Makefile b/design/tpl/Makefile
new file mode 100644
index 0000000..a7b570f
--- /dev/null
+++ b/design/tpl/Makefile
@@ -0,0 +1,15 @@
+
+inputs := tpl.tex tpl.sty tpl.bib \
+ $(wildcard sec/*.tex)
+
+
+.DEFAULT: pdf
+
+.PHONY: pdf
+pdf: tpl.pdf
+tpl.pdf: tpl.tex $(inputs)
+ latexmk --pdf $<
+
+clean:
+ latexmk -c
+ rm -f tpl.pdf
diff --git a/design/tpl/README.md b/design/tpl/README.md
new file mode 100644
index 0000000..bef80d8
--- /dev/null
+++ b/design/tpl/README.md
@@ -0,0 +1,19 @@
+# The TAME Programming Language: Design and Implementation
+
+This is a living document providing a formal definition of the TAME
+programming language.
+
+## Dependencies
+See [`tpl.sty`](tpl.sty) for the specific LaTeX packages that are
+needed. If you use a Debian-based system, the following command should be
+sufficient to install all necessary dependencies:
+
+```
+$ apt install --no-recommends \
+ make latexmk biber \
+ texlive-latex-extra texlive-fonts-extra texlive-bibtex-extra \
+ texlive-science
+```
+
+## Building
+Simply run `make`. The output is `tpl.pdf`.
diff --git a/design/tpl/autogen.sh b/design/tpl/autogen.sh
new file mode 100755
index 0000000..a42088b
--- /dev/null
+++ b/design/tpl/autogen.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+# Autoreconf runner
+
+which autoreconf &>/dev/null || {
+ echo "\`autoreconf' not found in PATH"
+ exit 1
+}
+
+exec autoreconf -fvi
+
diff --git a/design/tpl/conf.tex.in b/design/tpl/conf.tex.in
new file mode 100644
index 0000000..02d757b
--- /dev/null
+++ b/design/tpl/conf.tex.in
@@ -0,0 +1,5 @@
+% TAME Programming Language configuration
+%
+% @AUTOGENERATED@
+
+\tplappendix@ENABLE_APPENDIX@
diff --git a/design/tpl/configure.ac b/design/tpl/configure.ac
new file mode 100644
index 0000000..9334c32
--- /dev/null
+++ b/design/tpl/configure.ac
@@ -0,0 +1,38 @@
+# Autoconf configuration
+#
+# Copyright (C) 2021 Ryan Specialty Group, LLC
+#
+# 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 Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+##
+
+AC_INIT([tpl], [0.0.0], [])
+
+# TODO: Check for pdflatex and required packages
+
+use_appendix=false
+AC_ARG_ENABLE(
+ [appendix],
+ [AS_HELP_STRING([--enable-appendix],
+ [enable appendix (default no)])],
+ [test "x$enableval" == xyes && use_appendix=true])
+
+AC_SUBST([ENABLE_APPENDIX], [$use_appendix])
+
+AC_SUBST([AUTOGENERATED],
+ ["THIS FILE IS AUTOGENERATED! DO NOT MODIFY! See *.in."])
+
+# generate files from their *.in counterparts
+AC_CONFIG_FILES([conf.tex])
+AC_OUTPUT
+
diff --git a/design/tpl/sec/appendix-typesetting.tex b/design/tpl/sec/appendix-typesetting.tex
new file mode 100644
index 0000000..aba7bab
--- /dev/null
+++ b/design/tpl/sec/appendix-typesetting.tex
@@ -0,0 +1,92 @@
+
+\section{Meta: Typesetting}
+This appendix is a meta-document describing typographic considerations for
+ this document.
+It is intended to be included in debug/developer builds,
+ not to be included as official documentation for the software.
+
+\subsection{$\Classify$}
+\index{classification!\ensuremath{\Classify} design}
+The symbol representing classification is defined in \secref{class}.
+It uses the capital Fraktur letter~`C',
+ typeset as~$\Classify\!\!$.
+Compare this side-by-side with the summation operator Sigma:
+
+\def\EXSUM{\sum_k^n k}
+\def\EXCLASS{\Classify^\texttt{as}_\texttt{yields} P}
+
+\begin{equation*}
+ \EXSUM \mspace{50mu} \EXCLASS.
+\end{equation*}
+
+These are both written inline, respectively, as
+ $\EXSUM$ and $\EXCLASS$.
+
+Classifications are canonically referred to by their \xmlattr{as}~name,
+ which makes for a bit of an awkward-looking construction when the
+ superscript is provided but not the subscript.
+For example, consider the classification $\Classify^\texttt{foo-bar}$ inline,
+ compared to~$\Classify_\texttt{foo-bar}$.
+Now compare the display style
+
+\begin{equation*}
+ \Classify^\texttt{foo-bar}
+ \mspace{25mu}\text{vs.}\mspace{25mu}
+ \Classify_\texttt{foo-bar}.
+\end{equation*}
+
+Of course,
+ referring to the \xmlattr{yields}~name will use the subscript,
+ which (at least to the author) feels more natural.
+Why not swap them, then?
+
+The superscript always denotes a scalar Boolean value.
+The subscript,
+ however,
+ has a more complex type that's dependent on the predicates of the classification.
+Let's say we wanted to denote a classification with a dimensionality of~$2$:
+ $\Classify^\texttt{foo-bar}_{\texttt{fooBar}^2}$ versus
+ $\Classify_\texttt{foo-bar}^{\texttt{fooBar}^2}$,
+ typeset in display style as
+
+\begin{equation*}
+ \Classify^\texttt{foo-bar}_{\texttt{fooBar}^2}
+ \mspace{25mu}\text{vs.}\mspace{25mu}
+ \Classify_\texttt{foo-bar}^{\texttt{fooBar}^2}.
+\end{equation*}
+
+The amount of vertical space taken up by the first style is unchanged by the
+ superscript on the subscript,
+ but that's not true of the second style.
+
+The final consideration is that the subscript of the summation,
+ when the superscript is omitted,
+ denotes the range or set of values for the sum.
+For example,
+ one may have $\sum_{0\leq k \leq n}$ or
+ $\sum_{a\in A}$.
+Having the subscript of $\Classify$ represent the more complex set of values
+ is therefore more analogous to the sum,
+ and better fits readers' intuitive notational expectations.
+
+It is also worth noting that the distinction between the two is historical---%
+ \xmlattr{as} used to represent an accumulator,
+ which is a long-removed feature;
+ references to~\xmlattr{as} in \tame{} today end up resolving
+ to~\xmlattr{yields} anyway.
+If that name is repurposed,
+ one potential option is to have it take the place of~\xmlattr{yields},
+ in which case the superscript in~$\Classify$ goes away and the
+ notational awkwardness is removed.
+
+A final note on the choice of character:
+Admittedly, $\Classify$ does look a bit threatening,
+ but one could also interpret it as ``bold and distinguished''.
+$\mathcal{C}$ was considered,
+ but it looks too childish (and perhaps Comic Sans-like) when typeset large:
+
+\begin{equation*}
+ \displaystyle\mathop{\hbox{\huge$\mathcal{C}$}}^\texttt{rejected}_\texttt{style}.
+\end{equation*}
+
+It's easy enough to change in the future if we change our minds.
diff --git a/design/tpl/sec/class.tex b/design/tpl/sec/class.tex
new file mode 100644
index 0000000..8ce62fa
--- /dev/null
+++ b/design/tpl/sec/class.tex
@@ -0,0 +1,784 @@
+% The TAME Programming Language Classification System
+%
+% Copyright (C) 2021 Ryan Specialty Group, LLC.
+%
+% Licensed under the Creative Commons Attribution-ShareAlike 4.0
+% International License.
+%%
+
+\section{Classification System}\seclabel{class}
+\index{classification|textbf}
+A \dfn{classification} is a user-defined abstraction that describes
+ (``classifies'') arbitrary data.
+Classifications can be used as predicates, generating functions, and can be
+ composed into more complex classifications.
+Nearly all conditions in \tame{} are specified using classifications.
+
+\index{first-order logic!sentence}
+\index{classification!coupling}
+All classifications represent \dfn{first-order sentences}---%
+ that is,
+ they contain no \dfn{free variables}.
+Intuitively,
+ this means that all variables within a~classification are
+ \dfn{tightly coupled} to the classification itself.
+This limitation is mitigated through use of the template system.
+
+\begin{axiom}[Classification Introduction]\axmlabel{class-intro}
+\indexsym\Classify{classification}
+\indexsym\gamma{classification, yield}
+\index{classification!index set}
+\index{index set!classification}
+\index{classification!classify@\xmlnode{classify}}
+\index{classification!as@\xmlattr{as}}
+\index{classification!yields@\xmlattr{yields}}
+\todo{Symbol in place of $=$ here ($\equiv$ not appropriate).}
+\begin{subequations}
+\begin{gather}
+\begin{alignedat}{3}
+ &\xml{<classify as="$c$" }&&\xml{yields="$\gamma$" desc}&&\xml{="$\_$"
+ $\alpha$>}\label{eq:xml-classify} \\
+ &\quad \MFam{M^0}jJkK &&\VFam{v^0}jJ &&\quad s^0 \\[-4mm]
+ &\quad \quad\vdots &&\quad\vdots &&\quad \vdots \\
+ &\quad \MFam{M^l}jJkK &&\VFam{v^m}jJ &&\quad s^n \\[-3mm]
+ &\xml{</classify>}
+ % NB: This -50mu needs adjustment if you change the alignment above!
+ &&\mspace{-50mu}= \Classify^c_\gamma\left(\odot,M,v,s\right),
+\end{alignedat}
+\end{gather}
+
+\noindent
+where
+
+\indexsym\emptystr{empty string}
+\index{empty string (\ensuremath\emptystr)}
+\begin{align}
+ J &\subset\Int \neq\emptyset, \\
+ \forall{j\in J}\Big(K_j &\subset\Int \neq\emptyset\Big), \\
+ \forall{k}\Big(M^k &: J \rightarrow K_{j\in J} \rightarrow \Bool\Big),
+ \label{eq:class-matrix} \\
+ \forall{k}\Big(v^k &: J \rightarrow \Bool\Big), \\
+ \forall{k}\Big(s^k &\in\Bool\Big), \\
+ \alpha &\in\Set{\emptystr,\, \texttt{any="true"}}, \label{eq:xml-any-domain}
+\end{align}
+
+\noindent
+and the monoid~$\odot$ is defined as
+
+\indexsym\odot{classification, monoid}
+\index{classification!any@\xmlattr{any}}
+\index{classification!monoid|(}
+\begin{equation}\label{eq:classify-rel}
+ \odot = \begin{cases}
+ \Monoid\Bool\land\true &\alpha = \emptystr,\\
+ \Monoid\Bool\lor\false &\alpha = \texttt{any="true"}.
+ \end{cases}
+\end{equation}
+\end{subequations}
+\end{axiom}
+
+
+% This TODO was the initial motivation for this paper!
+\todo{Emphasize index sets, both relationships and nonempty.}
+We use a $4$-tuple $\Classify\left(\odot,M,v,s\right)$ to represent a
+ $\odot_1$-classification
+ (a classification with the binary operation $\land$ or~$\lor$)
+ consisting of a combination of matrix~($M$), vector~($v$), and
+ scalar~($s$) matches,
+ rendered above in columns.\footnote{%
+ The symbol~$\odot$ was chosen since the binary operation for a monoid
+ is~$\monoidop$
+ (see \secref{monoids})
+ and~$\odot$ looks vaguely like~$(\monoidop)$,
+ representing a portion of the monoid triple.}
+A $\land$-classification is pronounced ``conjunctive classification'',
+ and $\lor$ ``disjunctive''.\footnote{%
+ \index{classification!terminology history}
+ Conjunctive and disjunctive classifications used to be referred to,
+ respectively,
+ as \dfn{universal} and \dfn{existential},
+ referring to fact that
+ $\forall\Set{a_0,\ldots,a_n}(a) \equiv a_0\land\ldots\land a_n$,
+ and similarly for $\exists$.
+ This terminology has changed since all classifications are in fact
+ existential over their matches' index sets,
+ and so the terminology would otherwise lead to confusion.}
+
+The variables~$c$ and~$\gamma$ are required in~\tame{} but are both optional
+ in our notation~$\Classify^c_\gamma$,
+ and can be used to identify the two different data representations of
+ the classification.\footnote{%
+ \xpath{classify/@yields} is optional in the grammar of \tame{},
+ but the compiler will generate one for us if one is not provided.
+ As such,
+ we will for simplicity consider it to be required here.}
+
+$\alpha$~serves as a placeholder for an optional \xml{any="true"},
+ with $\emptystr$~representing the empty string in~\eqref{eq:xml-any-domain}.
+Note the wildcard variable matching \xmlattr{desc}---%
+ its purpose is only to provide documentation.
+
+\begin{corollary}[$\odot$ Commutative Monoid]\corlabel{odot-monoid}
+\index{classification!commutativity|(}
+ $\odot$ is a commutative monoid in \axmref{class-intro}.
+\end{corollary}
+\begin{proof}
+ By \axmref{class-intro},
+ $\odot$ must be a monoid.
+ Assume $\alpha=\emptystr$.
+ Then,
+ $\odot = \Monoid\Bool\land\true$,
+ which is proved by \lemref{monoid-land}.
+ Next, assume $\alpha=\texttt{any="true"}$.
+ Then,
+ $\odot = \Monoid\Bool\lor\false$,
+ which is proved by \lemref{monoid-land}.
+\end{proof}
+
+While \axmref{class-intro} seems to imply an ordering to matches,
+ users of the language are free to specify matches in any order
+ and the compiler will rearrange matches as it sees fit.
+\index{compiler!classification commutativity}
+This is due to the commutativity of~$\odot$ as proved by
+ \corref{odot-monoid},
+ and not only affords great ease of use to users of~\tame{},
+ but also great flexibility to compiler writers.
+\index{classification!commutativity|)}
+
+For notational convenience,
+ we will let
+
+\index{classification!monoid|)}
+\begin{equation}
+\begin{aligned}
+ \Classifyland(M,v,s)
+ &= \Classify\left(\Monoid\Bool\land\true,M,v,s\right), \\
+ \Classifylor(M,v,s)
+ &= \Classify\left(\Monoid\Bool\lor\true,M,v,s\right). \\
+\end{aligned}
+\end{equation}
+
+
+\def\cpredmatseq{{M^0_j}_k \monoidops {M^l_j}_k}
+\def\cpredvecseq{v^0_j\monoidops v^m_j}
+\def\cpredscalarseq{s^0\monoidops s^n}
+
+
+\begin{axiom}[Classification-Predicate Equivalence]\axmlabel{class-pred}
+\index{classification!as predicate}
+ Let $\Classify^c_\gamma\left(\Monoid\Bool\monoidop e,M,v,s\right)$ be a
+ classification by~\axmref{class-intro}.
+ We then have the first-order sentence
+ \begin{equation*}
+ c \equiv
+ {} \Exists{j\in J}{\Exists{k\in K_j}\cpredmatseq\monoidop\cpredvecseq}
+ \monoidop\cpredscalarseq.
+ \end{equation*}
+\end{axiom}
+
+
+\begin{axiom}[Classification Yield]\axmlabel{class-yield}
+\indexsym\Gamma{classification, yield}
+\index{classification!yield (\ensuremath\gamma, \ensuremath\Gamma)}
+ Let $\Classify^c_\gamma\left(\Monoid\Bool\monoidop e,M,v,s\right)$ be a
+ classification by~\axmref{class-intro}.
+ Then,
+
+ \begin{subequations}
+ \begin{align}
+ r &= \begin{cases}
+ 2 &M\neq\emptyset, \\
+ 1 &M=\emptyset \land v\neq\emptyset, \\
+ 0 &M\union v = \emptyset,
+ \end{cases} \\
+ \displaybreak[0]
+ \exists{j\in J}\Big(\exists{k\in K_j}\Big(
+ \Gamma^2_{j_k} &= \cpredmatseq\monoidop\cpredvecseq\monoidop\cpredscalarseq
+ \Big)\Big), \\
+ %
+ \exists{j\in J}\Big(
+ \Gamma^1_j &= \cpredvecseq\monoidop\cpredscalarseq
+ \Big), \\
+ %
+ \Gamma^0 &= \cpredscalarseq. \\
+ %
+ \gamma &= \Gamma^r.
+ \end{align}
+ \end{subequations}
+\end{axiom}
+
+\begin{theorem}[Classification Composition]\thmlabel{class-compose}
+\index{classification!composition|(}
+ Classifications may be composed to create more complex classifications
+ using the classification yield~$\gamma$ as in~\axmref{class-yield}.
+ This interpretation is equivalent to \axmref{class-pred} by
+ \begin{equation}
+ c \equiv \Exists{j\in J}{
+ \Exists{k\in K_j}{\Gamma^2_{j_k}}
+ \monoidop \Gamma^1_j
+ }
+ \monoidop \Gamma^0.
+ \end{equation}
+\end{theorem}
+
+\def\eejJ{\equiv \exists{j\in J}\Big(}
+
+\begin{proof}
+ Expanding each~$\Gamma$ in \axmref{class-yield},
+ we have
+
+ \begin{alignat*}{3}
+ c &\eejJ\Exists{k\in K_j}{\Gamma^2_{j_k}}
+ \monoidop \Gamma^1_j
+ \Big)
+ \monoidop \Gamma^0
+ &&\text{by \axmref{class-yield}} \\
+ %
+ &\eejJ\exists{k\in K_j}\Big(
+ \cpredmatseq \monoidop \cpredvecseq \monoidop \cpredscalarseq
+ \Big) \\
+ &\hphantom{\eejJ}\;\cpredvecseq \monoidop \cpredscalarseq \Big)
+ \monoidop \cpredscalarseq, \\
+ %
+ &\eejJ\exists{k\in K_j}\Big(\cpredmatseq\Big)
+ \monoidop \cpredvecseq \monoidop \cpredscalarseq \\
+ &\hphantom{\eejJ}\;\cpredvecseq \monoidop \cpredscalarseq \Big)
+ \monoidop \cpredscalarseq,
+ &&\text{by \dfnref{quant-conn}} \\
+ %
+ &\eejJ\exists{k\in K_j}\Big(\cpredmatseq\Big)
+ &&\text{by \dfnref{prop-taut}} \\
+ &\hphantom{\eejJ}\;\cpredvecseq \monoidop \cpredscalarseq \Big)
+ \monoidop \cpredscalarseq, \\
+ %
+ &\eejJ\exists{k\in K_j}\Big(\cpredmatseq\Big)
+ &&\text{by \dfnref{quant-conn}} \\
+ &\hphantom{\eejJ}\;\cpredvecseq\Big) \monoidop \cpredscalarseq
+ \monoidop \cpredscalarseq, \\
+ %
+ &\eejJ\exists{k\in K_j}\Big(\cpredmatseq\Big)
+ &&\text{by \dfnref{prop-taut}} \\
+ &\hphantom{\eejJ}\;\cpredvecseq\Big)
+ \monoidop \cpredscalarseq.
+ \tag*{\qedhere} \\
+ \end{alignat*}
+\end{proof}
+\index{classification!composition|)}
+
+
+\begin{lemma}[Classification Predicate Vacuity]\lemlabel{class-pred-vacu}
+\index{classification!vacuity|(}
+ Let $\Classify^c_\gamma\left(\Monoid\Bool\monoidop e,\emptyset,\emptyset,\emptyset\right)$
+ be a classification by~\axmref{class-intro}.
+ $\odot$ is a monoid by \corref{odot-monoid}.
+ Then $c \equiv \gamma \equiv e$.
+\end{lemma}
+\begin{proof}
+ First consider $c$.
+ \begin{alignat*}{3}
+ c &\equiv \Exists{j\in J}{\Exists{k}{e}\monoidop e} \monoidop e
+ \qquad&&\text{by \dfnref{monoid-seq}} \label{p:cri-c} \\
+ &\equiv \Exists{j\in J}{e \monoidop e} \monoidop e
+ &&\text{by \dfnref{quant-elim}} \\
+ &\equiv \Exists{j\in J}{e} \monoidop e
+ &&\text{by \ref{eq:monoid-identity}} \\
+ &\equiv e \monoidop e
+ &&\text{by \dfnref{quant-elim}} \\
+ &\equiv e.
+ &&\text{by \ref{eq:monoid-identity}}
+ \end{alignat*}
+
+ For $\gamma$,
+ we have $r=0$ by \axmref{class-yield},
+ and so by similar steps as~$c$,
+ $\gamma=\Gamma^r=e$.
+ Therefore $c\equiv e$.
+\end{proof}
+
+
+\begin{figure}[ht]
+ \begin{alignat*}{3}
+ \begin{aligned}
+ \xml{<classify }&\xml{as="always" yields="alwaysTrue"} \xmlnl
+ &\xml{desc="Always true" />}
+ \end{aligned}
+ \quad&=\quad
+ \Classifyland^\texttt{always}_\texttt{alwaysTrue}
+ &&\left(\emptyset,\emptyset,\emptyset\right). \\
+ %
+ \begin{aligned}
+ \xml{<classify }&\xml{as="never" yields="neverTrue"} \xmlnl
+ &\xml{any="true"} \xmlnl
+ &\xml{desc="Never true" />}
+ \end{aligned}
+ \quad&=\quad
+ \Classifylor^\texttt{never}_\texttt{neverTrue}
+ &&\left(\emptyset,\emptyset,\emptyset\right).
+ \end{alignat*}
+ \caption{\tameclass{always} and \tameclass{never} from package
+ \tamepkg{core/base}.}
+ \label{fig:always-never}
+\end{figure}
+
+\spref{fig:always-never} demonstrates \lemref{class-pred-vacu} in the
+ definitions of the classifications \tameclass{always} and
+ \tameclass{never}.
+These classifications are typically referenced directly for clarity rather
+ than creating other vacuous classifications,
+ encapsulating \lemref{class-pred-vacu}.
+\index{classification!vacuity|)}
+
+
+\begin{theorem}[Classification Rank Independence]\thmlabel{class-rank-indep}
+\index{classification!rank|(}
+ Let $\odot=\Monoid\Bool\monoidop e$.
+ Then,
+ \begin{equation}
+ \Classify_\gamma\left(\odot,M,v,s\right)
+ \equiv \Classify\left(
+ \odot,
+ \Classify_{\gamma'''}\left(\odot,M,\emptyset,\emptyset\right),
+ \Classify_{\gamma''}\left(\odot,\emptyset,v,\emptyset\right),
+ \Classify_{\gamma'}\left(\odot,\emptyset,\emptyset,s\right)
+ \right).
+ \end{equation}
+\end{theorem}
+
+\begin{proof}
+ First,
+ by \axmref{class-yield},
+ observe these special cases following from \lemref{class-pred-vacu}:
+ \begin{equation}
+ \begin{alignedat}{3}
+ \Gamma'''^2 &= \cpredmatseq, \qquad&&\text{assuming $v\union s=\emptyset$} \\
+ \Gamma''^1 &= \cpredvecseq, &&\text{assuming $M\union s=\emptyset$} \\
+ \Gamma'^0 &= \cpredscalarseq. &&\text{assuming $M\union v=\emptyset$}
+ \end{alignedat}
+ \end{equation}
+
+ By \thmref{class-compose},
+ we must prove
+ \begin{multline}\label{eq:rank-indep-goal}
+ \Exists{j\in J}{
+ \Exists{k\in K_j}{\cpredmatseq}
+ \monoidop \cpredvecseq
+ }
+ \monoidop \cpredscalarseq \\
+ \equiv c \equiv
+ \Exists{j\in J}{
+ \Exists{k\in K_j}{\gamma'''_{j_k}}
+ \monoidop \gamma''_j
+ }
+ \monoidop \gamma'.
+ \end{multline}
+
+ By \axmref{class-yield},
+ we have $r'''=2$, $r''=1$, and $r'=0$,
+ and so $\gamma'''=\Gamma'''^2$,
+ $\gamma''=\Gamma''^1$,
+ and $\gamma'=\Gamma'^0$.
+ By substituting these values in~\ref{eq:rank-indep-goal},
+ the theorem is proved.
+\end{proof}
+\index{classification!rank|)}
+
+These definitions may also be used as a form of pattern matching to look up
+ a corresponding variable.
+For example,
+ if we have $\Classify^\texttt{foo}$ and want to know its \xmlattr{yields},
+ we can write~$\Classify^\texttt{foo}_\gamma$ to bind the
+ \xmlattr{yields} to~$\gamma$.\footnote{%
+ This is conceptually like a symbol table lookup in the compiler.}
+
+
+
+\subsection{Matches}
+A classification consists of a set of binary predicates called
+ \emph{matches}.
+Matches may reference any values,
+ including the results of other classifications
+ (as in \thmpref{class-compose}),
+ allowing for the construction of complex abstractions over the data being
+ classified.
+
+Matches are intended to act intuitively across inputs of different ranks---%
+ that is,
+ one can match on any combination of matrix, vector, and scalar values.
+
+\begin{axiom}[Match Input Translation]\axmlabel{match-input}
+ Let $j$ and $k$ be free variables intended to be bound in the
+ context of \axmref{class-pred}.
+ Let $J$ and $K$ be defined by \axmref{class-intro}.
+ Given some input~$x$,
+
+ \begin{equation*}
+ \varsub x =
+ \begin{cases}
+ x_{j_k} &\rank{x} = 2; \\
+ x_j &\rank{x} = 1; \\
+ x &\rank{x} = 0,
+ \end{cases}
+ \qquad\qquad
+ \begin{aligned}
+ j&\in J, \\
+ k&\in K_j.
+ \end{aligned}
+ \end{equation*}
+\end{axiom}
+
+\begin{axiom}[Match Rank]\axmlabel{match-rank}
+ Let~$\sim{} : \Real\times\Real\rightarrow\Real$ be some binary relation.
+ Then,
+
+ \begin{equation*}
+ \rank{\varsub x \sim \varsub y} =
+ \begin{cases}
+ \rank{x} &\rank{x} \geq \rank{y}, \\
+ \rank{y} &\text{otherwise}.
+ \end{cases}
+ \end{equation*}
+\end{axiom}
+
+\def\xyequivish{\varsub x\equivish \varsub y}
+
+\begin{axiom}[Element-Wise Equivalence ($\equivish$)]
+\indexsym\equivish{equivalence, element-wise}
+\index{equivalence!element-wise (\ensuremath\equivish)}
+ \begin{align*}
+ \rank{\varsub x}=\rank{\varsub y}=2,\,
+ (\xyequivish) &\infer \Forall{j,k}{x_{j_k} \equiv y_{j_k}}, \\
+ \rank{\varsub x}=\rank{\varsub y}=1,\,
+ (\xyequivish) &\infer \Forall{j}{x_j \equiv y_j}, \\
+ \rank{\varsub x}=\rank{\varsub y}=0,\,
+ (\xyequivish) &\infer (x\equiv y).
+ \end{align*}
+\end{axiom}
+
+
+\index{package!core/match@\tamepkg{core/match}}
+Matches are represented by \xmlnode{match} nodes in \tame{}.
+Since the primitive is rather verbose,
+ \tamepkg{core/match} also defines templates providing a more concise
+ notation
+ (\xmlnode{t:match-$\zeta$} below).
+
+\index{classification!match@\xmlnode{match}}
+\begin{axiom}[Match Introduction]\axmlabel{match-intro}
+ \begin{alignat*}{2}
+ \begin{aligned}[b]
+ \xml{<t:match-$\zeta$ }&\xml{on="$x$"} \xmlnll
+ &\xml{value="$y$" />}
+ \end{aligned}
+ {}&\equivish{}
+ \begin{aligned}
+ &\xml{<match on="$x$">} \xmlnll
+ &\quad \xml{<c:$\zeta$>} \xmlnll
+ &\quad\quad \xml{<c:value-of name="$y$">} \xmlnll
+ &\quad \xml{</c:$\zeta$>} \xmlnll
+ &\xml{</match>}
+ \end{aligned}
+ \qquad
+ \sim{} = \smash{\begin{cases}
+ = &\zeta=\xml{eq}, \\
+ < &\zeta=\xml{lt}, \\
+ > &\zeta=\xml{gt}, \\
+ \leq &\zeta=\xml{leq}, \\
+ \geq &\zeta=\xml{geq}.
+ \end{cases}} \\
+ &\equivish \varsub x \sim \varsub y,
+ \end{alignat*}
+\end{axiom}
+
+\begin{axiom}[Match Equality Short Form]
+ \begin{equation*}
+ \xml{<match on="$x$" />}
+ \equivish \xml{<match on="$x$" value="TRUE" />}.
+ \end{equation*}
+\end{axiom}
+
+\todo{Define types and \xml{typedef}.}
+\begin{axiom}[Match Membership]
+ When $T$ is a type defined with \xmlnode{typedef},
+ \begin{equation*}
+ \xml{<match on="$x$" anyOf="$T$" />} \equivish \varsub x \in T.
+ \end{equation*}
+\end{axiom}
+
+\begin{theorem}[Classification Match Element-Wise Binary Relation]
+\thmlabel{class-match}
+ Within the context of \axmref{class-pred},
+ all \xmlnode{match} forms represent binary relations
+ $\Real\times\Real\rightarrow\Bool$
+ ranging over individual elements of all index sets $J$ and $K_j\in K$.
+\end{theorem}
+\begin{proof}
+ First,
+ observe that each of $=$, $<$, $>$, $\leq$, $\geq$, and $\in$
+ have type $\Real\times\Real\rightarrow\Bool$.
+ We must then prove that $\varsub x$ and $\varsub y$ are able to be
+ interpreted as~$\Real$ within the context of \axmref{class-pred}.
+
+ When $x,y\in\Real$,
+ we have the trivial case $\varsub x=x\in\Real$ and $\varsub y=y\in\Real$
+ by \axmref{match-input}.
+ Otherwise,
+ variables $j$ and $k$ are free.
+
+ Consider $\rank{\varsub x \sim \varsub y} = 2$;
+ then $\rank{\varsub x \sim \varsub y} \in\Matrices$ by \dfnref{rank},
+ and so by \thmref{class-rank-indep} we have
+ \begin{equation}\label{p:match-rel}
+ \Forall{j\in J}{\Forall{k\in K_j}{\cpredmatseq}}
+ \equiv
+ \Forall{j\in J}{\Forall{k\in K_j}{\varsub x \sim \varsub y}},
+ \end{equation}
+ which binds $j$ and $k$ to the variables of their respective quantifiers.
+ Proceed similarly for $\rank{\varsub x \sim \varsub y} = 1$ and observe that
+ $j$ becomes bound.
+
+ Assume $x\in\Matrices$;
+ then $x_{j_k}\in\Real$ by \dfnref{matrix}.
+ Assume $y\in\Vectors^\Real$;
+ then $y_j\in\Real$ by \dfnref{vec}.
+ Finally,
+ observe that $j$ ranges over $J$ in \ref{p:match-rel},
+ and $k$ over $K_j$.
+\end{proof}
+
+\thmref{class-match} is responsible for proving that matches range over each
+ individual index.
+More subtly,
+ it also shows that matches work with any combination of rank.
+\spref{f:ex:class-match-all-ranks} demonstrates a complete translation of
+ source \tame{}~XML using all ranks.
+
+\begin{figure}[ht]
+\begin{align}
+ &\begin{aligned}
+ &\xml{<classify as="fullrank" desc="Example of all ranks">} \xmlnl
+ &\quad\begin{aligned}
+ &\xml{<match on="$A$" value="$u$" />}
+ \quad&&\equivish \varsub A = \varsub u \\[-2mm]
+ &\xml{<match on="$A$" value="$t$" />}
+ \quad&&\equivish \varsub A = \varsub t \xmlnll
+ &\xml{<match on="$u$" value="$t$" />}
+ \quad&&\equivish \varsub u = \varsub t \xmlnll
+ &\xml{<match on="$t$" />}
+ \quad&&\equivish \varsub t = \true
+ \end{aligned} \xmlnll
+ &\xml{</classify>}
+ \end{aligned}
+ &\text{by \axmref{match-intro}} \\
+ &= \Classifyland^\texttt{fullrank}\left(
+ \Big(\left({A_j}_k = u_j\right),
+ \left({A_j}_k = t \right)
+ \Big),
+ \left(u_j = t\right),
+ t
+ \right)
+ &\text{by \axmref{class-intro}} \\
+ &\equiv \Exists{j\in J}{
+ \Exists*{k\in K_j}{\Big(
+ \left({A_j}_k = u_j\right)
+ \land \left({A_j}_k = t \right)
+ \Big)}
+ \land u_j = t
+ }
+ \land t.
+ &\text{by \thmref{class-match}}.
+\end{align}
+\caption{Example demonstrating \thmref{class-match} using all ranks.}
+\label{f:ex:class-match-all-ranks}
+\end{figure}
+
+Visually,
+ the one-dimensional construction of \axmref{class-pred} does not lend
+ itself well to how intuitive the behavior of the system actually is.
+We therefore establish a relationship to the notation of linear algebra
+ to emphasize the relationship between each of the inputs.
+
+\newcommand\matseqsup[1]{%
+ \begin{bmatrix}
+ M^{#1}_{0_0} & \dots & M^{#1}_{0_k} \\
+ \vdots & \ddots & \vdots \\
+ M^{#1}_{j_0} & \dots & M^{#1}_{j_k} \\
+ \end{bmatrix}%
+}
+\newcommand\vecseqsup[1]{%
+ \begin{bmatrix}
+ v^{#1}_0 \\
+ \vdots \\
+ v^{#1}_j \\
+ \end{bmatrix}%
+}
+
+% This must be an axiom because it defines how the connectives operate; see
+% the remark.
+\index{classification!matrix notation}
+\begin{axiom}[Classification Matrix Notation]\axmlabel{class-mat-not}
+ Let $\Gamma^2$ be defined by \axmref{class-yield}.
+ Then,
+ \begin{equation*}
+ \Gamma^2 =
+ \matseqsup{0}\monoidops\matseqsup{l}
+ \monoidop
+ \vecseqsup{0}\monoidops\vecseqsup{m}
+ \monoidop
+ s^0\monoidops s^n,
+ \end{equation*}
+ from which $\Gamma^1$, $\Gamma^0$, and $\gamma$ can be derived.
+\end{axiom}
+
+\begin{remark}[Logical Connectives With Matrix Notation]
+ From the definition of \axmref{class-mat-not},
+ it should be clear that the logical connective $\monoidop$ necessarily
+ acts like a Hadamard product\cite{wp:hadamard-product} with respect to
+ how individual elements are combined.
+\end{remark}
+
+\index{classification!intuition}
+\axmref{class-mat-not} makes it easy to visualize classification
+ operations simply by drawing horizontal boxes across the predicates,
+ as demonstrated by \spref{f:class-mat-boxes}.
+This visualization helps to show intuitively how the classification system
+ is intended to function,
+ with matrices serving as higher-resolution vectors.\footnote{%
+ For example,
+ with insurance,
+ one may have a vector of data by risk location,
+ and a matrix of chosen class codes by location.
+ Consequently,
+ we expect $M_j$ to be the set of class codes associated with
+ location~$j$ so that it can be easily matched against location-level
+ data~$v_j$.}
+
+% NB: Give this formatting extra attention if the document's formatting is
+% substantially changed, since it's not exactly responsible with it's
+% hard-coded units.
+\begingroup
+\begin{figure}[ht]
+ \def\classmatraise#1{%
+ \begin{aligned}
+ #1 \\ {} \\ #1
+ \end{aligned}
+ }
+ \def\classmateq{%
+ \matseqsup{0}
+ \classmatraise{\monoidop\cdots\monoidop}
+ \matseqsup{l}
+ \classmatraise\monoidop
+ \vecseqsup{0}
+ \classmatraise{\monoidop\cdots\monoidop}
+ \vecseqsup{m}
+ \classmatraise{%
+ {}\monoidop s^0\monoidop\cdots\monoidop s^n%
+ }
+ }
+ \def\classmatlines#1{%
+ \begin{alignedat}{2}
+ \Big( &M^0_{{#1}_0} \monoidops {}&&M^l_{{#1}_0} \Big)
+ \monoidop
+ v^0_{#1} \monoidops v^m_{#1}
+ \monoidop
+ s^0 \monoidops s^n \\
+ &\quad\!\vdots &&\quad\!\vdots \\
+ \Big( &M^0_{{#1}_k} \monoidops {}&&M^l_{{#1}_k} \Big)
+ \monoidop
+ v^0_{#1} \monoidops v^m_{#1}
+ \monoidop
+ s^0 \monoidops s^n
+ \end{alignedat}
+ }
+
+ \begin{align*}
+ &\quad\raisebox{-11mm}[0mm]{%
+ \begin{turn}{45}
+ $\equiv$
+ \end{turn}%
+ }\; \classmatlines{0} &\Gamma^2_0 \\[-2mm]
+ &\fbox{\raisebox{0mm}[0mm][6mm]{\hphantom{$\classmateq$}}} \\[-8mm]
+ %
+ &\classmateq &\vdots\; \\[-10mm]
+ %
+ &\fbox{\raisebox{0mm}[0mm][6mm]{\hphantom{$\classmateq$}}} \\
+ &\quad\raisebox{11mm}[0mm]{%
+ \begin{turn}{-45}
+ $\equiv$
+ \end{turn}%
+ }\; \classmatlines{j} &\Gamma^2_j
+ \end{align*}
+\caption{Visual interpretation of classification by \axmref{class-mat-not}.
+ For each boxed row of the matrix notation there is an equivalence
+ to the first-order logic of \thmref{class-compose}.}
+\label{f:class-mat-boxes}
+\end{figure}
+\endgroup
+
+\index{classification!as proposition|(}
+\begin{lemma}[Match As Proposition]\lemlabel{match-prop}
+ Matches can be represented using propositional logic provided that
+ binary operators of \axmref{match-intro} are restricted to $\cbif\Bool$.
+\end{lemma}
+\begin{proof}
+ \begin{alignat*}{4}
+ x = \true &\equiv x, &&\quad= &&: \cbif\Bool; \\
+ x = \false &\equiv \neg x, &&\quad= &&: \cbif\Bool; \\
+ x < y &\equiv \neg x \land y, &&\quad< &&: \cbif\Bool; \\
+ x > y &\equiv x \land \neg y, &&\quad> &&: \cbif\Bool; \\
+ x \leq y &\equiv \neg x \lor y, &&\quad\leq &&: \cbif\Bool; \\
+ x \geq y &\equiv x \lor \neg y, &&\quad\geq &&: \cbif\Bool; \\
+ x \in\Bool &\equiv \true, &&\quad\in &&: \cbif\Bool.\tag*{\qedhere}
+ \end{alignat*}
+\end{proof}
+
+\begin{theorem}[Classification As Proposition]
+\index{classification!as proposition|(}
+ Classifications with either $M\union v=\emptyset$ or with constant index
+ sets can be represented by propositional logic provided that the domains
+ of the binary operators of \axmref{match-intro} are restricted to
+ $\cbif\Bool$.
+\end{theorem}
+\begin{proof}
+ Propositional logic does not include quantifiers or relations.
+ Matches of the domain $\cbif\Bool$ are proved to be propositions by
+ \lemref{match-prop}.
+ Having eliminated relations,
+ we must now eliminate quantifiers.
+
+ Assume $M\union v=\emptyset$.
+ By \thmref{class-rank-indep},
+
+ \begin{align*}
+ c &\equiv \cpredscalarseq,
+ \end{align*}
+
+ \noindent
+ which is a propositional formula.
+
+ Similarly,
+ if we define our index set~$J$ to be constant,
+ we are then able to eliminate existential quantification over~$J$
+ as follows:
+ \begin{equation}\label{eq:prop-vec}
+ \begin{aligned}
+ c &\equiv \Exists{j\in J}{\cpredvecseq}, \\
+ &\equiv \left(v^0_0\monoidops v^m_0\right)
+ \lor\cdots\lor
+ \left(v^0_{|J|-1}\monoidops v^m_{|J|-1}\right),
+ \end{aligned}
+ \end{equation}
+ which is a propositional formula.
+ Similarly,
+ for matrices,
+
+ \begin{align*}
+ c &\equiv \Exists{j\in J}{\Exists{k\in K_j}{\cpredmatseq}}, \nonumber\\
+ &\equiv \Exists{j\in J}{
+ \left({M^0_j}_0\monoidops{M^0_j}_{|K_j|-1}\right)
+ \lor\cdots\lor
+ \left({M^l_j}_0\monoidops{M^l_j}_{|K_j|-1}\right)
+ },
+ \end{align*}
+ and then proceed as in~\ref{eq:prop-vec}.
+\end{proof}
+\index{classification!as proposition|)} \ No newline at end of file
diff --git a/design/tpl/sec/notation.tex b/design/tpl/sec/notation.tex
new file mode 100644
index 0000000..d2dc874
--- /dev/null
+++ b/design/tpl/sec/notation.tex
@@ -0,0 +1,642 @@
+% The TAME Programming Language Notational Conventions
+%
+% Copyright (C) 2021 Ryan Specialty Group, LLC.
+%
+% Licensed under the Creative Commons Attribution-ShareAlike 4.0
+% International License.
+%%
+
+\section{Notational Conventions}
+This section provides a fairly terse overview of the foundational
+ mathematical concepts used in this paper.
+While we try to reason about \tame{} in terms of algebra,
+ first-order logic;
+ and set theory;
+ notation varies even within those branches.
+To avoid ambiguity,
+ especially while introducing our own notation,
+ core operators and concepts are explicitly defined below.
+
+This section begins its numbering at~0.
+This is not only a hint that \tame{} (and this paper) use 0-indexing,
+ but also because equations; definitions; theorems; corollaries; and the
+ like are all numbered relative to their section.
+When you see any of these prefixed with ``0.'',
+ this sets those references aside as foundational mathematical concepts
+ that are not part of the theory and operation of \tame{} itself.
+
+
+\subsection{Propositional Logic}
+\index{logic!propositional}
+We reproduce here certain axioms and corollaries of propositional logic for
+ convenience and to clarify our interpretation of certain concepts.
+The use of the symbols $\land$, $\lor$, and~$\neg$ are standard.
+\indexsym\infer{infer}
+\index{infer (\ensuremath\infer)}
+The symbol $\infer$ means ``infer''.
+We use $\implies$ in place of $\rightarrow$ for implication,
+ since the latter is used to denote the mapping of a domain to a codomain
+ in reference to functions.
+We further use $\equiv$ in place of $\leftrightarrow$ to represent material
+ equivalence.
+
+\indexsym\land{conjunction}
+\index{conjunction (\ensuremath{\land})}
+\begin{definition}[Logical Conjunction]
+ $p,q \infer (p\land q)$.
+\end{definition}
+
+\indexsym\lor{disjunction}
+\index{disjunction (\ensuremath{\lor})}
+\begin{definition}[Logical Disjunction]
+ $p \infer (p\lor q)$ and $q \infer (p\lor q)$.
+\end{definition}
+
+\begin{definition}[$\land$-Associativity]\dfnlabel{conj-assoc}
+ $(p \land (q \land r)) \infer ((p \land q) \land r)$.
+\end{definition}
+
+\begin{definition}[$\lor$-Associativity]\dfnlabel{disj-assoc}
+ $(p \lor (q \lor r)) \infer ((p \lor q) \lor r)$.
+\end{definition}
+
+\begin{definition}[$\land$-Commutativity]\dfnlabel{conj-commut}
+ $(p \land q) \infer (q \land p)$.
+\end{definition}
+
+\begin{definition}[$\lor$-Commutativity]\dfnlabel{disj-commut}
+ $(p \lor q) \infer (q \lor p)$.
+\end{definition}
+
+\begin{definition}[$\land$-Simplification]\dfnlabel{conj-simpl}
+ $p \land q \infer p$.
+\end{definition}
+
+\begin{definition}[Double Negation]\dfnlabel{double-neg}
+ $\neg\neg p \infer p$.
+\end{definition}
+
+\indexsym\neg{negation}
+\index{negation (\ensuremath{\neg})}
+\index{law of excluded middle}
+\begin{definition}[Law of Excluded Middle]
+ $\infer (p \lor \neg p)$.
+\end{definition}
+
+\index{law of non-contradiction}
+\begin{definition}[Law of Non-Contradiction]
+ $\infer \neg(p \land \neg p)$.
+\end{definition}
+
+\index{De Morgan's theorem}
+\begin{definition}[De Morgan's Theorem]\dfnlabel{demorgan}
+ $\neg(p \land q) \infer (\neg p \lor \neg q)$
+ and $\neg(p \lor q) \infer (\neg p \land \neg q)$.
+\end{definition}
+
+\indexsym\equiv{equivalence}
+\index{equivalence!material (\ensuremath{\equiv})}
+\begin{definition}[Material Equivalence]
+ $p\equiv q \infer \big((p \land q) \lor (\neg p \land \neg q)\big)$.
+\end{definition}
+
+$\equiv$ denotes a logical identity.
+Consequently,
+ it'll often be used as a definition operator.
+
+\indexsym{\!\!\implies\!\!}{implication}
+\index{implication (\ensuremath{\implies})}
+\begin{definition}[Implication]\dfnlabel{implication}
+ $p\implies q \infer (\neg p \lor q)$.
+\end{definition}
+
+\begin{definition}[Tautologies]\dfnlabel{prop-taut}
+ $p\equiv (p\land p)$ and $p\equiv (p\lor p)$.
+\end{definition}
+
+\indexsym{\true}{boolean, true}
+\indexsym{\false}{boolean, false}
+\index{boolean!FALSE@\tamefalse{} (\false)}%
+\index{boolean!TRUE@\tametrue{} (\true)}%
+\begin{definition}[Truth Values]\dfnlabel{truth-values}
+ $\infer\true$ and $\infer\neg\false$.
+\end{definition}
+
+\indexsym\Int{integer}
+\index{integer (\Int)}%
+\begin{definition}[Boolean/Integer Equivalency]\dfnlabel{bool-int}
+ $\Set{0,1}\in\Int, \false \equiv 0$ and $\true \equiv 1$.
+\end{definition}
+
+
+\subsection{First-Order Logic and Set Theory}
+\index{logic!first-order}
+\indexsym\emptyset{set empty}
+\indexsym{\Set{}}{set}
+\index{set!empty (\ensuremath{\emptyset, \{\}})}
+The symbol $\emptyset$ represents the empty set---%
+ the set of zero elements.
+We assume that the axioms of ZFC~set theory hold,
+ but define $\in$ here for clarity.
+
+\todo{Introduce set-builder notation, $\union$, $\intersect$.}
+\indexsym\in{set membership}
+\indexsym\union{set, union}
+\indexsym\intersect{set, intersection}
+\index{set!membership@membership (\ensuremath\in)}
+\index{set!union (\ensuremath\union)}
+\index{set!intersection (\ensuremath\intersect)}
+\begin{definition}[Set Membership]
+ $x \in S \equiv \Set{x} \intersect S \not= \emptyset.$
+\end{definition}
+
+\index{domain of discourse}
+$\forall$ denotes first-order universal quantification (``for all''),
+ and $\exists$ first-order existential quantification (``there exists''),
+ over some domain of discourse.
+
+\indexsym\exists{quantification, existential}
+\index{quantification!existential (\ensuremath\exists)}
+\begin{definition}[Existential Quantification]\dfnlabel{exists}
+ $\Exists{x\in X}{P(x)} \equiv
+ \true \in \Set{P(x) \mid x\in X}$.
+\end{definition}
+
+\indexsym\forall{quantification, universal}
+\index{quantification!universal (\ensuremath\forall)}
+\begin{definition}[Universal Quantification]\dfnlabel{forall}
+ $\Forall{x\in X}{P(x)} \equiv \neg\Exists{x\in X}{\neg P(x)}$.
+\end{definition}
+
+\index{quantification!vacuous truth}
+\begin{remark}[Vacuous Truth]\remlabel{vacuous-truth}
+ By \dfnref{exists}, $\Exists{x\in\emptyset}P \equiv \false$
+ and by \dfnref{forall}, $\Forall{x\in\emptyset}P \equiv \true$.
+ And so we also have the tautologies $\infer \neg\Exists{x\in\emptyset}P$
+ and $\infer \Forall{x\in\emptyset}P$.
+ Empty domains lead to undesirable consequences---%
+ in particular,
+ we must carefully guard against them in \dfnref{quant-conn} and
+ \dfnref{quant-elim} to maintain soundness.
+\end{remark}
+
+We also have this shorthand notation:
+
+\index{quantification!\ensuremath{\forall x,y,z}}
+\index{quantification!\ensuremath{\exists x,y,z}}
+\begin{align}
+ \Forall{x,y,z\in S}P \equiv
+ \Forall{x\in S}{\Forall{y\in S}{\Forall{z\in S}P}}, \\
+ \Exists{x,y,z\in S}P \equiv
+ \Exists{x\in S}{\Exists{y\in S}{\Exists{z\in S}P}}.
+\end{align}
+
+\begin{definition}[Quantifiers Over Connectives]\dfnlabel{quant-conn}
+ Assuming that $x$ is not free in $\varphi$,
+ \begin{alignat*}{3}
+ \varphi\land\Exists{x\in X}{P(x)}
+ &\equiv \Exists{x\in X}{\varphi\land P(x)}, \\
+ \varphi\lor\Exists{x\in X}{P(x)}
+ &\equiv \Exists{x\in X}{\varphi\lor P(x)}
+ \qquad&&\text{assuming $X\neq\emptyset$}.
+ \end{alignat*}
+\end{definition}
+
+\begin{definition}[Quantifier Elimination]\dfnlabel{quant-elim}
+ $\Exists{x\in X}{\varphi} \equiv \varphi$ assuming $X\neq\emptyset$
+ and $x$ is not free in~$\varphi$.
+\end{definition}
+
+
+\subsection{Functions}
+\indexsym{f, g}{function}
+\indexsym\mapsto{function, map}
+\indexsym\rightarrow{function, domain map}
+\index{function}
+\index{function!map (\ensuremath\mapsto)}
+\index{map|see {function}}
+\index{function!domain}
+\index{function!codomain}
+\index{domain|see {function, domain}}
+\index{function!domain map (\ensuremath\rightarrow)}
+The notation $f = x \mapsto x' : A\rightarrow B$ represents a function~$f$
+ that maps from~$x$ to~$x'$,
+ where $x\in A$ (the domain of~$f$) and $x'\in B$ (the co-domain of~$f$).
+
+\indexsym\times{set, Cartesian product}
+\index{set!Cartesian product (\ensuremath\times)}
+A function $A\rightarrow B$ can be represented as the Cartesian
+ product of its domain and codomain, $A\times B$.
+For example,
+ $x\mapsto x^2 : \Int\rightarrow\Int$ is represented by the set of ordered
+ pairs $\Set{(x,x^2) \mid x\in\Int}$, which looks something like
+
+\begin{equation*}
+ \Set{\ldots,\,(0,0),\,(1,1),\,(2,4),\,(3,9),\,\ldots}.
+\end{equation*}
+
+\indexsym{[\,]}{function, image}
+\index{function!image (\ensuremath{[\,]})}
+\index{function!as a set}
+The set of values over which some function~$f$ ranges is its \dfn{image},
+ which is a subset of its codomain.
+In the example above,
+ both the domain and codomain are the set of integers~$\Int$,
+ but the image is $\Set{x^2 \mid x\in\Int}$,
+ which is clearly a subset of~$\Int$.
+
+We therefore have
+
+\begin{align}
+ A \rightarrow B &\subset A\times B, \\
+ f : A \rightarrow B &\infer f \subset A\times B, \\
+ f = \alpha \mapsto \alpha' : A \rightarrow B
+ &= \Set{(\alpha,\alpha')
+ \mid \alpha\in A \land \alpha'\in B}, \\
+ f[D\subseteq A] &= \Set{f(\alpha) \mid \alpha\in D} \subset B, \\
+ f[] &= f[A].
+\end{align}
+
+\indexsym{()}{tuple}
+\index{tuple (\ensuremath{()})}
+\index{relation|see {function}}
+An ordered pair $(x,y)$ is also called a \dfn{$2$-tuple}.
+Generally,
+ an \dfn{$n$-tuple} is used to represent an $n$-ary function,
+ where by convention we have $(x)=x$.
+So $f(x,y) = f((x,y)) = x+y$.
+If we let $t=(x,y)$,
+ then we also have $f(x,y) = ft$,
+ which we'll sometimes write as a subscript~$f_t$ where disambiguation is
+ necessary and where parenthesis may add too much noise;
+ this notation is especially well-suited to indexes,
+ as in $f_1$.
+Binary functions are often written using \dfn{infix} notation;
+ for example, we have $x+y$ rather than $+(x,y)$.
+
+\begin{equation}
+ f_x = f(x) \in \Set{b \mid (x,b) \in f}
+\end{equation}
+
+
+\subsubsection{Binary Operations On Functions}
+\indexsym{R}{relation}
+Consider two unary functions $f$ and~$g$,
+ and a binary relation~$R$.
+\indexsym{\bicomp{R}}{function, binary composition}
+\index{function!binary composition (\ensuremath{\bicomp{R}})}
+We introduce a notation~$\bicomp R$ to denote the composition of a binary
+ function with two unary functions.
+
+\begin{align}
+ f &: A \rightarrow B \\
+ g &: A \rightarrow D \\
+ R &: B\times D \rightarrow F \\
+ f \bicomp{R} g &= \alpha \mapsto f_\alpha R g_\alpha : A \rightarrow F
+\end{align}
+
+\indexsym\circ{function, composition}
+\index{function!composition (\ensuremath\circ)}
+Note that $f$ and~$g$ must share the same domain~$A$.
+In that sense,
+ this is the mapping of the operation~$R$ over the domain~$A$.
+This is analogous to unary function composition~$f\circ g$.
+
+\index{function!constant}
+A scalar value~$x$ can be mapped onto some function~$f$ using a constant
+ function.
+For example,
+ consider adding some number~$x$ to each element in the image of~$f$:
+
+\begin{equation*}
+ f \bicomp+ (\_\mapsto x) = \alpha \mapsto f_\alpha + x.
+\end{equation*}
+
+\indexsym{\_}{variable, wildcard}
+\index{variable!wildcard/hole (\ensuremath{\_})}
+The symbol~$\_$ is used to denote a variable that matches anything but is
+ never referenced,
+ and is often referred to as a ``wildcard'' (since it matches anything)
+ or a ``hole'' (since its value goes nowhere).
+
+Note that we consider the bracket notation for the image of a function
+ $(f:A\rightarrow B)[A]$ to itself be a binary function.
+Given that, we have $f\bicomp{[]} = f\bicomp{[A]}$ for functions returning
+ functions (such as vectors of vectors in \secref{vec}).
+
+
+\subsection{Monoids and Sequences}\seclabel{monoids}
+\index{abstract algebra!monoid}
+\index{monoid|see abstract algebra, monoid}
+\begin{definition}[Monoid]\dfnlabel{monoid}
+ Let $S$ be some set. A \dfn{monoid} is a triple $\Monoid S\monoidop e$
+ with the axioms
+
+ \begin{align}
+ \monoidop &: S\times S \rightarrow S
+ \tag{Monoid Binary Closure} \\
+ \Forall{a,b,c\in S&}{
+ a\monoidop(b\monoidop c) = (a\monoidop b)\monoidop c)
+ }, \tag{Monoid Associativity} \\
+ \Exists{e\in S&}{\Forall{a\in S}{e\monoidop a = a\monoidop e = a}}.
+ \tag{Monoid Identity}\label{eq:monoid-identity}
+ \end{align}
+\end{definition}
+
+\index{abstract algebra}
+\index{abstract algebra!semigroup}
+Monoids originate from abstract algebra.
+A monoid is a semigroup with an added identity element~$e$.
+Only the identity element must be commutative,
+ but if the binary operation~$\monoidop$ is \emph{also} commutative,
+ then the monoid is a \dfn{commutative monoid}.\footnote{%
+ A commutative monoid is less frequently referred to as an
+ \dfn{abelian monoid},
+ related to the common term \dfn{abelian group}.}
+
+Consider some sequence of operations
+ $x_0 \monoidops x_n \in S$.
+Intuitively,
+ a monoid tells us how to combine that sequence into a single element
+ of~$S$.
+When the sequence has one or zero elements,
+ we then use the identity element $e\in S$:
+ as $x_0 \monoidop e = x_0$ in the case of one element
+ or $e \monoidop e = e$ in the case of zero.
+
+\indexsym\cdots{sequence}
+\index{sequence}
+\begin{definition}[Monoidic Sequence]\dfnlabel{monoid-seq}
+Generally,
+ given some monoid $\Monoid S\monoidop e$ and a sequence $\Fam{x}jJ\in S$
+ where $n<|J|$,
+ we have
+ $x_0\monoidop x_1\monoidops x_{n-1}\monoidop x_n$
+ represent the successive binary operation on all indexed elements
+ of~$x$.
+When it's clear from context that the index is increasing by a constant
+ of~$1$,
+ that notation is shortened to $x_0\monoidops x_n$ to save
+ space.
+When $|J|=1$, then $n=0$ and we have the sequence $x_0$.
+When $|J|=0$, then $n=-1$,
+ and no such sequence exists,
+ in which case we expand into the identity element~$e$.
+\end{definition}
+
+For example,
+ given the monoid~$\Monoid\Int+0$,
+ the sequence $1+2+\cdots+4+5$ can be shortened to
+ $1+\cdots+5$ and represents the arithmetic progression
+ $1+2+3+4+5=15$.
+If $x=\Set{1,2,3,4,5}$,
+ $x_0+\cdots+x_n$ represents the same sequence.
+If $x=\Set{1}$,
+ that sequence evaluates to $1=1$.
+If $x=\Set{}$,
+ we have $0$.
+
+\index{conjunction!monoid}
+\begin{lemma}\lemlabel{monoid-land}
+ $\Monoid\Bool\land\true$ is a commutative monoid.
+\end{lemma}
+\begin{proof}
+ $\Monoid\Bool\land\true$ is associative by \dfnref{conj-assoc}
+ and commutative by \dfnref{conj-commut}.
+ The identity element is~$\true\in\Bool$ by \dfnref{conj-simpl}.
+\end{proof}
+
+\index{disjunction!monoid}
+\begin{lemma}\lemlabel{monoid-lor}
+ $\Monoid\Bool\lor\false$ is a commutative monoid.
+\end{lemma}
+\begin{proof}
+ $\Monoid\Bool\lor\false$ is associative by \dfnref{disj-assoc}
+ and commutative by \dfnref{disj-commut}.
+ The identity $\false\in\Bool$ follows from
+
+ \begin{alignat*}{3}
+ \false \lor p &\equiv p \lor \false &&\text{by \dfnref{disj-commut}} \\
+ &\equiv \neg(\neg p \land \neg\false)\qquad
+ &&\text{by \dfnref{demorgan}} \\
+ &\equiv \neg(\neg p) &&\text{by \dfnref{conj-simpl}} \\
+ &\equiv p. &&\text{by \dfnref{double-neg}} \tag*\qedhere
+ \end{alignat*}
+\end{proof}
+
+
+\goodbreak% Fits well on its own page, if we're near a page boundary
+\subsection{Vectors and Index Sets}\seclabel{vec}
+\tame{} supports scalar, vector, and matrix values.
+Unfortunately,
+ its implementation history leaves those concepts a bit tortured.
+
+A vector is a sequence of values, defined as a function of
+ an index.
+An \dfn{index~set} is a set that is used to index values from another set;
+ they are usually subscripts of another set.
+A \dfn{family} is a set that is indexed by the same index set.
+In this paper,
+ we assume that an index set represents a range of integer values from $0$
+ to some number.
+
+\begin{definition}[Family and Index Set]
+ Let $S$ be a family indexed by index set~$J$.
+ Then,
+
+ \begin{align}
+ \Fam{S}jJ,\qquad J = \Set{0,1,\dots,\len{J}-1}\in\PSet\Int.
+ \end{align}
+\end{definition}
+
+\indexsym{\PSet{S}}{set, power set}
+\index{set!power set (\ensuremath{\PSet{S}})}
+$\PSet{S}$ denotes the \dfn{power set} of $S$---%
+ the set of all subsets of~$S$ including $\emptyset$ and $S$~itself.
+
+% TODO: font changes in index, making langle unavailable
+%\indexsym{\Vector{}}{vector}
+\index{vector!definition (\ensuremath{\Vector{}})}
+\index{sequence|see vector}
+\indexsym\Vectors{vector}
+\index{real number (\ensuremath\Real)}
+\indexsym\Real{real number}
+\indexsym{\Fam{a}jJ}{index set}
+\index{family|see {index set}}
+\index{index set!_@notation (\ensuremath{\Fam{a}jJ})}
+\begin{definition}[Vector]\dfnlabel{vec}
+ Let $J\subset\Int$ represent an index set.
+ A \dfn{vector}~$v\in\Vectors^\Real$ is a totally ordered sequence of
+ elements represented as a function of an element of its index set:
+ \begin{equation}\label{vec}
+ v = \Vector{v_0,\ldots,v_j}^{\Real}_{j\in J}
+ = j \mapsto v_j : J \rightarrow \Real.
+ \end{equation}
+\end{definition}
+
+This definition means that $v_j = v(j)$,
+ making the subscript a notational convenience.
+We may omit the superscript such that $\Vectors^\Real=\Vectors$
+ and $\Vector{\ldots}^\Real=\Vector{\ldots}$.
+
+When appropriate,
+ a vector may also be styled in a manner similar to linear algebra,
+ noting that our indexes begin at $0$ instead of~$1$:
+
+\begin{equation}
+ \Vector{v_0,\dots,v_j}^\Real_{j\in J} =
+ \begin{bmatrix}
+ v_0 \\
+ \vdots \\
+ v_j
+ \end{bmatrix}_{j\in J}
+ =
+ \begin{bmatrix}
+ v_0 \\
+ \vdots \\
+ v_j
+ \end{bmatrix}.
+\end{equation}
+
+\index{matrix@see {vector}}
+\index{vector!matrix}
+\begin{definition}[Matrix]\dfnlabel{matrix}
+ Let $J\subset\Int$ represent an index set.
+ A \dfn{matrix}~$M\in\Matrices$ is a totally ordered sequence of
+ elements represented as a function of an element of its index set:
+ \begin{equation}
+ M = \Vector{M_0,\ldots,M_j}^{\Vectors^\Real}_{j\in J}
+ = j \mapsto M_j : J \rightarrow \Vectors^\Real.
+ \end{equation}
+\end{definition}
+
+The consequences of \dfnref{matrix}---%
+ defining a matrix as a vector of independent vectors---%
+ are important.
+This defines a matrix to be more like a multidimensional array,
+ with no requirement that the lengths of the vectors be equal.
+
+\begin{corollary}[Matrix Row Length Variance]\corlabel{matrix-row-len}
+ $\infer \Exists{M\in\Matrices}{\neg\Forall*{j}{\Forall{k}{\len{M_j} = \len{M_k}}}}$.
+\end{corollary}
+
+\corref{matrix-row-len} can be read ``there exists some matrix~$M$ such that
+ not all row lengths of~$M$ are equal''.
+In other words---%
+ the inner vectors of a matrix can vary in length.
+However,
+ certain systems (such as that of \axmref{class-intro}) may place
+ restrictions by specifying the inner index set as a dependent type:
+
+\begin{equation}
+ \MFam{M}jJkK : J \rightarrow K_j \rightarrow \Real, \quad K : J \rightarrow \PSet\Int.
+\end{equation}
+
+\index{vector!matrix!rectangular}
+This makes $K$ a set of index sets.
+When $\len{K[J]}=1$
+ (that is---all $K_j$ are the same index set),
+ the matrix is \dfn{rectangular},
+ and can be written in a manner similar to linear algebra,
+ noting that our indexes begin at $0$ instead of~$1$;
+ that we use double-subscripts
+ (since matrices are functions returning functions);
+ and that we use $j,k$ in place of~$m,n$.
+
+\begin{equation}
+ \begin{bmatrix}
+ M_{0_0} & M_{0_1} & \dots & M_{0_k} \\
+ M_{1_0} & M_{0_1} & \dots & M_{0_k} \\
+ \vdots & \vdots & \ddots & \vdots \\
+ M_{j_0} & M_{j_1} & \dots & M_{j_k} \\
+ \end{bmatrix}_{\underset{k\in K_0}{j\in J}}
+ \qquad
+ \text{if $|K[J]|=1$}.
+\end{equation}
+We may optionally omit the domains as in the vector notation.
+
+\indexsym\eleundef{undefined}
+\index{undefined}
+If a matrix is \emph{not} rectangular,
+ the symbol~$\eleundef$ can be used to explicitly denote that specific scalar
+ values are undefined;
+ this is useful when the matrix representation is desirable when
+ describing the transformation of non-rectangular data \emph{into}
+ rectangular data.
+For example,
+
+\begin{equation}
+ \begin{bmatrix}
+ 0 & 1 & 2 \\
+ 3 & 4 & \eleundef \\
+ 5 & \eleundef & \eleundef
+ \end{bmatrix}_{\underset{k\in K_j}{j\in J}}
+ =
+ \Vector{\Vector{0,1,2},\Vector{3,4},\Vector{5}},
+ \qquad
+ \begin{aligned}
+ J &= \Set{0,1,2}, \\
+ K &= \Set{(0,\Set{0,1,2}),\,
+ (1,\Set{0,1}),\,
+ (2,\Set{0})}.
+ \end{aligned}
+\end{equation}
+
+
+% TODO: symbol does not render properly in index
+\begin{definition}[Rank]\dfnlabel{rank}
+\index{rank}
+ The \dfn{rank} of some variable~$x$ is an integer value
+ \begin{equation*}
+ \rank{x} =
+ \begin{cases}
+ 2 &x\in\Matrices, \\
+ 1 &x\in\Vectors^\Real, \\
+ 0 &x\in\Real.
+ \end{cases}
+ \end{equation*}
+\end{definition}
+
+Intuitively, the rank represents the number of dimensions of some variable~$x$.
+A scalar has zero dimensions (a point);
+ a vector has one (a line);
+ and a matrix has two (a plane).
+\index{dimensions@see {rank}}
+\index{rank!dimensions}
+In \tame{},
+ the rank is referred to as \dfn{dimensions} using the attribute
+ \xmlattr{dim}.
+
+
+\subsection{XML Notation}
+\indexsym{\xml{<>}}{XML}
+\index{XML!notation (\xml{<>})}
+The grammar of \tame{} is XML.
+Equivalence relations will be used to map source expressions to an
+ underlying mathematical expression.
+For example,
+
+\begin{equation*}
+ \xml{<foo bar="$x$" baz="$y$" />} \equiv x = y
+\end{equation*}
+
+\noindent
+defines that pattern of \xmlnode{foo} expression to be materially
+ equivalent to~$x=y$---%
+ anywhere an equality relation appears,
+ you could equivalently replace it with that XML representation without
+ changing the meaning of the mathematical expression.
+
+Variables may also bind to literals in an XML expression.
+For example,
+
+\begin{equation*}
+ \xml{<quux $\alpha$ />},\qquad\alpha\in\Set{\emptystr, \xml{bar="baz"}}
+\end{equation*}
+
+\noindent
+can represent either \xml{<quux />} or \xml{<quux bar="baz" />}.
+\index{empty string}
+$\emptystr$~represents the empty string.
+Any text typeset in \texttt{typewriter} represents a literal
+ string of characters.
diff --git a/design/tpl/tpl.bib b/design/tpl/tpl.bib
new file mode 100644
index 0000000..fb5b6ef
--- /dev/null
+++ b/design/tpl/tpl.bib
@@ -0,0 +1,8 @@
+% TAME Programming Language Bibliography
+
+@online{wp:hadamard-product,
+ organization = {Wikipedia},
+ title = {Hadamard product (matrices)},
+ url = {https://en.wikipedia.org/wiki/Hadamard_product_(matrices)},
+ date = {2020-05-26},
+}
diff --git a/design/tpl/tpl.sty b/design/tpl/tpl.sty
new file mode 100644
index 0000000..52c1465
--- /dev/null
+++ b/design/tpl/tpl.sty
@@ -0,0 +1,268 @@
+% The TAME Programming Language LaTeX style and macros
+%
+% Copyright (C) 2021 Ryan Specialty Group, LLC.
+%
+% Licensed under the Creative Commons Attribution-ShareAlike 4.0
+% International License.
+%%
+
+%%
+% Package Imports
+%%
+\usepackage[letterpaper]{geometry} % US Letter paper
+\usepackage[amsfonts,amssymb]{concmath} % Fonts used by the text
+\usepackage{euler} % Concrete Mathematics
+\usepackage{makeidx} % Indexing
+\usepackage[backend=biber]{biblatex} % BibTeX replacement
+\usepackage[colorlinks=true,
+ linkcolor=href,
+ citecolor=cite,
+ urlcolor=href,
+ draft=false]{hyperref} % Hyperlinks (even in draft mode)
+\usepackage{xcolor} % Colors (for hyperlinks)
+\usepackage{amsmath,amsthm} % AMS macros, including theorems
+\usepackage{suffix} % To aid in defining star macros
+\usepackage{marginnote} % Notes in the margin
+\usepackage{ccicons} % CC license icons
+\usepackage{manfnt} % Dangerous Bend symbols
+\usepackage{rotating} % Rotating objects
+
+\addbibresource{tpl.bib}
+
+
+
+%%
+% Colors
+%
+% Colors from Tango Icon Theme
+% https://en.wikipedia.org/wiki/Tango_Desktop_Project
+\definecolor{href}{HTML}{204a87}
+\definecolor{cite}{HTML}{4e9a06}
+
+
+
+%%
+% Common typesetting
+%%
+% TAME
+\newcommand\tame{\textsc{Tame}}
+\newcommand\tamer{\textsc{Tamer}}
+\newcommand\tameparam[1]{\texttt{#1}}
+\newcommand\tameclass[1]{\texttt{#1}}
+\newcommand\tameconst[1]{\texttt{#1}}
+\newcommand\tametrue{\tameconst{TRUE}}
+\newcommand\tamefalse{\tameconst{FALSE}}
+\let\tamepkg\texttt
+
+% XML
+\let\xml\texttt
+\newcommand\xmlnl{\\[-3mm]}
+\newcommand\xmlnll{\\[-1mm]}
+\newcommand\xpath[1]{\texttt{#1}}
+\newcommand\xmlnode[1]{\texttt{#1}}
+\newcommand\xmlattr[1]{{@\texttt{#1}}}
+
+
+
+%%
+% Mathematics
+%%
+% Boolean
+\newcommand\true{\ensuremath\top}
+\newcommand\false{\ensuremath\bot}
+\newcommand\Bool{\ensuremath{\mathbb{B}}}
+
+% Numbers
+\newcommand\Int{\ensuremath{\mathbb{Z}}}
+\newcommand\Real{\ensuremath{\mathbb{R}}}
+
+% Sets and families
+\newcommand\Set[1]{\ensuremath{\left\{#1\right\}}}
+\newcommand\Fam[3]{\ensuremath{\left\{#1_{#2}\right\}_{#2\in #3}}}
+\newcommand\len[1]{\ensuremath{\left|#1\right|}}
+\newcommand\rank[1]{\ensuremath{\left\|#1\right\|}}
+\newcommand\PSet[1]{\mathcal{P}\left(#1\right)}
+\newcommand\PSetcard[2]{\mathcal{P}_{#1}\left(#2\right)}
+\let\union\cup
+\let\Union\bigcup
+\let\intersect\cap
+
+% Vectors and matrices with family notations
+\newcommand\Vectors{\ensuremath{\mathcal{V}}}
+\newcommand\Vector[1]{\ensuremath{\left\langle#1\right\rangle}}
+\newcommand\VFam[3]{\ensuremath{\Vector{#1_{#2}}_{#2\in #3}}}
+\newcommand\Matrices{\ensuremath{\Vectors^{\Vectors^\Real}}}
+\newcommand\MFam[5]{\ensuremath{%
+ \Vector{{#1_{#2}}_#4}_{\underset{#4\in {#5_#2}}{#2\in #3}}
+}}
+\newcommand\eleundef{\boxtimes}
+
+% Variable subscripts
+\let\varsubscript\imath
+\newcommand\varsub[1]{{#1}_{\varsubscript}}
+
+% Logic
+\let\infer\vdash
+\newcommand\Forall{\@ifstar\@Forallstar\@Forall}
+\newcommand\@Forall[2]{\forall #1\left(#2\right)}
+\newcommand\@Forallstar[2]{\forall #1 #2}
+\newcommand\Exists{\@ifstar\@Existsstar\@Exists}
+\newcommand\@Exists[2]{\exists #1\left(#2\right)}
+\newcommand\@Existsstar[2]{\exists #1 #2}
+
+% Equivalent, but some syntatic inference involved (e.g. equivalent by
+% index, as in classification matches).
+\newcommand\equivish{\equiv_{\varsubscript}}
+
+% Group theory
+\newcommand\Monoid[3]{\left({#1},{#2},{#3}\right)}
+\let\monoidop\bullet
+\newcommand\monoidops{\monoidop\cdots\monoidop}
+
+% Closed binary function
+\newcommand\cbif[1]{#1\times#1\rightarrow#1}
+
+% Binary function composition
+\newcommand\bicomp[1]{{#1}^\circ}
+
+% Grammar
+\let\emptystr\epsilon
+
+% TAME Classification
+\DeclareMathOperator*\Classify{%
+ \mathchoice{\vcenter{\hbox{\huge$\mathfrak{C}$}}}
+ {\vcenter{\hbox{\Large$\mathfrak{C}$}}}
+ {\frak{C}}
+ {\frak{C}}
+}
+% Convenience variants
+\DeclareMathOperator*\Classifyland{%
+ \mathchoice{\vcenter{\hbox{\huge$\mathfrak{C}^\land$}}}
+ {\vcenter{\hbox{\Large$\mathfrak{C}\land$}}}
+ {\frak{C^\land}}
+ {\frak{C^\land}}
+}
+\DeclareMathOperator*\Classifylor{%
+ \mathchoice{\vcenter{\hbox{\huge$\mathfrak{C}^\lor$}}}
+ {\vcenter{\hbox{\Large$\mathfrak{C}\lor$}}}
+ {\frak{C^\lor}}
+ {\frak{C^\lor}}
+}
+
+
+
+%%
+% Theorem environments
+%%
+\numberwithin{equation}{section} % number by section
+
+% Create a new theorem environment along with a set of label/ref/pref
+% commands.
+\newcommand\newtheoremwithlabel[3]{%
+ \newtheorem{#1}{#2}[section]
+ \expandafter\def\csname #1autorefname\endcsname{#2}
+ \expandafter\newcommand\csname #3label\endcsname[1]{\label{#1:##1}}
+ \expandafter\newcommand\csname #3ref\endcsname[1]{\autoref{#1:##1}}
+ \expandafter\newcommand\csname #3pref\endcsname[1]{\spref{#1:##1}}
+}
+
+\theoremstyle{definition}
+\newtheoremwithlabel{definition}{Definition}{dfn}
+\newtheoremwithlabel{axiom}{Axiom}{axm}
+
+\theoremstyle{plain}
+\newtheoremwithlabel{corollary}{Corollary}{cor}
+\newtheoremwithlabel{lemma}{Lemma}{lem}
+\newtheoremwithlabel{theorem}{Theorem}{thm}
+
+\theoremstyle{remark}
+\newtheoremwithlabel{remark}{Remark}{rem}
+
+
+%%
+% Optional page reference
+%%
+% "Smart" page reference changing the text depending on what page the
+% reference appears relative to the current.
+\newcount\@sprefn
+\newcommand\spref[1]{%
+ % We use labels to get the current page from the previous run, since
+ % \thepage can be inaccurate except right after a \newpage.
+ \advance\@sprefn by 1\relax%
+ \label{__:#1SELF\the\@sprefn}%
+ \xdef\@@realp{\getpagerefnumber{__:#1SELF\the\@sprefn}}%
+ \autoref{#1}%
+ \xdef\@pref{\getpagerefnumber{#1}}%
+ \ifnum\@pref=\@@realp
+ % Output nothing; it'd read really awkwardly, for example, if we output
+ % a figure using [h] and then say "Figure X on this page" in the
+ % paragraph that's _right_ below it. Even if we didn't use [h]. I
+ % noticed this awkward effect in Specifying Systems.
+ \else
+ \space
+ \newcount\@p\@p=\@@realp
+ \advance\@p by 1\relax
+ \ifnum\@p=\@pref
+ on the next page%
+ \else
+ \advance\@p by -2\relax
+ \ifnum\@p=\@pref
+ on the previous page%
+ \else
+ on \autopageref{#1}%
+ \fi
+ \fi
+ \fi
+}
+
+
+
+%%
+% General typesetting
+%%
+\newcommand\pref[1]{\ref{#1} on page~\pageref{#1}}
+\newcommand\seclabel[1]{\label{sec:#1}}
+\newcommand\secref[1]{Section~\ref{sec:#1}}
+\newcommand\secpref[1]{Section~\pref{sec:#1}}
+
+% Definitions (introduction of terms)
+\let\dfn\textsl
+\newcommand\todo[1]{%
+ \marginnote{\underline{\textsc{Todo:}}\\
+ \textsl{#1}}%
+}
+\newcommand\mremark[1]{\marginnote{\textsl{#1}}}
+
+% A really obnoxious notice making clear to the reader that this portion of
+% the work is unfinished, to the point where it's probably even
+% incorrect. Uses dangerous bend symbol from manfnt, which is admittedly a
+% misuse given that it's often used to represent difficult problems. Though
+% I suppose an unfinished work is a difficult problem.
+\newcommand\INCOMPLETE[1]{%
+ \bigskip
+ \par
+ \todo{This is incomplete!}
+ \hfill\textdbend\textbf{\textsl{#1}}\textdbend\hfill
+ \bigskip
+}
+
+
+
+%%
+% Indexing
+%%
+% Symbols appear at the beginning of the index
+\newcommand\indexsym[2]{\index{__sym_#2@{\ensuremath{#1}}|see {#2}}}
+
+
+
+%%
+% Dynamic Configuration
+%%
+\newif\iftplappendix
+
+\InputIfFileExists{./conf.tex}%
+ {\message{Loaded `./conf.tex'.}}%
+ {\message{`./conf.tex' not found (did you run `./configure'?).
+ Using defaults.}%
+}
diff --git a/design/tpl/tpl.tex b/design/tpl/tpl.tex
new file mode 100644
index 0000000..cce0513
--- /dev/null
+++ b/design/tpl/tpl.tex
@@ -0,0 +1,77 @@
+% The TAME Programming Language Living Document
+%
+% Copyright (C) 2021 Ryan Specialty Group, LLC.
+%
+% Licensed under the Creative Commons Attribution-ShareAlike 4.0
+% International License.
+%%
+
+\documentclass[draft,toc=index]{scrartcl}
+\usepackage[draft=false]{scrlayer-scrpage}
+\usepackage{tpl}
+
+\title{The TAME Programming Language}
+\subtitle{Design and Implementation (Living Document)}
+
+\author{Mike Gerwitz}
+\date{May 2021}% TODO dynamic
+
+% Copyright notice for bottom of first page
+\setlength\footheight{28pt}
+\cfoot[%
+ {\tiny Copyright \textcopyright{} 2021 Ryan Specialty Group, LLC.
+ CC-BY-SA 4.0.}\\\ccbysa]{\thepage}
+
+% Begin section numbering at 0 to emphasize that it's foundational material
+% not directly related to TAME itself
+\setcounter{section}{-1}
+
+\makeindex
+
+\begin{document}
+
+\maketitle
+
+\begin{abstract}
+ \tame{} is The Algebraic Metalanguage, a programming language and
+ collection of tools designed to aid in the development, understanding,
+ and maintenance of systems performing numerous calculations on a
+ complex graph of dependencies, conditions, and a large number of
+ inputs. \tame{} has existed for over a decade, and while its initial
+ design was successful and still in active use today, it does suffer
+ from inconsistencies and tradeoffs that introduce certain impediments
+ to users of the language, and compromise future optimizations and
+ language evolution. It also lacks documentation not just of the
+ language itself, but also of the underlying principles and
+ implementation.
+
+ This document is an attempt to formally consider certain parts of
+ \tame{} as it undergoes redesign and reimplementation as part of the
+ \tamer{} project. It is considered a living document---it is not
+ likely to ever be a finished work.
+\end{abstract}
+
+
+\tableofcontents
+
+\input{sec/notation.tex}
+\input{sec/class.tex}
+
+
+% Appendix may be enabled with `./configure --enable-appendix'.
+\iftplappendix
+ \clearpage
+ \appendix
+ \input{sec/appendix-typesetting.tex}
+\fi
+
+% Ensure Copyright line does not show
+\cfoot[\thepage]{\thepage}
+
+\printbibliography[heading=bibintoc]
+
+\clearpage
+\printindex
+
+\end{document}
+
diff --git a/doc/Makefile.am b/doc/Makefile.am
index e10f652..0292036 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -45,7 +45,7 @@ xsltexis: $(stexi)
html-local:
find tame.html/ -name '*.html' -type f \
| xargs sed -i \
- 's#\(<script.*\)\?</body>#<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script></body>#'
+ 's#\(<script.*\)\?</body>#<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML" integrity="sha512-tOav5w1OjvsSJzePRtt2uQPFwBoHt1VZcUq8l8nm5284LEKE9FSJBQryzMBzHxY5P0zRdNqEcpLIRVYFNgu1jw==" crossorigin="anonymous"></script></body>#'
clean-local:
-rm -f $(stexi)
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..48e341a
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,3 @@
+{
+ "lockfileVersion": 1
+}
diff --git a/progtest/package-lock.json b/progtest/package-lock.json
new file mode 100644
index 0000000..8aea2cf
--- /dev/null
+++ b/progtest/package-lock.json
@@ -0,0 +1,1586 @@
+{
+ "name": "tame-progtest",
+ "version": "0.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "JSONStream": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
+ "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
+ "dev": true,
+ "requires": {
+ "jsonparse": "^1.2.0",
+ "through": ">=2.2.7 <3"
+ }
+ },
+ "acorn": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
+ "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==",
+ "dev": true
+ },
+ "acorn-node": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
+ "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
+ "dev": true,
+ "requires": {
+ "acorn": "^7.0.0",
+ "acorn-walk": "^7.0.0",
+ "xtend": "^4.0.2"
+ }
+ },
+ "acorn-walk": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
+ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
+ "dev": true
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "asn1.js": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
+ "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "safer-buffer": "^2.1.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "assert": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
+ "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
+ "dev": true,
+ "requires": {
+ "object-assign": "^4.1.1",
+ "util": "0.10.3"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
+ "dev": true
+ },
+ "util": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+ "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.1"
+ }
+ }
+ }
+ },
+ "assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "base64-js": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
+ "dev": true
+ },
+ "bn.js": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz",
+ "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "brorand": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+ "dev": true
+ },
+ "browser-pack": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz",
+ "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==",
+ "dev": true,
+ "requires": {
+ "JSONStream": "^1.0.3",
+ "combine-source-map": "~0.8.0",
+ "defined": "^1.0.0",
+ "safe-buffer": "^5.1.1",
+ "through2": "^2.0.0",
+ "umd": "^3.0.0"
+ }
+ },
+ "browser-resolve": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz",
+ "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==",
+ "dev": true,
+ "requires": {
+ "resolve": "1.1.7"
+ },
+ "dependencies": {
+ "resolve": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
+ "dev": true
+ }
+ }
+ },
+ "browser-stdout": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz",
+ "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=",
+ "dev": true
+ },
+ "browserify": {
+ "version": "16.1.0",
+ "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.1.0.tgz",
+ "integrity": "sha512-jwSlLNDlNzX6ETpLN12n+BIXN5PlOWqnwNVFQeJ7oN4L26Uy7N8gXXvlVOdwTLi0Q1EVp2oGm1LMqYt0dvVryg==",
+ "dev": true,
+ "requires": {
+ "JSONStream": "^1.0.3",
+ "assert": "^1.4.0",
+ "browser-pack": "^6.0.1",
+ "browser-resolve": "^1.11.0",
+ "browserify-zlib": "~0.2.0",
+ "buffer": "^5.0.2",
+ "cached-path-relative": "^1.0.0",
+ "concat-stream": "^1.6.0",
+ "console-browserify": "^1.1.0",
+ "constants-browserify": "~1.0.0",
+ "crypto-browserify": "^3.0.0",
+ "defined": "^1.0.0",
+ "deps-sort": "^2.0.0",
+ "domain-browser": "^1.2.0",
+ "duplexer2": "~0.1.2",
+ "events": "^2.0.0",
+ "glob": "^7.1.0",
+ "has": "^1.0.0",
+ "htmlescape": "^1.1.0",
+ "https-browserify": "^1.0.0",
+ "inherits": "~2.0.1",
+ "insert-module-globals": "^7.0.0",
+ "labeled-stream-splicer": "^2.0.0",
+ "mkdirp": "^0.5.0",
+ "module-deps": "^6.0.0",
+ "os-browserify": "~0.3.0",
+ "parents": "^1.0.1",
+ "path-browserify": "~0.0.0",
+ "process": "~0.11.0",
+ "punycode": "^1.3.2",
+ "querystring-es3": "~0.2.0",
+ "read-only-stream": "^2.0.0",
+ "readable-stream": "^2.0.2",
+ "resolve": "^1.1.4",
+ "shasum": "^1.0.0",
+ "shell-quote": "^1.6.1",
+ "stream-browserify": "^2.0.0",
+ "stream-http": "^2.0.0",
+ "string_decoder": "~1.0.0",
+ "subarg": "^1.0.0",
+ "syntax-error": "^1.1.1",
+ "through2": "^2.0.0",
+ "timers-browserify": "^1.0.1",
+ "tty-browserify": "0.0.1",
+ "url": "~0.11.0",
+ "util": "~0.10.1",
+ "vm-browserify": "~0.0.1",
+ "xtend": "^4.0.0"
+ }
+ },
+ "browserify-aes": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+ "dev": true,
+ "requires": {
+ "buffer-xor": "^1.0.3",
+ "cipher-base": "^1.0.0",
+ "create-hash": "^1.1.0",
+ "evp_bytestokey": "^1.0.3",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "browserify-cipher": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+ "dev": true,
+ "requires": {
+ "browserify-aes": "^1.0.4",
+ "browserify-des": "^1.0.0",
+ "evp_bytestokey": "^1.0.0"
+ }
+ },
+ "browserify-des": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+ "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "des.js": "^1.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "browserify-rsa": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "randombytes": "^2.0.1"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "browserify-sign": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
+ "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^5.1.1",
+ "browserify-rsa": "^4.0.1",
+ "create-hash": "^1.2.0",
+ "create-hmac": "^1.1.7",
+ "elliptic": "^6.5.3",
+ "inherits": "^2.0.4",
+ "parse-asn1": "^5.1.5",
+ "readable-stream": "^3.6.0",
+ "safe-buffer": "^5.2.0"
+ },
+ "dependencies": {
+ "readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.2.0"
+ }
+ }
+ }
+ },
+ "browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "dev": true,
+ "requires": {
+ "pako": "~1.0.5"
+ }
+ },
+ "buffer": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
+ "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
+ "dev": true,
+ "requires": {
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4"
+ }
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
+ "buffer-xor": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+ "dev": true
+ },
+ "builtin-status-codes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+ "dev": true
+ },
+ "cached-path-relative": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz",
+ "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==",
+ "dev": true
+ },
+ "chai": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz",
+ "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=",
+ "dev": true,
+ "requires": {
+ "assertion-error": "^1.0.1",
+ "check-error": "^1.0.1",
+ "deep-eql": "^3.0.0",
+ "get-func-name": "^2.0.0",
+ "pathval": "^1.0.0",
+ "type-detect": "^4.0.0"
+ }
+ },
+ "check-error": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
+ "dev": true
+ },
+ "cipher-base": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "combine-source-map": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz",
+ "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=",
+ "dev": true,
+ "requires": {
+ "convert-source-map": "~1.1.0",
+ "inline-source-map": "~0.6.0",
+ "lodash.memoize": "~3.0.3",
+ "source-map": "~0.5.3"
+ }
+ },
+ "commander": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
+ "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "console-browserify": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
+ "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
+ "dev": true
+ },
+ "constants-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+ "dev": true
+ },
+ "convert-source-map": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
+ "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=",
+ "dev": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true
+ },
+ "create-ecdh": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
+ "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "elliptic": "^6.5.3"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "create-hash": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "inherits": "^2.0.1",
+ "md5.js": "^1.3.4",
+ "ripemd160": "^2.0.1",
+ "sha.js": "^2.4.0"
+ }
+ },
+ "create-hmac": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.3",
+ "create-hash": "^1.1.0",
+ "inherits": "^2.0.1",
+ "ripemd160": "^2.0.0",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "crypto-browserify": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+ "dev": true,
+ "requires": {
+ "browserify-cipher": "^1.0.0",
+ "browserify-sign": "^4.0.0",
+ "create-ecdh": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "create-hmac": "^1.1.0",
+ "diffie-hellman": "^5.0.0",
+ "inherits": "^2.0.1",
+ "pbkdf2": "^3.0.3",
+ "public-encrypt": "^4.0.0",
+ "randombytes": "^2.0.0",
+ "randomfill": "^1.0.3"
+ }
+ },
+ "dash-ast": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz",
+ "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==",
+ "dev": true
+ },
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "deep-eql": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
+ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+ "dev": true,
+ "requires": {
+ "type-detect": "^4.0.0"
+ }
+ },
+ "defined": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
+ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
+ "dev": true
+ },
+ "deps-sort": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz",
+ "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==",
+ "dev": true,
+ "requires": {
+ "JSONStream": "^1.0.3",
+ "shasum-object": "^1.0.0",
+ "subarg": "^1.0.0",
+ "through2": "^2.0.0"
+ }
+ },
+ "des.js": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
+ "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "detective": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
+ "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
+ "dev": true,
+ "requires": {
+ "acorn-node": "^1.6.1",
+ "defined": "^1.0.0",
+ "minimist": "^1.1.1"
+ }
+ },
+ "diff": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz",
+ "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==",
+ "dev": true
+ },
+ "diffie-hellman": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+ "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "miller-rabin": "^4.0.0",
+ "randombytes": "^2.0.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "domain-browser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+ "dev": true
+ },
+ "duplexer2": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+ "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
+ "dev": true,
+ "requires": {
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "easejs": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/easejs/-/easejs-0.2.9.tgz",
+ "integrity": "sha512-blPZOLJM2RHhgjlIBhiiwzjH3VjeSPKFVqPFTTJjH+Y2NV5FUaYyzk5qMxtf/7IUx+vv5FFbFfRQUQ5TMqP2Tw=="
+ },
+ "elliptic": {
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
+ "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.4.0",
+ "brorand": "^1.0.1",
+ "hash.js": "^1.0.0",
+ "hmac-drbg": "^1.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+ },
+ "events": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz",
+ "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==",
+ "dev": true
+ },
+ "evp_bytestokey": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+ "dev": true,
+ "requires": {
+ "md5.js": "^1.3.4",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "fast-safe-stringify": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
+ "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==",
+ "dev": true
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "get-assigned-identifiers": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz",
+ "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==",
+ "dev": true
+ },
+ "get-func-name": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "growl": {
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz",
+ "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==",
+ "dev": true
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+ "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+ "dev": true
+ },
+ "hash-base": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
+ "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.6.0",
+ "safe-buffer": "^5.2.0"
+ },
+ "dependencies": {
+ "readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.2.0"
+ }
+ }
+ }
+ },
+ "hash.js": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+ "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "minimalistic-assert": "^1.0.1"
+ }
+ },
+ "he": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
+ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
+ "dev": true
+ },
+ "hmac-drbg": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+ "dev": true,
+ "requires": {
+ "hash.js": "^1.0.3",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.1"
+ }
+ },
+ "htmlescape": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz",
+ "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=",
+ "dev": true
+ },
+ "https-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
+ "dev": true
+ },
+ "ieee754": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
+ "dev": true
+ },
+ "indexof": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
+ "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "inline-source-map": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz",
+ "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=",
+ "dev": true,
+ "requires": {
+ "source-map": "~0.5.3"
+ }
+ },
+ "insert-module-globals": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.0.tgz",
+ "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==",
+ "dev": true,
+ "requires": {
+ "JSONStream": "^1.0.3",
+ "acorn-node": "^1.5.2",
+ "combine-source-map": "^0.8.0",
+ "concat-stream": "^1.6.1",
+ "is-buffer": "^1.1.0",
+ "path-is-absolute": "^1.0.1",
+ "process": "~0.11.0",
+ "through2": "^2.0.0",
+ "undeclared-identifiers": "^1.1.2",
+ "xtend": "^4.0.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "dev": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
+ "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "json-stable-stringify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz",
+ "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=",
+ "dev": true,
+ "requires": {
+ "jsonify": "~0.0.0"
+ }
+ },
+ "jsonify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+ "dev": true
+ },
+ "jsonparse": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
+ "dev": true
+ },
+ "labeled-stream-splicer": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz",
+ "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "stream-splicer": "^2.0.0"
+ }
+ },
+ "lodash.memoize": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz",
+ "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=",
+ "dev": true
+ },
+ "md5.js": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+ "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "miller-rabin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "brorand": "^1.0.1"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true
+ },
+ "minimalistic-crypto-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "mocha": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.1.tgz",
+ "integrity": "sha512-SpwyojlnE/WRBNGtvJSNfllfm5PqEDFxcWluSIgLeSBJtXG4DmoX2NNAeEA7rP5kK+79VgtVq8nG6HskaL1ykg==",
+ "dev": true,
+ "requires": {
+ "browser-stdout": "1.3.0",
+ "commander": "2.11.0",
+ "debug": "3.1.0",
+ "diff": "3.3.1",
+ "escape-string-regexp": "1.0.5",
+ "glob": "7.1.2",
+ "growl": "1.10.3",
+ "he": "1.1.1",
+ "mkdirp": "0.5.1",
+ "supports-color": "4.4.0"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ }
+ }
+ },
+ "module-deps": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz",
+ "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==",
+ "dev": true,
+ "requires": {
+ "JSONStream": "^1.0.3",
+ "browser-resolve": "^2.0.0",
+ "cached-path-relative": "^1.0.2",
+ "concat-stream": "~1.6.0",
+ "defined": "^1.0.0",
+ "detective": "^5.2.0",
+ "duplexer2": "^0.1.2",
+ "inherits": "^2.0.1",
+ "parents": "^1.0.0",
+ "readable-stream": "^2.0.2",
+ "resolve": "^1.4.0",
+ "stream-combiner2": "^1.1.1",
+ "subarg": "^1.0.0",
+ "through2": "^2.0.0",
+ "xtend": "^4.0.0"
+ },
+ "dependencies": {
+ "browser-resolve": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz",
+ "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==",
+ "dev": true,
+ "requires": {
+ "resolve": "^1.17.0"
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "dev": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "os-browserify": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
+ "dev": true
+ },
+ "pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "dev": true
+ },
+ "parents": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz",
+ "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=",
+ "dev": true,
+ "requires": {
+ "path-platform": "~0.11.15"
+ }
+ },
+ "parse-asn1": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
+ "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
+ "dev": true,
+ "requires": {
+ "asn1.js": "^5.2.0",
+ "browserify-aes": "^1.0.0",
+ "evp_bytestokey": "^1.0.0",
+ "pbkdf2": "^3.0.3",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "path-browserify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
+ "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "dev": true
+ },
+ "path-platform": {
+ "version": "0.11.15",
+ "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz",
+ "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=",
+ "dev": true
+ },
+ "pathval": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
+ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
+ "dev": true
+ },
+ "pbkdf2": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
+ "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
+ "dev": true,
+ "requires": {
+ "create-hash": "^1.1.2",
+ "create-hmac": "^1.1.4",
+ "ripemd160": "^2.0.1",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+ "dev": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "dev": true
+ },
+ "public-encrypt": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
+ "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "parse-asn1": "^5.0.0",
+ "randombytes": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true
+ },
+ "querystring": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+ "dev": true
+ },
+ "querystring-es3": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+ "dev": true
+ },
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "randomfill": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.0.5",
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "read-only-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz",
+ "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=",
+ "dev": true,
+ "requires": {
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "resolve": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
+ "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "ripemd160": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "sha.js": {
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "shasum": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz",
+ "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=",
+ "dev": true,
+ "requires": {
+ "json-stable-stringify": "~0.0.0",
+ "sha.js": "~2.4.4"
+ }
+ },
+ "shasum-object": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.0.tgz",
+ "integrity": "sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==",
+ "dev": true,
+ "requires": {
+ "fast-safe-stringify": "^2.0.7"
+ }
+ },
+ "shell-quote": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
+ "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
+ "dev": true
+ },
+ "simple-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+ },
+ "stream-browserify": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+ "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
+ "dev": true,
+ "requires": {
+ "inherits": "~2.0.1",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "stream-combiner2": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
+ "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=",
+ "dev": true,
+ "requires": {
+ "duplexer2": "~0.1.0",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "stream-http": {
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
+ "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
+ "dev": true,
+ "requires": {
+ "builtin-status-codes": "^3.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.3.6",
+ "to-arraybuffer": "^1.0.0",
+ "xtend": "^4.0.0"
+ }
+ },
+ "stream-splicer": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz",
+ "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "string_decoder": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ }
+ }
+ },
+ "subarg": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
+ "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.1.0"
+ }
+ },
+ "supports-color": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz",
+ "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^2.0.0"
+ }
+ },
+ "syntax-error": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz",
+ "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==",
+ "dev": true,
+ "requires": {
+ "acorn-node": "^1.2.0"
+ }
+ },
+ "through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+ "dev": true
+ },
+ "through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ }
+ },
+ "timers-browserify": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
+ "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=",
+ "dev": true,
+ "requires": {
+ "process": "~0.11.0"
+ }
+ },
+ "to-arraybuffer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
+ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
+ "dev": true
+ },
+ "tty-browserify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",
+ "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==",
+ "dev": true
+ },
+ "type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+ "dev": true
+ },
+ "umd": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz",
+ "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==",
+ "dev": true
+ },
+ "undeclared-identifiers": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz",
+ "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==",
+ "dev": true,
+ "requires": {
+ "acorn-node": "^1.3.0",
+ "dash-ast": "^1.0.0",
+ "get-assigned-identifiers": "^1.2.0",
+ "simple-concat": "^1.0.0",
+ "xtend": "^4.0.1"
+ }
+ },
+ "url": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+ "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+ "dev": true,
+ "requires": {
+ "punycode": "1.3.2",
+ "querystring": "0.2.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+ "dev": true
+ }
+ }
+ },
+ "util": {
+ "version": "0.10.4",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
+ "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ }
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true
+ },
+ "vm-browserify": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
+ "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
+ "dev": true,
+ "requires": {
+ "indexof": "0.0.1"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true
+ }
+ }
+}
diff --git a/progtest/package.json b/progtest/package.json
index 9eca202..fd6aee9 100644
--- a/progtest/package.json
+++ b/progtest/package.json
@@ -1,18 +1,16 @@
{
- "name": "tame-progtest",
- "description": "TAME Program testing",
- "version": "0.0.0",
- "author": "Ryan Specialty Group, LLC",
-
- "dependencies": {
- "easejs": "0.2.9",
- "js-yaml": "3.10.0"
- },
- "devDependencies": {
- "browserify": "16.1.0",
- "mocha": "5.0.1",
- "chai": "4.1.2"
- },
-
- "license": "GPL-3.0+"
+ "name": "tame-progtest",
+ "description": "TAME Program testing",
+ "version": "0.0.0",
+ "author": "Ryan Specialty Group, LLC",
+ "dependencies": {
+ "easejs": "0.2.9",
+ "js-yaml": "3.10.0"
+ },
+ "devDependencies": {
+ "browserify": "16.1.0",
+ "mocha": "5.0.1",
+ "chai": "4.1.2"
+ },
+ "license": "GPL-3.0+"
}
diff --git a/src/current/Makefile b/src/current/Makefile
index dc6eb54..ad94d99 100644
--- a/src/current/Makefile
+++ b/src/current/Makefile
@@ -1,5 +1,9 @@
-.PHONY: dslc clean
+.PHONY: all dslc clean html
+
+all:
+
+html:
dslc:
$(MAKE) -C src/ dslc
diff --git a/src/current/compiler/js-calc.xsl b/src/current/compiler/js-calc.xsl
index 245d4c8..64b6163 100644
--- a/src/current/compiler/js-calc.xsl
+++ b/src/current/compiler/js-calc.xsl
@@ -69,21 +69,17 @@
<template mode="compile" priority="1"
match="c:*">
<if test="$calcc-debug = 'yes'">
- <text>( function() { var result = </text>
+ <text>/*!+*/(result=/*!-*/</text>
</if>
<apply-templates select="." mode="compile-pre" />
<if test="$calcc-debug = 'yes'">
- <text>; </text>
- <text>/*!+*/( debug['</text>
+ <text>/*!+*/,(D['</text>
<value-of select="@_id" />
- <text>'] || ( debug['</text>
+ <text>']||(D['</text>
<value-of select="@_id" />
- <text>'] = [] ) ).push( result );/*!-*/ </text>
-
- <text>return result; </text>
- <text>} )() </text>
+ <text>']=[])).push(result),result)/*!-*/</text>
</if>
</template>
@@ -112,30 +108,80 @@
<template match="c:*" mode="compile-pre" priority="1">
<!-- ensure everything is grouped (for precedence) and converted to a
number -->
- <text>( </text>
<apply-templates select="." mode="compile-calc" />
- <text> )</text>
</template>
<template match="c:const[ ./c:when ]|c:value-of[ ./c:when ]" mode="compile-pre" priority="5">
- <text>( </text>
+ <text>(</text>
<!-- first, do what we normally would do (compile the value) -->
- <text>( </text>
- <apply-templates select="." mode="compile-calc" />
- <text> )</text>
+ <apply-templates select="." mode="compile-calc" />
<!-- then multiply by the c:when result -->
- <text> * ( </text>
- <for-each select="./c:when">
- <if test="position() > 1">
- <text> * </text>
- </if>
+ <text> * </text>
- <apply-templates select="." mode="compile" />
- </for-each>
- <text> )</text>
- <text> )</text>
+ <for-each select="./c:when">
+ <if test="position() > 1">
+ <text> * </text>
+ </if>
+
+ <apply-templates select="." mode="compile" />
+ </for-each>
+ <text>)</text>
+</template>
+
+
+<template mode="js-name-ref" priority="5"
+ match="c:sum[@of]|c:product[@of]">
+ <variable name="of" select="@of" />
+ <variable name="func" select="ancestor::lv:function" />
+
+ <!-- XXX: this needs to use compile-calc-value, but can't right now
+ beacuse it's not a c:value-of! -->
+ <choose>
+ <!-- is @of a function param? -->
+ <when test="
+ $func
+ and root(.)/preproc:symtable/preproc:sym[
+ @type='lparam'
+ and @name=concat( ':', $func/@name, ':', $of )
+ ]
+ ">
+
+ <value-of select="@of" />
+ </when>
+
+ <!-- let expression -->
+ <when test="$of = ancestor::c:let/c:values/c:value/@name">
+ <value-of select="$of" />
+ </when>
+
+ <!-- maybe a constant? -->
+ <when test="
+ root(.)/preproc:symtable/preproc:sym[
+ @type='const'
+ and @name=$of
+ ]
+ ">
+
+ <text>C['</text>
+ <value-of select="@of" />
+ <text>']</text>
+ </when>
+
+ <otherwise>
+ <text>A['</text>
+ <value-of select="@of" />
+ <text>']</text>
+ </otherwise>
+ </choose>
+</template>
+
+
+<template mode="js-name-ref" priority="1"
+ match="*">
+ <message select="'internal error: invalid js-name-ref src'"
+ terminate="yes" />
</template>
@@ -162,11 +208,11 @@
<variable name="precision">
<choose>
<when test="@precision">
- <value-of select="@precision" />
+ <value-of select="concat( '1e', @precision )" />
</when>
<otherwise>
- <text>8</text>
+ <text>1e8</text>
</otherwise>
</choose>
</variable>
@@ -194,75 +240,36 @@
<!-- introduce scope both to encapsulate values and so we can insert this as
part of a larger expression (will return a value) -->
- <text>( function() {</text>
+ <text>(function() {</text>
<!-- will store result of the summation/product -->
- <text>var sum = 0;</text>
-
- <variable name="of" select="@of" />
-
- <variable name="func" select="ancestor::lv:function" />
+ <text>var sum=0;</text>
<!-- XXX: this needs to use compile-calc-value, but can't right now
beacuse it's not a c:value-of! -->
<variable name="value">
- <choose>
- <!-- is @of a function param? -->
- <when test="
- $func
- and root(.)/preproc:symtable/preproc:sym[
- @type='lparam'
- and @name=concat( ':', $func/@name, ':', $of )
- ]
- ">
-
- <value-of select="@of" />
- </when>
-
- <!-- let expression -->
- <when test="$of = ancestor::c:let/c:values/c:value/@name">
- <value-of select="$of" />
- </when>
-
- <!-- maybe a constant? -->
- <when test="
- root(.)/preproc:symtable/preproc:sym[
- @type='const'
- and @name=$of
- ]
- ">
-
- <text>consts['</text>
- <value-of select="@of" />
- <text>']</text>
- </when>
-
- <otherwise>
- <text>args['</text>
- <value-of select="@of" />
- <text>']</text>
- </otherwise>
- </choose>
+ <apply-templates mode="js-name-ref"
+ select="." />
</variable>
<!-- if we're looking to generate a set, initialize it -->
<if test="@generates">
- <text>var G = []; </text>
+ <text>var G=[];</text>
</if>
<!-- loop through each value -->
- <text>for ( var </text>
+ <text>for (var </text>
<value-of select="$index" />
<text> in </text>
<value-of select="$value" />
- <text> ) {</text>
+ <text>) {</text>
- <text>var result = </text>
+ <text>var result=</text>
<!-- if caller wants to yield a vector, don't cast -->
<sequence select="if ( not( $dim gt 0 ) ) then
- '+(+( '
+ concat( 'p(', $precision, ', +(')
else
- '(( '" />
+ '('" />
<choose>
<!-- if there are child nodes, use that as the summand/expression -->
<when test="./c:*">
@@ -277,12 +284,10 @@
<text>]</text>
</otherwise>
</choose>
- <text> ))</text>
+ <text>)</text>
<!-- if caller wants to yield a vector, don't truncate -->
<if test="not( $dim gt 0 )">
- <text>.toFixed(</text>
- <value-of select="$precision" />
<text>)</text>
</if>
@@ -290,26 +295,26 @@
<!-- if generating a set, store this result -->
<if test="@generates">
- <text>G.push( result ); </text>
+ <text>G.push(result); </text>
</if>
<!-- generate summand -->
<text>sum </text>
<value-of select="$operator" />
- <text>= +result;</text>
+ <text>=+result;</text>
<!-- end of loop -->
<text>}</text>
<!-- if a set has been generated, store it -->
<if test="@generates">
- <text>args['</text>
+ <text>A['</text>
<value-of select="@generates" />
- <text>'] = G; </text>
+ <text>']=G;</text>
</if>
<text>return sum;</text>
- <text>} )()</text>
+ <text>})()</text>
</template>
@@ -322,12 +327,12 @@
validator, then the result is undefined.
-->
<template match="c:product[@dot]" mode="compile-calc" priority="5">
- <text>( function() { </text>
+ <text>(function() { </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!) -->
- <text>var _$dlen$ = longerOf( </text>
+ <text>var _$dlen$=longerOf(</text>
<for-each select=".//c:value-of">
<if test="position() > 1">
<text>, </text>
@@ -337,30 +342,30 @@
compile)-->
<apply-templates select="." mode="compile-calc" />
</for-each>
- <text> ); </text>
+ <text>); </text>
<!-- will store the total sum -->
- <text>var _$dsum$ = 0;</text>
+ <text>var _$dsum$=0;</text>
<!-- sum the product of each -->
- <text disable-output-escaping="yes">for ( var _$d$ = 0; _$d$ &lt; _$dlen$; _$d$++ ) {</text>
- <text>_$dsum$ += </text>
+ <text disable-output-escaping="yes">for(var _$d$=0; _$d$ &lt; _$dlen$; _$d$++) {</text>
+ <text>_$dsum$ +=</text>
<!-- product of each -->
<for-each select=".//c:value-of">
<if test="position() > 1">
<text> * </text>
</if>
- <text>( ( </text>
+ <text>((</text>
<apply-templates select="." mode="compile" />
- <text> || [] )[ _$d$ ] || 0 )</text>
+ <text>||[])[_$d$]||0)</text>
</for-each>
<text>; </text>
<text>}</text>
<text>return _$dsum$;</text>
- <text> } )()</text>
+ <text>})()</text>
</template>
@@ -381,6 +386,7 @@
<apply-templates select="." mode="compile-getop" />
</variable>
+ <text>(</text>
<for-each select="./c:*">
<!-- add operator between each expression -->
<if test="position() > 1">
@@ -391,6 +397,7 @@
<apply-templates select="." mode="compile" />
</for-each>
+ <text>)</text>
</template>
@@ -421,25 +428,25 @@
<choose>
<!-- if a precision was explicitly provided, then use that -->
<when test="@precision">
- <value-of select="@precision" />
+ <value-of select="concat( '1e', @precision )" />
</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 -->
<otherwise>
- <text>8</text>
+ <text>1e8</text>
</otherwise>
</choose>
</variable>
<text>Math.</text>
<value-of select="local-name()" />
- <text>( +(</text>
- <apply-templates select="./c:*" mode="compile" />
- <text> ).toFixed( </text>
+ <text>(p(</text>
<value-of select="$precision" />
- <text> ) )</text>
+ <text>, +(</text>
+ <apply-templates select="./c:*" mode="compile" />
+ <text>)))</text>
</template>
@@ -470,13 +477,16 @@
<!-- TODO: this should really be decoupled -->
<!-- TODO: does not properly support matrices -->
<template match="c:value-of[ ancestor::lv:match ]" mode="compile-calc" priority="5">
+ <param name="noindex" as="xs:boolean" tunnel="yes"
+ select="false()" />
+
<variable name="name" select="@name" />
<choose>
<!-- scalar -->
<when test="
- root(.)/preproc:symtable/preproc:sym[ @name=$name ]
- /@dim = '0'
+ $noindex
+ or root(.)/preproc:symtable/preproc:sym[ @name=$name ]/@dim = '0'
">
<apply-templates select="." mode="compile-calc-value" />
</when>
@@ -508,8 +518,6 @@
<apply-templates select="." mode="compile-calc-index">
<with-param name="value" select="@name" />
</apply-templates>
-
- <text> || 0</text>
</template>
@@ -523,8 +531,6 @@
<apply-templates select="." mode="compile-calc-index">
<with-param name="value" select="@name" />
</apply-templates>
-
- <text> || 0</text>
</template>
@@ -559,7 +565,7 @@
<choose>
<!-- "magic" constants should not have their values inlined -->
<when test="$sym/@magic='true'">
- <text>consts['</text>
+ <text>C['</text>
<value-of select="@name" />
<text>']</text>
</when>
@@ -593,7 +599,7 @@
">
<variable name="value">
- <text>consts['</text>
+ <text>C['</text>
<value-of select="@name" />
<text>']</text>
</variable>
@@ -601,9 +607,6 @@
<apply-templates select="." mode="compile-calc-index">
<with-param name="value" select="$value" />
</apply-templates>
-
- <!-- undefined values in sets are considered to be 0 -->
- <text> || 0</text>
</template>
@@ -643,7 +646,7 @@
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 -->
<variable name="value">
- <text>args['</text>
+ <text>A['</text>
<value-of select="@name" />
<text>']</text>
</variable>
@@ -686,11 +689,6 @@
<apply-templates select="." mode="compile-calc-index">
<with-param name="value" select="$value" />
</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) -->
- <text> || 0</text>
</template>
@@ -719,11 +717,8 @@
<choose>
<when test="@index">
- <!-- output the value, falling back on an empty array to prevent errors
- when attempting to access undefined values -->
<text>(</text>
- <value-of select="$value" />
- <text>||[])</text>
+ <value-of select="$value" />
<text>[</text>
@@ -752,13 +747,14 @@
<!-- otherwise, it's a variable -->
<otherwise>
- <text>args['</text>
+ <text>A['</text>
<value-of select="@index" />
<text>']</text>
</otherwise>
</choose>
- <text>]</text>
+ <!-- be sure to default to 0 if the index is missing -->
+ <text>]||0)</text>
</when>
<!-- if index node(s) were provided, then recursively generate -->
@@ -784,7 +780,7 @@
<variable name="value">
<value-of select="$wrap" />
- <!-- generate index -->
+ <!-- generate index, with a default in case the lookup fails -->
<text>[</text>
<apply-templates select="./c:*[1]" mode="compile" />
<text>]</text>
@@ -795,24 +791,20 @@
<!-- recurse on any sibling indexes, wrapping with default value -->
<apply-templates select="$next" mode="compile-calc-index">
<with-param name="wrap">
- <!-- wrap the value in parenthesis so that we can provide a default value if
- the index lookup fails -->
<text>(</text>
-
<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 -->
- <text> || 0 )</text>
+ <!-- note that this default differs from ||0, since we're going to
+ be treating it as an array with this next index -->
+ <text>||[])</text>
</with-param>
</apply-templates>
</when>
- <!-- if there is no sibling, just output our value with the index, unwrapped
- (since the caller will handle its default value for us -->
<otherwise>
+ <text>(</text>
<value-of disable-output-escaping="yes" select="$value" />
+ <text>||0)</text>
</otherwise>
</choose>
</template>
@@ -828,9 +820,11 @@
-->
<template match="c:quotient" mode="compile-calc">
<!-- we only accept a numerator and a denominator -->
+ <text>div(</text>
<apply-templates select="./c:*[1]" mode="compile" />
- <text> / </text>
+ <text>,</text>
<apply-templates select="./c:*[2]" mode="compile" />
+ <text>)</text>
</template>
@@ -854,7 +848,7 @@
@return generated function application
-->
-<template match="c:apply" mode="compile-calc">
+<template match="c:apply" mode="compile-calc" priority="5">
<variable name="name" select="@name" />
<variable name="self" select="." />
@@ -862,7 +856,7 @@
<with-param name="name" select="@name" />
</call-template>
- <text>( args</text>
+ <text>(args</text>
<variable name="arg-prefix" select="concat( ':', $name, ':' )" />
@@ -893,7 +887,7 @@
</choose>
</for-each>
- <text> )</text>
+ <text>)</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) -->
@@ -904,22 +898,118 @@
</template>
+<!--
+ Whether the given function supports tail call optimizations (TCO)
+
+ This is an experimental feature that must be explicitly requested.
+-->
+<function name="compiler:function-supports-tco" as="xs:boolean">
+ <param name="func" as="element( lv:function )" />
+
+ <sequence select="exists( $func/lv:param[
+ @name='__experimental_guided_tco' ] )" />
+</function>
+
+
+<!--
+ Whether a recursive function application is marked as being in tail
+ position within a function supporting TCO
+
+ A human must determined if a recursive call is in tail position, and
+ hopefully the human is not wrong.
+-->
+<function name="compiler:apply-uses-tco" as="xs:boolean">
+ <param name="apply" as="element( c:apply )" />
+
+ <variable name="ancestor-func" as="element( lv:function )?"
+ select="$apply/ancestor::lv:function" />
+
+ <sequence select="exists( $apply/c:arg[ @name='__experimental_guided_tco' ] )
+ and $apply/@name = $ancestor-func/@name
+ and compiler:function-supports-tco( $ancestor-func ) " />
+</function>
+
+
+<!--
+ Experimental guided tail call optimization (TCO)
+
+ When the special param `__experimental_guided_tco' is defined and set to a
+ true value, the recursive call instead overwrites the original function
+ arguments and returns a dummy value. The function's trampoline is then
+ responsible for re-invoking the function's body.
+
+ Note that this only applies to self-recursive functions; mutual recursion
+ is not recognized.
+
+ By forcing a human to specify whether a recursive call is in tail
+ position, we free ourselves from having to track tail position within this
+ nightmare of a compiler; we can figure this out properly in TAMER.
+-->
+<template mode="compile-calc" priority="7"
+ match="c:apply[ compiler:apply-uses-tco( . ) ]">
+ <variable name="name" select="@name" />
+ <variable name="self" select="." />
+
+ <message select="concat('warning: ', $name, ' recursing with experimental guided TCO')" />
+
+ <variable name="arg-prefix" select="concat( ':', $name, ':' )" />
+
+ <text>(/*TCO*/function(){</text>
+
+ <!-- reassign function arguments -->
+ <variable name="args" as="element(c:arg)*">
+ <for-each select="
+ root(.)/preproc:symtable/preproc:sym[
+ @type='func'
+ and @name=$name
+ ]/preproc:sym-ref
+ ">
+
+ <variable name="pname" select="substring-after( @name, $arg-prefix )" />
+ <variable name="arg" select="$self/c:arg[@name=$pname]" />
+
+ <!-- if the call specified this argument, then use it -->
+ <sequence select="$arg" />
+ </for-each>
+ </variable>
+
+ <!-- store reassignments first in a temporary variable, since the
+ expressions may reference the original arguments and we do not want
+ to overwrite yet -->
+ <for-each select="$args">
+ <sequence select="concat( 'const __tco_', @name, '=' )" />
+ <apply-templates select="c:*[1]" mode="compile" />
+ <text>;</text>
+ </for-each>
+
+ <!-- perform final reassignments, now that expressions no longer need the
+ original values -->
+ <for-each select="$args">
+ <sequence select="concat( @name, '=__tco_', @name, ';' )" />
+ </for-each>
+
+ <!-- return value, which doesn't matter since it won't be used -->
+ <text>return 0;})()</text>
+
+ <!-- don't support c:when here; not worth the effort -->
+ <if test="./c:when">
+ <message terminate="yes"
+ select="'c:when unsupported on TCO c:apply: ', ." />
+ </if>
+</template>
+
+
<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 -->
- <text>( function() {</text>
<!-- return a 1 or a 0 depending on the result of the expression -->
- <text>return ( </text>
- <text>( </text>
- <!-- get the value associated with this node -->
- <apply-templates select="." mode="compile-calc-value" />
- <text> ) </text>
+ <text>+(</text>
+ <apply-templates select="." mode="compile-calc-value" />
<!-- generate remainder of expression -->
<apply-templates select="./c:*[1]" mode="compile-calc-when" />
- <text>) ? 1 : 0; </text>
- <text>} )()</text>
+ <text>)</text>
</template>
@@ -936,6 +1026,7 @@
<calc-compiler:c id="lte">&lt;=</calc-compiler:c>
</variable>
+ <text> </text>
<value-of disable-output-escaping="yes" select="$map/*[ @id=$name ]" />
<text> </text>
@@ -949,7 +1040,7 @@
<template match="c:cases" mode="compile-calc">
- <text>( function() {</text>
+ <text>((function() {</text>
<for-each select="./c:case">
<!-- turn "if" into an "else if" if needed -->
@@ -966,10 +1057,10 @@
<apply-templates select="." mode="compile" />
</for-each>
- <text> ) { return </text>
+ <text>){return </text>
<!-- process on its own so that we can debug its final value -->
<apply-templates select="." mode="compile" />
- <text>; } </text>
+ <text>;}</text>
</for-each>
<!-- check for the existence of an "otherwise" clause, which should be
@@ -984,7 +1075,7 @@
</when>
<otherwise>
- <text>if ( true )</text>
+ <text>if (true)</text>
</otherwise>
</choose>
@@ -993,7 +1084,7 @@
<text>; } </text>
</if>
- <text> } )() || 0</text>
+ <text> })()||0)</text>
</template>
<template match="c:case" mode="compile-calc">
@@ -1041,12 +1132,12 @@
<text>(function(){</text>
<!-- duplicate the array just in case...if we notice a performance impact,
then we can determine if such a duplication is necessary -->
- <text>var cdr = Array.prototype.slice.call(</text>
+ <text>var cdr=Array.prototype.slice.call(</text>
<apply-templates select="$cdr" mode="compile" />
<text>, 0);</text>
<text>cdr.unshift( </text>
<apply-templates select="$car" mode="compile" />
- <text> ); </text>
+ <text>); </text>
<!-- no longer the cdr -->
<text>return cdr; </text>
<text>})()</text>
@@ -1069,9 +1160,9 @@
Returns the length of any type of set (not just a vector)
-->
<template match="c:length-of" mode="compile-calc">
- <text>( </text>
+ <text>(</text>
<apply-templates select="./c:*[1]" mode="compile" />
- <text>.length || 0 )</text>
+ <text>.length||0)</text>
</template>
@@ -1089,9 +1180,9 @@
</if>
</variable>
- <text>function </text>
+ <text>(function </text>
<value-of select="$fname" />
- <text>( </text>
+ <text>(</text>
<!-- generate arguments -->
<for-each select="$values">
<if test="position() > 1">
@@ -1100,28 +1191,25 @@
<value-of select="@name" />
</for-each>
- <text> ) { </text>
+ <text>){</text>
<!-- the second node is the body -->
<text>return </text>
<apply-templates select="./c:*[2]" mode="compile" />
<text>;</text>
- <text>}</text>
+ <text>})</text>
<!-- assign the arguments according to the calculations -->
- <text>( </text>
+ <text>(</text>
<for-each select="$values">
<if test="position() > 1">
<text>,</text>
</if>
- <!-- compile the argument value (the parenthesis are just to make it
- easier to read the compiled code) -->
- <text>(</text>
- <apply-templates select="./c:*[1]" mode="compile" />
- <text>)</text>
+ <!-- compile the argument value -->
+ <apply-templates select="./c:*[1]" mode="compile" />
</for-each>
- <text> ) </text>
+ <text>)</text>
</template>
@@ -1141,20 +1229,20 @@
<template match="c:debug-to-console" mode="compile-calc">
<text>(function(){</text>
- <text>var result = </text>
+ <text>var result=</text>
<apply-templates select="./c:*[1]" mode="compile" />
<text>;</text>
<!-- log the result and return it so that we do not inhibit the calculation
(allowing it to be inlined anywhere) -->
- <text>console.log( </text>
+ <text>console.log(</text>
<if test="@label">
<text>'</text>
<value-of select="@label" />
<text>', </text>
</if>
- <text>result ); </text>
+ <text>result); </text>
<text>return result; </text>
<text>})()</text>
</template>
@@ -1171,11 +1259,8 @@
@return self-executing anonymous error function
-->
<template match="c:*" mode="compile-calc">
- <text>( function () {</text>
- <text>throw Error( "Unknown calculation: </text>
- <value-of select="name()" />
- <text>" ); </text>
- <text>} )() </text>
+ <message terminate="yes"
+ select="'unknown calculation type:', name()" />
</template>
</stylesheet>
diff --git a/src/current/compiler/js-legacy.xsl b/src/current/compiler/js-legacy.xsl
new file mode 100644
index 0000000..2f2b1c0
--- /dev/null
+++ b/src/current/compiler/js-legacy.xsl
@@ -0,0 +1,508 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ Compiles rater XML into JavaScript (legacy classification system)
+
+ Copyright (C) 2014-2021 Ryan Specialty Group, LLC.
+
+ This file is part of TAME.
+
+ TAME is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+-->
+<stylesheet version="2.0"
+ xmlns="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:map="http://www.w3.org/2005/xpath-functions/map"
+ 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">
+
+
+<function name="compiler:compile-classify-legacy" as="xs:string+">
+ <param name="symtable-map" as="map(*)" />
+ <param name="classify" as="element( lv:classify )" />
+
+ <apply-templates mode="compile-legacy" select="$classify">
+ <with-param name="symtable-map" select="$symtable-map"
+ tunnel="yes" />
+ </apply-templates>
+</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.
+
+ @return generated classification expression
+-->
+<template match="lv:classify" mode="compile-legacy">
+ <param name="symtable-map" as="map(*)" tunnel="yes" />
+ <param name="noclass" />
+ <param name="ignores" />
+
+ <variable name="self" select="." />
+
+ <value-of select="$compiler:nl" />
+
+ <variable name="dest">
+ <text>A['</text>
+ <value-of select="@yields" />
+ <text>']</text>
+ </variable>
+
+ <if test="not( $noclass )">
+ <sequence select="concat( $dest, '=[];', $compiler:nl )" />
+
+ <if test="@preproc:generated='true'">
+ <text>g</text>
+ </if>
+
+ <text>c['</text>
+ <value-of select="@as" />
+ <text>'] = (function(){var result,tmp; </text>
+ </if>
+
+ <!-- locate classification predicates (since lv:any and lv:all are split
+ into their own classifications, matching on any depth ensures we get
+ into any preproc:* nodes as well) -->
+ <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="for $match in $criteria
+ return $symtable-map( $match/@on )" />
+
+ <!-- generate boolean value from match expressions -->
+ <choose>
+ <!-- if classification criteria were provided, then use them -->
+ <when test="$criteria">
+ <variable name="op" as="xs:string"
+ select="compiler:match-group-op( $self )" />
+
+ <text></text>
+ <!-- order matches from highest to lowest dimensions (required for
+ the cmatch algorithm)-->
+ <for-each select="reverse( xs:integer( min( $criteria-syms/@dim ) )
+ to xs:integer( max( $criteria-syms/@dim ) ) )">
+ <apply-templates mode="compile-legacy"
+ select="$criteria[
+ @on = $criteria-syms[
+ @dim = current() ]/@name ]">
+ <with-param name="ignores" select="$ignores" />
+ <with-param name="operator" select="$op" />
+ </apply-templates>
+ </for-each>
+ </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">
+ <value-of select="$dest" />
+ <choose>
+ <!-- universal -->
+ <when test="not( @any='true' )">
+ <text> = 1;</text>
+ </when>
+
+ <!-- existential -->
+ <otherwise>
+ <text> = 0;</text>
+ </otherwise>
+ </choose>
+ </if>
+ </otherwise>
+ </choose>
+
+ <text> return tmp;})();</text>
+
+ <!-- support termination on certain classifications (useful for eligibility
+ and error conditions) -->
+ <if test="@terminate = 'true'">
+ <text>if ( _canterm &amp;&amp; </text>
+
+ <if test="@preproc:generated='true'">
+ <text>g</text>
+ </if>
+ <text>c['</text>
+ <value-of select="@as" />
+ <text>'] ) throw Error( '</text>
+ <value-of select="replace( @desc, '''', '\\''' )" />
+ <text>' );</text>
+
+ <value-of select="$compiler:nl" />
+ </if>
+
+ <variable name="sym"
+ select="$symtable-map( $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 and ( $sym/@dim='0' )">
+ <value-of select="$dest" />
+ <text> = </text>
+ <value-of select="$dest" />
+ <text>[0];</text>
+
+ <value-of select="$compiler:nl" />
+ </if>
+</template>
+
+
+<!--
+ Generate 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-legacy" priority="1">
+ <param name="symtable-map" as="map(*)" tunnel="yes" />
+
+ <!-- default to all matches being required -->
+ <param name="operator" select="'&amp;&amp;'" />
+ <param name="yields" select="../@yields" />
+
+ <variable name="name" select="@on" />
+
+ <variable name="sym-on" as="element( preproc:sym )"
+ select="$symtable-map( $name )" />
+
+ <text> tmp = </text>
+
+ <variable name="input-raw">
+ <choose>
+ <!-- if we have assumptions, then we'll be recalculating (rather than
+ referencing) an existing classification -->
+ <when test="lv:assuming">
+ <text>_cassume</text>
+ </when>
+
+ <otherwise>
+ <choose>
+ <when test="$sym-on/@type = 'const'">
+ <text>consts</text>
+ </when>
+ <otherwise>
+ <text>A</text>
+ </otherwise>
+ </choose>
+
+ <text>['</text>
+ <value-of select="translate( @on, &quot;'&quot;, '' )" />
+ <text>']</text>
+ </otherwise>
+ </choose>
+ </variable>
+
+ <!-- yields (if not set, generate one so that cmatches still works properly)
+ -->
+ <variable name="yieldto">
+ <call-template name="compiler:gen-match-yieldto">
+ <with-param name="yields" select="$yields" />
+ </call-template>
+ </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" as="element( preproc:sym )?"
+ select="$symtable-map( $value )" />
+
+ <choose>
+ <!-- value unavailable (TODO: vector/matrix support) -->
+ <when test="$sym and not( $sym/@value )">
+ <message>
+ <text>[jsc] !!! bad classification match: `</text>
+ <value-of select="$value" />
+ <text>' is not a scalar constant</text>
+ </message>
+ </when>
+
+ <!-- simple constant -->
+ <when test="$sym and @value">
+ <value-of select="$sym/@value" />
+ </when>
+
+ <otherwise>
+ <text>'</text>
+ <!-- TODO: Should we disallow entirely? -->
+ <message>
+ <text>[jsc] warning: static classification match '</text>
+ <value-of select="$value" />
+ <text>' in </text>
+ <value-of select="ancestor::lv:classify[1]/@as" />
+ <text>; use calculation predicate or constant instead</text>
+ </message>
+
+ <value-of select="$value" />
+ <text>'</text>
+ </otherwise>
+ </choose>
+ </when>
+
+ <when test="@pattern">
+ <text>function( val ) { </text>
+ <text>return /</text>
+ <value-of select="@pattern" />
+ <text>/.test( val );</text>
+ <text> }</text>
+ </when>
+
+ <when test="./c:*">
+ <text>function( val, __$$i ) { </text>
+ <text>return ( </text>
+ <for-each select="./c:*">
+ <if test="position() > 1">
+ <text disable-output-escaping="yes"> &amp;&amp; </text>
+ </if>
+
+ <text>( val </text>
+ <apply-templates select="." mode="compile-calc-when" />
+ <text> ) </text>
+ </for-each>
+ <text>);</text>
+ <text>}</text>
+ </when>
+
+ <otherwise>
+ <apply-templates select="." mode="compiler:match-anyof-legacy" />
+ </otherwise>
+ </choose>
+
+ <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 -->
+ <if test="$debug-id-on-stack">
+ <text>/*!+*/,"</text>
+ <value-of select="$input" />
+ <text>"/*!-*/</text>
+ </if>
+
+ <!-- end of anyValue() call -->
+ <text> ) </text>
+
+ <!-- end of assuming function call -->
+ <if test="lv:assuming">
+ <text>})()</text>
+ </if>
+
+ <text>;</text>
+
+ <text>/*!+*/( D['</text>
+ <value-of select="@_id" />
+ <text>'] || ( D['</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>
+
+
+<!--
+ 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-legacy" priority="5">
+ <!-- ceil(x) - floor(x) = [ x is not an integer ] -->
+ <text>function( val ) {</text>
+ <text>return ( typeof +val === 'number' ) </text>
+ <text disable-output-escaping="yes">&amp;&amp; </text>
+ <!-- note: greater than or equal to, since we want to permit integers as
+ well -->
+ <text disable-output-escaping="yes">( Math.ceil( val ) >= Math.floor( val ) )</text>
+ <text>;</text>
+ <text>}</text>
+</template>
+
+<!--
+ Handles the special "float" domain
+
+ Rather than the impossible task of calculating all possible integers and
+ asserting that the given values is within that set, the obvious task is to
+ assert whether or not the value is logically capable of existing in such a
+ set based on a definition of such a set.
+
+ See also "float"
+-->
+<template match="lv:match[ @anyOf='integer' ]" mode="compiler:match-anyof-legacy" priority="5">
+ <!-- ceil(x) - floor(x) = [ x is not an integer ] -->
+ <text>function( val ) {</text>
+ <text>return ( typeof +val === 'number' ) </text>
+ <text disable-output-escaping="yes">&amp;&amp; </text>
+ <text>( Math.floor( val ) === Math.ceil( val ) )</text>
+ <text>;</text>
+ <text>}</text>
+</template>
+
+<!--
+ Handles matching on an empty set
+
+ This is useful for asserting against fields that may have default values,
+ since in such a case an empty value would be permitted.
+-->
+<template match="lv:match[ @anyOf='empty' ]" mode="compiler:match-anyof-legacy" 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-legacy" 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-legacy" priority="1">
+ <text>types['</text>
+ <value-of select="@anyOf" />
+ <text>'].values</text>
+</template>
+
+
+</stylesheet>
diff --git a/src/current/compiler/js.xsl b/src/current/compiler/js.xsl
index 7c0c94f..2612c7e 100644
--- a/src/current/compiler/js.xsl
+++ b/src/current/compiler/js.xsl
@@ -27,6 +27,7 @@
<stylesheet version="2.0"
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:lvp="http://www.lovullo.com"
xmlns:c="http://www.lovullo.com/calc"
@@ -38,6 +39,8 @@
xmlns:util="http://www.lovullo.com/util"
xmlns:ext="http://www.lovullo.com/ext">
+<!-- legacy classification system -->
+<include href="js-legacy.xsl" />
<!-- calculation compiler -->
<include href="js-calc.xsl" />
@@ -68,9 +71,9 @@
<!-- 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>/**@expose*/var consts = {};</text>
- <text>/**@expose*/var debug = {};</text>
- <text>/**@expose*/var params = {};</text>
+ <text>/**@expose*/var C, consts = C = {};</text>
+ <text>/**@expose*/var D, debug = D = {};</text>
+ <text>/**@expose*/var P, params = P = {};</text>
<text>/**@expose*/var types = {};</text>
<text>/**@expose*/var meta = {};</text>
</template>
@@ -83,7 +86,7 @@
<text>_canterm = ( _canterm == undefined ) ? true : !!_canterm;</text>
<!-- XXX: fix; clear debug from any previous runs -->
- <text>debug = {};</text>
+ <text>debug = D = {};</text>
<!-- magic constants (N.B. these ones must be re-calculated with each
call, otherwise the data may be incorrect!) -->
@@ -94,7 +97,7 @@
<!-- 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>
+ <text>var A, args = A = Object.create( arglist );</text>
<!-- will store the global params that we ended up requiring -->
<text>var req_params = {};</text>
@@ -103,8 +106,11 @@
<text>init_defaults( args, params );</text>
<value-of select="$compiler:nl" />
- <text>/**@expose*/var classes = {};</text>
- <text>/**@expose*/var genclasses = {};</text>
+ <text>/**@expose*/var c, classes = c = {};</text>
+ <text>/**@expose*/var gc, genclasses = gc = {};</text>
+
+ <!-- temporaries used in computations -->
+ <text>var result, tmp;</text>
</template>
<template name="compiler:classifier">
@@ -183,7 +189,7 @@
<text>'</text>
<value-of select="@name" />
- <text>': true</text>
+ <text>': !1</text>
</for-each>
<text>}; </text>
@@ -240,18 +246,18 @@
-->
<template match="lv:param" mode="compile">
<!-- generate key using param name -->
- <text>params['</text>
+ <text>P['</text>
<value-of select="@name" />
- <text>'] = {</text>
+ <text>']={</text>
<!-- param properties -->
<text>type: '</text>
<value-of select="@type" />
<text>',</text>
- <text>'default': '</text>
- <value-of select="@default" />
- <text>',</text>
+ <text>'default':</text>
+ <value-of select="if ( @default ) then number(@default) else '0'" />
+ <text>,</text>
<text>depth: </text>
<!-- TODO: this logic is duplicated multiple places -->
@@ -289,7 +295,7 @@
<!-- generate key using type name -->
<text>types['</text>
<value-of select="../@name" />
- <text>'] = {</text>
+ <text>']={</text>
<!-- its type will be the type of its first enum (all must share the same
domain) -->
@@ -321,7 +327,7 @@
<!-- generate key using type name -->
<text>types['</text>
<value-of select="../@name" />
- <text>'] = {</text>
+ <text>']={</text>
<!-- domain of all values -->
<text>type: '</text>
@@ -346,7 +352,7 @@
<template match="lv:typedef/lv:base-type" mode="compile" priority="5">
<text>types['</text>
<value-of select="../@name" />
- <text>'] = {</text>
+ <text>']={</text>
<!-- base types are their own type -->
<text>type: '</text>
@@ -392,9 +398,9 @@
<!-- we are only interest in its value; its constant is an internal value -->
<sequence select="if ( $as-const ) then
- concat( 'consts[''', @name, '''] = ', $value, ';' )
+ concat( 'C[''', @name, ''']=', $value, ';' )
else
- concat( '''', $value, ''': true' )" />
+ concat( '''', $value, ''':1' )" />
</template>
@@ -408,37 +414,37 @@
-->
<template mode="compile" priority="2"
match="lv:const[ element() or @values ]">
- <text>consts['</text>
+ <text>C['</text>
<value-of select="@name" />
- <text>'] = [ </text>
+ <text>']=[</text>
<!-- matrices -->
<for-each select="compiler:const-sets( . )[ not( . = '' ) ]">
<if test="position() > 1">
- <text>, </text>
+ <text>,</text>
</if>
- <text>[ </text>
+ <text>[</text>
<for-each select="compiler:set-items( ., true() )">
<if test="position() > 1">
- <text>, </text>
+ <text>,</text>
</if>
<value-of select="compiler:js-number( . )" />
</for-each>
- <text> ]</text>
+ <text>]</text>
</for-each>
<!-- vectors -->
<for-each select="compiler:set-items( ., false() )">
<if test="position() > 1">
- <text>, </text>
+ <text>,</text>
</if>
<value-of select="compiler:js-number( . )" />
</for-each>
- <text> ]; </text>
+ <text>];</text>
</template>
@@ -447,9 +453,9 @@
-->
<template mode="compile" priority="1"
match="lv:const">
- <text>consts['</text>
+ <text>C['</text>
<value-of select="@name" />
- <text>'] = </text>
+ <text>']=</text>
<value-of select="compiler:js-number( @value )" />
<text>;</text>
</template>
@@ -531,147 +537,730 @@
<!--
- Generate code to perform a classification
+ Single-TRUE-match classifications are effectively aliases
+-->
+<template mode="compile" priority="7"
+ match="lv:classify[ count( lv:match ) = 1
+ and lv:match/@value='TRUE'
+ and not( lv:match/@preproc:inline ) ]">
+ <param name="symtable-map" as="map(*)" tunnel="yes" />
- Based on the criteria provided by the classification, generate and store the
- result of a boolean expression performing the classification using global
- arguments.
+ <variable name="src" as="xs:string"
+ select="lv:match/@on" />
+ <variable name="src-sym" as="element( preproc:sym )"
+ select="$symtable-map( $src )" />
- @return generated classification expression
+ <choose>
+ <!-- we only handle aliasing of other classifications -->
+ <when test="$src-sym/@type = 'cgen'">
+ <sequence select="$compiler:nl" />
+
+ <!-- simply alias the @yields -->
+ <sequence select="concat( 'A[''', @yields, '''] = ',
+ 'A[''', $src, ''']; ')" />
+
+ <variable name="class-sym" as="element( preproc:sym )"
+ select="$symtable-map( $src-sym/@parent )" />
+
+ <variable name="cdest" as="xs:string"
+ select="if ( @preproc:generated = 'true' ) then
+ 'gc'
+ else
+ 'c'" />
+
+ <variable name="cdest-src" as="xs:string"
+ select="if ( $class-sym/@preproc:generated = 'true' ) then
+ 'gc'
+ else
+ 'c'" />
+
+ <sequence select="concat( $cdest, '[''', @as, '''] = ',
+ $cdest-src, '[''',
+ $class-sym/@orig-name, '''];' )" />
+ </when>
+
+ <!-- they must otherwise undergo the usual computation -->
+ <otherwise>
+ <next-match />
+ </otherwise>
+ </choose>
+</template>
+
+
+<!--
+ Classification with no predicates always yields true/false, depending on
+ whether it's conjunctive or disjunctive
+-->
+<template mode="compile" priority="7"
+ match="lv:classify[ empty( lv:match ) ]">
+ <variable name="val" as="xs:string"
+ select="if ( not( @any = 'true' ) ) then '1' else '0'" />
+
+ <value-of select="$compiler:nl" />
+ <sequence select="concat( compiler:class-var(.), '=!!', $val, ';' )" />
+
+ <if test="@yields">
+ <sequence select="concat( compiler:class-yields-var(.), '=', $val, ';' )" />
+ </if>
+</template>
+
+
+<!--
+ JS variable to which boolean class result will be assigned
-->
-<template match="lv:classify" mode="compile">
+<function name="compiler:class-var" as="xs:string">
+ <param name="class" as="element( lv:classify )" />
+
+ <variable name="prefix" as="xs:string"
+ select="if ( $class/@preproc:generated='true' ) then
+ 'g'
+ else
+ ''" />
+
+ <sequence select="concat( $prefix, 'c[''', $class/@as, ''']' )" />
+</function>
+
+
+<!--
+ JS variable to which class @yields will be assigned
+-->
+<function name="compiler:class-yields-var" as="xs:string">
+ <param name="class" as="element( lv:classify )" />
+
+ <sequence select="concat( 'A[''', $class/@yields, ''']' )" />
+</function>
+
+
+<template mode="compile" priority="6"
+ match="lv:classify[ compiler:use-legacy-classify(.) ]">
+ <param name="symtable-map" as="map(*)" tunnel="yes" />
+
+ <sequence select="concat(
+ $compiler:nl,
+ '/*!lc*/',
+ string-join(
+ compiler:compile-classify-legacy( $symtable-map, . ),
+ '' ),
+ '/*lc!*/' )" />
+</template>
+
+
+<template match="lv:classify" mode="compile" priority="5">
<param name="symtable-map" as="map(*)" tunnel="yes" />
- <param name="noclass" />
- <param name="ignores" />
- <variable name="self" select="." />
- <value-of select="$compiler:nl" />
+ <message select="concat( 'internal: new: ', @as )" />
+ <sequence select="compiler:compile-classify-assign( $symtable-map, . )" />
+</template>
- <variable name="dest">
- <text>args['</text>
- <value-of select="@yields" />
- <text>']</text>
- </variable>
- <if test="not( $noclass )">
- <sequence select="concat( $dest, '=[];', $compiler:nl )" />
+<template mode="compile" priority="8"
+ match="lv:classify[
+ @preproc:inline='true'
+ and not( compiler:use-legacy-classify(.) ) ]">
+ <!-- emit nothing; it'll be inlined at the match site -->
+</template>
- <if test="@preproc:generated='true'">
- <text>gen</text>
- </if>
- <text>classes['</text>
- <value-of select="@as" />
- <text>'] = (function(){var result,tmp; </text>
- </if>
+<template mode="compile" priority="7"
+ match="lv:classify[ @terminate='true' ]">
+ <next-match />
+
+ <variable name="var" as="xs:string"
+ select="compiler:class-var( . )" />
+
+ <text>if (_canterm &amp;&amp; </text>
+ <value-of select="$var" />
+ <text>) throw Error( '</text>
+ <value-of select="replace( @desc, '''', '\\''' )" />
+ <text>');</text>
+</template>
+
+
+<!--
+ Raise $inner of type $from to $outer of type $to with universal or
+ existential ($ue) quantification
+
+ If the inner value is empty, simply return the outer without any action.
+-->
+<function name="compiler:lift-match" as="xs:string">
+ <param name="from" as="xs:string" />
+ <param name="to" as="xs:string" />
+ <param name="ue" as="xs:string" />
+ <param name="inner" as="xs:string" />
+ <param name="outer" as="xs:string" />
+
+ <sequence select="if ( $inner = '' ) then
+ $outer
+ else if ( $outer = '' ) then
+ $inner
+ else
+ concat( $from, $to, $ue, '(',
+ $outer, ',', $inner, ')' )" />
+</function>
+
+
+<function name="compiler:use-legacy-classify" as="xs:boolean">
+ <param name="classify" as="element( lv:classify )" />
+
+ <variable name="flagname" as="xs:string"
+ select="'___feature-newclassify'" />
+
+ <sequence select="empty(
+ ( $classify | $classify/ancestor::* )
+ /preceding-sibling::preproc:tpl-meta[
+ @name=$flagname and @value = '1' ] )" />
+</function>
+
+
+<function name="compiler:compile-classify-assign" as="xs:string">
+ <param name="symtable-map" as="map(*)" />
+ <param name="classify" as="element( lv:classify )" />
+
+ <sequence select="string-join(
+ ( $compiler:nl,
+ compiler:compile-classify(
+ $symtable-map, $classify ) ) )" />
+</function>
+
+
+<function name="compiler:compile-classify-inline" as="xs:string">
+ <param name="symtable-map" as="map(*)" />
+ <param name="classify" as="element( lv:classify )" />
+
+ <!-- keep only the JS expression, grouping to ensure that surrounding
+ expressions (scalars, specifically, that lack grouping) maintain
+ their precedence -->
+ <sequence select="concat(
+ '(',
+ compiler:compile-classify(
+ $symtable-map, $classify )[ 2 ],
+ ')' )" />
+</function>
+
+<!--
+ Generate code to perform a classification
+
+ This return a sequence of (assignment prefix, compiled js, assignment
+ suffix); the caller should keep the assignment prefix and suffix for
+ normal compilation, but should keep only the JS portion (which is a
+ standalone expression) for inlining.
+-->
+<function name="compiler:compile-classify" as="xs:string+">
+ <param name="symtable-map" as="map(*)" />
+ <param name="classify" as="element( lv:classify )" />
+
+ <variable name="dest" as="xs:string"
+ select="compiler:class-yields-var( $classify )" />
<!-- locate classification predicates (since lv:any and lv:all are split
into their own classifications, matching on any depth ensures we get
into any preproc:* nodes as well) -->
<variable name="criteria" as="element( lv:match )*"
- select="./lv:match[
- not( $ignores )
- or not( @on=$ignores/@ref ) ]" />
+ select="$classify/lv:match" />
- <variable name="criteria-syms" as="element( preproc:sym )*"
- select="for $match in $criteria
- return $symtable-map( $match/@on )" />
+ <variable name="criteria-syms"
+ as="map( xs:string, element( preproc:sym ) )"
+ select="map:merge(
+ for $match in $criteria
+ return map{ string( $match/@on ) :
+ $symtable-map( $match/@on ) } )" />
<!-- generate boolean value from match expressions -->
+ <variable name="op" as="xs:string"
+ select="compiler:match-group-op( $classify )" />
+
+ <variable name="scalars" as="element( lv:match )*"
+ select="$criteria[ $criteria-syms( @on )/@dim = '0' ]" />
+ <variable name="vectors" as="element( lv:match )*"
+ select="$criteria[ $criteria-syms( @on )/@dim = '1' ]" />
+ <variable name="matrices" as="element( lv:match )*"
+ select="$criteria[ $criteria-syms( @on )/@dim = '2' ]" />
+
+ <variable name="ns" select="count( $scalars )" />
+ <variable name="nv" select="count( $vectors )" />
+ <variable name="nm" select="count( $matrices )" />
+
+ <variable name="var" as="xs:string"
+ select="compiler:class-var( $classify )" />
+
+ <variable name="m1" as="element( lv:match )?" select="$matrices[1]" />
+ <variable name="v1" as="element( lv:match )?" select="$vectors[1]" />
+
+ <variable name="yield-to">
+ <call-template name="compiler:gen-match-yieldto">
+ <with-param name="yields" select="$classify/@yields" />
+ </call-template>
+ </variable>
+
+ <!-- existential, universal -->
+ <variable name="ctype" as="xs:string"
+ select="if ( $classify/@any='true' ) then 'e' else 'u'" />
+
+ <variable name="js-matrix" as="xs:string"
+ select="compiler:optimized-matrix-matches(
+ $symtable-map, $classify, $matrices )" />
+ <variable name="js-vec" as="xs:string"
+ select="compiler:optimized-vec-matches(
+ $symtable-map, $classify, $vectors )" />
+ <variable name="js-scalar" as="xs:string"
+ select="compiler:optimized-scalar-matches(
+ $symtable-map, $classify, $scalars )" />
+
+ <variable name="reduce" as="xs:string"
+ select="if ( $nm > 0 ) then
+ 'Em'
+ else if ( $nv > 0 ) then
+ 'E'
+ else
+ '!!'" />
+
+ <variable name="outer-type" as="xs:string"
+ select="if ( $nm > 0 ) then
+ 'm'
+ else if ( $nv > 0 ) then
+ 'v'
+ else
+ 's'" />
+
+ <variable name="js" as="xs:string"
+ select="compiler:lift-match(
+ 's', $outer-type, $ctype,
+ $js-scalar,
+ compiler:lift-match(
+ 'v', $outer-type, $ctype,
+ $js-vec,
+ $js-matrix ) )" />
+
+ <!-- sequence of (assignment prefix, js, assignment suffix); it's up to
+ the caller to determine which of these to keep -->
+ <sequence select="concat( $var, '=', $reduce,
+ '(', $yield-to, '=' ),
+ $js,
+ ');'" />
+</function>
+
+
+<!--
+ Generate JS suitable for a value match
+
+ If the value is _not_ a basic @value equality match, then it will be
+ wrapped in the necessary expression to transform it into a binary result
+ that can then be matched against `TRUE`.
+-->
+<function name="compiler:match-on" as="xs:string">
+ <param name="symtable-map" as="map(*)" />
+ <param name="match" as="element( lv:match )" />
+
+ <variable name="dim" as="xs:integer"
+ select="$symtable-map( $match/@on )/@dim" />
+
+ <variable name="inner" as="xs:string"
+ select="compiler:match-name-on( $symtable-map, $match )" />
+
+ <variable name="mf" as="xs:string"
+ select="if ( $dim = 2 ) then 'MM' else 'M'" />
+ <variable name="nf" as="xs:string"
+ select="if ( $dim = 2 ) then 'NN' else 'N'" />
+
<choose>
- <!-- if classification criteria were provided, then use them -->
- <when test="$criteria">
- <variable name="op" as="xs:string"
- select="compiler:match-group-op( $self )" />
-
- <text></text>
- <!-- order matches from highest to lowest dimensions (required for
- the cmatch algorithm)-->
- <for-each select="reverse( xs:integer( min( $criteria-syms/@dim ) )
- to xs:integer( max( $criteria-syms/@dim ) ) )">
- <apply-templates mode="compile"
- select="$criteria[
- @on = $criteria-syms[
- @dim = current() ]/@name ]">
- <with-param name="ignores" select="$ignores" />
- <with-param name="operator" select="$op" />
- </apply-templates>
- </for-each>
+ <!-- only basic TRUE equality can be used verbatim -->
+ <when test="$match/@value = 'TRUE'">
+ <sequence select="$inner" />
</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>
+ <when test="$match/@anyOf">
+ <variable name="anyof" as="xs:string"
+ select="compiler:compile-anyof( $symtable-map, $match )" />
+
+ <choose>
+ <when test="$dim > 0">
+ <sequence select="concat( $mf, '(', $inner, ',', $anyof, ')' )" />
+ </when>
+ <otherwise>
+ <sequence select="concat( $anyof, '(', $inner, ')' )" />
+ </otherwise>
+ </choose>
+ </when>
- <!-- 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">
- <value-of select="$dest" />
- <choose>
- <!-- universal -->
- <when test="not( @any='true' )">
- <text> = 1;</text>
- </when>
-
- <!-- existential -->
- <otherwise>
- <text> = 0;</text>
- </otherwise>
- </choose>
+ <when test="$match[c:eq|c:ne|c:gt|c:lt|c:gte|c:lte]">
+ <variable name="c" as="element()"
+ select="$match/c:*" />
+ <variable name="name" as="xs:string"
+ select="$c/local-name()" />
+
+ <!-- should only be _one_ (@as validates this) -->
+ <variable name="expr" as="element()" select="$c/c:*" />
+
+ <!-- if it's not c:value-of, it must be scalar c:const (see lv:match
+ in validator) -->
+ <variable name="cdim" as="xs:integer"
+ select="if ( $c/c:value-of ) then
+ $symtable-map( $c/c:value-of/@name )/@dim
+ else
+ 0" />
+ <variable name="cval" as="xs:float?"
+ select="if ( $c/c:value-of ) then
+ $symtable-map( $c/c:value-of/@name )/@value
+ else
+ $c/c:const/@value" />
+
+ <variable name="indexed" as="xs:boolean" select="$cdim gt 0" />
+
+ <variable name="f" as="xs:string"
+ select="concat( 'c', $name,
+ ( if ( $indexed ) then 'i' else '' ) )" />
+
+ <!-- TODO: remove generation of useless debug output! -->
+ <variable name="exprjs" as="xs:string"
+ select="compiler:compile( $symtable-map, $expr )" />
+ <variable name="transform" as="xs:string"
+ select="concat( $f, '(', $exprjs, ')' )" />
+
+ <!-- scalars should have already been caught during validation, but we
+ do not support matrices -->
+ <if test="$indexed and $dim = 2">
+ <message terminate="yes"
+ select="concat( 'error: lv:match/c:*/c:index unsupported ',
+ 'for matrix `',
+ $match/parent::lv/classify/@as, '''' )" />
</if>
+
+ <choose>
+ <when test="$dim > 0">
+ <choose>
+ <!-- negation, very common, so save some bytes -->
+ <when test="$match/c:eq and $cval = 0">
+ <sequence select="concat( $nf, '(', $inner, ')' )" />
+ </when>
+
+ <otherwise>
+ <sequence select="concat( $mf, '(', $inner, ',', $transform, ')' )" />
+ </otherwise>
+ </choose>
+ </when>
+
+ <!-- scalar, simply apply -->
+ <otherwise>
+ <choose>
+ <!-- negation, very common, so save some bytes -->
+ <when test="$match/c:eq and $cval = 0">
+ <sequence select="concat( 'n(', $inner, ')' )" />
+ </when>
+
+ <otherwise>
+ <sequence select="concat( $transform, '(', $inner, ')' )" />
+ </otherwise>
+ </choose>
+ </otherwise>
+ </choose>
+ </when>
+
+ <otherwise>
+ <message terminate="yes" select="'error: not yet handled', $match" />
</otherwise>
</choose>
+</function>
- <text> return tmp;})();</text>
- <!-- support termination on certain classifications (useful for eligibility
- and error conditions) -->
- <if test="@terminate = 'true'">
- <text>if ( _canterm &amp;&amp; </text>
+<function name="compiler:compile" as="xs:string">
+ <param name="symtable-map" as="map(*)" />
+ <param name="element" as="element()" />
+
+ <variable name="result">
+ <apply-templates select="$element" mode="compile">
+ <with-param name="symtable-map" select="$symtable-map"
+ tunnel="true" />
+
+ <!-- suppress match index generation, since we handle it ourselves now
+ (in a different way) -->
+ <with-param name="noindex" select="true()"
+ tunnel="true" />
+ </apply-templates>
+ </variable>
- <if test="@preproc:generated='true'">
- <text>gen</text>
- </if>
- <text>classes['</text>
- <value-of select="@as" />
- <text>'] ) throw Error( '</text>
- <value-of select="replace( @desc, '''', '\\''' )" />
- <text>' );</text>
+ <sequence select="string-join( $result, '' )" />
+</function>
- <value-of select="$compiler:nl" />
- </if>
- <variable name="sym"
- select="$symtable-map( $self/@yields )" />
+<function name="compiler:compile-anyof" as="xs:string">
+ <param name="symtable-map" as="map(*)" />
+ <param name="element" as="element()" />
- <!-- 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 and ( $sym/@dim='0' )">
- <value-of select="$dest" />
- <text> = </text>
- <value-of select="$dest" />
- <text>[0];</text>
+ <apply-templates select="$element" mode="compiler:match-anyof">
+ <with-param name="symtable-map" select="$symtable-map"
+ tunnel="true" />
+ </apply-templates>
+</function>
- <value-of select="$compiler:nl" />
+
+<!--
+ Whether a set of matches is matching against a list of values and can be
+ optimized as such
+-->
+<function name="compiler:is-value-list" as="xs:boolean">
+ <param name="symtable-map" as="map(*)" />
+ <param name="matches" as="element( lv:match )+" />
+
+ <sequence select="
+ count( $matches ) > 1
+ and count( distinct-values( $matches/@on ) ) = 1
+ and empty(
+ $matches[
+ not( c:eq )
+ or (
+ c:eq/c:value-of
+ and $symtable-map( c:eq/c:value-of/@name )/@dim != '0' ) ] )" />
+</function>
+
+
+<!--
+ Output an optimized match against a list of values.
+
+ This must only be used if compiler:is-value-list is `true()`.
+-->
+<function name="compiler:value-list" as="xs:string">
+ <param name="symtable-map" as="map(*)" />
+ <param name="classify" as="element( lv:classify )" />
+ <param name="matches" as="element( lv:match )+" />
+
+ <!-- if this is not @any, then it's nonsense -->
+ <if test="not( $classify/@any = 'true' )">
+ <message terminate="yes"
+ select="concat( 'error: ', $classify/@as, ' match ',
+ $matches[1]/@on, 'will never succeed' )" />
</if>
-</template>
+
+
+ <variable name="values" as="xs:string+"
+ select="$matches/c:eq/c:const/@value,
+ for $name in $matches/c:eq/c:value-of/@name
+ return if ( $symtable-map( $name )/@value ) then
+ $symtable-map( $name )/@value
+ else
+ concat( 'A[''', $name, ''']' )" />
+
+ <sequence select="concat( 'new Set([', string-join( $values, ',' ), '])' )" />
+</function>
+
+
+<!--
+ Output optmized matrix matching
+
+ This should only be called in contexts where the compiler is absolutely
+ certain that the optimzation ought to be applied.
+-->
+<function name="compiler:optimized-matrix-matches" as="xs:string">
+ <param name="symtable-map" as="map(*)" />
+ <param name="classify" as="element( lv:classify )" />
+ <param name="matrices" as="element( lv:match )*" />
+
+ <variable name="nm" as="xs:integer"
+ select="count( $matrices )" />
+
+ <!-- existential, universal -->
+ <variable name="ctype" as="xs:string"
+ select="if ( $classify/@any='true' ) then 'e' else 'u'" />
+
+ <choose>
+ <when test="$nm = 0">
+ <sequence select="''" />
+ </when>
+
+ <when test="$nm = 1">
+ <sequence select="compiler:match-on( $symtable-map, $matrices[1] )" />
+ </when>
+
+ <when test="$nm > 1 and compiler:is-value-list( $symtable-map, $matrices )">
+ <variable name="values" as="xs:string+"
+ select="compiler:value-list(
+ $symtable-map, $classify, $matrices )" />
+
+ <sequence select="concat( 'II(',
+ compiler:match-name-on( $symtable-map, $matrices[1] ),
+ ',', $values, ')' )" />
+ </when>
+
+ <otherwise>
+ <sequence select="concat( 'm', $ctype, '([',
+ string-join(
+ for $m in $matrices
+ return compiler:match-on( $symtable-map, $m ),
+ ','),
+ '])' )" />
+ </otherwise>
+ </choose>
+</function>
+
+
+<!--
+ Output optmized vector matching
+
+ This should only be called in contexts where the compiler is absolutely
+ certain that the optimzation ought to be applied.
+-->
+<function name="compiler:optimized-vec-matches" as="xs:string">
+ <param name="symtable-map" as="map(*)" />
+ <param name="classify" as="element( lv:classify )" />
+ <param name="vectors" as="element( lv:match )*" />
+
+ <variable name="nv" as="xs:integer"
+ select="count( $vectors )" />
+
+ <!-- existential, universal -->
+ <variable name="ctype" as="xs:string"
+ select="if ( $classify/@any='true' ) then 'e' else 'u'" />
+
+ <choose>
+ <when test="$nv = 0">
+ <sequence select="''" />
+ </when>
+
+ <when test="$nv > 1 and compiler:is-value-list( $symtable-map, $vectors )">
+ <variable name="values" as="xs:string+"
+ select="compiler:value-list(
+ $symtable-map, $classify, $vectors )" />
+
+ <sequence select="concat( 'I(',
+ compiler:match-name-on( $symtable-map, $vectors[1] ),
+ ',', $values, ')' )" />
+ </when>
+
+ <otherwise>
+ <sequence select="concat( 'v', $ctype, '([',
+ string-join(
+ for $v in $vectors
+ return compiler:match-on( $symtable-map, $v ),
+ ','),
+ '])' )" />
+ </otherwise>
+ </choose>
+</function>
+
+
+<!--
+ Output optmized scalar matching
+
+ This should only be called in contexts where the compiler is absolutely
+ certain that the optimzation ought to be applied.
+-->
+<function name="compiler:optimized-scalar-matches" as="xs:string">
+ <param name="symtable-map" as="map(*)" />
+ <param name="classify" as="element( lv:classify )" />
+ <param name="scalars" as="element( lv:match )*" />
+
+ <variable name="ns" as="xs:integer"
+ select="count( $scalars )" />
+
+ <!-- existential, universal -->
+ <variable name="cop" as="xs:string"
+ select="if ( $classify/@any = 'true' ) then '|' else '&amp;'" />
+
+ <choose>
+ <when test="$ns = 0">
+ <sequence select="''" />
+ </when>
+
+ <when test="$ns > 1 and compiler:is-value-list( $symtable-map, $scalars )">
+ <variable name="values" as="xs:string+"
+ select="compiler:value-list(
+ $symtable-map, $classify, $scalars )" />
+
+ <sequence select="concat( 'i(',
+ compiler:match-name-on( $symtable-map, $scalars[1] ),
+ ',', $values, ')' )" />
+ </when>
+
+ <!-- either a single match or matches on >1 distinct @on -->
+ <otherwise>
+ <sequence select="string-join(
+ for $s in $scalars
+ return compiler:match-on( $symtable-map, $s ),
+ $cop )" />
+ </otherwise>
+ </choose>
+</function>
+
+
+<!--
+ JS variable to serve as the source for a match (@on)
+-->
+<function name="compiler:match-name-on" as="xs:string">
+ <param name="symtable-map" as="map(*)" />
+ <param name="match" as="element( lv:match )" />
+
+ <choose>
+ <when test="$match/@preproc:inline='true'">
+ <variable name="classify" as="element( lv:classify )?"
+ select="( $match/parent::lv:classify
+ /preceding-sibling::lv:classify[ @yields=$match/@on ] )[1]" />
+
+ <if test="empty( $classify )">
+ <message terminate="yes"
+ select="concat( 'internal error: inline: ',
+ 'cannot locate class `', $match/@on, '''' )" />
+ </if>
+
+ <sequence select="compiler:compile-classify-inline(
+ $symtable-map, $classify )" />
+ </when>
+
+ <otherwise>
+ <variable name="sym" as="element( preproc:sym )"
+ select="$symtable-map( $match/@on )" />
+
+ <variable name="var" as="xs:string"
+ select="if ( $sym/@type = 'const' ) then 'C' else 'A'" />
+
+ <sequence select="concat( $var, '[''', $match/@on, ''']' )" />
+ </otherwise>
+ </choose>
+</function>
+
+
+<function name="compiler:match-value" as="xs:string">
+ <param name="symtable-map" as="map(*)" />
+ <param name="match" as="element( lv:match )" />
+
+ <!-- non-@value matches are transformed prior to matching against
+ (see compiler:match-on) -->
+ <variable name="value" as="xs:string"
+ select="if ( $match/@value ) then $match/@value else 'TRUE'" />
+
+ <variable name="sym" as="element( preproc:sym )?"
+ select="$symtable-map( $value )" />
+
+ <choose>
+ <!-- value unavailable -->
+ <when test="$sym and not( $sym/@value )">
+ <message>
+ <text>[jsc] !!! bad classification match: `</text>
+ <value-of select="$value" />
+ <text>' is not a scalar constant</text>
+ </message>
+ </when>
+
+ <!-- simple constant -->
+ <when test="$sym">
+ <value-of select="$sym/@value" />
+ </when>
+
+ <otherwise>
+ <text>'</text>
+ <!-- TODO: Should we disallow entirely? -->
+ <message>
+ <text>[jsc] warning: static classification match '</text>
+ <value-of select="$value" />
+ <text>' in </text>
+ <value-of select="$match/ancestor::lv:classify[1]/@as" />
+ <text>; use calculation predicate or constant instead</text>
+ </message>
+
+ <value-of select="$value" />
+ <text>'</text>
+ </otherwise>
+ </choose>
+</function>
<!--
@@ -691,35 +1280,10 @@
<variable name="name" select="@on" />
- <variable name="sym-on" as="element( preproc:sym )"
- select="$symtable-map( $name )" />
-
- <text> tmp = </text>
-
- <variable name="input-raw">
- <choose>
- <!-- if we have assumptions, then we'll be recalculating (rather than
- referencing) an existing classification -->
- <when test="lv:assuming">
- <text>_cassume</text>
- </when>
+ <text> tmp=</text>
- <otherwise>
- <choose>
- <when test="$sym-on/@type = 'const'">
- <text>consts</text>
- </when>
- <otherwise>
- <text>args</text>
- </otherwise>
- </choose>
-
- <text>['</text>
- <value-of select="translate( @on, &quot;'&quot;, '' )" />
- <text>']</text>
- </otherwise>
- </choose>
- </variable>
+ <variable name="input-raw" as="xs:string"
+ select="compiler:match-name-on( $symtable-map, . )" />
<!-- yields (if not set, generate one so that cmatches still works properly)
-->
@@ -735,7 +1299,7 @@
<when test="@scalar = 'true'">
<text>stov( </text>
<value-of select="$input-raw" />
- <text>, ( ( </text>
+ <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)
@@ -744,7 +1308,7 @@
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>
+ <text>||[]).length||1))</text>
</when>
<otherwise>
@@ -753,22 +1317,6 @@
</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" />
@@ -777,61 +1325,28 @@
<!-- TODO: error if multiple; also, refactor -->
<choose>
<when test="@value">
- <variable name="value" select="@value" />
- <variable name="sym" as="element( preproc:sym )?"
- select="$symtable-map( $value )" />
-
- <choose>
- <!-- value unavailable (TODO: vector/matrix support) -->
- <when test="$sym and not( $sym/@value )">
- <message>
- <text>[jsc] !!! bad classification match: `</text>
- <value-of select="$value" />
- <text>' is not a scalar constant</text>
- </message>
- </when>
-
- <!-- simple constant -->
- <when test="$sym and @value">
- <value-of select="$sym/@value" />
- </when>
-
- <otherwise>
- <text>'</text>
- <!-- TODO: Should we disallow entirely? -->
- <message>
- <text>[jsc] warning: static classification match '</text>
- <value-of select="$value" />
- <text>' in </text>
- <value-of select="ancestor::lv:classify[1]/@as" />
- <text>; use calculation predicate or constant instead</text>
- </message>
-
- <value-of select="$value" />
- <text>'</text>
- </otherwise>
- </choose>
+ <value-of select="compiler:match-value( $symtable-map, . )" />
</when>
<when test="@pattern">
- <text>function( val ) { </text>
+ <text>function(val) {</text>
<text>return /</text>
<value-of select="@pattern" />
- <text>/.test( val );</text>
- <text> }</text>
+ <text>/.test(val);</text>
+ <text>}</text>
</when>
<when test="./c:*">
- <text>function( val, __$$i ) { </text>
- <text>return ( </text>
+ <text>function(val, __$$i) { </text>
+ <text>return (</text>
<for-each select="./c:*">
<if test="position() > 1">
<text disable-output-escaping="yes"> &amp;&amp; </text>
</if>
- <text>( val </text>
+ <text>(val </text>
<apply-templates select="." mode="compile-calc-when" />
- <text> ) </text>
+ <text>)</text>
</for-each>
<text>);</text>
<text>}</text>
@@ -875,41 +1390,19 @@
</if>
<!-- end of anyValue() call -->
- <text> ) </text>
-
- <!-- end of assuming function call -->
- <if test="lv:assuming">
- <text>})()</text>
- </if>
-
- <text>;</text>
+ <text>);</text>
- <text>/*!+*/( debug['</text>
+ <text>/*!+*/(D['</text>
<value-of select="@_id" />
- <text>'] || ( debug['</text>
+ <text>']||(D['</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>
+ <text>']=[])).push(tmp);/*!-*/ </text>
</template>
<template name="compiler:gen-match-yieldto">
<param name="yields" />
- <text>args['</text>
+ <text>A['</text>
<choose>
<when test="$yields">
<value-of select="$yields" />
@@ -927,58 +1420,19 @@
<!--
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"
+ Every possible value is a float, so this is always true.
-->
<template match="lv:match[ @anyOf='float' ]" mode="compiler:match-anyof" priority="5">
- <!-- ceil(x) - floor(x) = [ x is not an integer ] -->
- <text>function( val ) {</text>
- <text>return ( typeof +val === 'number' ) </text>
- <text disable-output-escaping="yes">&amp;&amp; </text>
- <!-- note: greater than or equal to, since we want to permit integers as
- well -->
- <text disable-output-escaping="yes">( Math.ceil( val ) >= Math.floor( val ) )</text>
- <text>;</text>
- <text>}</text>
+ <text>Tf</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.
+ Whether the provided value is an integer
- See also "float"
+ Everything is a number, so we need only check that it has no remainer mod 1.
-->
<template match="lv:match[ @anyOf='integer' ]" mode="compiler:match-anyof" priority="5">
- <!-- ceil(x) - floor(x) = [ x is not an integer ] -->
- <text>function( val ) {</text>
- <text>return ( typeof +val === 'number' ) </text>
- <text disable-output-escaping="yes">&amp;&amp; </text>
- <text>( Math.floor( val ) === Math.ceil( val ) )</text>
- <text>;</text>
- <text>}</text>
-</template>
-
-<!--
- Handles matching on an empty set
-
- This is useful for asserting against fields that may have default values,
- since in such a case an empty value would be permitted.
--->
-<template match="lv:match[ @anyOf='empty' ]" mode="compiler:match-anyof" priority="5">
- <!-- ceil(x) - floor(x) = [ x is not an integer ] -->
- <text>function( val ) {</text>
- <text>return ( val === '' ) </text>
- <text>|| ( val === undefined ) || ( val === null )</text>
- <text>;</text>
- <text>}</text>
+ <text>Ti</text>
</template>
<!--
@@ -988,20 +1442,16 @@
<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>
+ <message terminate="yes"
+ select="concat( 'internal error: unhandled base type: ',
+ @anyOf )" />
</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>
+ <sequence select="concat( 'TE(types[''', @anyOf, '''].values)' )" />
</template>
@@ -1022,6 +1472,10 @@
will return the result of its expression (represented by a calculation in the
XML).
+ If the special param __experimental_guided_tco is defined, recursive calls
+ to the same function can set it to a true value to perform tail call
+ optimization (TCO). See js-calc.xsl for more information.
+
@return generated function
-->
<template match="lv:function" mode="compile">
@@ -1041,13 +1495,32 @@
<text>) {</text>
- <text>return ( </text>
+ <variable name="tco" as="xs:boolean"
+ select="compiler:function-supports-tco( . )" />
+
+ <if test="$tco">
+ <message select="concat('warning: ', @name, ' enabled experimental guided TCO')" />
+ </if>
+
+ <!-- top of this function's trampoline, if TCO was requested -->
+ <if test="$tco">
+ <text>do{__experimental_guided_tco=0;</text>
+ </if>
+
+ <text>var fresult=(</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>
- <text>} </text>
+ <!-- bottom of this function's trampoline, if TCO was requested; if the
+ flag is set (meaning a relevant tail call was hit), jump back to
+ the beginning of the function -->
+ <if test="$tco">
+ <text>}while(__experimental_guided_tco);</text>
+ </if>
+
+ <text>return fresult;} </text>
</template>
@@ -1072,44 +1545,63 @@
<variable name="precision">
<choose>
<when test="@precision">
- <value-of select="@precision" />
+ <value-of select="concat( '1e', @precision )" />
</when>
<otherwise>
- <text>8</text>
+ <text>1e8</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>
+ <apply-templates select="." mode="compile-cmatch" />
- <!-- store the premium -->
- <value-of select="$store" />
- <text> = </text>
+ <variable name="predmatch">
+ <apply-templates select="." mode="compile-class-condition" />
+ </variable>
- <text>( function rate_</text>
- <!-- dashes, which may end up in generated code from templates, must be
- removed -->
- <value-of select="translate( @yields, '-', '_' )" />
- <text>() {</text>
+ <!-- destination var -->
+ <variable name="store">
+ <!-- TODO: escape single quotes (even though there should never be any) -->
+ <text>A['</text>
+ <value-of select="@yields" />
+ <text>']</text>
+ </variable>
+
+ <if test="$predmatch != 'true'">
+ <!-- preempt expensive logic, but still return a vector of the proper
+ length -->
+ <!-- TODO: when writing TAMER, note that this must be improved upon: it
+ only detects iterators of immedite children -->
+ <text>if(!</text>
+ <value-of select="$predmatch" />
+ <text>){</text>
+ <for-each select="c:sum[@generates]|c:product[@generates]">
+ <variable name="value">
+ <apply-templates mode="js-name-ref"
+ select="." />
+ </variable>
+
+ <text>A['</text>
+ <value-of select="@generates" />
+ <text>']=stov(0,</text>
+ <value-of select="$value" />
+ <text>.length);</text>
+ </for-each>
- <text>var predmatch = ( </text>
- <apply-templates select="." mode="compile-class-condition" />
- <text> ); </text>
+ <value-of select="$store" />
+ <text>=0;</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>
+ <!-- predicate matches -->
+ <text>}else{</text>
+ </if>
+ <!-- store the premium -->
+ <value-of select="$store" />
+ <text>=p(</text>
+ <value-of select="$precision" />
<!-- return the result of the calculation for this rate block -->
- <text>return (+( </text>
+ <text>,+(</text>
<!-- yield 0 if there are no calculations (rather than a syntax error!) -->
<if test="empty( c:* )">
<message>
@@ -1124,11 +1616,11 @@
<!-- 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>
- <text>; </text>
+ <if test="$predmatch != 'true'">
+ <text>}</text>
+ </if>
</template>
<template match="lv:rate" mode="compile-class-condition">
@@ -1141,61 +1633,98 @@
set by rate-each expansion, then we want to ignore them entirely,
since we do not want it to clear our the final yield (generators take
care of this using _CMATCH_). -->
- <text>( </text>
- <variable name="class-set"
- select="./lv:class[
- ( @no = 'true'
- and not( $rate/@gentle-no = 'true' ) )
- or not( @no = 'true' ) ]" />
+ <variable name="class-set"
+ select="./lv:class[
+ ( @no = 'true'
+ and not( $rate/@gentle-no = 'true' ) )
+ or not( @no = 'true' ) ]" />
- <choose>
- <when test="$class-set">
- <for-each select="$class-set">
- <!-- join class expressions with AND operator -->
- <if test="position() > 1">
- <text disable-output-escaping="yes"> &amp;&amp; </text>
- </if>
+ <choose>
+ <when test="$class-set">
+ <if test="count( $class-set ) > 1">
+ <text>(</text>
+ </if>
- <!-- negate if @no -->
- <if test="@no='true'">
- <text>!</text>
- </if>
+ <for-each select="$class-set">
+ <!-- join class expressions with AND operator -->
+ <if test="position() > 1">
+ <text disable-output-escaping="yes"> &amp;&amp; </text>
+ </if>
+
+ <!-- negate if @no -->
+ <if test="@no='true'">
+ <text>!</text>
+ </if>
- <variable name="ref" select="@ref" />
+ <variable name="ref" select="@ref" />
- <if test="$symtable-map( concat( ':class:', $ref ) )
- /@preproc:generated='true'">
- <text>gen</text>
- </if>
+ <if test="$symtable-map( concat( ':class:', $ref ) )
+ /@preproc:generated='true'">
+ <text>g</text>
+ </if>
- <text>classes['</text>
- <value-of select="@ref" />
- <text>']</text>
- </for-each>
- </when>
+ <text>c['</text>
+ <value-of select="@ref" />
+ <text>']</text>
+ </for-each>
- <!-- well, we need to output something -->
- <otherwise>
- <text>true</text>
- </otherwise>
- </choose>
- <text> )</text>
+ <if test="count( $class-set ) > 1">
+ <text>)</text>
+ </if>
+ </when>
+
+ <!-- well, we need to output something -->
+ <otherwise>
+ <text>true</text>
+ </otherwise>
+ </choose>
+</template>
+
+
+<!-- Non-predicated lv:rate -->
+<template mode="compile-cmatch" priority="7"
+ match="lv:rate[ count( lv:class ) = 0 ]">
+ <!-- nothing (but the body must not reference it, or the body will have an
+ old value!) -->
</template>
-<template match="lv:rate" mode="compile-cmatch">
+<!--
+ Single-predicate lv:rate can be aliased
+
+ @no is excluded for simplicity (and it's also generally used with a
+ non-@no, and it's uncommon).
+-->
+<template mode="compile-cmatch" priority="7"
+ match="lv:rate[ count( lv:class ) = 1
+ and not( lv:class/@no ) ]">
+ <param name="symtable-map" as="map(*)" tunnel="yes" />
+
+ <text>C['_CMATCH_']=</text>
+ <call-template name="compiler:get-class-yield">
+ <with-param name="symtable-map" select="$symtable-map" />
+ <with-param name="name" select="@ref" />
+ <with-param name="search" select="root(.)" />
+ </call-template>
+ <text>;</text>
+</template>
+
+
+<template match="lv:rate" mode="compile-cmatch" priority="5">
<param name="symtable-map" as="map(*)" tunnel="yes" />
<variable name="root" select="root(.)" />
- <!-- generate cmatch call that will generate the cmatch set -->
- <text>cmatch( [</text>
+ <!-- set the magic _CMATCH_ var to represent a list of indexes that meet all
+ the classifications (note: this has to be calculated even on a
+ non-match, since it is often referenced by c:sum/c:product) -->
+ <text>C['_CMATCH_']=cmatch([</text>
<for-each select="lv:class[ not( @no='true' ) ]">
<if test="position() > 1">
<text>, </text>
</if>
- <text>args['</text>
+ <text>A['</text>
<call-template name="compiler:get-class-yield">
<with-param name="symtable-map" select="$symtable-map" />
<with-param name="name" select="@ref" />
@@ -1209,7 +1738,7 @@
<text>, </text>
</if>
- <text>args['</text>
+ <text>A['</text>
<call-template name="compiler:get-class-yield">
<with-param name="symtable-map" select="$symtable-map" />
<with-param name="name" select="@ref" />
@@ -1217,7 +1746,7 @@
</call-template>
<text>']</text>
</for-each>
- <text>] )</text>
+ <text>]);</text>
</template>
@@ -1269,7 +1798,7 @@
<template match="lv:meta/lv:prop" mode="compile">
<text>meta['</text>
<value-of select="@name" />
- <text>'] = </text>
+ <text>']=</text>
<call-template name="util:json">
<with-param name="array">
@@ -1298,6 +1827,9 @@
</template>
+<template match="text()" mode="compile" priority="1">
+ <!-- do not output e.g. whitespace between nodes -->
+</template>
<template match="lvp:*" mode="compile" priority="1">
<!-- do nothing with UI nodes -->
@@ -1318,29 +1850,194 @@
<template name="compiler:static">
<text>
<![CDATA[
- var domains = {
- 'integer': function( value )
- {
- return ( value == +value );
- },
+ // precision
+ function p(p, x)
+ {
+ if (x % 1 === 0) return x;
+ return Math.round(x * p) / p;
+ }
- 'float': function( value )
- {
- return ( value == +value );
- },
+ // apply vector to matrix
+ function vmu(m, v)
+ {
+ const result = m.map(function(mv, i) {
+ return (mv.length ? mv : [0]).map(function(ms) { return ms & v[i] });
+ });
- 'boolean': function( value )
- {
- return ( ( +value === 1 ) || ( +value === 0 ) );
- },
+ for (let i = result.length; i < v.length; i++) {
+ result[i] = [0];
+ }
- 'string': function( value )
- {
- // well, everything is a string
- return true;
+ return result;
+ }
+ function vme(m, v)
+ {
+ const result = m.map(function(mv, i) {
+ return (mv.length ? mv : [0]).map(function(ms) { return ms | v[i] });
+ });
+
+ for (let i = result.length; i < v.length; i++) {
+ result[i] = [v[i]];
}
- };
+ return result;
+ }
+
+ function mu(ms)
+ {
+ const longest_row = Math.max.apply(null, ms.map(function(m) {
+ return m.length;
+ }));
+ const longest_col = Math.max.apply(null, ms.map(function(m) {
+ return Math.max.apply(null, m.map(function(v) { return v.length; }));
+ }));
+
+ const base = new Array(longest_row).fill(
+ new Array(longest_col).fill(1)
+ );
+
+ return ms.reduce(function(final, m) {
+ return final.map(function(v, i) {
+ return vu([v, m[i]||[0]]);
+ });
+ }, base);
+ }
+
+ function me(ms)
+ {
+ const longest_row = Math.max.apply(null, ms.map(function(m) {
+ return m.length;
+ }));
+ const longest_col = Math.max.apply(null, ms.map(function(m) {
+ return Math.max.apply(null, m.map(function(v) { return v.length; }));
+ }));
+
+ const base = new Array(longest_row).fill(
+ new Array(longest_col).fill(0)
+ );
+
+ return ms.reduce(function(final, m) {
+ return final.map(function(v, i) {
+ return ve([v, m[i]||[0]]);
+ });
+ }, base);
+ }
+
+ function vu(vs)
+ {
+ const longest = Math.max.apply(null, vs.map(function(v) {
+ return v.length;
+ }));
+
+ const base = new Array(longest).fill(1);
+
+ const result = vs.reduce(
+ function(final, v, vi) {
+ return final.map(function(x, i) { return x & v[i] });
+ },
+ base
+ );
+
+ return result;
+ }
+
+ function ve(vs)
+ {
+ const longest = Math.max.apply(null, vs.map(function(v) {
+ return v.length;
+ }));
+
+ const base = new Array(longest).fill(0);
+
+ const result = vs.reduce(
+ function(final, v, vi) {
+ return final.map(function(x, i) { return x | v[i] });
+ },
+ base
+ );
+
+ return result;
+ }
+
+ // apply scalar to vector
+ function svu(v, s)
+ {
+ return v.map(function(x) { return x & s });
+ }
+ function sve(v, s)
+ {
+ return v.map(function(x) { return x | s });
+ }
+
+ // apply scalar to matrix
+ function smu(m, s)
+ {
+ return m.map(function(v) {
+ return v.map(function(x) { return x & s });
+ });
+ }
+ function sme(m, s)
+ {
+ return m.map(function(v) {
+ return v.map(function(x) { return x | s });
+ });
+ }
+
+
+ // existential (any)
+ function E(v)
+ {
+ return v.some(function(s) { return s === 1 });
+ }
+
+ // existential (any) for matrices
+ function Em(m)
+ {
+ return m.some(E);
+ }
+
+ function div(x, y)
+ {
+ return x / y;
+ }
+
+
+ // types
+ function Tf(x) { return 1; }
+ function Ti(x) { return +(x % 1 === 0); }
+ function TE(xs) {
+ return function(x) {
+ return +(xs[x] === 1);
+ }
+ }
+
+ function M(vs, f) { return vs.map(f); }
+ function MM(ms, f) { return ms.map(function(vs) { return vs.map(f) }) }
+ var n = ceq(0);
+ function N(vs) { return vs.map(n); }
+ function NN(ms) { return ms.map(N); }
+
+ function i(s, xs) { return +xs.has(s) };
+ function I(v, xs) { return v.map(function(s) { return +xs.has(s) }) }
+ function II(m, xs) {
+ return m.map(function(v) {
+ return v.map(function(s) { return +xs.has(s) });
+ });
+ }
+
+ function ceq(y) { return function (x) { return +(x === y); }; }
+ function cne(y) { return function (x) { return +(x !== y); }; }
+ function cgt(y) { return function (x) { return +(x > y); }; }
+ function clt(y) { return function (x) { return +(x < y); }; }
+ function cgte(y) { return function (x) { return +(x >= y); }; }
+ function clte(y) { return function (x) { return +(x <= y); }; }
+
+ function ceqi(y) { return function (x, i) { return +(x === (y[i]||0)); }; }
+ function cnei(y) { return function (x, i) { return +(x !== (y[i]||0)); }; }
+ function cgti(y) { return function (x, i) { return +(x > (y[i]||0)); }; }
+ function clti(y) { return function (x, i) { return +(x < (y[i]||0)); }; }
+ function cgtei(y) { return function (x, i) { return +(x >= (y[i]||0)); }; }
+ function cltei(y) { return function (x, i) { return +(x <= (y[i]||0)); }; }
/**
* Checks for matches against values for any param value
@@ -1363,14 +2060,7 @@
// 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' )
+ if ( !Array.isArray( param ) )
{
param = [ param ];
}
@@ -1411,10 +2101,10 @@
v = returnOrReduceOr( store[ i ], u );
// recurse on vectors
- if ( typeof param[ i ] === 'object' || typeof store[ i ] === 'object' )
+ if ( Array.isArray( param[ i ] ) || Array.isArray( store[ i ] ) )
{
var r = deepClone( store[ i ] || [] );
- if ( typeof r !== 'object' )
+ if ( !Array.isArray( r ) )
{
r = [ r ];
}
@@ -1422,7 +2112,7 @@
var rfound = !!anyValue( param[ i ], values_orig, r, false, clear, _id );
found = ( found || rfound );
- if ( ( typeof store[ i ] === 'object' )
+ if ( Array.isArray( store[ i ] )
|| ( store[ i ] === undefined )
)
{
@@ -1487,24 +2177,11 @@
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;
+ return preds.some( function( p ) {
+ return (typeof p === 'function')
+ ? p(value, index)
+ : p == value;
+ } );
}
@@ -1519,9 +2196,8 @@
return arr;
}
- return reduce( arr, function( a, b )
- {
- return returnOrReduceOr( a, c ) || returnOrReduceOr( b, c );
+ return arr.reduce( function( a, b ) {
+ return a || returnOrReduceOr( b, c );
} );
}
@@ -1537,32 +2213,16 @@
return arr;
}
- return reduce( arr, function( a, b )
- {
- return returnOrReduceAnd( a, c ) && returnOrReduceAnd( b, c );
+ return arr.reduce( function( a, b ) {
+ return a && returnOrReduceAnd( b, c );
} );
}
- function deepClone( obj )
+ function deepClone( arr )
{
- 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;
+ if ( !Array.isArray( arr ) ) return arr;
+ return arr.map( deepClone );
}
@@ -1584,10 +2244,9 @@
{
if ( Array.isArray( match ) )
{
- return reduce( match, function( a, b )
- {
+ return match.reduce( function( a, b ) {
return a + b;
- } );
+ }, 0);
}
return +match;
@@ -1628,7 +2287,7 @@
{
// if we're dealing with a scalar, then it should be used for
// every index
- var mdata = ( ( typeof match[ i ] !== 'object' )
+ var mdata = ( !Array.isArray( match[ i ] )
? match[ i ]
: ( match[ i ] || [] )[ len ]
);
@@ -1644,7 +2303,7 @@
{
// if we're dealing with a scalar, then it should be used for
// every index
- var mdata = ( ( typeof nomatch[ i ] !== 'object' )
+ var mdata = ( !Array.isArray( nomatch[ i ] )
? nomatch[ i ]
: ( nomatch[ i ] || [] )[ len ]
);
@@ -1687,33 +2346,11 @@
}
- /**
- * 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 ( Array.isArray( s ) )
{
// 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
@@ -1726,13 +2363,7 @@
s = s[ 0 ];
}
- var v = [];
- for ( var i = 0; i < n; i++ )
- {
- v.push( s );
- }
-
- return v;
+ return (new Array(n)).fill(s);
}
@@ -1769,21 +2400,26 @@
// scalar
if ( depth === 0 )
{
- return ( input === '' || input === undefined ) ? value : input;
+ // TODO: error
+ if ( Array.isArray( input ) ) input = input[0];
+ return ( input === '' || input === undefined ) ? value : +input;
}
- input = input || [];
+ // TODO: error for both
+ if (!Array.isArray(input)) input = [input];
+ if (depth === 1 && Array.isArray(input[0])) input = input[0];
- // vector or matrix
- var i = input.length || 1;
- var ret = [];
- var value = ( depth === 2 ) ? [ value ] : value;
+ // TODO: this maintains old behavior, but maybe should be an error;
+ // we cannot have empty index sets (see design/tpl).
+ if (input.length === 0) input = [value];
- while ( i-- ) {
- ret[i] = ( input[i] === '' || input[i] === undefined ) ? value : input[i];
- }
-
- return ret;
+ return input.map( function( x ) {
+ return ( depth === 2 )
+ ? Array.isArray( x )
+ ? x.map( function(s) { return +s; } )
+ : [ x ]
+ : ( x === '' || x === undefined ) ? value : +x;
+ } );
}
@@ -1801,7 +2437,7 @@
return input.map( map_method_uppercase );
}
- return input.toUpperCase();
+ return ( ''+input ).toUpperCase();
}
@@ -1822,7 +2458,7 @@
return input.map( map_method_hash );
}
- const hash = sha256( input ).substr( 0, 8 );
+ const hash = sha256( ''+input ).substr( 0, 8 );
return parseInt( hash, 16 );
}
]]>
diff --git a/src/current/compiler/map.xsl b/src/current/compiler/map.xsl
index 5cd60b0..a979cff 100644
--- a/src/current/compiler/map.xsl
+++ b/src/current/compiler/map.xsl
@@ -1214,6 +1214,7 @@
<variable name="knowns" as="xs:string*"
select="$ui//lvp:question/@id,
$ui//lvp:external/@id,
+ $ui//lvp:flag/@id,
$ui//lvp:calc/@id,
for $id in $ui//lvp:meta/lvp:field/@id
return concat( 'meta:', $id )" />
diff --git a/src/current/compiler/validate.xsl b/src/current/compiler/validate.xsl
index 9c1242d..8321ea9 100644
--- a/src/current/compiler/validate.xsl
+++ b/src/current/compiler/validate.xsl
@@ -38,6 +38,7 @@
xmlns:lv="http://www.lovullo.com/rater"
xmlns:ext="http://www.lovullo.com/ext"
xmlns:c="http://www.lovullo.com/calc"
+ xmlns:compiler="http://www.lovullo.com/rater/compiler"
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">
@@ -267,6 +268,34 @@
<apply-templates select="." mode="lvv:validate-match" />
</template>
+
+<template match="lv:match[@pattern]" mode="lvv:validate-match" priority="9">
+ <choose>
+ <!-- warn of upcoming removal -->
+ <when test="compiler:use-legacy-classify( ancestor::lv:classify )">
+ <message select="concat( 'warning: ',
+ ancestor::lv:classify/@as,
+ ': lv:match[@pattern] support is deprecated ',
+ 'and is removed with the new classification ',
+ 'system; use lookup tables instead' )" />
+ </when>
+
+ <!-- @pattern support removed in the new classification system -->
+ <otherwise>
+ <call-template name="lvv:error">
+ <with-param name="desc" select="'lv:match[@pattern] support removed'" />
+ <with-param name="refnode" select="." />
+ <with-param name="content">
+ <text>use lookup tables in place of @pattern in `</text>
+ <value-of select="parent::lv:classify/@as" />
+ <text>'</text>
+ </with-param>
+ </call-template>
+ </otherwise>
+ </choose>
+</template>
+
+
<!--
Validate that non-numeric value matches actually exist and are constants
-->
@@ -300,38 +329,68 @@
<apply-templates mode="lvv:validate-match" />
</template>
+
+<!-- simplify optimizations -->
+<template match="lv:match[ count(c:*) gt 1 ]" mode="lvv:validate-match" priority="4">
+ <call-template name="lvv:error">
+ <with-param name="desc" select="'Multi-c:* match expression'" />
+ <with-param name="refnode" select="." />
+ <with-param name="content">
+ <text>`</text>
+ <value-of select="@on" />
+ <text>' must include separate matches for each c:* in `</text>
+ <value-of select="parent::lv:classify/@as" />
+ <text>'</text>
+ </with-param>
+ </call-template>
+
+ <apply-templates mode="lvv:validate-match" />
+</template>
+
+
<template match="lv:match" mode="lvv:validate-match" priority="2">
<apply-templates mode="lvv:validate-match" />
</template>
-<!--
- Classification match assumptions must operate only on other classifiers and
- must assume values that the referenced classifier actually matches on
--->
-<template match="lv:match/lv:assuming" mode="lvv:validate-match" priority="5">
- <variable name="on" select="../@on" />
- <variable name="ref" select="root(.)//lv:classify[ @yields=$on ]" />
- <!-- assumptions must only operate on variables mentioned in the referenced
- classification -->
- <for-each select="
- .//lv:that[
- not( @name=$ref//lv:match/@on )
- ]
- ">
+<!-- simplify optimizations -->
+<template mode="lvv:validate-match" priority="5"
+ match="c:*[ not( c:value-of or c:const ) ]">
+ <call-template name="lvv:error">
+ <with-param name="desc" select="'invalid lv:match/c:*/c:*'" />
+ <with-param name="refnode" select="." />
+ <with-param name="content">
+ <text>`</text>
+ <value-of select="@on" />
+ <text>' must contain only c:value of or c:const in `</text>
+ <value-of select="parent::lv:classify/@as" />
+ <text>'</text>
+ </with-param>
+ </call-template>
- <call-template name="lvv:error">
- <with-param name="desc" select="'Invalid classification assumption'" />
- <with-param name="refnode" select="." />
- <with-param name="content">
- <value-of select="@name" />
- <text> is not used to classify </text>
- <value-of select="$on" />
- </with-param>
- </call-template>
- </for-each>
+ <apply-templates mode="lvv:validate-match" />
+</template>
+
+
+<!-- simplify optimizations -->
+<template mode="lvv:validate-match" priority="4"
+ match="c:*[ c:const[ c:* ] ]">
+ <call-template name="lvv:error">
+ <with-param name="desc" select="'non-scalar lv:match/c:*/c:*'" />
+ <with-param name="refnode" select="." />
+ <with-param name="content">
+ <text>`</text>
+ <value-of select="@on" />
+ <text>' must contain only scalar c:const in `</text>
+ <value-of select="parent::lv:classify/@as" />
+ <text>'</text>
+ </with-param>
+ </call-template>
+
+ <apply-templates mode="lvv:validate-match" />
</template>
+
<template match="c:*" mode="lvv:validate-match" priority="2">
<apply-templates select="." mode="lvv:validate" />
</template>
diff --git a/src/current/doc/.gitignore b/src/current/doc/.gitignore
deleted file mode 100644
index 2fa45d0..0000000
--- a/src/current/doc/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-*.aux
-*.pdf
-*.log
-*.toc
diff --git a/src/current/doc/chapters/class.tex b/src/current/doc/chapters/class.tex
deleted file mode 100644
index 82a2f62..0000000
--- a/src/current/doc/chapters/class.tex
+++ /dev/null
@@ -1,372 +0,0 @@
-\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
deleted file mode 100644
index b3524f4..0000000
--- a/src/current/doc/manual.sty
+++ /dev/null
@@ -1,30 +0,0 @@
-% 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
deleted file mode 100644
index 457376c..0000000
--- a/src/current/doc/manual.tex
+++ /dev/null
@@ -1,19 +0,0 @@
-\documentclass[10pt]{book}
-
-%%begin preamble
- \usepackage{manual}
-
- \author{Ryan Specialty Group}
- \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/include/depgen.xsl b/src/current/include/depgen.xsl
index c0c55f2..b081c72 100644
--- a/src/current/include/depgen.xsl
+++ b/src/current/include/depgen.xsl
@@ -312,6 +312,10 @@
<next-match />
</template>
+<template mode="preproc:depgen" priority="9"
+ match="lv:classify[ preproc:inline='true' ]">
+ <!-- ignore; dependencies will be inlined -->
+</template>
<template mode="preproc:depgen" priority="7"
match="lv:classify">
@@ -460,6 +464,29 @@
</template>
+<!--
+ Inlined matches will not be counted as dependencies themselves, but their
+ dependencies are our own
+-->
+<template match="lv:match[ @preproc:inline='true' ]"
+ mode="preproc:depgen" priority="7">
+ <variable name="self" as="element( lv:match )" select="." />
+
+ <variable name="classify" as="element( lv:classify )?"
+ select="( parent::lv:classify
+ /preceding-sibling::lv:classify[ @yields=$self/@on ] )[1]" />
+
+ <if test="empty( $classify )">
+ <message terminate="yes"
+ select="concat( 'internal error: inline depgen: ',
+ 'cannot locate class `', @on, '''' )" />
+ </if>
+
+ <apply-templates mode="preproc:depgen"
+ select="$classify/element()" />
+</template>
+
+
<template match="lv:match[ @value ]" mode="preproc:depgen" priority="5">
<!-- process the @value -->
<call-template name="preproc:depgen-c-normal">
diff --git a/src/current/include/preproc/expand.xsl b/src/current/include/preproc/expand.xsl
index 5d4a238..c4b3efb 100644
--- a/src/current/include/preproc/expand.xsl
+++ b/src/current/include/preproc/expand.xsl
@@ -247,6 +247,7 @@
not(
@name=$overrides/@name
or @name=$self/@*/local-name()
+ or starts-with( @name, '__experimental_' )
)
]
">
@@ -595,46 +596,18 @@
</template>
-<template mode="preproc:expand"
- match="lv:join[ @all='true' ]"
- priority="8">
- <call-template name="preproc:mk-class-join-contents" />
-</template>
-
-
-<template mode="preproc:expand"
- match="lv:join"
- priority="7">
- <lv:any>
- <call-template name="preproc:mk-class-join-contents" />
- </lv:any>
-</template>
-
+<!-- expand lv:match/@value != 'TRUE' into a c:* expression to simpliy
+ optimizations -->
+<template match="lv:match[ @value and @value != 'TRUE' ]"
+ mode="preproc:expand" priority="7">
-<template name="preproc:mk-class-join-contents">
- <variable name="prefix" select="@prefix" />
+ <copy>
+ <copy-of select="@*[ not( local-name() = 'value' ) ]" />
- <!-- TODO: remove lv:template nodes in a pass before this so that this
- check is not necessary -->
- <for-each select="root(.)/lv:classify[
- starts-with( @as, $prefix )
- and not( ancestor::lv:template )
- ]">
- <lv:match value="TRUE">
- <attribute name="on">
- <choose>
- <when test="@yields">
- <value-of select="@yields" />
- </when>
-
- <otherwise>
- <text>__is</text>
- <value-of select="@as" />
- </otherwise>
- </choose>
- </attribute>
- </lv:match>
- </for-each>
+ <c:eq>
+ <c:value-of name="{@value}" />
+ </c:eq>
+ </copy>
</template>
diff --git a/src/current/include/preproc/macros.xsl b/src/current/include/preproc/macros.xsl
index 54f6d88..4dab083 100644
--- a/src/current/include/preproc/macros.xsl
+++ b/src/current/include/preproc/macros.xsl
@@ -27,6 +27,8 @@
xmlns:lv="http://www.lovullo.com/rater"
xmlns:t="http://www.lovullo.com/rater/apply-template"
xmlns:c="http://www.lovullo.com/calc"
+ xmlns:compiler="http://www.lovullo.com/rater/compiler"
+ xmlns:eseq="http://www.lovullo.com/tame/preproc/expand/eseq"
xmlns:ext="http://www.lovullo.com/ext">
@@ -68,7 +70,9 @@
<preproc:repass-record />
-->
+ <!-- output is slow and this happens a lot
<message>[preproc] *REPASS*</message>
+ -->
<!-- perform the repass -->
<apply-templates select="$nodeset" mode="preproc:macropass">
@@ -243,9 +247,52 @@
</template>
-<template match="lv:classify[ .//lv:any|.//lv:all ]" mode="preproc:macros" priority="6">
+<!--
+ Strip template barriers after expansion is complete.
+
+ These barriers really frustrate static analysis, and serve no use after
+ templates have been expanded, aside from indicating _what_ templates were
+ expanded. The benefits there do not outweigh the optimization
+ opportunities.
+-->
+<template mode="preproc:macros" priority="8"
+ match="lv:classify[
+ .//preproc:tpl-barrier
+ and not( eseq:is-expandable(.) ) ]">
+ <copy>
+ <sequence select="@*" />
+
+ <apply-templates mode="preproc:strip-tpl-barrier" />
+ <preproc:repass src="lv:classify tpl barrier strip" />
+ </copy>
+</template>
+
+
+<!-- strip preproc:* nodes -->
+<template mode="preproc:strip-tpl-barrier" priority="5"
+ match="preproc:*">
+ <apply-templates mode="preproc:strip-tpl-barrier" />
+</template>
+
+<template mode="preproc:strip-tpl-barrier" priority="1"
+ match="element()">
+ <copy>
+ <sequence select="@*" />
+ <apply-templates mode="preproc:strip-tpl-barrier" />
+ </copy>
+</template>
+
+
+<template mode="preproc:macros" priority="6"
+ match="lv:classify[
+ ( lv:any | lv:all )
+ and not( eseq:is-expandable(.) ) ]">
<variable name="result">
- <apply-templates select="." mode="preproc:class-groupgen" />
+ <apply-templates select="." mode="preproc:class-groupgen">
+ <with-param name="legacy-classify"
+ select="compiler:use-legacy-classify( . )"
+ tunnel="yes" />
+ </apply-templates>
</variable>
<apply-templates select="$result/lv:classify" mode="preproc:class-extract" />
@@ -262,14 +309,112 @@
</template>
-<template mode="preproc:class-groupgen" priority="9"
- match="lv:any[ not( element() ) ]
- |lv:all[ not( element() ) ]">
- <!-- useless; remove -->
+<!--
+ Not only should we not generate a group for single-predicate any/all, but
+ we should remove it entirely.
+-->
+<template mode="preproc:class-groupgen" priority="7"
+ match="(lv:any|lv:all)[ count( lv:* ) lt 2 ]">
+ <apply-templates mode="preproc:class-groupgen" />
+</template>
+
+
+<!--
+ A very specific optimization targeting a common scenario from template
+ expansions.
+
+ If there is an <any> expression of this form:
+
+ <any>
+ <all>
+ <match on="foo" />
+ <match on="bar" value="BAZ" />
+ </all>
+ <all>
+ <match on="foo" />
+ <match on="bar" value="QUUX" />
+ </all>
+ </any>
+
+ Then the common "foo" will be hoisted out and the expression will become:
+
+ <match on="foo" />
+ <any>
+ <match on="bar" value="BAZ" />
+ <match on="bar" value="QUUX" />
+ </any>
+
+ The goal of this optimization is primarily to significantly reduce the
+ number of wasteful intermediate classifications that are generated.
+-->
+<template mode="preproc:class-groupgen" priority="6"
+ match="(lv:classify[ @any='true' ] | lv:any)
+ [ count( lv:all[ count(lv:*) = 2 ] ) = count(lv:*) ]">
+
+ <!-- TODO: missing @value may not have been expanded yet...! -->
+ <variable name="ons" as="element( lv:match )*"
+ select="lv:all/lv:match[
+ @value = 'TRUE'
+ or (
+ not( @value )
+ and not( @anyOf )
+ and not( c:* ) ) ]" />
+
+ <variable name="distinct-ons" as="xs:string*"
+ select="distinct-values( $ons/@on )" />
+
+ <variable name="nall" as="xs:integer"
+ select="count( lv:all )" />
+
+ <choose>
+ <when test="count( $distinct-ons ) = 1
+ and count( $ons ) = $nall
+ and count( $ons/parent::lv:all ) = $nall">
+ <!-- then replace the <alls> with their remaining predicate (which is
+ either before or after) -->
+ <choose>
+ <when test="@any = 'true'">
+ <copy>
+ <sequence select="@*[ not( local-name() = 'any' ) ]" />
+
+ <!-- they're all the same, so hoist the first one out of the <any>,
+ which is now in a universal context because we omitted @any
+ above -->
+ <sequence select="$ons[1]" />
+
+ <lv:any>
+ <apply-templates mode="preproc:class-groupgen"
+ select="$ons/preceding-sibling::lv:match
+ | $ons/following-sibling::lv:match"/>
+ </lv:any>
+ </copy>
+ </when>
+
+ <otherwise>
+ <!-- they're all the same, so hoist the first one out of the <any>,
+ which must be in a universal context -->
+ <sequence select="$ons[1]" />
+
+ <copy>
+ <apply-templates mode="preproc:class-groupgen"
+ select="$ons/preceding-sibling::lv:match
+ | $ons/following-sibling::lv:match"/>
+ </copy>
+ </otherwise>
+ </choose>
+ </when>
+
+ <!-- none, or more than one -->
+ <otherwise>
+ <next-match />
+ </otherwise>
+ </choose>
</template>
<template match="lv:any|lv:all" mode="preproc:class-groupgen" priority="5">
+ <param name="legacy-classify" as="xs:boolean" tunnel="yes" />
+
<!-- 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
@@ -289,11 +434,20 @@
<attribute name="any" select="'true'" />
</if>
+ <if test="not( $legacy-classify )">
+ <attribute name="preproc:inline" select="'true'" />
+ </if>
+
<apply-templates mode="preproc:class-groupgen" />
</lv:classify>
<!-- this will remain in its place -->
- <lv:match on="{$yields}" value="TRUE" preproc:generated="true" />
+ <lv:match on="{$yields}" value="TRUE"
+ preproc:generated="true">
+ <if test="not( $legacy-classify )">
+ <attribute name="preproc:inline" select="'true'" />
+ </if>
+ </lv:match>
</template>
diff --git a/src/current/include/preproc/package.xsl b/src/current/include/preproc/package.xsl
index 6c53a03..a3fcda4 100644
--- a/src/current/include/preproc/package.xsl
+++ b/src/current/include/preproc/package.xsl
@@ -594,10 +594,6 @@
<copy>
<sequence select="@*" />
- <message>
- <text>[preproc] *resolving symbol attributes...</text>
- </message>
-
<apply-templates mode="preproc:resolv-syms">
<with-param name="orig-root" select="$orig-root" />
<with-param name="symtable-map" select="$symtable-map" tunnel="yes" />
@@ -612,20 +608,6 @@
<choose>
<!-- repass scheduled; go for it -->
<when test="$repass">
- <message>[preproc] *SYM REPASS*</message>
- <message>
- <text>[preproc] The following </text>
- <value-of select="count( $repass )" />
- <text> symbol(s) are still unresolved:</text>
- </message>
-
- <for-each select="$repass">
- <message>
- <text>[preproc] - </text>
- <value-of select="@ref" />
- </message>
- </for-each>
-
<apply-templates select="$result" mode="preproc:resolv-syms">
<with-param name="orig-root" select="$orig-root" />
<with-param name="rpcount" select="$rpcount + 1" />
diff --git a/src/current/include/preproc/symtable.xsl b/src/current/include/preproc/symtable.xsl
index b949db7..63e26a2 100644
--- a/src/current/include/preproc/symtable.xsl
+++ b/src/current/include/preproc/symtable.xsl
@@ -119,10 +119,6 @@
<sequence select="@*" />
<variable name="new">
- <message>
- <text>[preproc/symtable] discovering symbols...</text>
- </message>
-
<preproc:syms>
<apply-templates mode="preproc:symtable">
<!-- we only need this param for the root children, so this is the only
@@ -309,8 +305,6 @@
</apply-templates>
</preproc:symtable>
- <message select="'[preproc/symtable] done.'" />
-
<!-- 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 -->
@@ -456,10 +450,6 @@
<param name="new" as="element( preproc:syms )" />
<param name="this-pkg" as="element( lv:package )" />
- <message>
- <text>[preproc/symtable] processing symbol table...</text>
- </message>
-
<variable name="cursym" as="element( preproc:sym )*"
select="preproc:symtable/preproc:sym[
not( @held = 'true' ) ]" />
@@ -549,8 +539,6 @@
</choose>
</for-each>
</preproc:syms>
-
- <message select="'[preproc/symtable] done processing symbol table.'" />
</template>
@@ -606,12 +594,6 @@
assertions, resolve relative paths -->
<variable name="import-default-path" select="$import-path" />
- <message>
- <text>[preproc/symtable] importing symbol table of </text>
- <value-of select="$import-path" />
- <text>...</text>
- </message>
-
<!-- attempt to import symbols from the processed package -->
<if test="not( $syms )">
<message terminate="yes">
diff --git a/src/current/include/preproc/template.xsl b/src/current/include/preproc/template.xsl
index 743e515..58cd04d 100644
--- a/src/current/include/preproc/template.xsl
+++ b/src/current/include/preproc/template.xsl
@@ -414,11 +414,6 @@
<variable name="name" select="concat( '___i', generate-id(.), '___' )" />
<variable name="inline" select="." />
- <message>
- <text>[preproc] preparing inline template </text>
- <value-of select="$name" />
- </message>
-
<!-- generate template -->
<lv:template name="{$name}" desc="Inline template"
preproc:from-inline="{$name}"
@@ -1264,16 +1259,11 @@
<choose>
<when test="not( $yields ) or $yields=''">
- <message>
+ <preproc:error>
<text>error: unable to determine @yields for class `</text>
<value-of select="$as" />
<text>' (has the class been imported?)</text>
- </message>
-
- <!-- just retain the name; it'll be used for an error message,
- since it won't be foudn -->
- <!-- TODO: this is dangerous; find a way to propagate the error -->
- <value-of select="$as" />
+ </preproc:error>
</when>
<otherwise>
diff --git a/src/current/neo4j.xsl b/src/current/neo4j.xsl
new file mode 100644
index 0000000..4377307
--- /dev/null
+++ b/src/current/neo4j.xsl
@@ -0,0 +1,328 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ Produces CREATE statements to export package dependency graph into Neo4j
+
+ Copyright (C) 2014-2019 Ryan Specialty Group, LLC.
+
+ This file is part of TAME.
+
+ TAME is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+
+ Unlike the dot exporter, this is merciless: it'll export all the
+ information that it can, with some minor cleanup to hide some unnecessary
+ compiler implementation deatils (like classification splitting). You are
+expected to filter out what you're interested in when querying Neo4j.
+
+ This also produces more realtionships than the dot format, including
+ relationships between packges. So Neo4j can be used as a static analysis
+ tool down to the lowest levels of Tame.
+-->
+<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:l="http://www.lovullo.com/rater/linker"
+ xmlns:neo="http://www.lovullo.com/calc/dot"
+ xmlns:st="http://www.lovullo.com/liza/proguic/util/struct"
+ xmlns:preproc="http://www.lovullo.com/rater/preproc">
+
+<include href="util/serialize.xsl" />
+<include href="include/preproc/path.xsl" />
+
+<output method="text" />
+
+
+<!--
+ Newline character
+-->
+<variable name="nl" select="'&#10;'" />
+
+<variable name="fqn-prefix" as="xs:string"
+ select="'mega-demo/'" />
+
+<variable name="neo:orig-root" as="document-node( element( lv:package ) )"
+ select="/" />
+
+<variable name="neo:orig-package" as="element( lv:package )"
+ select="$neo:orig-root/lv:package" />
+
+
+<!--
+ Linked symbols
+
+ This is expected to be executed in Neo4j _after_ package-level data has
+ been added to the graph.
+-->
+<template match="lv:package[ l:dep ]" priority="7">
+ <sequence select="concat(
+ ':param refs => ',
+ st:to-neo4j-attrs(
+ st:array( neo:linked-syms( l:dep ) ) ),
+ ';', $nl )" />
+</template>
+
+
+<function name="neo:linked-syms" as="element( st:item )+">
+ <param name="deps" as="element( l:dep )" />
+
+ <variable name="pkg-path" as="xs:string"
+ select="preproc:get-path( $deps/ancestor::lv:package/@name )" />
+
+ <sequence select="for $dep in $deps/preproc:sym
+ return st:item(
+ preproc:resolv-path(
+ concat( $fqn-prefix, '/',
+ $pkg-path, '/',
+ $dep/@src, '/', $dep/@name ) ) )" />
+</function>
+
+
+<template match="lv:package" priority="5">
+ <variable name="sym-deps" select="preproc:sym-deps" />
+
+ <sequence select="concat(
+ ':param syms => ',
+ st:to-neo4j-attrs(
+ st:array(
+ for $sym in neo:get-package-syms( preproc:symtable )
+ return st:item( neo:dict-from-sym( $sym ) ) ) ),
+ ';',
+ $nl,
+
+ 'CREATE (pkg:TamePackage ',
+ st:to-neo4j-attrs(
+ st:dict( (
+ st:item( concat( $fqn-prefix, '/', @name ),
+ 'fqn' ),
+ st:items-from-attrs( @* ) ) ) ),
+ ')',
+ $nl,
+ 'WITH pkg',
+ $nl,
+ 'UNWIND $syms AS symdata ',
+ 'MERGE (sym:TameSymbol {fqn: symdata.fqn}) ',
+ 'SET sym += symdata ',
+ 'CREATE (pkg)-[:DEFINES]->(sym) ',
+ ';', $nl,
+ ':commit', $nl,
+
+ ':begin', $nl,
+ ':param deps => ',
+ st:to-neo4j-attrs(
+ st:array(
+ for $dep in neo:get-package-deps( . )
+ return st:item( neo:gen-dep-data( $dep ) ) ) ),
+ $nl,
+ 'UNWIND $deps AS depdata ',
+ 'MATCH (from:TameSymbol {fqn: depdata.from}) ',
+ 'UNWIND depdata.to AS toname ',
+ 'MERGE (to:TameSymbol {fqn: toname}) ',
+ 'CREATE (from)-[:USES]->(to)',
+ ';', $nl )" />
+
+
+ <!--
+ <apply-templates select="preproc:sym-deps/preproc:sym-dep" />
+ <apply-templates select="preproc:sym-deps/preproc:sym-dep/preproc:sym-ref" />
+-->
+</template>
+
+
+<function name="neo:sym-src" as="xs:string">
+ <param name="sym" as="element( preproc:sym )" />
+
+ <sequence select="if ( $sym/@src and not( $sym/@src = '' ) ) then
+ neo:package-lookup( $sym/@src )/@name
+ else
+ $sym/ancestor::lv:package/@name" />
+</function>
+
+
+<function name="neo:sym-fqn" as="xs:string">
+ <param name="sym" as="element( preproc:sym )" />
+
+ <sequence select="concat( $fqn-prefix, '/',
+ neo:sym-src( $sym ), '/', $sym/@name )" />
+</function>
+
+
+<function name="neo:dict-from-sym" as="element( st:dict )">
+ <param name="sym" as="element( preproc:sym )" />
+
+ <variable name="attrs" as="attribute()+">
+ <sequence select="$sym/@*" />
+
+ <attribute name="fqn" select="neo:sym-fqn( $sym )" />
+ </variable>
+
+ <sequence select="st:dict( st:items-from-attrs( $attrs ) )" />
+</function>
+
+
+<function name="neo:package-lookup" as="element( lv:package )">
+ <param name="src" as="xs:string?" />
+
+ <sequence select="if ( $src and not( $src = '' ) ) then
+ document( concat( $src, '.xmlo' ), $neo:orig-root )/lv:*
+ else
+ $neo:orig-package" />
+</function>
+
+
+<function name="neo:is-mergable" as="xs:boolean">
+ <param name="sym" as="element( preproc:sym )" />
+
+ <!-- externs aren't resolved until linking, so we cannot hope to merge
+ them here -->
+ <sequence select="( exists( $sym/@preproc:generated-from )
+ or $sym/@type = 'cgen'
+ or ( $sym/@type = 'tpl'
+ and $sym/@preproc:generated = 'true' ) )
+ and not( $sym/@extern = 'true' )" />
+</function>
+
+
+<function name="neo:is-mergable-ref" as="xs:boolean">
+ <param name="sym-ref" as="element( preproc:sym-ref )" />
+
+ <variable name="name" as="xs:string"
+ select="$sym-ref/@name" />
+
+ <variable name="sym" as="element( preproc:sym )"
+ select="$sym-ref/ancestor::lv:package/preproc:symtable
+ /preproc:sym[ @name = $name ]" />
+
+ <sequence select="neo:is-mergable( $sym )" />
+</function>
+
+
+<function name="neo:is-mergable-dep" as="xs:boolean">
+ <param name="sym-dep" as="element( preproc:sym-dep )" />
+
+ <variable name="name" as="xs:string"
+ select="$sym-dep/@name" />
+
+ <variable name="sym" as="element( preproc:sym )"
+ select="$sym-dep/ancestor::lv:package/preproc:symtable
+ /preproc:sym[ @name = $name ]" />
+
+ <sequence select="neo:is-mergable( $sym )" />
+</function>
+
+
+<function name="neo:get-package-syms" as="element( preproc:sym )*">
+ <param name="symtable" as="element( preproc:symtable )" />
+
+ <sequence select="$symtable/preproc:sym[
+ not( @src )
+ and not( neo:is-mergable( . ) ) ]" />
+</function>
+
+
+<function name="neo:get-package-deps" as="element( preproc:sym-dep )*">
+ <param name="package" as="element( lv:package )" />
+
+ <sequence select="$package/preproc:sym-deps/preproc:sym-dep[
+ not( neo:is-mergable-dep( . ) ) ]" />
+</function>
+
+
+<function name="neo:get-sym-deps-by-ref" as="element( preproc:sym-ref )*">
+ <param name="sym-ref" as="element( preproc:sym-ref )" />
+
+ <variable name="name" as="xs:string"
+ select="$sym-ref/@name" />
+
+ <variable name="local-deps" as="element( preproc:sym-dep )?"
+ select="$sym-ref/ancestor::preproc:sym-deps/preproc:sym-dep[
+ @name = $name ]" />
+
+ <variable name="deps" as="element( preproc:sym-dep )"
+ select="if ( exists( $local-deps ) ) then
+ $local-deps
+ else
+ neo:package-lookup-by-ref( $sym-ref )
+ /preproc:sym-deps/preproc:sym-dep[ @name = $name ]" />
+
+ <sequence select="neo:get-sym-deps( $deps )" />
+</function>
+
+
+<function name="neo:package-lookup-by-ref" as="element( lv:package )">
+ <param name="sym-ref" as="element( preproc:sym-ref )" />
+
+ <variable name="name" as="xs:string"
+ select="$sym-ref/@name" />
+
+ <variable name="sym" as="element( preproc:sym )"
+ select="$sym-ref/ancestor::lv:package/preproc:symtable
+ /preproc:sym[ @name = $name ]" />
+
+ <sequence select="neo:package-lookup( $sym/@src )" />
+</function>
+
+
+<function name="neo:is-ignorable-ref" as="xs:boolean">
+ <param name="sym-ref" as="element( preproc:sym-ref )" />
+
+ <!-- much quicker check than using neo:is-mergable -->
+ <sequence select="$sym-ref/@name = '_CMATCH_'" />
+</function>
+
+
+<function name="neo:get-sym-deps" as="element( preproc:sym-ref )*">
+ <param name="sym-dep" as="element( preproc:sym-dep )" />
+
+ <sequence select="for $ref in $sym-dep/preproc:sym-ref[
+ not( neo:is-ignorable-ref( . ) ) ]
+ return if ( neo:is-mergable-ref( $ref ) ) then
+ neo:get-sym-deps-by-ref( $ref )
+ else
+ $ref" />
+</function>
+
+
+<function name="neo:gen-dep-data" as="element( st:dict )">
+ <param name="dep" as="element( preproc:sym-dep )" />
+
+ <variable name="refs" as="element( preproc:sym-ref )*"
+ select="neo:get-sym-deps( $dep )" />
+
+ <variable name="sym" as="element( preproc:sym )"
+ select="$dep/ancestor::lv:package/preproc:symtable
+ /preproc:sym[ @name = $dep/@name ]" />
+
+ <sequence select="st:dict( (
+ st:item( neo:sym-fqn( $sym ), 'from' ),
+ st:item(
+ st:array(
+ for $ref in $refs
+ return st:item( neo:sym-ref-fqn( $ref ) ) ),
+ 'to' ) ) )" />
+</function>
+
+
+<function name="neo:sym-ref-fqn" as="xs:string">
+ <param name="sym-ref" as="element( preproc:sym-ref )" />
+
+ <variable name="sym" as="element( preproc:sym )"
+ select="$sym-ref/ancestor::lv:package/preproc:symtable
+ /preproc:sym[ @name = $sym-ref/@name ]" />
+
+ <sequence select="neo:sym-fqn( $sym )" />
+</function>
+
+</stylesheet>
+
diff --git a/src/current/neo4j/post.neo4j b/src/current/neo4j/post.neo4j
new file mode 100644
index 0000000..061bd22
--- /dev/null
+++ b/src/current/neo4j/post.neo4j
@@ -0,0 +1,60 @@
+
+:commit
+
+// it's easier just to create these labels after-the-fact rather than muddy
+// the query generation
+:begin
+MATCH (n:TameSymbol {type: "class"}) SET n:TameClass;
+MATCH (n:TameSymbol {type: "rate"}) SET n:TameRate;
+MATCH (n:TameSymbol {type: "gen"}) SET n:TameGen;
+MATCH (n:TameSymbol {type: "cgen"}) SET n:TameCgen;
+MATCH (n:TameSymbol {type: "type"}) SET n:TameType;
+MATCH (n:TameSymbol {type: "const"}) SET n:TameConst;
+MATCH (n:TameSymbol {type: "func"}) SET n:TameFunc;
+MATCH (n:TameSymbol {type: "param"}) SET n:TameParam;
+MATCH (n:TameSymbol {type: "map"}) SET n:TameMapInput;
+MATCH (n:TameSymbol {type: "retmap"}) SET n:TameMapReturn;
+
+MATCH (n:TameSymbol {extern: "true"}) SET n:TameExtern;
+
+MATCH (n:TameSymbol {generated: "true"}) SET n:TameSymbolGenerated;
+MATCH (n:TamePackage {program: "true"}) SET n:TameProgram;
+:commit
+
+// these are created in post.neo4j beacuse (a) they're used only for
+// querying and (b) they're more convenient to define with the new labels
+:begin
+CREATE INDEX ON :TameSymbol(name);
+CREATE INDEX ON :TameConst(value);
+CREATE INDEX ON :TameClass(orig_name);
+:commit
+
+// cleanup that's easier to do here than in XSLT
+:begin
+// typedefs define constants, not use them
+MATCH (c:TameConst)<-[r:USES]-(t:TameType)
+CREATE UNIQUE (c)<-[:DEFINES]-(t)
+DELETE r;
+
+// same with unions
+MATCH (t:TameType)<-[r:USES]-(u:TameType)
+CREATE UNIQUE (t)<-[:DEFINES]-(u)
+DELETE r;
+
+// maps map, not use
+MATCH (n:TameMapInput)-[r:USES]->(p:TameParam)
+CREATE (n)-[:MAPS]->(p)
+DELETE r;
+:commit
+
+// for convenience in querying; framework-wide conventions
+:begin
+MATCH (n:TameClass) WHERE n.orig_name STARTS WITH "submit-" SET n:SubmitRule;
+MATCH (n:TamePackage {program: "true"}) WHERE n.name STARTS WITH "suppliers/" SET n:Supplier;
+:commit
+
+// megarater-specific
+:begin
+MATCH (c:TameConst)<-[*0..3]-(:TameType {name: "classCode"}) SET c:ClassCode;
+:commit
+
diff --git a/src/current/neo4j/pre.neo4j b/src/current/neo4j/pre.neo4j
new file mode 100644
index 0000000..c646669
--- /dev/null
+++ b/src/current/neo4j/pre.neo4j
@@ -0,0 +1,8 @@
+// some of the indexes are created in post.neo4j
+:begin
+CREATE CONSTRAINT ON (n:TamePackage) ASSERT n.fqn IS UNIQUE;
+CREATE CONSTRAINT ON (n:TameSymbol) ASSERT n.fqn IS UNIQUE;
+:commit
+
+:begin
+
diff --git a/src/current/pkg-dep.xsl b/src/current/pkg-dep.xsl
index 2cdb07a..a4a20ee 100644
--- a/src/current/pkg-dep.xsl
+++ b/src/current/pkg-dep.xsl
@@ -42,9 +42,9 @@
</xsl:for-each>
</xsl:template>
-<xsl:template match="lvp:program">
+<xsl:template match="lvp:program|lvp:program-fragment">
<!-- output deps, one per line -->
- <xsl:for-each select="lvp:import[ @package ]">
+ <xsl:for-each select="( . | lvp:step )/lvp:import[ @package ]">
<xsl:value-of select="concat( @package, $nl )" />
</xsl:for-each>
</xsl:template>
diff --git a/src/current/rater.xsd b/src/current/rater.xsd
index be038a3..650fa87 100644
--- a/src/current/rater.xsd
+++ b/src/current/rater.xsd
@@ -1,5 +1,5 @@
<?xml version="1.0"?>
-<!-- TODO: Remove @keep -->
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lovullo.com/rater"
xmlns="http://www.lovullo.com/rater"
@@ -16,6 +16,11 @@
namespace="http://www.lovullo.com/rater/map"
schemaLocation="map.xsd" />
+<!-- C1 map -->
+<xs:import
+ namespace="http://www.lovullo.com/rater/map/c1"
+ schemaLocation="c1map.xsd" />
+
<!-- worksheets -->
<xs:import
namespace="http://www.lovullo.com/rater/worksheet"
@@ -83,7 +88,7 @@
</xs:annotation>
<xs:restriction base="xs:NCName">
- <xs:pattern value="[a-z_]+" />
+ <xs:pattern value="[a-z_-]+" />
<xs:minLength value="1" />
<xs:maxLength value="15" />
</xs:restriction>
@@ -117,8 +122,6 @@
<xs:restriction base="xs:string">
<xs:pattern value="_*[a-z][a-zA-Z0-9]+|\{?@[a-z][a-zA-Z0-9_]*@\}?" />
- <xs:minLength value="1" />
- <xs:maxLength value="50" />
</xs:restriction>
</xs:simpleType>
@@ -288,6 +291,13 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
+ <xs:attribute name="values" type="xs:token">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+ Short-hand GNU Octave / MATLAB Style matrix specification.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
<xs:attribute name="type" type="typeNameType">
<xs:annotation>
<xs:documentation xml:lang="en">
@@ -333,7 +343,7 @@
</xs:annotation>
<xs:restriction base="xs:string">
- <xs:pattern value="[a-z./][a-z0-9./-]+" />
+ <xs:pattern value="[a-z./][a-zA-Z0-9./-]+" />
<xs:minLength value="2" />
<xs:maxLength value="100" />
</xs:restriction>
@@ -427,6 +437,17 @@
</xs:annotation>
</xs:attribute>
+ <xs:attribute name="no-extclass" type="xs:boolean">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+ Do not import symbols flagged as external to the classifier.
+
+ This is of limited use outside of specialized settings, such as the
+ UI classifier.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+
<xs:attribute name="keep-classes" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
@@ -729,7 +750,7 @@
</xs:complexType>
- <xs:complexType name="inlineTemplateType">
+ <xs:complexType name="inlineTemplateType" mixed="true">
<xs:annotation>
<xs:documentation xml:lang="en">
This node will be replaced with the processed template.
@@ -740,7 +761,12 @@
<xs:element name="for-each" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:sequence>
- <xs:element name="set" minOccurs="1" maxOccurs="unbounded">
+ <xs:element name="sym-set" minOccurs="0" maxOccurs="unbounded">
+ <xs:complexType>
+ <xs:anyAttribute namespace="##any" processContents="lax" />
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="set" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:anyAttribute namespace="##any" processContents="lax" />
</xs:complexType>
@@ -952,6 +978,14 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
+
+ <xs:attribute name="dim" type="xs:string">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+ Number of dimensions as integer or alias.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
@@ -1100,6 +1134,14 @@
</xs:annotation>
</xs:attribute>
+ <xs:attribute name="rmunderscore" type="xs:boolean">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+ Converts '_' to ''
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+
<xs:attribute name="dash" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
@@ -1108,7 +1150,7 @@
</xs:annotation>
</xs:attribute>
- <xs:attribute name="identifier" type="xs:boolean">
+ <xs:attribute name="identifier" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Strip all characters that do not constitute a valid
@@ -1160,7 +1202,7 @@
<xs:complexType>
<xs:attribute name="name" type="xs:string" use="required" />
- <xs:attribute name="value" type="xs:string" use="required" />
+ <xs:anyAttribute namespace="##any" processContents="lax" />
</xs:complexType>
</xs:element>
@@ -1211,14 +1253,12 @@
<!-- TODO: if -->
<xs:element name="unless">
- <xs:complexType>
+ <xs:complexType mixed="true">
<xs:sequence>
<xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax" />
</xs:sequence>
- <xs:attribute name="name" type="xs:string" use="required" />
- <!-- TODO support others -->
- <xs:attribute name="eq" type="xs:string" />
+ <xs:anyAttribute namespace="##any" processContents="lax" />
</xs:complexType>
</xs:element>
@@ -1821,6 +1861,10 @@
</xs:documentation>
</xs:annotation>
</xs:element>
+
+ <!-- with how dynamic the system is, maintaining this XSD is becoming
+ cumbersome; we need to generate one -->
+ <xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax" />
</xs:choice>
<xs:attribute name="name" type="packageNameType">
@@ -1911,6 +1955,15 @@
</xs:annotation>
</xs:attribute>
+ <xs:attribute name="no-extclass-keeps" type="xs:boolean">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+ When importing @keep symbols from packages, ignore those flagged
+ as @extclass
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+
<xs:attribute name="auto-keep-imports" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
diff --git a/src/current/scripts/entry-form.js b/src/current/scripts/entry-form.js
index 0b1a3d6..c4d3ead 100644
--- a/src/current/scripts/entry-form.js
+++ b/src/current/scripts/entry-form.js
@@ -1108,9 +1108,11 @@ var client = ( function()
}
- function updateSummaryDebug( debug, parent, callback )
+ function updateSummaryDebug( results, parent, callback )
{
var queue = [];
+ var debug = results.debug;
+ var vars = results.vars || {};
// do nothing if debug data is not yet available
if ( !debug )
@@ -1149,8 +1151,11 @@ var client = ( function()
try
{
- setPlaceholderValue( id, '', ( debug[ did ] )
- ? JSON.stringify( debug[ did ] )
+ // accommodate fallback values; see summary.xsl `ubd-*` generation
+ var dval = debug[ did ] || vars[ did ];
+
+ setPlaceholderValue( id, '', ( dval )
+ ? JSON.stringify( dval )
: ''
);
}
@@ -1422,7 +1427,7 @@ var client = ( function()
if ( !( Array.isArray( fdata ) ) )
{
var element = document.querySelector(
- '[name="' + field + '"]'
+ '#param-input-' + field + ' [name="' + field + '"]'
);
if ( element )
@@ -1434,7 +1439,7 @@ var client = ( function()
}
var elements = document.querySelectorAll(
- '[name="' + field + '[]"]'
+ '#param-input-' + field + ' [name="' + field + '[]"]'
);
var total = 0;
diff --git a/src/current/summary.xsl b/src/current/summary.xsl
index 7cb9ea4..d7f2c33 100644
--- a/src/current/summary.xsl
+++ b/src/current/summary.xsl
@@ -214,8 +214,10 @@
skipStartupTypeset: true,
});
</script>
- <script type="text/javascript"
- src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
+ <script
+ src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"
+ integrity="sha512-tOav5w1OjvsSJzePRtt2uQPFwBoHt1VZcUq8l8nm5284LEKE9FSJBQryzMBzHxY5P0zRdNqEcpLIRVYFNgu1jw=="
+ crossorigin="anonymous">
</script>
<!-- global functions for developers -->
@@ -249,7 +251,7 @@
if ( target.className.match( /\bmath-typeset-hover\b/ ) )
{
MathJax.Hub.Queue( [ "Typeset", MathJax.Hub, target ] );
- client.updateSummaryDebug( rate_result.debug, target );
+ client.updateSummaryDebug( rate_result, target );
}
}
@@ -1156,7 +1158,10 @@
<p class="debugid">
<xsl:attribute name="id">
<xsl:text>ubd-</xsl:text>
- <xsl:value-of select="@_id" />
+ <!-- debug collection was removed, for now, during the classification
+ system rewrite; fall back to the name of what we're matching on,
+ which should be plenty good enough -->
+ <xsl:value-of select="@on" />
</xsl:attribute>
<a class="sym-ref sym-{$sym/@type}">
@@ -1188,44 +1193,10 @@
<xsl:text> must </xsl:text>
<xsl:apply-templates select="." mode="match-desc" />
-
- <xsl:if test="lv:assuming">
- <xsl:text>, assuming that:</xsl:text>
-
- <ul>
- <xsl:for-each select="lv:assuming/lv:that">
- <li>
- <!-- link to the ref -->
- <a>
- <xsl:attribute name="href">
- <xsl:text>#</xsl:text>
- <xsl:value-of select="@name" />
- </xsl:attribute>
-
- <xsl:value-of select="@name" />
- </a>
-
- <xsl:text> </xsl:text>
- <xsl:apply-templates select="." />
- </li>
- </xsl:for-each>
- </ul>
- </xsl:if>
</p>
</xsl:template>
-<xsl:template match="lv:assuming/lv:that[ @ignored ]" priority="5">
- <xsl:text>is ignored during classification</xsl:text>
-</xsl:template>
-
-<!-- we only do consts right now -->
-<xsl:template match="lv:assuming/lv:that" priority="1">
- <xsl:text>has the value </xsl:text>
- <xsl:value-of select="@const" />
-</xsl:template>
-
-
<!--
Outputs a type match in plain english
diff --git a/src/current/util/serialize.xsl b/src/current/util/serialize.xsl
new file mode 100644
index 0000000..3ebe839
--- /dev/null
+++ b/src/current/util/serialize.xsl
@@ -0,0 +1,651 @@
+<!--
+ Serialization utility functions
+
+ Copyright (C) 2017, 2018 R-T Specialty, LLC.
+
+ This file is part of the Liza Program UI Compiler.
+
+ liza-proguic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+-->
+
+<stylesheet version="2.0"
+ xmlns="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:struct="http://www.lovullo.com/liza/proguic/util/struct"
+ xmlns:f="http://mikegerwitz.com/hoxsl/apply"
+ xmlns:_struct="http://www.lovullo.com/liza/proguic/util/struct/_priv">
+
+<import href="../hoxsl/src/apply.xsl" />
+
+<!--
+@node Serialization
+@section Serialization
+@cindex Serialization
+
+@luic{} uses a primitive API for representing and serializing objects,
+ most notably JSON (@pxref{JSON Transformation}).
+This avoids having to handle string generation
+ (and couple with an implementation) in various systems.
+-->
+
+<variable name="struct:error-qname" as="xs:QName"
+ select="QName(
+ 'http://www.lovullo.com/liza/proguic/util/struct/error',
+ 'err:BADSTRUCT' )" />
+
+<!--
+@cindex Array
+An @dfn{array} is an untyped list of items.
+Usually,
+ this provides @math{O(n)} lookups.
+It is ideal for linear processing of data.
+
+The term ``array'' is abused in certain languages;
+ if you are looking for a key/value store, use
+ @ref{struct:dict#1,,@code{struct-dict}}.
+-->
+
+
+<!--
+ Generate an empty array.
+-->
+<function name="struct:array" as="element( struct:array )">
+ <struct:array />
+</function>
+
+
+<!--
+ Generate an untyped array of values.
+
+ Arrays must contain only @xmlnode{struct:item} elements,
+ but unlike dictionaries,
+ they @emph{must not} contain a@tie{}@xmlattr{key}.
+-->
+<function name="struct:array" as="element( struct:array )">
+ <param name="values" as="element( struct:item )*" />
+
+ <struct:array>
+ <sequence select="$values" />
+ </struct:array>
+</function>
+
+
+<!--
+@cindex Dictionary
+A @dfn{dictionary} is a key/value store.
+Like arrays,
+ dictionaries contain items,
+ but they are indexed by keys.
+Usually,
+ languages implement this as a hash table,
+ providing @math{O(1)} lookups.
+-->
+
+
+<!--
+ Create an empty dictionary.
+-->
+<function name="struct:dict" as="element( struct:dict )">
+ <struct:dict />
+</function>
+
+
+<!--
+ Create a dictionary of values.
+
+ This is a key-value store containing only @xmlnode{struct:item}
+ elements with @xmlattr{key} attributes (@pxref{struct:item#2}).
+-->
+<function name="struct:dict" as="element( struct:dict )">
+ <param name="values" as="element( struct:item )*" />
+
+ <struct:dict>
+ <sequence select="$values" />
+ </struct:dict>
+</function>
+
+
+<!--
+@cindex Item
+An @dfn{item} can be either @dfn{keyed} or @dfn{unkeyed}:
+ the former is suitable only for dictionaries,
+ while the latter is suitable only for arrays.
+
+@devnotice{Item type metadata should be added;
+ otherwise, we can only serialize as a string.}
+-->
+
+
+<!--
+ Associate a value with a key in a dictionary.
+
+ A key value may be a primitive value or another structure.
+ Keyed items must be children of a@tie{}dictionary
+ (@pxref{struct:dict#1,,@code{struct:dict}}).
+
+ Attribute values are converted into strings.
+-->
+<function name="struct:item" as="element( struct:item )">
+ <param name="value" />
+ <param name="id" as="xs:string" />
+
+ <struct:item key="{$id}">
+ <sequence select="if ( $value instance of attribute() ) then
+ string( $value )
+ else
+ $value" />
+ </struct:item>
+</function>
+
+
+<!--
+ Create a keyless item.
+
+ A key value may be a primitive value or another structure.
+ Keyless items must be children of a@tie{}array
+ (@pxref{struct:array#1,,@code{struct:array}}).
+
+ Attribute values are converted into strings.
+-->
+<function name="struct:item" as="element( struct:item )">
+ <param name="value" />
+
+ <struct:item>
+ <sequence select="if ( $value instance of attribute() ) then
+ string( $value )
+ else
+ $value" />
+ </struct:item>
+</function>
+
+<!--
+Since deriving item values from attributes is common,
+ they will automatically be convered into strings.@footnote{
+ Really, it makes no sense to permit attributes,
+ since that will result in the attribute being assigned to the
+ @xmlnode{struct:item} itself,
+ which does not make any sense
+ (and could corrupt internal state depending on what attribute
+ was set).}
+-->
+
+
+<!--
+@subsection Auto-Generating Structures
+
+It's common (and natural) to want to serialize key/value pairs from
+ attributes.
+Two functions provide this convenience:
+-->
+
+
+<!--
+ Generate keys from attributes.
+
+ A key/value pair will be created for each attribute in @var{attrs}
+ using the attribute's local name as the@tie{}key. Whitespace in attribute
+ values will be normalized.
+-->
+<function name="struct:items-from-attrs" as="element( struct:item )*">
+ <param name="attrs" as="attribute()*" />
+
+ <sequence select="for $attr in $attrs
+ return struct:item( normalize-space( $attr ),
+ $attr/local-name() )" />
+</function>
+
+
+<!--
+ Convert an element into a dictinary using its attributes as
+ key/value pairs.
+
+ The name of the element is not used.
+ The attributes of the node are passed to
+ @ref{struct:item#1,,@code{struct:item}}.
+-->
+<function name="struct:dict-from-attrs" as="element( struct:dict )">
+ <param name="element" as="element()" />
+
+ <sequence select="struct:dict(
+ struct:items-from-attrs( $element/@* ) )" />
+</function>
+
+
+<!--
+ Convert a sequence of elements into an array of dictionaries using element
+ attributes as dictionary key/value pairs.
+
+ Each element is processed using
+ @ref{struct:dict-from-attrs#1,,@code{struct:dict-from-attrs}}.
+-->
+<function name="struct:dict-array-from-elements" as="element( struct:array )">
+ <param name="elements" as="element()*" />
+
+ <sequence select="struct:array(
+ for $element in $elements
+ return struct:item(
+ struct:dict-from-attrs( $element ) ) )" />
+</function>
+
+
+<!--
+Another function allows allows using one of the attibutes as
+ a@tie{}key to recursively generate a dictionary of multiple
+ elements,
+ provided that those elements have unquie keys.
+-->
+
+<!--
+ Recurisvely generate dictionary using an attribute as a key.
+
+ This generates a new dictionary with @code{n}@tie{}entires where the
+ value of the key is another dictionary containing the key/value
+ representation of the remaining attributes,
+ where @code{n = count($element)}.
+
+ If @var{$recf} is non-empty,
+ it will be applied to each element that generates an item in the
+ parent dictionary;
+ this allows for recursive processing.
+ The function is applied within the context of the dictionary and
+ should therefore return one or more @xmlnode{struct:item}s.
+
+ Beware: the given key @var{$key} is compared only by @code{local-name}.
+
+ @emph{No check is performed to ensure they all keys are unique in
+ the toplevel dictionary.}
+ Conflicts result in undefined behavior dependent on the serializer.
+ For example,
+ when serialized to JSON,
+ the latter key takes precedence and the former keys are overwritten.
+
+ @devnotice{Should probably handle more gracefully a situation where the
+ key attribute does not exist on one of the elements.}
+-->
+<function name="struct:dict-from-keyed-elements" as="element( struct:dict )">
+ <param name="key" as="xs:string" />
+ <param name="elements" as="element()*" />
+ <param name="recf" as="item()*" />
+
+ <sequence select="
+ struct:dict(
+ for $element in $elements
+ return struct:item(
+ struct:dict(
+ ( struct:items-from-attrs(
+ $element/@*[ not( local-name() = $key ) ] ),
+ if ( $recf ) then
+ f:apply( $recf, $element )
+ else
+ () ) ),
+ $element/@*[ local-name() = $key ] ) )" />
+</function>
+
+
+<!--
+ Recurisvely generate dictionary using an attribute as a key.
+
+ This two-argument version simply invokes
+ @coderef{struct:dict-from-keyed-elements#3} without a child function.
+-->
+<function name="struct:dict-from-keyed-elements" as="element( struct:dict )">
+ <param name="key" as="xs:string" />
+ <param name="elements" as="element()*" />
+
+ <sequence select="struct:dict-from-keyed-elements( $key, $elements, () )" />
+</function>
+
+
+<!--
+An example usage of this function is provided in
+ @ref{f:dict-from-keyed-elements}.
+
+@float Figure, f:dict-from-keyed-elements
+ Given some document:
+
+ @example
+ <meta>
+ <field id="foo" desc="Has nested" type="string">
+ <nested name="n1" />
+ </field>
+ <field id="bar" desc="No nested" type="boolean" />
+ </meta>
+ @end example
+
+ With function:
+
+ @example
+ <function name="nestedf" as="element( struct:item )+">
+ <param name="field" as="element( field )" />
+ <sequence select="struct:item( $field/nested/@@name, 'nestedf' )" />
+ </function>
+ @end example
+
+ Transformed with:
+
+ @example
+ <sequence select="struct:dict-from-keyed-elements( 'id', meta, nestedf() )" />
+ @end example
+
+ Results in:
+
+ @example
+ <struct:dict>
+ <struct:item key="foo">
+ <struct:dict>
+ <struct:item key="desc">Has nested</struct:item>
+ <struct:item key="type">string</struct:item>
+ <struct:item key="nestedf">n1</struct:item>
+ </struct:dict>
+ </struct:item>
+ <struct:item key="bar">
+ <struct:dict>
+ <struct:item key="desc">No nested</struct:item>
+ <struct:item key="type">boolean</struct:item>
+ <struct:item key="nestedf"></struct:item>
+ </struct:dict>
+ </struct:item>
+ </struct:dict>
+ @end example
+@caption{Generating a dictionary from keyed elements.}
+@end float
+
+
+Extracting key/value pairs from element attributes is also a common
+ operation:
+-->
+
+
+<!--
+ Generate keyed items for each element in @var{$elements} using one
+ attribute @var{$key}@tie{}as the key and another attribute
+ @var{$value}@tie{}as the value.
+
+ Beware: the given key @var{$key} is compared only by @code{local-name}.
+
+ The arguments are ordered such that this is useful as a partially
+ applied function for processing lists of elements with lambdas.
+-->
+<function name="struct:items-from-keyed-elements" as="element( struct:item )*">
+ <param name="key" as="xs:string" />
+ <param name="value" as="xs:string" />
+ <param name="elements" as="element()*" />
+
+ <sequence select="for $element in $elements
+ return struct:item(
+ $element/@*[ local-name() = $value ],
+ $element/@*[ local-name() = $key ] )" />
+</function>
+
+
+<!--
+When generating dictionary items in a loop from numerous elements,
+ it can be inconvenient keeping track of unique keys.
+If the goal is to create an array of items grouped by unique keys,
+ you're in luck:
+-->
+
+
+<!--
+ Group keyed items into arrays indexed by their respective keys.
+
+ Every unique key@tie{}@code{k} will result in an array@mdash{
+ }indexed by@tie{}@code{k}@mdash{
+ }containing each respective item.
+
+ @emph{Items without keys will not be retained!}
+-->
+<function name="struct:group-items-by-key" as="element( struct:item )*">
+ <param name="items" as="element( struct:item )*" />
+
+ <for-each-group select="$items" group-by="@key">
+ <struct:item key="{current-grouping-key()}">
+ <struct:array>
+ <sequence select="for $item in current-group()
+ return struct:item( $item/node() )" />
+ </struct:array>
+ </struct:item>
+ </for-each-group>
+</function>
+
+
+<!--
+@menu
+* JSON Transformation:: Serializing to JSON.
+@end menu
+-->
+
+
+
+<!--
+@node JSON Transformation
+@subsection JSON Transformation
+@cindex JSON
+
+The recommended way to serialize a structure as JSON is to apply
+ @ref{struct:to-neo4j-attrs#1,,@code{struct:to-neo4j-attrs}}.
+-->
+
+<!--
+ Transform structure into JSON.
+-->
+<function name="struct:to-neo4j-attrs" as="xs:string">
+ <param name="struct" as="element()" />
+
+ <variable name="result" as="xs:string*">
+ <apply-templates mode="struct:to-neo4j-attrs"
+ select="$struct" />
+ </variable>
+
+ <!-- force to a single string rather than a sequence of them -->
+ <sequence select="string-join( $result, '' )" />
+</function>
+
+
+<!--
+We assume that the structure is already well-formed;@footnote{
+ That might not necessarily be assured by this implementation,
+ but validations belong there (@pxref{Serialization}), not here.}
+ this makes serialization a trivial task.
+
+We proceed by recursive descent.
+Let's start with arrays.
+
+@subsubsection Array Serialization
+
+An array simply encapsulates items in square brackets:
+-->
+
+<!--
+ Transform array into JSON.
+-->
+<template mode="struct:to-neo4j-attrs" priority="5"
+ match="struct:array">
+ <text>[</text>
+ <apply-templates mode="struct:to-neo4j-attrs"
+ select="node()" />
+ <text>]</text>
+</template>
+
+<!--
+Items are simple too,
+ since we don't have to deal with keys.
+If the item contains an element,
+ we consider it to be a nested structure and recurse:
+-->
+
+<!--
+ Transform nested structure into JSON.
+-->
+<template mode="struct:to-neo4j-attrs" priority="5"
+ match="struct:item[ element() ]">
+ <sequence select="struct:to-neo4j-attrs( ./element() )" />
+
+ <if test="following-sibling::struct:item">
+ <sequence select="','" />
+ </if>
+</template>
+
+<!--
+ Otherwise, we consider it to be a primitive.
+ At this point,
+ items are untyped,
+ so we have no choice but to serialize as a string:
+-->
+
+<!--
+ Transform primitive data into JSON.
+
+ Until items are typed, we have no choice but to serialize all items
+ as strings.
+-->
+<template mode="struct:to-neo4j-attrs" priority="4"
+ match="struct:item">
+ <sequence select="concat(
+ '&quot;',
+ _struct:json-escape-str( . ),
+ '&quot;' )" />
+
+ <if test="following-sibling::struct:item">
+ <sequence select="','" />
+ </if>
+</template>
+
+<!--
+Note that we took care to escape the provided value so that double
+ quotes do not break out of the serialized string.
+-->
+
+<!--
+ Escape double quotes within a string.
+-->
+<function name="_struct:json-escape-str" as="xs:string">
+ <param name="str" as="xs:string" />
+
+ <sequence select="replace(
+ replace( $str, '\\', '\\\\' ), '&quot;', '\\&quot;' )" />
+</function>
+
+
+<!--
+@subsubsection Dictionary Serialization
+
+Dictionaries are serialized similarly.
+In JSON,
+ we represent them as objects:
+-->
+
+
+<!--
+ Transform dictionary into JSON object.
+-->
+<template mode="struct:to-neo4j-attrs" priority="5"
+ match="struct:dict">
+ <text>{</text>
+ <apply-templates mode="struct:to-neo4j-attrs-dict"
+ select="node()" />
+ <text>}</text>
+</template>
+
+<!--
+Since dictionaries are key/value,
+ every item needs to be assigned to a field on the object,
+ where field name is specified by @xmlattr{key}.
+Otherwise,
+ serialization proceeds the same way as arrays:
+-->
+
+<!--
+ Transform dictionary key into JSON field on an object.
+
+ The field name is specified by @xmlnode{struct:item/@@key}.
+ Until items are typed, we have no choice but to serialize all items
+ as strings.
+-->
+<template mode="struct:to-neo4j-attrs-dict" priority="5"
+ match="struct:item[ @key ]">
+ <sequence select="concat(_struct:neo4j-translate-prop( @key ),
+ ':' )" />
+
+ <apply-templates mode="struct:to-neo4j-attrs"
+ select="." />
+</template>
+
+
+<function name="_struct:neo4j-translate-prop" as="xs:string">
+ <param name="name" as="xs:string" />
+
+ <sequence select="replace( $name, '-', '_' )" />
+</function>
+
+
+<!--
+Note that we escape the field.
+
+At a lower priority,
+ we have a catch-all that will fail if it encounters a non-keyed
+ structure:
+-->
+
+<!--
+ Error on unrecognized structures during dictionary JSON
+ transformation.
+-->
+<template mode="struct:to-neo4j-attrs-dict" priority="1"
+ match="node()">
+ <sequence select="error( $struct:error-qname,
+ concat( 'Unexpected non-key structure: ',
+ name( . ) ),
+ . )"/>
+</template>
+
+
+<!--
+@subsubsection Miscellaneous
+
+We use comments in test cases to annotate structures.
+It's unlikely that they will be used in practice,
+ but since they are nodes too,
+ we need to make sure we don't consider them to be errors:
+-->
+
+<!--
+ Ignore comments during processing.
+-->
+<template mode="struct:to-neo4j-attrs" priority="2"
+ match="comment()">
+</template>
+
+<!--
+ Everything else we don't know about during processing results in an
+ error:
+-->
+
+<!--
+ Error on unrecognized structures during JSON transformation.
+-->
+<template mode="struct:to-neo4j-attrs" priority="1"
+ match="node()">
+ <sequence select="error( $struct:error-qname,
+ concat( 'Unexpected structure: ',
+ string( . ) ),
+ . )"/>
+</template>
+
+<!--
+And we're done.
+-->
+
+</stylesheet>
diff --git a/tamer/Cargo.lock b/tamer/Cargo.lock
index d40a2ef..db0e2c4 100644
--- a/tamer/Cargo.lock
+++ b/tamer/Cargo.lock
@@ -2,312 +2,324 @@
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
-version = "0.7.10"
+version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
- "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr",
]
[[package]]
name = "assert_cmd"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7ac5c260f75e4e4ba87b7342be6edcecbcb3eb6741a0507fda7ad115845cc65"
dependencies = [
- "escargot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "predicates 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "predicates-tree 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "escargot",
+ "predicates",
+ "predicates-core",
+ "predicates-tree",
]
[[package]]
name = "autocfg"
-version = "1.0.0"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bumpalo"
-version = "3.2.1"
+version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
[[package]]
name = "byteorder"
-version = "1.3.4"
+version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "difference"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
[[package]]
name = "escargot"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19db1f7e74438642a5018cdf263bb1325b2e792f02dd0a3ca6d6c0f0d7b1d5a5"
dependencies = [
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde",
+ "serde_json",
]
[[package]]
name = "exitcode"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193"
[[package]]
name = "fixedbitset"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
[[package]]
name = "float-cmp"
-version = "0.6.0"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4"
dependencies = [
- "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
- "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder",
]
[[package]]
name = "getopts"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
dependencies = [
- "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width",
]
[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+
+[[package]]
name = "indexmap"
-version = "1.3.2"
+version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
dependencies = [
- "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg",
+ "hashbrown",
]
[[package]]
name = "itoa"
-version = "0.4.5"
+version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "memchr"
-version = "2.3.3"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "normalize-line-endings"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]]
name = "num-traits"
-version = "0.2.11"
+version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
- "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg",
]
[[package]]
name = "petgraph"
-version = "0.5.0"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "petgraph-graphml"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1a24b19072d6f11cec958e21af7f80d45ee135e9d459b7703510aaf0550b47b"
dependencies = [
- "fixedbitset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "petgraph",
+ "xml-rs",
]
[[package]]
name = "predicates"
-version = "1.0.4"
+version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df"
dependencies = [
- "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "float-cmp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "normalize-line-endings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "difference",
+ "float-cmp",
+ "normalize-line-endings",
+ "predicates-core",
+ "regex",
]
[[package]]
name = "predicates-core"
-version = "1.0.0"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451"
[[package]]
name = "predicates-tree"
-version = "1.0.0"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2"
dependencies = [
- "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "predicates-core",
+ "treeline",
]
[[package]]
name = "proc-macro2"
-version = "1.0.10"
+version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
dependencies = [
- "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid",
]
[[package]]
name = "quick-xml"
-version = "0.18.1"
+version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b"
dependencies = [
- "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr",
]
[[package]]
name = "quote"
-version = "1.0.3"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
]
[[package]]
name = "regex"
-version = "1.3.7"
+version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
- "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)",
- "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
]
[[package]]
name = "regex-syntax"
-version = "0.6.17"
+version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "ryu"
-version = "1.0.4"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "serde"
-version = "1.0.106"
+version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
- "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.106"
+version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "serde_json"
-version = "1.0.52"
+version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
- "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa",
+ "ryu",
+ "serde",
]
[[package]]
name = "syn"
-version = "1.0.18"
+version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
]
[[package]]
name = "tamer"
version = "0.0.0"
dependencies = [
- "assert_cmd 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "bumpalo 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "exitcode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "petgraph 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "predicates 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "quick-xml 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "thread_local"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "assert_cmd",
+ "bumpalo",
+ "exitcode",
+ "fxhash",
+ "getopts",
+ "lazy_static",
+ "petgraph",
+ "petgraph-graphml",
+ "predicates",
+ "quick-xml",
]
[[package]]
name = "treeline"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
[[package]]
name = "unicode-width"
-version = "0.1.7"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"
-version = "0.2.0"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
-[metadata]
-"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
-"checksum assert_cmd 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b7ac5c260f75e4e4ba87b7342be6edcecbcb3eb6741a0507fda7ad115845cc65"
-"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
-"checksum bumpalo 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187"
-"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
-"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
-"checksum escargot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19db1f7e74438642a5018cdf263bb1325b2e792f02dd0a3ca6d6c0f0d7b1d5a5"
-"checksum exitcode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193"
-"checksum fixedbitset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
-"checksum float-cmp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "da62c4f1b81918835a8c6a484a397775fff5953fe83529afd51b05f5c6a6617d"
-"checksum fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
-"checksum getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
-"checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
-"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
-"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
-"checksum normalize-line-endings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
-"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
-"checksum petgraph 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29c127eea4a29ec6c85d153c59dc1213f33ec74cead30fe4730aecc88cc1fd92"
-"checksum predicates 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "347a1b6f0b21e636bc9872fb60b83b8e185f6f5516298b8238699f7f9a531030"
-"checksum predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178"
-"checksum predicates-tree 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124"
-"checksum proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
-"checksum quick-xml 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3cc440ee4802a86e357165021e3e255a9143724da31db1e2ea540214c96a0f82"
-"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
-"checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
-"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
-"checksum ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1"
-"checksum serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)" = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
-"checksum serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)" = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
-"checksum serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)" = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd"
-"checksum syn 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213"
-"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
-"checksum treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
-"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
-"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+[[package]]
+name = "xml-rs"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a"
diff --git a/tamer/Cargo.toml b/tamer/Cargo.toml
index 5060300..db72ec4 100644
--- a/tamer/Cargo.toml
+++ b/tamer/Cargo.toml
@@ -34,4 +34,5 @@ quick-xml = ">= 0.17.0"
getopts = "0.2"
exitcode = "1.1.2"
lazy_static = ">= 1.4.0"
+petgraph-graphml = ">= 2.0.1"
diff --git a/tamer/build-aux/intra_rustdoc_links_check.rs b/tamer/build-aux/intra_rustdoc_links_check.rs
deleted file mode 100644
index 53c15e1..0000000
--- a/tamer/build-aux/intra_rustdoc_links_check.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Feature check for `test`
-//
-// Copyright (C) 2014-2020 Ryan Specialty Group, LLC.
-//
-// This file is part of TAME.
-//
-// 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 Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-// As of the time of writing, this feature is unstable and can only be
-// enabled in nightly. This file is intended to be used in the `configure`
-// script to determine whether a nightly version of Rust must be used to
-// build documentation.
-#![feature(intra_rustdoc_links)]
-
diff --git a/tamer/configure.ac b/tamer/configure.ac
index 8295505..a4c9c6c 100644
--- a/tamer/configure.ac
+++ b/tamer/configure.ac
@@ -45,7 +45,7 @@ AC_CHECK_PROGS(CARGO, [cargo])
test -n "$CARGO" || AC_MSG_ERROR([cargo not found])
-rustc_ver_req=1.42.0
+rustc_ver_req=1.52.0
AC_CHECK_PROGS(RUSTC, [rustc])
AC_MSG_CHECKING([rustc version >= $rustc_ver_req])
@@ -58,15 +58,9 @@ AX_COMPARE_VERSION([$rustc_version], [ge], [$rustc_ver_req],
AC_ARG_VAR([CARGO_BUILD_FLAGS],
[Flags to be passed to `cargo build' when invoked via Make])
-# The `intra_rustdoc_links` feature is required for building
-# documentation. If unavailable, then it's still an unstable feature and
-# we'll need to use nightly. We don't check for nightly here, though---if
-# it's missing, then cargo will tell the user what to do.
-AC_MSG_CHECKING([`intra_rustdoc_links_check` feature support])
-AS_IF(["$RUSTC" --crate-type lib build_aux/intra_rustdoc_links_check.rs &>/dev/null],
- [AC_MSG_RESULT(available)],
- [AC_MSG_RESULT([no (nightly required)])
- AC_SUBST([CARGO_DOC_FLAGS], [+nightly])])
+# All currently-used doc features are stable (this used to be used for
+# intra-doc links)
+AC_SUBST([CARGO_DOC_FLAGS], [])
# The `test` feature is required for benchmarking. If unavailable, then
# it's still an unstable feature and we'll need to use nightly. We don't
diff --git a/tamer/src/bin/tameld.rs b/tamer/src/bin/tameld.rs
index bf91e94..b2215f2 100644
--- a/tamer/src/bin/tameld.rs
+++ b/tamer/src/bin/tameld.rs
@@ -33,27 +33,41 @@ use tamer::ld::poc;
/// Types of commands
enum Command {
- Link(String, String),
+ Link(String, String, Emit),
Usage,
}
+/// Ways to emit the linked objects
+enum Emit {
+ /// The typical desired `Emit`
+ ///
+ /// Outputs the linked object files in a format that can be used in an
+ /// application.
+ Xmle,
+
+ /// Used for exploring the linked graph
+ Graphml,
+}
+
/// Entrypoint for the linker
pub fn main() -> Result<(), Box<dyn Error>> {
let args: Vec<String> = env::args().collect();
let program = &args[0];
let opts = get_opts();
- let usage = opts.usage(&format!("Usage: {} -o OUTPUT FILE", program));
+ let usage =
+ opts.usage(&format!("Usage: {} [OPTIONS] -o OUTPUT FILE", program));
match parse_options(opts, args) {
- Ok(Command::Link(input, output)) => match poc::main(&input, &output) {
- Err(e) => {
- eprintln!("error: {}", e);
- eprintln!("fatal: failed to link `{}`", output);
+ Ok(Command::Link(input, output, emit)) => match emit {
+ Emit::Xmle => poc::xmle(&input, &output),
+ Emit::Graphml => poc::graphml(&input, &output),
+ }
+ .or_else(|e| {
+ eprintln!("error: {}", e);
+ eprintln!("fatal: failed to link `{}`", output);
- std::process::exit(1);
- }
- ok => ok,
- },
+ std::process::exit(1);
+ }),
Ok(Command::Usage) => {
println!("{}", usage);
std::process::exit(exitcode::OK);
@@ -77,6 +91,12 @@ fn get_opts() -> Options {
let mut opts = Options::new();
opts.optopt("o", "output", "set output file name", "NAME");
opts.optflag("h", "help", "print this help menu");
+ opts.optopt(
+ "",
+ "emit",
+ "set the output to be emitted",
+ "--emit xmle|graphml",
+ );
opts
}
@@ -107,7 +127,16 @@ fn parse_options(opts: Options, args: Vec<String>) -> Result<Command, Fail> {
}
};
- Ok(Command::Link(input, output))
+ let emit = match matches.opt_str("emit") {
+ Some(m) => match &m[..] {
+ "xmle" => Emit::Xmle,
+ "graphml" => Emit::Graphml,
+ em => return Err(Fail::ArgumentMissing(format!("--emit {}", em))),
+ },
+ None => Emit::Xmle,
+ };
+
+ Ok(Command::Link(input, output, emit))
}
#[cfg(test)]
@@ -208,6 +237,29 @@ mod test {
}
#[test]
+ fn parse_options_valid_long_emit_invalid() {
+ let opts = get_opts();
+ let result = parse_options(
+ opts,
+ vec![
+ String::from("program"),
+ String::from("foo"),
+ String::from("--output"),
+ String::from("bar"),
+ String::from("--emit"),
+ String::from("foo"),
+ ],
+ );
+
+ match result {
+ Err(Fail::ArgumentMissing(message)) => {
+ assert_eq!("--emit foo", message);
+ }
+ _ => panic!("Extra option not caught"),
+ }
+ }
+
+ #[test]
fn parse_options_valid() {
let opts = get_opts();
let result = parse_options(
@@ -221,7 +273,7 @@ mod test {
);
match result {
- Ok(Command::Link(infile, outfile)) => {
+ Ok(Command::Link(infile, outfile, Emit::Xmle)) => {
assert_eq!("foo", infile);
assert_eq!("bar", outfile);
}
@@ -239,11 +291,37 @@ mod test {
String::from("foo"),
String::from("--output"),
String::from("bar"),
+ String::from("--emit"),
+ String::from("xmle"),
+ ],
+ );
+
+ match result {
+ Ok(Command::Link(infile, outfile, Emit::Xmle)) => {
+ assert_eq!("foo", infile);
+ assert_eq!("bar", outfile);
+ }
+ _ => panic!("Unexpected result"),
+ }
+ }
+
+ #[test]
+ fn parse_options_valid_long_emit_graphml() {
+ let opts = get_opts();
+ let result = parse_options(
+ opts,
+ vec![
+ String::from("program"),
+ String::from("foo"),
+ String::from("--output"),
+ String::from("bar"),
+ String::from("--emit"),
+ String::from("graphml"),
],
);
match result {
- Ok(Command::Link(infile, outfile)) => {
+ Ok(Command::Link(infile, outfile, Emit::Graphml)) => {
assert_eq!("foo", infile);
assert_eq!("bar", outfile);
}
diff --git a/tamer/src/ir/asg/base.rs b/tamer/src/ir/asg/base.rs
index 6dc690c..b775975 100644
--- a/tamer/src/ir/asg/base.rs
+++ b/tamer/src/ir/asg/base.rs
@@ -20,7 +20,8 @@
//! Base concrete [`Asg`] implementation.
use super::graph::{
- Asg, AsgEdge, AsgError, AsgResult, IndexType, Node, ObjectRef, SortableAsg,
+ Asg, AsgEdge, AsgResult, IndexType, Node, ObjectRef, SortableAsg,
+ SortableAsgError, SortableAsgResult,
};
use super::ident::IdentKind;
use super::object::{
@@ -97,6 +98,11 @@ where
}
}
+ /// Get the underlying Graph
+ pub fn into_inner(self) -> DiGraph<Node<O>, AsgEdge, Ix> {
+ self.graph
+ }
+
/// Index the provided symbol `name` as representing the identifier `node`.
///
/// This index permits `O(1)` identifier lookups.
@@ -153,7 +159,7 @@ where
&mut self,
name: &'i Symbol<'i>,
f: F,
- ) -> AsgResult<ObjectRef<Ix>, Ix>
+ ) -> AsgResult<ObjectRef<Ix>>
where
F: FnOnce(O) -> TransitionResult<O>,
{
@@ -172,7 +178,7 @@ where
&mut self,
identi: ObjectRef<Ix>,
f: F,
- ) -> AsgResult<ObjectRef<Ix>, Ix>
+ ) -> AsgResult<ObjectRef<Ix>>
where
F: FnOnce(O) -> TransitionResult<O>,
{
@@ -192,56 +198,6 @@ where
Err(err.into())
})
}
-
- /// Check graph for cycles
- ///
- /// We want to catch any cycles before we start using the graph.
- /// Unfortunately, we need to allow cycles for our [`IdentKind::Func`]
- /// so we cannot use the typical algorithms in a straightforward manner.
- ///
- /// We loop through all SCCs and check that they are not all functions. If
- /// they are, we ignore the cycle, otherwise we will return an error.
- fn check_cycles(&self) -> AsgResult<(), Ix> {
- // While `tarjan_scc` does do a topological sort, it does not suit our
- // needs because we need to filter out some allowed cycles. It would
- // still be possible to use this, but we also need to only check nodes
- // that are attached to our "roots". We are doing our own sort and as of
- // the initial writing, this does not have a significant performance
- // impact.
- let sccs = petgraph::algo::tarjan_scc(&self.graph);
-
- let cycles: Vec<_> = sccs
- .into_iter()
- .filter_map(|scc| {
- // For single-node SCCs, we just need to make sure they are
- // not neighbors with themselves.
- if scc.len() == 1
- && !self.graph.neighbors(scc[0]).any(|nx| nx == scc[0])
- {
- return None;
- }
-
- let is_all_funcs = scc.iter().all(|nx| {
- let ident = self.get(*nx).expect("missing node");
- matches!(ident.kind(), Some(IdentKind::Func(_, _)))
- });
-
- if is_all_funcs {
- None
- } else {
- let cycles =
- scc.iter().map(|nx| ObjectRef::from(*nx)).collect();
- Some(cycles)
- }
- })
- .collect();
-
- if cycles.is_empty() {
- Ok(())
- } else {
- Err(AsgError::Cycles(cycles))
- }
- }
}
impl<'i, O, Ix> Asg<'i, O, Ix> for BaseAsg<O, Ix>
@@ -254,7 +210,7 @@ where
name: &'i Symbol<'i>,
kind: IdentKind,
src: Source<'i>,
- ) -> AsgResult<ObjectRef<Ix>, Ix> {
+ ) -> AsgResult<ObjectRef<Ix>> {
self.with_ident_lookup(name, |obj| obj.resolve(kind, src))
}
@@ -263,7 +219,7 @@ where
name: &'i Symbol<'i>,
kind: IdentKind,
src: Source<'i>,
- ) -> AsgResult<ObjectRef<Ix>, Ix> {
+ ) -> AsgResult<ObjectRef<Ix>> {
self.with_ident_lookup(name, |obj| obj.extern_(kind, src))
}
@@ -271,7 +227,7 @@ where
&mut self,
identi: ObjectRef<Ix>,
text: FragmentText,
- ) -> AsgResult<ObjectRef<Ix>, Ix> {
+ ) -> AsgResult<ObjectRef<Ix>> {
self.with_ident(identi, |obj| obj.set_fragment(text))
}
@@ -326,10 +282,10 @@ where
fn sort(
&'i self,
roots: &[ObjectRef<Ix>],
- ) -> AsgResult<Sections<'i, O>, Ix> {
+ ) -> SortableAsgResult<Sections<'i, O>, Ix> {
let mut deps = Sections::new();
- self.check_cycles()?;
+ check_cycles(self)?;
// This is technically a topological sort, but functions have cycles.
let mut dfs = DfsPostOrder::empty(&self.graph);
@@ -339,7 +295,7 @@ where
}
while let Some(index) = dfs.next(&self.graph) {
- let ident = self.get(index).expect("missing node");
+ let ident = self.get(index).expect("missing node").resolved()?;
match ident.kind() {
Some(kind) => match kind {
@@ -357,10 +313,13 @@ where
_ => deps.rater.push_body(ident),
},
None => {
- return Err(AsgError::UnexpectedNode(format!(
- "{:?}",
- ident.as_ident()
- )))
+ return Err(SortableAsgError::MissingObjectKind(
+ ident
+ .name()
+ .map(|name| name.as_ref())
+ .unwrap_or("<unknown>")
+ .into(),
+ ))
}
}
}
@@ -369,11 +328,67 @@ where
}
}
+/// Check graph for cycles
+///
+/// We want to catch any cycles before we start using the graph.
+/// Unfortunately, we need to allow cycles for our [`IdentKind::Func`]
+/// so we cannot use the typical algorithms in a straightforward manner.
+///
+/// We loop through all SCCs and check that they are not all functions. If
+/// they are, we ignore the cycle, otherwise we will return an error.
+fn check_cycles<'i, O, Ix>(asg: &BaseAsg<O, Ix>) -> SortableAsgResult<(), Ix>
+where
+ Ix: IndexType,
+ O: IdentObjectData<'i> + IdentObjectState<'i, O>,
+{
+ // While `tarjan_scc` does do a topological sort, it does not suit our
+ // needs because we need to filter out some allowed cycles. It would
+ // still be possible to use this, but we also need to only check nodes
+ // that are attached to our "roots". We are doing our own sort and as of
+ // the initial writing, this does not have a significant performance
+ // impact.
+ let sccs = petgraph::algo::tarjan_scc(&asg.graph);
+
+ let cycles: Vec<_> = sccs
+ .into_iter()
+ .filter_map(|scc| {
+ // For single-node SCCs, we just need to make sure they are
+ // not neighbors with themselves.
+ if scc.len() == 1
+ && !asg.graph.neighbors(scc[0]).any(|nx| nx == scc[0])
+ {
+ return None;
+ }
+
+ let is_all_funcs = scc.iter().all(|nx| {
+ let ident = Asg::get(asg, *nx).expect("missing node");
+ matches!(ident.kind(), Some(IdentKind::Func(_, _)))
+ });
+
+ if is_all_funcs {
+ None
+ } else {
+ let cycles =
+ scc.iter().map(|nx| ObjectRef::from(*nx)).collect();
+ Some(cycles)
+ }
+ })
+ .collect();
+
+ if cycles.is_empty() {
+ Ok(())
+ } else {
+ Err(SortableAsgError::Cycles(cycles))
+ }
+}
+
#[cfg(test)]
mod test {
use super::super::graph::AsgError;
use super::*;
- use crate::ir::asg::{Dim, IdentObject, TransitionError, TransitionResult};
+ use crate::ir::asg::{
+ Dim, IdentObject, TransitionError, TransitionResult, UnresolvedError,
+ };
use crate::ir::legacyir::SymDtype;
use crate::sym::SymbolIndex;
use std::cell::RefCell;
@@ -387,6 +402,7 @@ mod test {
fail_redeclare: RefCell<Option<TransitionError>>,
fail_extern: RefCell<Option<TransitionError>>,
fail_set_fragment: RefCell<Option<TransitionError>>,
+ fail_resolved: RefCell<Option<UnresolvedError>>,
}
impl<'i> IdentObjectData<'i> for StubIdentObject<'i> {
@@ -433,6 +449,14 @@ mod test {
Ok(self)
}
+ fn resolved(&self) -> Result<&StubIdentObject<'i>, UnresolvedError> {
+ if self.fail_resolved.borrow().is_some() {
+ return Err(self.fail_resolved.replace(None).unwrap());
+ }
+
+ Ok(self)
+ }
+
fn extern_(
mut self,
kind: IdentKind,
@@ -479,7 +503,7 @@ mod test {
}
#[test]
- fn declare_new_unique_idents() -> AsgResult<(), u8> {
+ fn declare_new_unique_idents() -> AsgResult<()> {
let mut sut = Sut::new();
// NB: The index ordering is important! We first use a larger
@@ -537,7 +561,7 @@ mod test {
}
#[test]
- fn lookup_by_symbol() -> AsgResult<(), u8> {
+ fn lookup_by_symbol() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "lookup");
@@ -556,7 +580,7 @@ mod test {
}
#[test]
- fn declare_returns_existing() -> AsgResult<(), u8> {
+ fn declare_returns_existing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "symdup");
@@ -582,7 +606,7 @@ mod test {
// Builds upon declare_returns_existing.
#[test]
- fn declare_fails_if_transition_fails() -> AsgResult<(), u8> {
+ fn declare_fails_if_transition_fails() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "symdup");
@@ -618,7 +642,7 @@ mod test {
}
#[test]
- fn declare_extern_returns_existing() -> AsgResult<(), u8> {
+ fn declare_extern_returns_existing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "symext");
@@ -644,7 +668,7 @@ mod test {
// Builds upon declare_returns_existing.
#[test]
- fn declare_extern_fails_if_transition_fails() -> AsgResult<(), u8> {
+ fn declare_extern_fails_if_transition_fails() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "symdup");
@@ -684,7 +708,7 @@ mod test {
}
#[test]
- fn add_fragment_to_ident() -> AsgResult<(), u8> {
+ fn add_fragment_to_ident() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "tofrag");
@@ -714,7 +738,7 @@ mod test {
}
#[test]
- fn add_fragment_to_ident_fails_if_transition_fails() -> AsgResult<(), u8> {
+ fn add_fragment_to_ident_fails_if_transition_fails() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "failfrag");
@@ -748,7 +772,7 @@ mod test {
}
#[test]
- fn add_ident_dep_to_ident() -> AsgResult<(), u8> {
+ fn add_ident_dep_to_ident() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "sym");
@@ -769,7 +793,7 @@ mod test {
// same as above test
#[test]
- fn add_dep_lookup_existing() -> AsgResult<(), u8> {
+ fn add_dep_lookup_existing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "sym");
@@ -785,7 +809,7 @@ mod test {
}
#[test]
- fn add_dep_lookup_missing() -> AsgResult<(), u8> {
+ fn add_dep_lookup_missing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "sym");
@@ -802,7 +826,7 @@ mod test {
}
#[test]
- fn declare_return_missing_symbol() -> AsgResult<(), u8> {
+ fn declare_return_missing_symbol() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "sym");
@@ -850,7 +874,7 @@ mod test {
let sym = symbol_dummy!(i, stringify!($name));
- $sut.declare(&sym, $kind, Source::default())?;
+ $sut.declare(&sym, $kind, Source::default()).unwrap();
let (_, _) = $sut.add_dep_lookup($base, &sym);
$dest.push(sym);
@@ -859,7 +883,7 @@ mod test {
}
#[test]
- fn graph_sort() -> AsgResult<(), u8> {
+ fn graph_sort() -> SortableAsgResult<(), u8> {
let mut sut = Sut::new();
let mut meta = vec![];
@@ -868,8 +892,9 @@ mod test {
let mut retmap = vec![];
let base = symbol_dummy!(1, "sym1");
- let base_node =
- sut.declare(&base, IdentKind::Map, Source::default())?;
+ let base_node = sut
+ .declare(&base, IdentKind::Map, Source::default())
+ .unwrap();
add_syms!(sut, &base, {
meta <- meta1: IdentKind::Meta,
@@ -903,28 +928,31 @@ mod test {
}
#[test]
- fn graph_sort_missing_node() -> AsgResult<(), u8> {
+ fn graph_sort_missing_node() -> SortableAsgResult<(), u8> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "sym");
let dep = symbol_dummy!(2, "dep");
- let sym_node = sut.declare(
- &sym,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym_node = sut
+ .declare(
+ &sym,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- sut.set_fragment(sym_node, FragmentText::from("foo"))?;
+ sut.set_fragment(sym_node, FragmentText::from("foo"))
+ .unwrap();
let (_, _) = sut.add_dep_lookup(&sym, &dep);
match sut.sort(&vec![sym_node]) {
Ok(_) => panic!("Unexpected success - dependency is not in graph"),
- Err(AsgError::UnexpectedNode(_)) => (),
+ Err(SortableAsgError::MissingObjectKind(_)) => (),
_ => {
panic!("Incorrect error result when dependency is not in graph")
}
@@ -934,7 +962,7 @@ mod test {
}
#[test]
- fn graph_sort_no_roots() -> AsgResult<(), u8> {
+ fn graph_sort_no_roots() -> SortableAsgResult<(), u8> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "sym");
@@ -950,32 +978,38 @@ mod test {
}
#[test]
- fn graph_sort_simple_cycle() -> AsgResult<(), u8> {
+ fn graph_sort_simple_cycle() -> SortableAsgResult<(), u8> {
let mut sut = Sut::new();
- let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
- let dep = Symbol::new_dummy(SymbolIndex::from_u32(3), "dep");
+ let sym = symbol_dummy!(2, "sym");
+ let dep = symbol_dummy!(3, "dep");
- let sym_node = sut.declare(
- &sym,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym_node = sut
+ .declare(
+ &sym,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let dep_node = sut.declare(
- &dep,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let dep_node = sut
+ .declare(
+ &dep,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- sut.set_fragment(sym_node, FragmentText::from("foo"))?;
- sut.set_fragment(dep_node, FragmentText::from("bar"))?;
+ sut.set_fragment(sym_node, FragmentText::from("foo"))
+ .unwrap();
+ sut.set_fragment(dep_node, FragmentText::from("bar"))
+ .unwrap();
let (_, _) = sut.add_dep_lookup(&sym, &dep);
let (_, _) = sut.add_dep_lookup(&dep, &sym);
@@ -986,7 +1020,7 @@ mod test {
vec![vec![dep_node.into(), sym_node.into()]];
match result {
Ok(_) => panic!("sort did not detect cycle"),
- Err(AsgError::Cycles(scc)) => assert_eq!(expected, scc),
+ Err(SortableAsgError::Cycles(scc)) => assert_eq!(expected, scc),
Err(e) => panic!("unexpected error: {}", e),
}
@@ -994,54 +1028,66 @@ mod test {
}
#[test]
- fn graph_sort_two_simple_cycles() -> AsgResult<(), u8> {
+ fn graph_sort_two_simple_cycles() -> SortableAsgResult<(), u8> {
let mut sut = Sut::new();
- let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
- let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(3), "sym2");
- let dep = Symbol::new_dummy(SymbolIndex::from_u32(4), "dep");
- let dep2 = Symbol::new_dummy(SymbolIndex::from_u32(5), "dep2");
+ let sym = symbol_dummy!(2, "sym");
+ let sym2 = symbol_dummy!(3, "sym2");
+ let dep = symbol_dummy!(4, "dep");
+ let dep2 = symbol_dummy!(5, "dep2");
- let sym_node = sut.declare(
- &sym,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
-
- let sym2_node = sut.declare(
- &sym2,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym_node = sut
+ .declare(
+ &sym,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let dep_node = sut.declare(
- &dep,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym2_node = sut
+ .declare(
+ &sym2,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let dep2_node = sut.declare(
- &dep2,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let dep_node = sut
+ .declare(
+ &dep,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- sut.set_fragment(sym_node, FragmentText::from("foo"))?;
- sut.set_fragment(sym2_node, FragmentText::from("bar"))?;
- sut.set_fragment(dep_node, FragmentText::from("baz"))?;
- sut.set_fragment(dep2_node, FragmentText::from("huh"))?;
+ let dep2_node = sut
+ .declare(
+ &dep2,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
+
+ sut.set_fragment(sym_node, FragmentText::from("foo"))
+ .unwrap();
+ sut.set_fragment(sym2_node, FragmentText::from("bar"))
+ .unwrap();
+ sut.set_fragment(dep_node, FragmentText::from("baz"))
+ .unwrap();
+ sut.set_fragment(dep2_node, FragmentText::from("huh"))
+ .unwrap();
let (_, _) = sut.add_dep_lookup(&sym, &dep);
let (_, _) = sut.add_dep_lookup(&dep, &sym);
@@ -1056,7 +1102,7 @@ mod test {
];
match result {
Ok(_) => panic!("sort did not detect cycle"),
- Err(AsgError::Cycles(scc)) => assert_eq!(expected, scc),
+ Err(SortableAsgError::Cycles(scc)) => assert_eq!(expected, scc),
Err(e) => panic!("unexpected error: {}", e),
}
@@ -1064,32 +1110,39 @@ mod test {
}
#[test]
- fn graph_sort_no_cycle_with_edge_to_same_node() -> AsgResult<(), u8> {
+ fn graph_sort_no_cycle_with_edge_to_same_node() -> SortableAsgResult<(), u8>
+ {
let mut sut = Sut::new();
- let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
- let dep = Symbol::new_dummy(SymbolIndex::from_u32(3), "dep");
+ let sym = symbol_dummy!(2, "sym");
+ let dep = symbol_dummy!(3, "dep");
- let sym_node = sut.declare(
- &sym,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym_node = sut
+ .declare(
+ &sym,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let dep_node = sut.declare(
- &dep,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let dep_node = sut
+ .declare(
+ &dep,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- sut.set_fragment(sym_node, FragmentText::from("foo"))?;
- sut.set_fragment(dep_node, FragmentText::from("bar"))?;
+ sut.set_fragment(sym_node, FragmentText::from("foo"))
+ .unwrap();
+ sut.set_fragment(dep_node, FragmentText::from("bar"))
+ .unwrap();
let (_, _) = sut.add_dep_lookup(&sym, &dep);
let (_, _) = sut.add_dep_lookup(&sym, &dep);
@@ -1105,43 +1158,52 @@ mod test {
}
#[test]
- fn graph_sort_cycle_with_a_few_steps() -> AsgResult<(), u8> {
+ fn graph_sort_cycle_with_a_few_steps() -> SortableAsgResult<(), u8> {
let mut sut = Sut::new();
- let sym1 = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym1");
- let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym2");
- let sym3 = Symbol::new_dummy(SymbolIndex::from_u32(3), "sym3");
+ let sym1 = symbol_dummy!(1, "sym1");
+ let sym2 = symbol_dummy!(2, "sym2");
+ let sym3 = symbol_dummy!(3, "sym3");
- let sym1_node = sut.declare(
- &sym1,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym1_node = sut
+ .declare(
+ &sym1,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let sym2_node = sut.declare(
- &sym2,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym2_node = sut
+ .declare(
+ &sym2,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let sym3_node = sut.declare(
- &sym3,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym3_node = sut
+ .declare(
+ &sym3,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- sut.set_fragment(sym1_node, FragmentText::from("foo"))?;
- sut.set_fragment(sym2_node, FragmentText::from("bar"))?;
- sut.set_fragment(sym3_node, FragmentText::from("baz"))?;
+ sut.set_fragment(sym1_node, FragmentText::from("foo"))
+ .unwrap();
+ sut.set_fragment(sym2_node, FragmentText::from("bar"))
+ .unwrap();
+ sut.set_fragment(sym3_node, FragmentText::from("baz"))
+ .unwrap();
let (_, _) = sut.add_dep_lookup(&sym1, &sym2);
let (_, _) = sut.add_dep_lookup(&sym2, &sym3);
@@ -1153,7 +1215,7 @@ mod test {
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
match result {
Ok(_) => panic!("sort did not detect cycle"),
- Err(AsgError::Cycles(scc)) => assert_eq!(expected, scc),
+ Err(SortableAsgError::Cycles(scc)) => assert_eq!(expected, scc),
Err(e) => panic!("unexpected error: {}", e),
}
@@ -1162,43 +1224,52 @@ mod test {
#[test]
fn graph_sort_cyclic_function_with_non_function_with_a_few_steps(
- ) -> AsgResult<(), u8> {
+ ) -> SortableAsgResult<(), u8> {
let mut sut = Sut::new();
- let sym1 = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym1");
- let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym2");
- let sym3 = Symbol::new_dummy(SymbolIndex::from_u32(3), "sym3");
+ let sym1 = symbol_dummy!(1, "sym1");
+ let sym2 = symbol_dummy!(2, "sym2");
+ let sym3 = symbol_dummy!(3, "sym3");
- let sym1_node = sut.declare(
- &sym1,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym1_node = sut
+ .declare(
+ &sym1,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let sym2_node = sut.declare(
- &sym2,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym2_node = sut
+ .declare(
+ &sym2,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let sym3_node = sut.declare(
- &sym3,
- IdentKind::Func(Dim::default(), SymDtype::Empty),
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym3_node = sut
+ .declare(
+ &sym3,
+ IdentKind::Func(Dim::default(), SymDtype::Empty),
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- sut.set_fragment(sym1_node, FragmentText::from("foo"))?;
- sut.set_fragment(sym2_node, FragmentText::from("bar"))?;
- sut.set_fragment(sym3_node, FragmentText::from("baz"))?;
+ sut.set_fragment(sym1_node, FragmentText::from("foo"))
+ .unwrap();
+ sut.set_fragment(sym2_node, FragmentText::from("bar"))
+ .unwrap();
+ sut.set_fragment(sym3_node, FragmentText::from("baz"))
+ .unwrap();
let (_, _) = sut.add_dep_lookup(&sym1, &sym2);
let (_, _) = sut.add_dep_lookup(&sym2, &sym3);
@@ -1210,7 +1281,7 @@ mod test {
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
match result {
Ok(_) => panic!("sort did not detect cycle"),
- Err(AsgError::Cycles(scc)) => assert_eq!(expected, scc),
+ Err(SortableAsgError::Cycles(scc)) => assert_eq!(expected, scc),
Err(e) => panic!("unexpected error: {}", e),
}
@@ -1218,43 +1289,52 @@ mod test {
}
#[test]
- fn graph_sort_cyclic_bookended_by_functions() -> AsgResult<(), u8> {
+ fn graph_sort_cyclic_bookended_by_functions() -> SortableAsgResult<(), u8> {
let mut sut = Sut::new();
- let sym1 = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym1");
- let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym2");
- let sym3 = Symbol::new_dummy(SymbolIndex::from_u32(3), "sym3");
+ let sym1 = symbol_dummy!(1, "sym1");
+ let sym2 = symbol_dummy!(2, "sym2");
+ let sym3 = symbol_dummy!(3, "sym3");
- let sym1_node = sut.declare(
- &sym1,
- IdentKind::Func(Dim::default(), SymDtype::Empty),
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym1_node = sut
+ .declare(
+ &sym1,
+ IdentKind::Func(Dim::default(), SymDtype::Empty),
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let sym2_node = sut.declare(
- &sym2,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym2_node = sut
+ .declare(
+ &sym2,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let sym3_node = sut.declare(
- &sym3,
- IdentKind::Func(Dim::default(), SymDtype::Empty),
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym3_node = sut
+ .declare(
+ &sym3,
+ IdentKind::Func(Dim::default(), SymDtype::Empty),
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- sut.set_fragment(sym1_node, FragmentText::from("foo"))?;
- sut.set_fragment(sym2_node, FragmentText::from("bar"))?;
- sut.set_fragment(sym3_node, FragmentText::from("baz"))?;
+ sut.set_fragment(sym1_node, FragmentText::from("foo"))
+ .unwrap();
+ sut.set_fragment(sym2_node, FragmentText::from("bar"))
+ .unwrap();
+ sut.set_fragment(sym3_node, FragmentText::from("baz"))
+ .unwrap();
let (_, _) = sut.add_dep_lookup(&sym1, &sym2);
let (_, _) = sut.add_dep_lookup(&sym2, &sym3);
@@ -1266,7 +1346,7 @@ mod test {
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
match result {
Ok(_) => panic!("sort did not detect cycle"),
- Err(AsgError::Cycles(scc)) => assert_eq!(expected, scc),
+ Err(SortableAsgError::Cycles(scc)) => assert_eq!(expected, scc),
Err(e) => panic!("unexpected error: {}", e),
}
@@ -1274,32 +1354,38 @@ mod test {
}
#[test]
- fn graph_sort_cyclic_function_ignored() -> AsgResult<(), u8> {
+ fn graph_sort_cyclic_function_ignored() -> SortableAsgResult<(), u8> {
let mut sut = Sut::new();
- let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
- let dep = Symbol::new_dummy(SymbolIndex::from_u32(3), "dep");
+ let sym = symbol_dummy!(2, "sym");
+ let dep = symbol_dummy!(3, "dep");
- let sym_node = sut.declare(
- &sym,
- IdentKind::Func(Dim::default(), SymDtype::Empty),
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym_node = sut
+ .declare(
+ &sym,
+ IdentKind::Func(Dim::default(), SymDtype::Empty),
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let dep_node = sut.declare(
- &dep,
- IdentKind::Func(Dim::default(), SymDtype::Empty),
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let dep_node = sut
+ .declare(
+ &dep,
+ IdentKind::Func(Dim::default(), SymDtype::Empty),
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- sut.set_fragment(sym_node, FragmentText::from("foo"))?;
- sut.set_fragment(dep_node, FragmentText::from("bar"))?;
+ sut.set_fragment(sym_node, FragmentText::from("foo"))
+ .unwrap();
+ sut.set_fragment(dep_node, FragmentText::from("bar"))
+ .unwrap();
let (_, _) = sut.add_dep_lookup(&sym, &dep);
let (_, _) = sut.add_dep_lookup(&dep, &sym);
@@ -1315,43 +1401,52 @@ mod test {
}
#[test]
- fn graph_sort_cyclic_function_is_bookended() -> AsgResult<(), u8> {
+ fn graph_sort_cyclic_function_is_bookended() -> SortableAsgResult<(), u8> {
let mut sut = Sut::new();
- let sym1 = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym1");
- let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym2");
- let sym3 = Symbol::new_dummy(SymbolIndex::from_u32(3), "sym3");
+ let sym1 = symbol_dummy!(1, "sym1");
+ let sym2 = symbol_dummy!(2, "sym2");
+ let sym3 = symbol_dummy!(3, "sym3");
- let sym1_node = sut.declare(
- &sym1,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym1_node = sut
+ .declare(
+ &sym1,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let sym2_node = sut.declare(
- &sym2,
- IdentKind::Func(Dim::default(), SymDtype::Empty),
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym2_node = sut
+ .declare(
+ &sym2,
+ IdentKind::Func(Dim::default(), SymDtype::Empty),
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let sym3_node = sut.declare(
- &sym3,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym3_node = sut
+ .declare(
+ &sym3,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- sut.set_fragment(sym1_node, FragmentText::from("foo"))?;
- sut.set_fragment(sym2_node, FragmentText::from("bar"))?;
- sut.set_fragment(sym3_node, FragmentText::from("baz"))?;
+ sut.set_fragment(sym1_node, FragmentText::from("foo"))
+ .unwrap();
+ sut.set_fragment(sym2_node, FragmentText::from("bar"))
+ .unwrap();
+ sut.set_fragment(sym3_node, FragmentText::from("baz"))
+ .unwrap();
let (_, _) = sut.add_dep_lookup(&sym1, &sym2);
let (_, _) = sut.add_dep_lookup(&sym2, &sym3);
@@ -1363,7 +1458,7 @@ mod test {
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
match result {
Ok(_) => panic!("sort did not detect cycle"),
- Err(AsgError::Cycles(scc)) => assert_eq!(expected, scc),
+ Err(SortableAsgError::Cycles(scc)) => assert_eq!(expected, scc),
Err(e) => panic!("unexpected error: {}", e),
}
@@ -1371,43 +1466,52 @@ mod test {
}
#[test]
- fn graph_sort_ignore_non_linked() -> AsgResult<(), u8> {
+ fn graph_sort_ignore_non_linked() -> SortableAsgResult<(), u8> {
let mut sut = Sut::new();
- let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
- let dep = Symbol::new_dummy(SymbolIndex::from_u32(3), "dep");
- let ignored = Symbol::new_dummy(SymbolIndex::from_u32(4), "ignored");
+ let sym = symbol_dummy!(2, "sym");
+ let dep = symbol_dummy!(3, "dep");
+ let ignored = symbol_dummy!(4, "ignored");
- let sym_node = sut.declare(
- &sym,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let sym_node = sut
+ .declare(
+ &sym,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let dep_node = sut.declare(
- &dep,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let dep_node = sut
+ .declare(
+ &dep,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- let ignored_node = sut.declare(
- &ignored,
- IdentKind::Tpl,
- Source {
- virtual_: true,
- ..Default::default()
- },
- )?;
+ let ignored_node = sut
+ .declare(
+ &ignored,
+ IdentKind::Tpl,
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
- sut.set_fragment(sym_node, FragmentText::from("foo"))?;
- sut.set_fragment(dep_node, FragmentText::from("bar"))?;
- sut.set_fragment(ignored_node, FragmentText::from("baz"))?;
+ sut.set_fragment(sym_node, FragmentText::from("foo"))
+ .unwrap();
+ sut.set_fragment(dep_node, FragmentText::from("bar"))
+ .unwrap();
+ sut.set_fragment(ignored_node, FragmentText::from("baz"))
+ .unwrap();
let (_, _) = sut.add_dep_lookup(&sym, &dep);
let (_, _) = sut.add_dep_lookup(&ignored, &sym);
@@ -1421,4 +1525,36 @@ mod test {
Ok(())
}
+
+ /// A graph containing unresolved objects cannot be sorted.
+ #[test]
+ fn graph_sort_fail_unresolved() -> SortableAsgResult<(), u8> {
+ let mut sut = Sut::new();
+
+ let sym = symbol_dummy!(1, "unresolved");
+ let node = sut
+ .declare(&sym, IdentKind::Meta, Default::default())
+ .unwrap();
+ let ident = sut.get(node).unwrap();
+
+ let expected = UnresolvedError::Missing {
+ name: sym.to_string(),
+ };
+
+ // Cause resolved() to fail.
+ ident.fail_resolved.replace(Some(expected.clone()));
+
+ let result = sut
+ .sort(&vec![node])
+ .expect_err("expected sort failure due to unresolved identifier");
+
+ match result {
+ SortableAsgError::UnresolvedObject(err) => {
+ assert_eq!(expected, err);
+ }
+ _ => panic!("expected SortableAsgError::Unresolved: {:?}", result),
+ }
+
+ Ok(())
+ }
}
diff --git a/tamer/src/ir/asg/graph.rs b/tamer/src/ir/asg/graph.rs
index 96afc3b..d5c3684 100644
--- a/tamer/src/ir/asg/graph.rs
+++ b/tamer/src/ir/asg/graph.rs
@@ -22,6 +22,7 @@
use super::ident::IdentKind;
use super::object::{
FragmentText, IdentObjectData, IdentObjectState, Source, TransitionError,
+ UnresolvedError,
};
use super::Sections;
use crate::sym::Symbol;
@@ -86,7 +87,7 @@ where
name: &'i Symbol<'i>,
kind: IdentKind,
src: Source<'i>,
- ) -> AsgResult<ObjectRef<Ix>, Ix>;
+ ) -> AsgResult<ObjectRef<Ix>>;
/// Declare an abstract identifier.
///
@@ -113,7 +114,7 @@ where
name: &'i Symbol<'i>,
kind: IdentKind,
src: Source<'i>,
- ) -> AsgResult<ObjectRef<Ix>, Ix>;
+ ) -> AsgResult<ObjectRef<Ix>>;
/// Set the fragment associated with a concrete identifier.
///
@@ -124,7 +125,7 @@ where
&mut self,
identi: ObjectRef<Ix>,
text: FragmentText,
- ) -> AsgResult<ObjectRef<Ix>, Ix>;
+ ) -> AsgResult<ObjectRef<Ix>>;
/// Retrieve an object from the graph by [`ObjectRef`].
///
@@ -186,17 +187,28 @@ where
O: IdentObjectData<'i>,
Ix: IndexType,
{
+ /// Sort graph into [`Sections`].
+ ///
+ /// Sorting will fail if the graph contains unresolved objects,
+ /// or identifiers whose kind cannot be determined
+ /// (see [`UnresolvedError`]).
fn sort(
&'i self,
roots: &[ObjectRef<Ix>],
- ) -> AsgResult<Sections<'i, O>, Ix>;
+ ) -> SortableAsgResult<Sections<'i, O>, Ix>;
}
/// A [`Result`] with a hard-coded [`AsgError`] error type.
///
/// This is the result of every [`Asg`] operation that could potentially
/// fail in error.
-pub type AsgResult<T, Ix> = Result<T, AsgError<Ix>>;
+pub type AsgResult<T> = Result<T, AsgError>;
+
+/// A [`Result`] with a hard-coded [`SortableAsgError`] error type.
+///
+/// This is the result of every [`SortableAsg`] operation that could
+/// potentially fail in error.
+pub type SortableAsgResult<T, Ix> = Result<T, SortableAsgError<Ix>>;
/// Reference to an [object][super::object] stored within the [`Asg`].
///
@@ -240,7 +252,7 @@ pub type Node<O> = Option<O>;
/// so this stores only owned values.
/// The caller will know the problem values.
#[derive(Debug, PartialEq)]
-pub enum AsgError<Ix: IndexType> {
+pub enum AsgError {
/// An object could not change state in the manner requested.
///
/// See [`Asg::declare`] and [`Asg::set_fragment`] for more
@@ -250,24 +262,20 @@ pub enum AsgError<Ix: IndexType> {
/// The node was not expected in the current context
UnexpectedNode(String),
-
- /// The graph has a cyclic dependency
- Cycles(Vec<Vec<ObjectRef<Ix>>>),
}
-impl<Ix: IndexType> std::fmt::Display for AsgError<Ix> {
+impl std::fmt::Display for AsgError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::ObjectTransition(err) => std::fmt::Display::fmt(&err, fmt),
Self::UnexpectedNode(msg) => {
write!(fmt, "unexpected node: {}", msg)
}
- Self::Cycles(_) => write!(fmt, "cyclic dependencies"),
}
}
}
-impl<Ix: IndexType> std::error::Error for AsgError<Ix> {
+impl std::error::Error for AsgError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::ObjectTransition(err) => err.source(),
@@ -276,12 +284,72 @@ impl<Ix: IndexType> std::error::Error for AsgError<Ix> {
}
}
-impl<Ix: IndexType> From<TransitionError> for AsgError<Ix> {
+impl From<TransitionError> for AsgError {
fn from(err: TransitionError) -> Self {
Self::ObjectTransition(err)
}
}
+/// Error during graph sorting.
+///
+/// These errors reflect barriers to meaningfully understanding the
+/// properties of the data in the graph with respect to sorting.
+/// It does not represent bad underlying data that does not affect the
+/// sorting process.
+#[derive(Debug, PartialEq)]
+pub enum SortableAsgError<Ix: IndexType> {
+ /// An unresolved object was encountered during sorting.
+ ///
+ /// An unresolved object means that the graph has an incomplete picture
+ /// of the program,
+ /// and so sorting cannot be reliably performed.
+ /// Since all objects are supposed to be resolved prior to sorting,
+ /// this represents either a problem with the program being compiled
+ /// or a bug in the compiler itself.
+ UnresolvedObject(UnresolvedError),
+
+ /// The kind of an object encountered during sorting could not be
+ /// determined.
+ ///
+ /// Sorting uses the object kind to place objects into their appropriate
+ /// sections.
+ /// It should never be the case that a resolved object has no kind,
+ /// so this likely represents a compiler bug.
+ MissingObjectKind(String),
+
+ /// The graph has a cyclic dependency.
+ Cycles(Vec<Vec<ObjectRef<Ix>>>),
+}
+
+impl<Ix: IndexType> From<UnresolvedError> for SortableAsgError<Ix> {
+ fn from(err: UnresolvedError) -> Self {
+ Self::UnresolvedObject(err)
+ }
+}
+
+impl<Ix: IndexType> std::fmt::Display for SortableAsgError<Ix> {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ Self::UnresolvedObject(err) => std::fmt::Display::fmt(err, fmt),
+ Self::MissingObjectKind(name) => write!(
+ fmt,
+ "internal error: missing object kind for object `{}` (this may be a compiler bug!)",
+ name,
+ ),
+ Self::Cycles(_) => write!(fmt, "cyclic dependencies"),
+ }
+ }
+}
+
+impl<Ix: IndexType> std::error::Error for SortableAsgError<Ix> {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::UnresolvedObject(err) => Some(err),
+ _ => None,
+ }
+ }
+}
+
#[cfg(test)]
mod test {
use super::*;
diff --git a/tamer/src/ir/asg/ident.rs b/tamer/src/ir/asg/ident.rs
index d0841d1..bab1af7 100644
--- a/tamer/src/ir/asg/ident.rs
+++ b/tamer/src/ir/asg/ident.rs
@@ -145,6 +145,31 @@ pub enum IdentKind {
Worksheet,
}
+impl AsRef<str> for IdentKind {
+ fn as_ref(&self) -> &'static str {
+ match self {
+ Self::Cgen(_) => "cgen",
+ Self::Class(_) => "class",
+ Self::Const(_, _) => "const",
+ Self::Func(_, _) => "func",
+ Self::Gen(_, _) => "gen",
+ Self::Lparam(_, _) => "lparam",
+ Self::Param(_, _) => "param",
+ Self::Rate(_) => "rate",
+ Self::Tpl => "tpl",
+ Self::Type(_) => "type",
+ Self::MapHead => "map:head",
+ Self::Map => "map",
+ Self::MapTail => "map:tail",
+ Self::RetMapHead => "retmap:head",
+ Self::RetMap => "retmap",
+ Self::RetMapTail => "retmap:tail",
+ Self::Meta => "meta",
+ Self::Worksheet => "worksheet",
+ }
+ }
+}
+
impl std::fmt::Display for IdentKind {
/// Format identifier type for display to the user.
///
@@ -152,31 +177,33 @@ impl std::fmt::Display for IdentKind {
/// new type system,
/// so for now this just uses a syntax similar to Rust.
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ let name = self.as_ref();
+
match self {
Self::Cgen(dim) => {
- write!(fmt, "cgen[{}; {}]", DataType::Boolean, dim)
+ write!(fmt, "{}[{}; {}]", name, DataType::Boolean, dim)
}
Self::Class(dim) => {
- write!(fmt, "class[{}; {}]", DataType::Boolean, dim)
+ write!(fmt, "{}[{}; {}]", name, DataType::Boolean, dim)
+ }
+ Self::Const(dim, dtype) => {
+ write!(fmt, "{}[{}; {}]", name, dtype, dim)
+ }
+ Self::Func(dim, dtype) => {
+ write!(fmt, "{}[{}; {}]", name, dtype, dim)
+ }
+ Self::Gen(dim, dtype) => {
+ write!(fmt, "{}[{}; {}]", name, dtype, dim)
}
- Self::Const(dim, dtype) => write!(fmt, "const[{}; {}]", dtype, dim),
- Self::Func(dim, dtype) => write!(fmt, "func[{}; {}]", dtype, dim),
- Self::Gen(dim, dtype) => write!(fmt, "gen[{}; {}]", dtype, dim),
Self::Lparam(dim, dtype) => {
- write!(fmt, "lparam[{}; {}]", dtype, dim)
+ write!(fmt, "{}[{}; {}]", name, dtype, dim)
+ }
+ Self::Param(dim, dtype) => {
+ write!(fmt, "{}[{}; {}]", name, dtype, dim)
}
- Self::Param(dim, dtype) => write!(fmt, "param[{}; {}]", dtype, dim),
- Self::Rate(dtype) => write!(fmt, "rate[{}; 0]", dtype),
- Self::Tpl => write!(fmt, "tpl"),
- Self::Type(dtype) => write!(fmt, "type[{}]", dtype),
- Self::MapHead => write!(fmt, "map:head"),
- Self::Map => write!(fmt, "map"),
- Self::MapTail => write!(fmt, "map:tail"),
- Self::RetMapHead => write!(fmt, "retmap:head"),
- Self::RetMap => write!(fmt, "retmap"),
- Self::RetMapTail => write!(fmt, "retmap:tail"),
- Self::Meta => write!(fmt, "meta"),
- Self::Worksheet => write!(fmt, "worksheet"),
+ Self::Rate(dtype) => write!(fmt, "{}[{}; 0]", name, dtype),
+ Self::Type(dtype) => write!(fmt, "{}[{}]", name, dtype),
+ _ => write!(fmt, "{}", name),
}
}
}
diff --git a/tamer/src/ir/asg/mod.rs b/tamer/src/ir/asg/mod.rs
index 761716c..1b66d92 100644
--- a/tamer/src/ir/asg/mod.rs
+++ b/tamer/src/ir/asg/mod.rs
@@ -196,11 +196,14 @@ mod ident;
mod object;
mod section;
-pub use graph::{Asg, AsgError, AsgResult, IndexType, ObjectRef, SortableAsg};
+pub use graph::{
+ Asg, AsgError, AsgResult, IndexType, ObjectRef, SortableAsg,
+ SortableAsgError,
+};
pub use ident::{DataType, Dim, IdentKind, IdentKindError};
pub use object::{
FragmentText, IdentObject, IdentObjectData, IdentObjectState, Source,
- TransitionError, TransitionResult,
+ TransitionError, TransitionResult, UnresolvedError,
};
pub use section::{Section, SectionIterator, Sections};
diff --git a/tamer/src/ir/asg/object.rs b/tamer/src/ir/asg/object.rs
index 628521e..658bb5a 100644
--- a/tamer/src/ir/asg/object.rs
+++ b/tamer/src/ir/asg/object.rs
@@ -135,6 +135,15 @@ pub trait IdentObjectData<'i> {
fn as_ident(&self) -> Option<&IdentObject<'i>>;
}
+impl<'i> std::fmt::Display for IdentObject<'i> {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self.name() {
+ Some(n) => write!(f, "{}", n),
+ _ => write!(f, "missing"),
+ }
+ }
+}
+
impl<'i> IdentObjectData<'i> for IdentObject<'i> {
fn name(&self) -> Option<&'i Symbol<'i>> {
match self {
@@ -203,6 +212,23 @@ where
/// since rules may vary between implementations.
fn resolve(self, kind: IdentKind, src: Source<'i>) -> TransitionResult<T>;
+ /// Assertion to return self if identifier is resolved,
+ /// otherwise failing with [`UnresolvedError`].
+ ///
+ /// This simplifies working with identifiers without having to match on
+ /// specific variants,
+ /// and will continue to work if new variants are added in the
+ /// future that are considered to be unresolved.
+ ///
+ /// Since this does not cause a state transition and is useful in
+ /// contexts where ownership over the identifier is not possible,
+ /// this accepts and returns a reference to the identifier.
+ ///
+ /// At present,
+ /// both [`IdentObject::Missing`] and [`IdentObject::Extern`] are
+ /// considered to be unresolved.
+ fn resolved(&self) -> Result<&T, UnresolvedError>;
+
/// Resolve identifier against an extern declaration or produce an
/// extern.
///
@@ -364,6 +390,25 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
}
}
+ fn resolved(&self) -> Result<&IdentObject<'i>, UnresolvedError> {
+ match self {
+ IdentObject::Missing(name) => Err(UnresolvedError::Missing {
+ name: name.to_string(),
+ }),
+
+ IdentObject::Extern(name, ref kind, ref src) => {
+ Err(UnresolvedError::Extern {
+ name: name.to_string(),
+ kind: kind.clone(),
+ pkg_name: src.pkg_name.map(|s| s.to_string()),
+ })
+ }
+
+ IdentObject::Ident(_, _, _)
+ | IdentObject::IdentFragment(_, _, _, _) => Ok(self),
+ }
+ }
+
fn extern_(
self,
kind: IdentKind,
@@ -515,6 +560,52 @@ impl std::error::Error for TransitionError {
}
}
+/// Resolved identifier was expected.
+#[derive(Clone, Debug, PartialEq)]
+pub enum UnresolvedError {
+ /// Expected identifier is missing and nothing about it is known.
+ Missing { name: String },
+
+ /// Expected identifier has not yet been resolved with a concrete
+ /// definition.
+ Extern {
+ /// Identifier name.
+ name: String,
+ /// Expected identifier type.
+ kind: IdentKind,
+ /// Name of package where the extern was defined.
+ pkg_name: Option<String>,
+ },
+}
+
+impl std::fmt::Display for UnresolvedError {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ UnresolvedError::Missing { name } => {
+ write!(fmt, "missing expected identifier `{}`", name,)
+ }
+
+ UnresolvedError::Extern {
+ name,
+ kind,
+ pkg_name,
+ } => write!(
+ fmt,
+ "unresolved extern `{}` of type `{}`, declared in `{}`",
+ name,
+ kind,
+ pkg_name.as_ref().unwrap_or(&"<unknown>".into()),
+ ),
+ }
+ }
+}
+
+impl std::error::Error for UnresolvedError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ None
+ }
+}
+
/// Compiled fragment for identifier.
///
/// This represents the text associated with an identifier.
@@ -773,8 +864,24 @@ mod test {
}
#[test]
- fn ident_object_ident() {
+ fn resolved_on_missing() {
let sym = symbol_dummy!(1, "missing");
+
+ let result = IdentObject::declare(&sym)
+ .resolved()
+ .expect_err("expected error asserting resolved() on missing");
+
+ match result {
+ UnresolvedError::Missing { name: e_name } => {
+ assert_eq!(sym.to_string(), e_name);
+ }
+ _ => panic!("expected UnresolvedError {:?}", result),
+ }
+ }
+
+ #[test]
+ fn ident_object_ident() {
+ let sym = symbol_dummy!(1, "ident");
let kind = IdentKind::Meta;
let src = Source {
desc: Some("ident ctor".into()),
@@ -789,6 +896,25 @@ mod test {
);
}
+ #[test]
+ fn resolved_on_ident() {
+ let sym = symbol_dummy!(1, "ident resolve");
+ let kind = IdentKind::Meta;
+ let src = Source {
+ desc: Some("ident ctor".into()),
+ ..Default::default()
+ };
+
+ assert_eq!(
+ &IdentObject::Ident(&sym, kind.clone(), src.clone()),
+ IdentObject::declare(&sym)
+ .resolve(kind.clone(), src.clone())
+ .unwrap()
+ .resolved()
+ .unwrap(),
+ );
+ }
+
// Note that we don't care about similar sources. It's expected
// that the system populating the ASG will only resolve local
// symbols, and so redeclarations should represent that multiple
@@ -825,7 +951,7 @@ mod test {
#[test]
fn ident_object() {
- let sym = symbol_dummy!(1, "missing");
+ let sym = symbol_dummy!(1, "extern");
let kind = IdentKind::Class(Dim::from_u8(1));
let src = Source {
desc: Some("extern".into()),
@@ -838,6 +964,72 @@ mod test {
);
}
+ #[test]
+ fn resolved_on_extern() {
+ let sym = symbol_dummy!(1, "extern resolved");
+ let kind = IdentKind::Class(Dim::from_u8(1));
+ let pkg_name = symbol_dummy!(2, "pkg/name");
+ let src = Source {
+ pkg_name: Some(&pkg_name),
+ desc: Some("extern".into()),
+ ..Default::default()
+ };
+
+ let result =
+ IdentObject::Extern(&sym, kind.clone(), src.clone())
+ .resolved()
+ .expect_err(
+ "expected error asserting resolved() on extern",
+ );
+
+ match result {
+ UnresolvedError::Extern {
+ name: e_name,
+ kind: e_kind,
+ pkg_name: e_pkg_name,
+ } => {
+ assert_eq!(sym.to_string(), e_name);
+ assert_eq!(kind, e_kind);
+ assert_eq!(Some(pkg_name.to_string()), e_pkg_name);
+ }
+ _ => panic!("expected UnresolvedError: {:?}", result),
+ }
+ }
+
+ #[test]
+ fn resolved_on_extern_error_fmt_without_pkg() {
+ let meta = IdentKind::Meta;
+ let err = UnresolvedError::Extern {
+ name: "foo".into(),
+ kind: IdentKind::Meta,
+ pkg_name: None,
+ };
+
+ let msg = format!("{}", err);
+
+ assert!(msg.contains("`foo`"));
+ assert!(msg.contains("in `<unknown>`"));
+ assert!(msg.contains(&format!("`{}`", meta)));
+ }
+
+ #[test]
+ fn resolved_on_extern_error_fmt_with_pkg() {
+ let meta = IdentKind::Meta;
+ let pkg = "pkg".to_string();
+
+ let err = UnresolvedError::Extern {
+ name: "foo".into(),
+ kind: IdentKind::Meta,
+ pkg_name: Some(pkg.clone()),
+ };
+
+ let msg = format!("{}", err);
+
+ assert!(msg.contains("`foo`"));
+ assert!(msg.contains(&format!("in `{}`", pkg)));
+ assert!(msg.contains(&format!("`{}`", meta)));
+ }
+
// Extern first, then identifier
#[test]
fn redeclare_compatible_resolves() {
@@ -1012,6 +1204,27 @@ mod test {
}
#[test]
+ fn resolved_on_fragment() {
+ let sym = symbol_dummy!(1, "tofrag resolved");
+ let src = Source {
+ generated: true,
+ ..Default::default()
+ };
+
+ let kind = IdentKind::Meta;
+ let ident = IdentObject::declare(&sym)
+ .resolve(kind.clone(), src.clone())
+ .unwrap();
+ let text = FragmentText::from("a fragment for resolved()");
+ let ident_with_frag = ident.set_fragment(text.clone());
+
+ assert_eq!(
+ Ok(&IdentObject::IdentFragment(&sym, kind, src, text)),
+ ident_with_frag.unwrap().resolved(),
+ );
+ }
+
+ #[test]
fn add_fragment_to_fragment_fails() {
let sym = symbol_dummy!(1, "badsym");
let ident = IdentObject::declare(&sym)
diff --git a/tamer/src/ir/legacyir.rs b/tamer/src/ir/legacyir.rs
index 1d23f3f..63c9ed6 100644
--- a/tamer/src/ir/legacyir.rs
+++ b/tamer/src/ir/legacyir.rs
@@ -224,7 +224,7 @@ impl TryFrom<&[u8]> for SymType {
/// Determine symbol type from source `preproc:sym/@type`.
///
/// This raises source `xmlo` data into this IR.
- /// See [`crate::obj::xmlo::reader`].
+ /// See [`crate::obj::xmlo::XmloReader`].
fn try_from(value: &[u8]) -> Result<SymType, Self::Error> {
match value {
b"cgen" => Ok(SymType::Cgen),
@@ -289,7 +289,7 @@ impl TryFrom<&[u8]> for SymDtype {
/// Determine data type from source `preproc:sym/@dtype`.
///
/// This raises source `xmlo` data into this IR.
- /// See [`crate::obj::xmlo::reader`].
+ /// See [`crate::obj::xmlo::XmloReader`].
fn try_from(value: &[u8]) -> Result<SymDtype, Self::Error> {
match value {
b"boolean" => Ok(SymDtype::Boolean),
diff --git a/tamer/src/ir/mod.rs b/tamer/src/ir/mod.rs
index 3b94703..78009ca 100644
--- a/tamer/src/ir/mod.rs
+++ b/tamer/src/ir/mod.rs
@@ -65,10 +65,10 @@
//! IRs are progressively _lowered_ to other IRs that are closer to the
//! final representation emitted by the compiler ("lower"-level).
//!
-//! - [`xmlo::reader`](crate::obj::xmlo::reader) produces
+//! - [`xmlo::XmloReader`](crate::obj::xmlo::XmloReader) produces
//! [`XmloEvent`](crate::obj::xmlo::XmloEvent)s containing
//! [`legacyir`].
-//! - [`xmlo::asg_builder`](crate::obj::xmlo::asg_builder) immediately lowers
+//! - [`xmlo::AsgBuilder`](crate::obj::xmlo::AsgBuilder) immediately lowers
//! those into [`asg`].
pub mod asg;
diff --git a/tamer/src/ld/poc.rs b/tamer/src/ld/poc.rs
index 11f2f23..e5a22a0 100644
--- a/tamer/src/ld/poc.rs
+++ b/tamer/src/ld/poc.rs
@@ -25,13 +25,14 @@ use crate::fs::{
};
use crate::global;
use crate::ir::asg::{
- Asg, AsgError, DefaultAsg, IdentObject, IdentObjectData, Sections,
- SortableAsg,
+ Asg, DefaultAsg, IdentObject, IdentObjectData, Sections, SortableAsg,
+ SortableAsgError,
};
use crate::obj::xmle::writer::XmleWriter;
use crate::obj::xmlo::{AsgBuilder, AsgBuilderState, XmloReader};
use crate::sym::{DefaultInterner, Interner, Symbol};
use fxhash::FxBuildHasher;
+use petgraph_graphml::GraphMl;
use std::error::Error;
use std::fs;
use std::io::BufReader;
@@ -42,7 +43,7 @@ type LinkerAsg<'i> = DefaultAsg<'i, IdentObject<'i>, global::ProgIdentSize>;
type LinkerAsgBuilderState<'i> =
AsgBuilderState<'i, FxBuildHasher, global::ProgIdentSize>;
-pub fn main(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
+pub fn xmle(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
let mut fs = VisitOnceFilesystem::new();
let mut depgraph = LinkerAsg::with_capacity(65536, 65536);
let interner = DefaultInterner::new();
@@ -71,7 +72,7 @@ pub fn main(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
let mut sorted = match depgraph.sort(&roots) {
Ok(sections) => sections,
- Err(AsgError::Cycles(cycles)) => {
+ Err(SortableAsgError::Cycles(cycles)) => {
let msg: Vec<String> = cycles
.into_iter()
.map(|cycle| {
@@ -96,8 +97,6 @@ pub fn main(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
Err(e) => return Err(e.into()),
};
- //println!("Sorted ({}): {:?}", sorted.len(), sorted);
-
output_xmle(
&depgraph,
&interner,
@@ -110,6 +109,59 @@ pub fn main(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
Ok(())
}
+pub fn graphml(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
+ let mut fs = VisitOnceFilesystem::new();
+ let mut depgraph = LinkerAsg::with_capacity(65536, 65536);
+ let interner = DefaultInterner::new();
+
+ let _ = load_xmlo(
+ package_path,
+ &mut fs,
+ &mut depgraph,
+ &interner,
+ AsgBuilderState::new(),
+ )?;
+
+ // if we move away from petgraph, we will need to abstract this away
+ let g = depgraph.into_inner();
+ let graphml =
+ GraphMl::new(&g)
+ .pretty_print(true)
+ .export_node_weights(Box::new(|node| {
+ // eprintln!("{:?}", node);
+
+ let (name, kind, generated) = match node {
+ Some(n) => {
+ let generated = match n.src() {
+ Some(src) => src.generated,
+ None => false,
+ };
+
+ (
+ format!("{}", n),
+ n.kind().unwrap().as_ref(),
+ format!("{}", generated),
+ )
+ }
+ None => (
+ String::from("missing"),
+ "missing",
+ format!("{}", false),
+ ),
+ };
+
+ vec![
+ ("label".into(), name.into()),
+ ("kind".into(), kind.into()),
+ ("generated".into(), generated.into()),
+ ]
+ }));
+
+ fs::write(output, graphml.to_string())?;
+
+ Ok(())
+}
+
fn load_xmlo<'a, 'i, I: Interner<'i>, P: AsRef<Path>>(
path_str: P,
fs: &mut VisitOnceFilesystem<FsCanonicalizer, FxBuildHasher>,
diff --git a/tamer/src/lib.rs b/tamer/src/lib.rs
index 569d74d..f0159a5 100644
--- a/tamer/src/lib.rs
+++ b/tamer/src/lib.rs
@@ -19,6 +19,9 @@
//! An incremental rewrite of TAME in Rust.
+// We build docs for private items
+#![allow(rustdoc::private_intra_doc_links)]
+
pub mod global;
#[macro_use]
diff --git a/tamer/src/obj/xmle/writer/xmle.rs b/tamer/src/obj/xmle/writer/xmle.rs
index 3d2e606..9f57ce0 100644
--- a/tamer/src/obj/xmle/writer/xmle.rs
+++ b/tamer/src/obj/xmle/writer/xmle.rs
@@ -294,7 +294,10 @@ impl<W: Write> XmleWriter<W> {
self.writer.write_event(Event::Empty(sym))?;
}
- _ => unreachable!("filtered out during sorting"),
+ _ => unreachable!(
+ "identifier should have been filtered out during sorting: {:?}",
+ ident,
+ ),
}
}
diff --git a/tamer/src/obj/xmlo/asg_builder.rs b/tamer/src/obj/xmlo/asg_builder.rs
index b957c95..00c2443 100644
--- a/tamer/src/obj/xmlo/asg_builder.rs
+++ b/tamer/src/obj/xmlo/asg_builder.rs
@@ -81,7 +81,7 @@ use std::fmt::Display;
use std::hash::BuildHasher;
pub type Result<'i, S, Ix> =
- std::result::Result<AsgBuilderState<'i, S, Ix>, AsgBuilderError<Ix>>;
+ std::result::Result<AsgBuilderState<'i, S, Ix>, AsgBuilderError>;
/// Builder state between imports.
///
@@ -289,7 +289,7 @@ where
/// Error populating graph with [`XmloResult`]-derived data.
#[derive(Debug, PartialEq)]
-pub enum AsgBuilderError<Ix: IndexType> {
+pub enum AsgBuilderError {
/// Error with the source `xmlo` file.
XmloError(XmloError),
@@ -297,7 +297,7 @@ pub enum AsgBuilderError<Ix: IndexType> {
IdentKindError(IdentKindError),
/// [`Asg`] operation error.
- AsgError(AsgError<Ix>),
+ AsgError(AsgError),
/// Fragment encountered for an unknown identifier.
MissingFragmentIdent(String),
@@ -309,7 +309,7 @@ pub enum AsgBuilderError<Ix: IndexType> {
BadEligRef(String),
}
-impl<Ix: IndexType> Display for AsgBuilderError<Ix> {
+impl Display for AsgBuilderError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::XmloError(e) => e.fmt(fmt),
@@ -331,25 +331,25 @@ impl<Ix: IndexType> Display for AsgBuilderError<Ix> {
}
}
-impl<Ix: IndexType> From<XmloError> for AsgBuilderError<Ix> {
+impl From<XmloError> for AsgBuilderError {
fn from(src: XmloError) -> Self {
Self::XmloError(src)
}
}
-impl<Ix: IndexType> From<IdentKindError> for AsgBuilderError<Ix> {
+impl From<IdentKindError> for AsgBuilderError {
fn from(src: IdentKindError) -> Self {
Self::IdentKindError(src)
}
}
-impl<Ix: IndexType> From<AsgError<Ix>> for AsgBuilderError<Ix> {
- fn from(src: AsgError<Ix>) -> Self {
+impl From<AsgError> for AsgBuilderError {
+ fn from(src: AsgError) -> Self {
Self::AsgError(src)
}
}
-impl<Ix: IndexType> Error for AsgBuilderError<Ix> {
+impl Error for AsgBuilderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::XmloError(e) => Some(e),
diff --git a/tamer/tests/tameld.rs b/tamer/tests/tameld.rs
index 30b2fea..1bbad58 100644
--- a/tamer/tests/tameld.rs
+++ b/tamer/tests/tameld.rs
@@ -68,6 +68,19 @@ fn link_input_file_does_not_exist() -> Result<(), Box<dyn std::error::Error>> {
}
#[test]
+fn link_invalid_emit() -> Result<(), Box<dyn std::error::Error>> {
+ let mut cmd = Command::cargo_bin("tameld")?;
+ cmd.arg("foobar");
+ cmd.arg("--emit").arg("notgood");
+ cmd.arg("-o").arg("tests/data/test-output.xmle");
+ cmd.assert()
+ .failure()
+ .stderr(predicate::str::contains("--emit notgood"));
+
+ Ok(())
+}
+
+#[test]
fn link_empty_input_file() -> Result<(), Box<dyn std::error::Error>> {
let mut cmd = Command::cargo_bin("tameld")?;
cmd.arg("tests/data/empty.xmlo");
diff --git a/tools/pkg-graph b/tools/pkg-graph
new file mode 100755
index 0000000..6d69f27
--- /dev/null
+++ b/tools/pkg-graph
@@ -0,0 +1,73 @@
+#!/bin/bash
+# Output DOT file representing package dependencies
+#
+# Copyright (C) 2014-2021 Ryan Specialty Group, LLC.
+#
+# 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 Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+##
+
+
+relpath()
+{
+ while read p; do
+ if [[ ! "$p" =~ \.\. ]]; then
+ echo "$p"
+ else
+ sed 's|[^/]\+/\.\./|/|' | relpath
+ fi
+ done
+}
+
+
+undepify()
+{
+ sed 's|^\(\./\)\?\(.*\)\.dep:|/\2 |'
+}
+
+
+dotify()
+{
+ echo 'digraph {'
+
+ local from to
+ while read -r from to; do
+ printf '"%s" -> "%s";\n' "$from" "$to"
+ done
+
+ echo '}'
+}
+
+
+main()
+{
+ local root="${1?Missing project root}"
+
+ cd "$root"
+
+ find . -name '*.dep' | while read -r depfile; do
+ # Absolute paths can be output verbatim
+ grep -H '^/' "$depfile"
+
+ # Relative paths need processing
+ local dir=$( dirname "$depfile" | sed 's|^\./|/|' )
+ grep -Hv '^/' "$depfile" \
+ | sed "s|:|:$dir/|"
+ done \
+ | grep -v '\$$' \
+ | relpath \
+ | undepify \
+ | dotify
+}
+
+main "$@"