[
  {
    "path": ".gitattributes",
    "content": "build/* linguist-vendored\ncsrc/core/* linguist-vendored\ncsrc/crypto/* linguist-vendored\ncsrc/http/* linguist-vendored\ncsrc/luapower/* linguist-vendored\ncsrc/luautf8/* linguist-vendored\ncsrc/unix/* linguist-vendored\ncsrc/dns/* linguist-vendored\nexamples/* linguist-vendored\njssrc/codemirror.js linguist-vendored\nindex.html linguist-vendored\nreadme.md linguist-vendored\n*.css linguist-vendored\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled Lua sources\nluac.out\n\n# luarocks build files\n*.src.rock\n*.zip\n*.tar.gz\n\n# Object files\n*.o\n*.os\n*.ko\n*.obj\n*.elf\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Libraries\n*.lib\n*.a\n*.la\n*.lo\n*.def\n*.exp\n\n# Shared objects (inc. Windows DLLs)\n*.dll\n*.so\n*.so.*\n*.dylib\n\n# Executables\n*.exe\n*.out\n*.app\n*.i*86\n*.x86_64\n*.hex\n\nlua_modules/\n\n*~\ndist/\nbuild/\nnode_modules/\n.idea\n\nexperimental/build/continuation_templates.h\nexperimental/build/eve\nexperimental/build/path.c\nexperimental/build/Eve.xcodeproj/xcuserdata\nexperimental/build/Eve.xcodeproj/*/xcuserdata\nexperimental/build/lua\nexperimental/build/luajit-2.0\nexperimental/build/*.js\n\nexperimental/build/*.js\nexperimental/build/*.js.map\n"
  },
  {
    "path": ".npmignore",
    "content": ""
  },
  {
    "path": "ATTRIBUTIONS.md",
    "content": "# Software Attributions\n\nEve is built using the following technologies generously supplied by their attributed authors in accordance with the following licenses. If you recognize anything in this list as incorrect, please bring it to our attention and we will correct it.\n\n-------------------------------------------------------------------------------\n \n### @types/body-parser\n- TypeScript definitions for body-parser\n- By Santi Albo (https://github.com/santialbo/), VILIC VANE (https://vilic.info), Jonathan Häberle (https://github.com/dreampulse)\n- [MIT][MIT] License\n- https://www.npmjs.com/package/@types/body-parser\n\n-------------------------------------------------------------------------------\n\n### @types/commonmark\n- TypeScript definitions for commonmark.js 0.22.1\n- By Nico Jansen (https://github.com/nicojs)\n- [MIT][MIT] License\n- https://www.npmjs.com/package/@types/commonmark\n\n-------------------------------------------------------------------------------\n\n### @types/express\n- TypeScript definitions for Express 4.x\n- By by Boris Yankov https://github.com/borisyankov/.\n- [MIT][MIT] License\n- https://www.npmjs.com/package/@types/express\n\n-------------------------------------------------------------------------------\n\n### @types/glob \n- TypeScript definitions for Glob 5.0.10\n- By vvakame (https://github.com/vvakame)\n- [MIT][MIT] License\n- https://www.npmjs.com/package/@types/glob \n\n-------------------------------------------------------------------------------\n\n### @types/minimist \n- TypeScript definitions for minimist 1.1.3\n- By Bart van der Schoor (https://github.com/Bartvds), Necroskillz (https://github.com/Necroskillz)\n- [MIT][MIT] License\n- https://www.npmjs.com/package/@types/minimist\n\n-------------------------------------------------------------------------------\n\n### @types/mkdirp\n- TypeScript definitions for mkdirp 0.3.0\n- By Bart van der Schoor (https://github.com/Bartvds)\n- [MIT][MIT] License\n- https://www.npmjs.com/package/@types/mkdirp\n\n-------------------------------------------------------------------------------\n\n### @types/node\n- TypeScript definitions for Node.js v6.x\n- By Microsoft TypeScript (http://typescriptlang.org), DefinitelyTyped (https://github.com/DefinitelyTyped/DefinitelyTyped)\n- [MIT][MIT] License\n- https://www.npmjs.com/package/@types/node\n\n-------------------------------------------------------------------------------\n\n### @types/request\n- TypeScript definitions for request\n- By Carlos Ballesteros Velasco (https://github.com/soywiz), et. al.\n- [MIT][MIT] License\n- https://www.npmjs.com/package/@types/request\n\n-------------------------------------------------------------------------------\n\n### @types/tape\n- TypeScript definitions for tape v4.2.2\n- By Bart van der Schoor (https://github.com/Bartvds), Haoqun Jiang (https://github.com/sodatea)\n- [MIT][MIT] License\n- https://www.npmjs.com/package/@types/tape\n\n-------------------------------------------------------------------------------\n\n### @types/ws \n- TypeScript definitions for ws\n- By Paul Loyd (https://github.com/loyd)\n- [MIT][MIT] License\n- https://www.npmjs.com/package/@types/ws\n\n-------------------------------------------------------------------------------\n\n### codemirror.d.ts\n- TypeScript definitions for codemirror\n- By mihailik (https://github.com/mihailik)\n- [MIT][MIT] License\n- [https://github.com/DefinitelyTyped/DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/03b3450d08a0b0a1b9e820e581a52c8e6c21f32e/codemirror/codemirror.d.ts)\n\n-------------------------------------------------------------------------------\n\n### System.js\n- Universal dynamic module loader \n- [MIT][MIT] License\n- https://github.com/systemjs/systemjs\n\n-------------------------------------------------------------------------------\n\n### body-parser\n- Node.js body parsing middleware\n- [MIT][MIT] License\n- https://www.npmjs.com/package/body-parser\n\n-------------------------------------------------------------------------------\n\n### chevrotain\n- Chevrotain is a high performance fault tolerant javascript parsing DSL for building recursive decent parsers\n- [Apache 2.0][Apache] License\n- https://www.npmjs.com/package/chevrotain\n\n-------------------------------------------------------------------------------\n\n### commonmark\n- a strongly specified, highly compatible variant of Markdown\n- [BSD-2-Clause][BSD] License\n- https://www.npmjs.com/package/commonmark\n\n-------------------------------------------------------------------------------\n\n### express\n- Fast, unopinionated, minimalist web framework\n- [MIT][MIT] License\n- https://www.npmjs.com/package/express\n\n-------------------------------------------------------------------------------\n\n### glob\n- a little globber\n- by Isaac Schlueter (https://github.com/isaacs)\n- [ISC][ISC] License\n- https://www.npmjs.com/package/glob\n\n-------------------------------------------------------------------------------\n\n### minimist\n- parse argument options\n- [MIT][MIT] License\n- https://www.npmjs.com/package/minimist\n\n-------------------------------------------------------------------------------\n\n### mkdirp\n- Recursively mkdir, like mkdir -p\n- [MIT][MIT] License\n- https://www.npmjs.com/package/mkdirp\n\n-------------------------------------------------------------------------------\n\n### node-uuid\n- Rigorous implementation of RFC4122 (v1 and v4) UUIDs.\n- [MIT][MIT] License\n- https://www.npmjs.com/package/node-uuid\n\n-------------------------------------------------------------------------------\n\n### request\n- Simplified HTTP request client\n- [Apache 2.0][Apache] License\n- https://www.npmjs.com/package/request\n\n-------------------------------------------------------------------------------\n\n### typescript\n- TypeScript is a language for application scale JavaScript development\n- [Apache 2.0][Apache] License\n- https://www.npmjs.com/package/typescript\n\n-------------------------------------------------------------------------------\n\n### ws\n- simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455\n- [MIT][MIT] License\n- https://www.npmjs.com/package/ws\n\n-------------------------------------------------------------------------------\n\n### uuid.js\n- Simple, fast generation of RFC4122 UUIDS.\n- By Robert Kieffer (https://github.com/broofa)\n- [MIT][MIT] License\n- https://github.com/broofa/node-uuid\n\n-------------------------------------------------------------------------------\n\n### Codemirror\n- In-browser code editor \n- By Marijn Haverbeke (https://github.com/marijnh)\n- [MIT][MIT] License\n- https://github.com/codemirror/codemirror\n\n-------------------------------------------------------------------------------\n\n### Simple Scrollbars\n- Addon for CodeMirror\n- By Marijn Haverbeke (https://github.com/marijnh)\n- [MIT][MIT] License\n- https://codemirror.net/doc/manual.html#addon_simplescrollbars\n\n-------------------------------------------------------------------------------\n\n### Annotate Scrollbar\n- Addon for CodeMirror\n- By Marijn Haverbeke (https://github.com/marijnh)\n- [MIT][MIT] License\n- https://codemirror.net/doc/manual.html#addon_annotatescrollbar\n\n-------------------------------------------------------------------------------\n\n### ionicons\nThe premium icon font for Ionic\n- [MIT][MIT] License\n- by Ben Sperry (https://twitter.com/benjsperry)\n- https://github.com/driftyco/ionicons\n\n-------------------------------------------------------------------------------\n\n[BSD]: https://spdx.org/licenses/BSD-2-Clause\n[MIT]: https://spdx.org/licenses/MIT\n[Apache]: https://spdx.org/licenses/Apache-2.0\n[ISC]: https://spdx.org/licenses/ISC"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:6-slim\nMAINTAINER Kodowa, Inc. <info@kodowa.com>\nADD / /eve\nRUN chown -R node:node /eve\nUSER node\nENV HOME /eve\nWORKDIR /eve\nRUN npm install\nEXPOSE 8080\nCMD npm start"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Procfile",
    "content": "web: ./node_modules/.bin/tsc && cp src/*.js build/src/ && cp ./node_modules/chevrotain/lib/chevrotain.js build/src/ && node build/src/runtime/server.js\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img src=\"http://www.witheve.com/logo.png\" alt=\"Eve logo\" width=\"10%\" />\n</p>\n\n---\n \nEve is a programming language based on years of research into building a human-first programming platform. \n\n**This repository hosts a preview of Eve v0.3 alpha, which is no longer under active development.**\n\n## Getting Started with Eve v0.3 preview\n\nInstall [Node](https://nodejs.org/en/download/) for your platform, then clone and build the [Eve starter repository](https://github.com/witheve/eve-starter):\n\n```\ngit clone git@github.com:witheve/eve-starter.git\ncd eve-starter\nnpm install\n```\n\nYou can start the program switcher, which allows you to browse included example programs:\n\n```\nnpm start\n```\n\nOr you can run a specific program by providing its path as an argument:\n\n```\nnpm start -- path/to/program.js\n```\n\n## Integrating Eve into an existing project\n\nYou can get Eve as an npm package\n\n```\nnpm install witheve@preview\n```\n\nThen import Eve to use it in your project\n\n```\nimport {Program} from \"witheve\";\n```\n\n## Learning Eve\n\nYou can learn about Eve with the following resources:\n\n- [Read the Quick Start Tutorial](http://play.witheve.com/) (use Chrome for best results)\n- [Syntax Quick Reference](https://witheve.github.io/assets/docs/SyntaxReference.pdf)\n- [Language Handbook (draft)](http://docs.witheve.com)\n\nAlso, the [mailing list archive](https://groups.google.com/forum/#!forum/eve-talk) is a good resource for help and inspiration. In particular, the [Puzzles & Paradoxes series](https://groups.google.com/forum/#!searchin/eve-talk/Puzzles$20$26$20Paradoxes%7Csort:date) answers a lot of questions beginners face about the Eve langauge.\n\n## Get Involved\n\n### Join the Community\n\nThe Eve community is small but constantly growing, and everyone is welcome!\n\n- Join or start a discussion on our [mailing list](https://groups.google.com/forum/#!forum/eve-talk).\n- Impact the future of Eve by getting involved with our [Request for Comments](https://github.com/witheve/rfcs) process.\n- Read our [development blog](http://incidentalcomplexity.com/).\n- Follow us on [Twitter](https://twitter.com/with_eve).\n\n### How to Contribute\n\nThe best way to contribute right now is to write Eve code and report your experiences. [Let us know](https://groups.google.com/forum/#!forum/eve-talk) what kind of programs you’re trying to write, what barriers you are facing in writing code (both mental and technological), and any errors you encounter along the way.\n\n### How to File an Issue\n\nPlease file any issues in this repository. Before you file an issue, please take a look to see if the issue already exists. When you file an issue, please include:\n\n1. The steps needed to reproduce the bug\n2. Your operating system and browser.\n3. If applicable, the `.*eve` file that causes the bug.\n\n## License\n\nEve is licensed under the Apache 2.0 license, see [LICENSE](https://github.com/witheve/eve/blob/master/LICENSE) for details.\n\n## Disclaimer\n\nEve is currently at a very early, \"alpha\" stage of development. This means the language, tools, and docs are largely incomplete, but undergoing rapid and continuous development. If you encounter errors while using Eve, don't worry: it's likely our fault. Please bring the problem to our attention by [filing an issue](https://github.com/witheve/eve#how-to-file-an-issue).\n\nAs always, with pre-release software, don’t use this for anything important. We are continuously pushing to this codebase, so you can expect very rapid changes. At this time, we’re not prepared make the commitment that our changes will not break your code, but we’ll do our best to [update you](https://groups.google.com/forum/#!forum/eve-talk) on the biggest changes.\n"
  },
  {
    "path": "assets/css/app-preview.css",
    "content": "/*\n * app preview iframe wrapper stylesheets\n */\n\n* { box-sizing:border-box; }\nbody { background: rgb(47,47,49); color: #d8d8d8; font-family: Avenir, \"Helvetica neue\", sans-serif; display: flex; flex-direction: row; margin:0; width: 100%; }\nhtml, body, __root { height: 100%; }\nbody { background: #f5f5f5; color: #555; }\nbody { justify-content: stretch; }\n.__root { display: flex; flex-direction: column; align-self: stretch; }\n.application-root { overflow: auto; }\n.__root.application-root { order: 2; flex: 1 1 auto; color: #555; }\n.application-container { display: flex; flex-direction: column; flex: 1; }\n.program { position: relative; flex: 1; flex-direction: column; align-self: stretch; padding:20px; overflow: auto; display:flex; }"
  },
  {
    "path": "assets/css/base.css",
    "content": "/******************************************************************************\\\n * UI Components                                                              *\n\\******************************************************************************/\n\nrow { display: flex; flex-direction: row; }\ncolumn {display: flex;flex-direction: column;align-items: stretch;}\n\nspacer { display: flex; flex: 1; }\n\ntext { display: inline-block; white-space: pre-wrap; }\n\nbutton { display: flex; flex-direction: row; padding: 5px 10px; background: transparent; border: none; outline: none; -webkit-appearance: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; background-clip: padding-box !important; }\n\nbutton:before { align-self: center; }\n\nbutton:not(:empty):before { margin-right: 10px; }\n\nbutton { background: #f0f0f0; border-radius: 4px; border: 1px solid #e5e5e5; }\nbutton:hover { background: #e9e9e9; }\nbutton:active { background: #e0e0e0; }\n\nbutton.inset { border-radius: 4px; background: #EEE; border: 1px solid rgba(0, 0, 0, 0.1); }\nbutton.inset:not(.disabled):hover { background: #EEE; box-shadow: inset 0 0 1000px rgba(0, 0, 0, 0.05), inset 0 1px 1px rgba(0, 0, 0, 0.25); border-top-color: rgba(0, 0, 0, 0.2); }\nbutton.inset:not(.disabled):active { background: #EEE; box-shadow: inset 0 0 1000px rgba(0, 0, 0, 0.1), inset 0 2px 1px rgba(0, 0, 0, 0.25); border-top-color: rgba(0, 0, 0, 0.3); }\n\nbutton.flat { background: transparent; border: none; border-radius: 0; }\nbutton.flat:not(.disabled):hover { background: rgba(0, 0, 0, 0.05); }\nbutton.flat:not(.disabled):active { background: rgba(0, 0, 0, 0.1); }\n\n.dark button.flat:not(.disabled):hover { background: rgba(255, 255, 255, 0.1); }\n.dark button.flat:not(.disabled):active { background: rgba(255, 255, 255, 0.2); }\n\n.ui-field-table {border-collapse: collapse;}\n.ui-field-table td {padding: 0;margin: 0;vertical-align: top; border: 1px solid #ccc;}\n.ui-field-table .ui-field-table-attribute {}\n.ui-field-table .ui-field-table-value-set {display: flex;flex-direction: column;}\n.ui-field-table .ui-field-table-cell { padding: 0 10; margin: 0; }\n.ui-field-table .ui-field-table-cell + .ui-field-table-cell { border-top: 1px solid #ccc; }\n\n.ui-field-table input.ui-field-table-cell {min-width:100%;font-size: 1em;font-weight: inherit;font-family: inherit;background: transparent;border: none;color: inherit;}\n.ui-field-table input::-webkit-input-placeholder { font-weight: 300; }\n.ui-field-table input.ui-field-table-attribute {padding-right: 0;}\n\n.ui-autocomplete { position: relative; flex: 0 0 auto; width: 200px; }\n.ui-autocomplete-matches { position: absolute; top: 100%; width: 100%; flex: 0 0 auto; z-index: 5; background: white; border-top: 0px solid #f0f0f0; border-radius: 4px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.2); }\n.ui-autocomplete[open=\"true\"] .ui-autocomplete-matches { border-top-width: 1px; }\n\n.ui-autocomplete-match { padding: 0 10px; height: 0; overflow: hidden; }\n.ui-autocomplete[open=\"true\"] .ui-autocomplete-match { padding: 0 10px; height: 100%; overflow: hidden; }\n.ui-autocomplete-match:hover { background: #f9f9f9; }\n.ui-autocomplete-match:active { background: #f0f0f0; }\n\n/******************************************************************************\\\n * Shape                                                                      *\n\\******************************************************************************/\n.shape-hexagon {position: relative;}\n.shape-hexagon > canvas {position: absolute;top: 0;left: 0;width: 100%;height: 100%;}\n.shape-hexagon > div { display: flex; flex-direction: column; justify-content: center; align-items: center; position: absolute; }\n\n/******************************************************************************\\\n * compiler\n\\******************************************************************************/\n\n.eve-compiler-error { background:#333; color: #ccc; margin:20px; padding:10px 20px; align-self:center; width:700px; border-radius:3px; box-shadow:2px 2px 8px #999; }\n.eve-compiler-line-info { margin-right:20px; color: #999; margin-right: 20px; padding-right: 20px; color: #888; border-right: 1px solid #666; justify-content: center;}\n.eve-compiler-error-sample { font-family:\"Inconsolata\", \"Monaco\", \"Consolas\", \"Ubuntu Mono\", monospace; margin-top:10px; background:#444; padding:10px; border-radius:3px; }\n.eve-compiler-error-message-line { font-size:20pt; color: #ff8c24; }\n.eve-compiler-error-message { color: #ff8c24; }\n.eve-compiler-error-content {padding: 5px; flex: 1;}\n\n/******************************************************************************\\\n * Notify\n\\******************************************************************************/\n@keyframes slide-down {\n    0% { padding: 0px 30px; max-height: 0; }\n    100% { padding: 5px 30px; max-height: 80px; }\n}\n\n.notify-wrapper { position: relative; max-height: 165px; overflow-y: hidden; }\n.notify-wrapper:after {position: absolute; content: \" \"; display: \"block\"; left: 0; right: 0; top: 155px; height: 10px; box-shadow: inset 0 -5px 5px rgba(0, 0, 0, 0.1); }\n.notify-scroller { overflow-y: auto; max-height: 165px; overflow-y: auto; }\n.notify-root { border-bottom: 1px solid rgba(0, 0, 0, 0.1);  }\n\n.notify-notice-wrapper { flex: 0 0 auto; align-items: center; padding: 5px 30px; padding-right: 40px; animation: slide-down 0.3s 1; animation-timing-function: ease-in; overflow: hidden; }\n.notify-notice { align-items: center; }\n.notify-notice-wrapper + .notify-notice-wrapper { border-top: 1px solid rgba(0, 0, 0, 0.1); }\n.notify-notice-wrapper:before { margin-left: -30px; width: 30px; text-align: center; font-family: \"Ionicons\"; line-height: 1; }\n.notify-notice-wrapper[type=\"warning\"] { background: rgba(255, 255, 0, 0.1); }\n.notify-notice-wrapper[type=\"warning\"]:before {content: \"\\f100\"; color: rgb(128, 128, 0); }\n.notify-notice-wrapper[type=\"error\"] { background: rgba(255, 96, 96, 0.2); }\n.notify-notice-wrapper[type=\"error\"]:before {content: \"\\f101\"; color: rgb(255, 0, 0); }\n.notify-notice-wrapper .notify-dismiss { margin-right: -40px; width: 30px; margin-left: 10px; }\n"
  },
  {
    "path": "assets/css/codemirror.css",
    "content": "/* BASICS */\n\n.CodeMirror {\n  /* Set height, width, borders, and global font properties here */\n  font-family: monospace;\n  height: 300px;\n  color: black;\n}\n\n/* PADDING */\n\n.CodeMirror-lines {\n  padding: 4px 0; /* Vertical padding around content */\n}\n.CodeMirror pre {\n  padding: 0 4px; /* Horizontal padding of content */\n}\n\n.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {\n  background-color: white; /* The little square between H and V scrollbars */\n}\n\n/* GUTTER */\n\n.CodeMirror-gutters {\n  border-right: 1px solid #ddd;\n  background-color: #f7f7f7;\n  white-space: nowrap;\n}\n.CodeMirror-linenumbers {}\n.CodeMirror-linenumber {\n  padding: 0 3px 0 5px;\n  min-width: 20px;\n  text-align: right;\n  color: #999;\n  white-space: nowrap;\n}\n\n.CodeMirror-guttermarker { color: black; }\n.CodeMirror-guttermarker-subtle { color: #999; }\n\n/* CURSOR */\n\n.CodeMirror-cursor {\n  border-left: 1px solid black;\n  border-right: none;\n  width: 0;\n}\n/* Shown when moving in bi-directional text */\n.CodeMirror div.CodeMirror-secondarycursor {\n  border-left: 1px solid silver;\n}\n.cm-fat-cursor .CodeMirror-cursor {\n  width: auto;\n  border: 0 !important;\n  background: #7e7;\n}\n.cm-fat-cursor div.CodeMirror-cursors {\n  z-index: 1;\n}\n\n.cm-animate-fat-cursor {\n  width: auto;\n  border: 0;\n  -webkit-animation: blink 1.06s steps(1) infinite;\n  -moz-animation: blink 1.06s steps(1) infinite;\n  animation: blink 1.06s steps(1) infinite;\n  background-color: #7e7;\n}\n@-moz-keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n@-webkit-keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n@keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n\n/* Can style cursor different in overwrite (non-insert) mode */\n.CodeMirror-overwrite .CodeMirror-cursor {}\n\n.cm-tab { display: inline-block; text-decoration: inherit; }\n\n.CodeMirror-rulers {\n  position: absolute;\n  left: 0; right: 0; top: -50px; bottom: -20px;\n  overflow: hidden;\n}\n.CodeMirror-ruler {\n  border-left: 1px solid #ccc;\n  top: 0; bottom: 0;\n  position: absolute;\n}\n\n/* DEFAULT THEME */\n\n.cm-s-default .cm-header {color: blue;}\n.cm-s-default .cm-quote {color: #090;}\n.cm-negative {color: #d44;}\n.cm-positive {color: #292;}\n.cm-header, .cm-strong {font-weight: bold;}\n.cm-em {font-style: italic;}\n.cm-link {text-decoration: underline;}\n.cm-strikethrough {text-decoration: line-through;}\n\n.cm-s-default .cm-keyword {color: #708;}\n.cm-s-default .cm-atom {color: #219;}\n.cm-s-default .cm-number {color: #164;}\n.cm-s-default .cm-def {color: #00f;}\n.cm-s-default .cm-variable,\n.cm-s-default .cm-punctuation,\n.cm-s-default .cm-property,\n.cm-s-default .cm-operator {}\n.cm-s-default .cm-variable-2 {color: #05a;}\n.cm-s-default .cm-variable-3 {color: #085;}\n.cm-s-default .cm-comment {color: #a50;}\n.cm-s-default .cm-string {color: #a11;}\n.cm-s-default .cm-string-2 {color: #f50;}\n.cm-s-default .cm-meta {color: #555;}\n.cm-s-default .cm-qualifier {color: #555;}\n.cm-s-default .cm-builtin {color: #30a;}\n.cm-s-default .cm-bracket {color: #997;}\n.cm-s-default .cm-tag {color: #170;}\n.cm-s-default .cm-attribute {color: #00c;}\n.cm-s-default .cm-hr {color: #999;}\n.cm-s-default .cm-link {color: #00c;}\n\n.cm-s-default .cm-error {color: #f00;}\n.cm-invalidchar {color: #f00;}\n\n.CodeMirror-composing { border-bottom: 2px solid; }\n\n/* Default styles for common addons */\n\ndiv.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}\ndiv.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}\n.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }\n.CodeMirror-activeline-background {background: #e8f2ff;}\n\n/* STOP */\n\n/* The rest of this file contains styles related to the mechanics of\n   the editor. You probably shouldn't touch them. */\n\n.CodeMirror {\n  position: relative;\n  overflow: hidden;\n  background: white;\n}\n\n.CodeMirror-scroll {\n  overflow: scroll !important; /* Things will break if this is overridden */\n  /* 30px is the magic margin used to hide the element's real scrollbars */\n  /* See overflow: hidden in .CodeMirror */\n  margin-bottom: -30px; margin-right: -30px;\n  padding-bottom: 30px;\n  height: 100%;\n  outline: none; /* Prevent dragging from highlighting the element */\n  position: relative;\n}\n.CodeMirror-sizer {\n  position: relative;\n  border-right: 30px solid transparent;\n}\n\n/* The fake, visible scrollbars. Used to force redraw during scrolling\n   before actual scrolling happens, thus preventing shaking and\n   flickering artifacts. */\n.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {\n  position: absolute;\n  z-index: 6;\n  display: none;\n}\n.CodeMirror-vscrollbar {\n  right: 0; top: 0;\n  overflow-x: hidden;\n  overflow-y: scroll;\n}\n.CodeMirror-hscrollbar {\n  bottom: 0; left: 0;\n  overflow-y: hidden;\n  overflow-x: scroll;\n}\n.CodeMirror-scrollbar-filler {\n  right: 0; bottom: 0;\n}\n.CodeMirror-gutter-filler {\n  left: 0; bottom: 0;\n}\n\n.CodeMirror-gutters {\n  position: absolute; left: 0; top: 0;\n  min-height: 100%;\n  z-index: 3;\n}\n.CodeMirror-gutter {\n  white-space: normal;\n  height: 100%;\n  display: inline-block;\n  vertical-align: top;\n  margin-bottom: -30px;\n  /* Hack to make IE7 behave */\n  *zoom:1;\n  *display:inline;\n}\n.CodeMirror-gutter-wrapper {\n  position: absolute;\n  z-index: 4;\n  background: none !important;\n  border: none !important;\n}\n.CodeMirror-gutter-background {\n  position: absolute;\n  top: 0; bottom: 0;\n  z-index: 4;\n}\n.CodeMirror-gutter-elt {\n  position: absolute;\n  cursor: default;\n  z-index: 4;\n}\n.CodeMirror-gutter-wrapper {\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n}\n\n.CodeMirror-lines {\n  cursor: text;\n  min-height: 1px; /* prevents collapsing before first draw */\n}\n.CodeMirror pre {\n  /* Reset some styles that the rest of the page might have set */\n  -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;\n  border-width: 0;\n  background: transparent;\n  font-family: inherit;\n  font-size: inherit;\n  margin: 0;\n  white-space: pre;\n  word-wrap: normal;\n  line-height: inherit;\n  color: inherit;\n  z-index: 2;\n  position: relative;\n  overflow: visible;\n  -webkit-tap-highlight-color: transparent;\n  -webkit-font-variant-ligatures: none;\n  font-variant-ligatures: none;\n}\n.CodeMirror-wrap pre {\n  word-wrap: break-word;\n  white-space: pre-wrap;\n  word-break: normal;\n}\n\n.CodeMirror-linebackground {\n  position: absolute;\n  left: 0; right: 0; top: 0; bottom: 0;\n  z-index: 0;\n}\n\n.CodeMirror-linewidget {\n  position: relative;\n  z-index: 2;\n  overflow: auto;\n}\n\n.CodeMirror-widget {}\n\n.CodeMirror-code {\n  outline: none;\n}\n\n/* Force content-box sizing for the elements where we expect it */\n.CodeMirror-scroll,\n.CodeMirror-sizer,\n.CodeMirror-gutter,\n.CodeMirror-gutters,\n.CodeMirror-linenumber {\n  -moz-box-sizing: content-box;\n  box-sizing: content-box;\n}\n\n.CodeMirror-measure {\n  position: absolute;\n  width: 100%;\n  height: 0;\n  overflow: hidden;\n  visibility: hidden;\n}\n\n.CodeMirror-cursor {\n  position: absolute;\n  pointer-events: none;\n}\n.CodeMirror-measure pre { position: static; }\n\ndiv.CodeMirror-cursors {\n  visibility: hidden;\n  position: relative;\n  z-index: 3;\n}\ndiv.CodeMirror-dragcursors {\n  visibility: visible;\n}\n\n.CodeMirror-focused div.CodeMirror-cursors {\n  visibility: visible;\n}\n\n.CodeMirror-selected { background: #d9d9d9; }\n.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }\n.CodeMirror-crosshair { cursor: crosshair; }\n.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }\n.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }\n\n.cm-searching {\n  background: #ffa;\n  background: rgba(255, 255, 0, .4);\n}\n\n/* IE7 hack to prevent it from returning funny offsetTops on the spans */\n.CodeMirror span { *vertical-align: text-bottom; }\n\n/* Used to force a border model for a node */\n.cm-force-border { padding-right: .1px; }\n\n@media print {\n  /* Hide the cursor when printing */\n  .CodeMirror div.CodeMirror-cursors {\n    visibility: hidden;\n  }\n}\n\n/* See issue #2901 */\n.cm-tab-wrap-hack:after { content: ''; }\n\n/* Help users use markselection to safely style text background */\nspan.CodeMirror-selectedtext { background: none; }\n"
  },
  {
    "path": "assets/css/editor.css",
    "content": ".editor-view {padding: 40px; align-items: stretch;height: 100%;background: white;}\n\n/****************************************************************************\\\n * Editor Nav\n\\****************************************************************************/\n\n.editor-view .editor-nav { margin-right: 20px; }\n.editor-view .editor-nav .editor-nav-tag { padding: 10px; }\n.editor-view .editor-nav .editor-nav-block { padding: 5px; padding-left: 20px; padding-right: 0; }\n\n\n\n/****************************************************************************\\\n * Editor Main\n\\****************************************************************************/\n\n.editor-view .editor-main { flex: 1; }\n.editor-view .editor-block-header { margin-bottom: 40px; }\n.editor-view .editor-block-description {max-width: 480px;font-weight: 300; line-height: 1.5; padding: 10px 0;margin-right: 40px;flex: 1;font-size: 1em;}\n.editor-view .editor-block-title {font-size: 1.25em; margin-bottom: 0.25em;font-weight: 500; margin-bottom: 0.5em; }\n\n.editor-view .editor-block-storyboard { flex: 1; align-self: flex-start; }\n\n.editor-view .editor-block-frame { width: 130px; height: 80px; margin: 10px; margin-top: 3em;justify-content: center; align-items: center; border: 2px solid #888; border-radius: 3px; }\n.editor-view .editor-block-frame.active,\n.editor-view .editor-block-frame:not([open=\"true\"]).active:hover{ background: #f0f0f0; }\n\n.editor-view .editor-block-frame:not([open=\"true\"]):hover { background: #fcfcfc; }\n.editor-view .editor-block-frame:not([open=\"true\"]):active { background: #f0f0f0; }\n\n\n.editor-view .editor-new-frame { border-color: #ccc; color: #ccc; justify-content: space-around; }\n.editor-view .editor-new-frame:empty:after { display: flex; content: \"+\"; font-size: 3em; font-weight: 200;}\n\n.editor-view .editor-new-frame-type { align-self: stretch; color: #606060; }\n\n\n.editor-view .editor-block-content { flex: 1; }\n\n/****************************************************************************\\\n * Editor Node Tree\n\\****************************************************************************/\n\n.editor-node-tree { align-self: flex-start; padding-left: 44px; padding-top: 16px; }\n.editor-node-tree input {font-size: 1em; font-weight: inherit;font-family: inherit; border: none; color: inherit;}\n.editor-node-tree-node { position: relative; padding: 5px 0; margin-top: -16px; min-height: 60px; }\n.editor-node-tree > .editor-node-tree-node { min-height: 65px; }\n\n.editor-node-tree-node-new {margin-top: -16px; align-items: flex-start; }\n.editor-node-tree-node-new > .ui-autocomplete {margin-top: 10px;width: 100px;z-index: 2;}\n.editor-node-tree-node-new > .button {margin-top: 7px;}\n.editor-node-tree-node-new > .button:before {width: 1.3em; line-height: 1.3em;text-align: center; font-size: 0.9em; border: 1px solid #ccc; border-radius: 100px;}\n\n.editor-node-tree-node-pattern { margin-top: 11px; margin-left: 15px; }\n.editor-node-tree-node-pattern .editor-node-tree-node-pattern-name { margin-left: -15px; margin-bottom: 8px;}\n.editor-node-tree-node-pattern-field {position: relative; min-height: 22px; z-index: 1; }\n.editor-node-tree-node-pattern-field > text:not(:last-child):after {content: \":\"}\n.editor-node-tree-node-pattern-value {padding:0;padding-left: 5px; max-width: 100px;}\n\n.editor-node-tree-node-pattern div.button {position: relative;width: 40px; line-height: 1.3em; margin-top: -4px;margin-left: -34px;margin-bottom:-4px;font-size: 0.9em; color: #999; }\n.editor-node-tree-node-pattern div.button:before { width: 1.3em; line-height: 1.3em; text-align: center; border: 1px solid #ddd; border-radius: 100px; background: white; font-weight: 100; }\n.editor-node-tree-node-pattern div.button:after {content: \" \"; display: block; position: absolute; left: 50%; right: 5px; top: 50%; margin-top: 0; z-index: -1; border-top: 1px solid #ccc; }\n\n.editor-node-tree-node-controls { position: absolute; top: 38px; left: -21px; padding: 4px 0;}\n.editor-node-tree-node-controls > div.button {position:relative; margin:0; margin-right: -4px; ;z-index: 1;font-size:0.9em;}\n\n.editor-node-tree-node-controls:before {content: \" \";display: block;position: absolute;top: 4px;bottom: 18px;right: -4px;border-left: 1px solid #ccc;}\n.editor-node-tree-node-controls > div.button:before {width: 1.3em;margin-top: -5px;text-align: center;line-height: 1.3em;border: 1px solid #ccc;border-radius: 100px;background: white;font-weight: 100;}\n.editor-node-tree-node-controls > div.button:after {content: \" \"; display: block; position: absolute; left: 50%; right: 0; top: 50%; margin-top: -3px; z-index: -1; border-top: 1px solid #ccc; }\n\n.editor-node-tree-node-field-new-attribute {width: 100px;z-index: 2;}\n\n/* Crazy tree positioning magic */\n\n.editor-node-tree-node-hex { position: absolute; left: -44px; cursor: default; -webkit-user-select: none; }\n.editor-node-tree-node-pattern .editor-node-tree-node-hex { position: absolute; left: -59px; }\n.editor-node-tree-node-pattern .editor-node-tree-node-pattern .editor-node-tree-node-hex { position: absolute; left: -74px; }\n.editor-node-tree-node-pattern .editor-node-tree-node-pattern .editor-node-tree-node-pattern .editor-node-tree-node-hex { position: absolute; left: -89px; }\n.editor-node-tree-node-pattern .editor-node-tree-node-pattern .editor-node-tree-node-pattern .editor-node-tree-node-pattern .editor-node-tree-node-hex { position: absolute; left: -104px; }\n/* okay, you win. */\n\n.editor-node-tree-node-pattern-field:before {display: block;content: \" \";position: absolute;top: 11px;left: -13px;width: 9px;height: 1px;border-top: 1px solid #ccc;}\n\n.editor-node-tree-node-pattern-name:before {display: block;content: \" \";position: absolute;top: 26px;left: -13px;width: 9px;height: 1px;border-top: 1px solid #ccc;}\n.editor-node-tree > .editor-node-tree-node > .editor-node-tree-node-pattern > row > .editor-node-tree-node-pattern-name:before {display: none; }\n\n.editor-node-tree-node-pattern:before {display: block;content: \" \";position: absolute; left: 1px; top: 38px; bottom: 6px;width: 0;border-left: 1px solid #ccc;}\n\n.editor-node-tree-fields:last-child > .editor-node-tree-node-pattern-field:last-child:after {display: block;content: \" \";position: absolute;top: 11px;bottom: -10px;left:-14px;width: 0;margin-top: 1px;border-left: 2px solid white;}\n\n.editor-node-tree-subnodes:last-child .editor-node-tree-node:last-child:after {display: block;content: \" \";position: absolute;top: 26px;bottom: -10px;left: -14px;width: 0;margin-top: 1px;border-left: 1px solid white;}\n\n.editor-node-tree-node-pattern row:first-child:last-child > .editor-node-tree-node-pattern-name:after {display: block;content: \" \";position: absolute;top: 38px;bottom: 5px;left:0;width: 0;border-left: 2px solid white;}\n\n.editor-node-tree-subnodes:nth-child(2) { margin-top: 20px;}\n.editor-node-tree-subnodes:nth-child(3) { margin-top: 5px;}\n\n/* Animations */\n.editor-node-tree-node-hex > canvas { transition: transform 0.2s ease-out; }\n.editor-node-tree-node[open=\"true\"] > .editor-node-tree-node-hex > canvas { transform: rotateZ(60deg); }\n\n/****************************************************************************\\\n * Editor Molecule List\n\\****************************************************************************/\n.editor-molecule-list { position: relative; flex: 1; }\n.editor-molecule-list-molecule { position: relative; display: flex; flex: 0 0 auto; padding-bottom: 10px;}\n.editor-molecule-list-molecule-grid { position: relative;}\n.editor-molecule-list-molecule-cell { cursor: default; -webkit-user-select: none; }\n\n.editor-infobox { position: absolute; top: 100%; left: 0; /*left: 75%; transform: translateX(-50%);*/ width: 250px; padding: 10px; z-index: 3; background: white; border-radius: 3px; border: 1px solid #bbb; }\n\n.editor-infobox-field-new  {position: relative;width: 40px; line-height: 1.3em; margin-top: 0;margin-left: -4px;margin-bottom:-4px;font-size: 0.9em; color: #999; }\n.editor-infrobox-field-new:before { width: 1.3em; line-height: 1.3em; text-align: center; background: white; font-weight: 100; }\n\n.editor-infobox-field-attribute { margin-left: -8px; width: auto; }\n.editor-infobox-field-attribute input { font-size: 1em; line-height: 1.3em; padding: 5px 10px; font-weight: inherit;font-family: inherit; border: none; color: inherit;}\n\n.editor-infobox-node { margin-bottom: 10px; }\n\n.editor-infobox-node-header { margin: 0 9px; margin-bottom: 0.25rem; }\n.editor-infobox-node-name { font-size: 1.25em; font-weight: 500; color: #606060; }\n\n.editor-infobox-atom { width: 100%; }\n.editor-infobox .ui-field-table td { border: none; }\n.editor-infobox .ui-field-table td:first-child { width: 75px; }\n\n.editor-infobox .editor-paginator { padding-left: 0.5em; color: #909090; margin-top: 4px; }\n.editor-infobox .editor-paginator > div { margin-top: -2px; }\n\n/****************************************************************************\\\n * Editor Block Canvas\n\\****************************************************************************/\n.editor-view .editor-query-tree { width: 180px; margin-right: 10px; }\n\n\n/****************************************************************************\\\n * Data Editor\n\\****************************************************************************/\n.editor-block-data-canvas {position: relative;flex: 1;align-items: flex-start;}\n.editor-block-data-canvas .editor-data-toolbar { flex: 0 0 auto; background: #eee; border-radius: 3px; }\n.editor-block-data-canvas .editor-data-toolbar > .ui-button { display: flex; flex-direction: column; align-items: center; padding: 10px; color: #999; }\n.editor-block-data-canvas .editor-data-toolbar > .ui-button:before { font-size: 1.5em; color: #404040;}\n.editor-block-data-canvas .editor-data-toolbar > .ui-button.editor-active {  color: #66f; }\n.editor-block-data-canvas .editor-data-toolbar > .ui-button.editor-active:before {  color: #44f; }\n.editor-block-data-canvas .editor-data-toolbar > .editor-data-toolbar-select:before { transform: rotateZ(-30deg); }\n\n.editor-block-data-canvas .editor-data-molecule-infobox {position: absolute;top: 300px;left: 70px;padding: 10px;z-index: 2;border: 1px solid gray;background: rgba(255, 255, 255, 0.5);}\n.editor-block-data-canvas .editor-data-node-infobox { padding: 10px; }\n.editor-block-data-canvas .editor-data-node-infobox + .editor-node-infobox { border-top: 1px solid gray; }\n.editor-block-data-canvas .editor-data-atom-infobox + .editor-atom-infobox { margin-top: 20px; }\n\n.editor-block-data-canvas .editor-data-node-header { font-weight: 500; text-align: center; }\n.editor-block-data-canvas .editor-data-field-row { min-width: 200px; min-height: 20px; margin-bottom: -1px; border: 1px solid #ccc; }\n.editor-block-data-canvas .editor-data-field-attribute {display: flex;flex: 0;width: auto;min-width: 100px;padding: 0 10px; margin: -1px; margin-right: 0; background: transparent; border: 1px solid transparent; border-right-color: #ccc;}\n.editor-block-data-canvas .editor-data-field-value-set {flex: 1 1 auto;/* max-width: 140px; */}\n.editor-block-data-canvas .editor-data-field-value {display: flex;flex: 1;min-width: 80px;min-height: 20px;padding: 0 10px;margin: -1px;background: transparent;border: 1px solid transparent;}\n\n.editor-block-data-canvas .editor-data-molecule-infobox input { font-size: 1em; font-weight: inherit;font-family: inherit; color: inherit;}\n.editor-block-data-canvas .editor-data-molecule-infobox input::-webkit-input-placeholder { font-weight: 300; }\n\n.editor-block-data-canvas .editor-data-field-new-row { background: #ddffdd; border-color: #44cc44; }\n\n.editor-block-data-canvas .editor-data-field-row.editor-data-erasing { background: #ffdddd; border-color: #cc4444; }\n.editor-block-data-canvas .editor-data-field-value.editor-data-erasing { background: #ffdddd; border-color: #cc4444; }\n\n\n\n/****************************************************************************\\\n * Shapes\n\\****************************************************************************/\n\n/* .shape-hexagon { position: relative; } */\n/* .shape-hexagon .shape-hexagon-body { justify-content: center; align-items: center; } */\n/* .shape-hexagon .shape-hexagon-cap { } */\n\n/* .shape-hexagon .shape-hexagon-inner {  } */\n\n/* .shape-hex-grid {position: relative;left: 70px;} */\n\n\n\n\n\n.hex-grid > .shape-hexagon-body,\n.hex-grid > .shape-hexagon-cap { transition: 0.5s background ease-in, 0.5s border-top-color ease-in, 0.5s border-bottom-color ease-in; }\n\n.hex-grid:hover > .shape-hexagon-body,\n.hex-grid:hover > .shape-hexagon-cap { transition: 0.2s background ease-out, 0.2s border-top-color ease-out, 0.2s border-bottom-color ease-out; }\n\n\n.hex-grid:hover > .shape-hexagon-body { background: #ccc !important; }\n.hex-grid:hover > .shape-hexagon-cap.first { border-bottom-color: #ccc !important; }\n.hex-grid:hover > .shape-hexagon-cap.last { border-top-color: #ccc !important; }\n\ntext { flex: 0 0 auto; }\ncolumn { flex: 0 0 auto; }\nrow { flex: 0 0 auto; }\n"
  },
  {
    "path": "assets/css/examples/crm.css",
    "content": "h3, h4 { font-weight:normal; font-size:16px; }\n\n.container {\n  box-shadow: 0 3px 8px #bbb;\n  width: 400px;\n  height: 711px;\n  background: white;\n  font-family: sans-serif;\n  font-size: 14px;\n  display: flex;\n  flex-direction: column;\n}\n\n.scroll {\n  flex: 1;\n  overflow: auto;\n  display:flex;\n  flex-direction:column;\n}\n\n.avatar {\n  width: 128;\n  height: 128;\n}\n\n.banner {\n  background: linear-gradient(-90deg, rgb(74,64,136), rgb(0,158,224));\n  width: 100%;\n  height: 150px;\n  flex:none;\n}\n\n.avatar-container {\n  border-radius: 6px;\n  width: 128px;\n  height: 128px;\n  overflow: hidden;\n  border: 5px solid white;\n  margin: auto;\n  margin-top: -60px;\n}\n\n.name2 {\n  width: 100%;\n  text-align: center;\n  color: rgb(11,11,37);\n  font-family: sans-serif;\n  font-size: 20px;\n  font-weight: bold;\n  margin-top: 10px;\n}\n\n.info {\n  width: 100%;\n  text-align: center;\n  color: rgb(147,167,178);\n  color: #999;\n  font-family: sans-serif;\n  font-size: 14px;\n  margin-top: 5px;\n}\n\n.navigation {\n  display: flex;\n  flex:none;\n  border-top: 1px solid #eee;\n  padding:5px 0;\n}\n\n.nav-button {\n  position:relative;\n  flex-grow: 1;\n  text-align: center;\n  height: 60px;\n  cursor: pointer;\n  color: #999;\n}\n\n.icon {\n  font-size: 30px;\n  padding: 3px;\n  height: 35px;\n  margin-top:2px;\n}\n\n.middle {\n  border-right: 1px solid #eee;\n}\n\n.bubble {\n  position: absolute;\n  top:8px;\n  left: 56px;\n  color: white;\n  font-weight: bold;\n}\n\n.content {\n  margin-top: 10px;\n  padding: 0px;\n  flex-grow: 1;\n  display: flex;\n  flex-direction: column;\n}\n\n.convo {\n  height: 280px;\n  margin-bottom: 10px;\n  padding: 10px 20px;\n  overflow: auto;\n}\n\n.msg-avatar {\n  height: 40px;\n  width: 40px;\n  margin-right: 15px;\n  float: left;\n  border-radius: 4px;\n  border: 1px solid #ddd;\n}\n\n.msg-name {\n  font-weight: bold;\n  color: #555;\n  margin-top:2px;\n  margin-bottom:2px;\n}\n\n.msg {\n  margin-bottom: 10px;\n  min-height: 40px;\n  flex:none;\n}\n\n.thread:first-child { margin-top:30px; border-top:1px solid #eee; }\n\n.thread {\n  padding: 15px 20px;\n  display: flex;\n  flex-direction: row;\n  border-bottom: 1px solid #eee;\n  align-items: center;\n  color: #999;\n}\n\n.thread img {}\n\n.thread-box {\n  flex-grow: 1;\n}\n\n.archive {\n  cursor: pointer;\n  color: #999;\n  font-size: 25px;\n}\n\n.contact-avatar {\n  width: 80px;\n  height: 80px;\n  float: left;\n  margin-right: 10px;\n  border-radius: 4px;\n}\n\n.contact-name {\n  font-weight: bold;\n  font-size: 18px;\n  margin-top:3px;\n  margin-bottom: 5px;\n  color: #666;\n}\n\n.contact {\n  flex: none;\n  padding: 15px 20px;\n  border-bottom: 1px solid #eee;\n  color: #999;\n}\n\n.contact:first-child { margin-top: 20px; border-top:1px solid #eee; }\n\n.recent-avatar {\n  width: 40px;\n  height: 40px;\n  margin-right: 10px;\n}\n\n.msg-input {\n  width: 360px;\n  margin: 0 20px;\n  border: 1px solid #ddd;\n  border-radius: 2px;\n  padding: 5px;\n  font-size: 12pt;\n}\n\n.msg-time {\n  color: rgb(175,175,175);\n  margin-left: 10px;\n  font-size: 12px;\n}\n\n.about { padding: 0 40px; padding-top: 10px; }\n\n.about-line {\n  display: flex;\n  flex-direction:column;\n  margin-top:20px;\n}\n\n.about-label, .about h3 {\n  color: rgb(152, 171, 181);\n  color: #999;\n  margin-bottom:6px;\n  font-size: 14px;\n}\n\n.about h3 { margin-top:20px; margin-bottom:10px; }\n.about img { border-radius:4px; border:1px solid #ddd; }\n\n.plus {\n  background-color: rgb(111, 165, 81);\n  color: white;\n  width: 16px;\n  height: 16px;\n  float: left;\n  border-radius: 50%;\n  text-align: center;\n  font-size: 14px;\n  font-weight: bold;\n  margin-right: 5px;\n  cursor: pointer;\n}\n\na {\n color: rgb(0,158,224);\n line-height: 20px;\n text-decoration: none;\n border-bottom: 1px dashed rgb(0,158,224);\n}\n\na:hover {\n  color: rgb(91,89,164);\n}\n\n.more {\n  padding: 0 40px;\n}\n\n.more h2 { font-weight: normal; font-size: 16pt; margin-top: 30px; margin-bottom: 10px; }\n\n.more ul { margin: 5px 0; }\n.more li { margin-bottom:8px; }\n\n.button {\n  background-color: rgb(111, 165, 81);\n  color: white;\n  width: 80%;\n  height: 25px;\n  border-radius: 10px;\n  text-align: center;\n  font-size: 14px;\n  font-weight: bold;\n  margin-right: auto;\n  margin-left: auto;\n  margin-top: 10px;\n  padding-top: 4px;\n  cursor: pointer;\n}"
  },
  {
    "path": "assets/css/examples/todomvc.css",
    "content": ".program {\n    margin: 0 auto;\n    padding: 0;\n    font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;\n    line-height: 1.4em;\n    background: #f5f5f5;\n    color: #4d4d4d;\n    -webkit-font-smoothing: antialiased;\n    -moz-font-smoothing: antialiased;\n    font-smoothing: antialiased;\n    font-weight: 300;\n}\n\n.todoapp {\n    flex: 0 0 auto;\n    min-width: 450px;\n    max-width: 650px;\n    height: auto;\n    margin: 130px auto 40px auto;\n\n    background: #fff;\n    position: relative;\n    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),\n        0 25px 50px 0 rgba(0, 0, 0, 0.1);\n}\n\n.todoapp button {\n    margin: 0;\n    padding: 0;\n    border: 0;\n    background: none;\n    font-size: 100%;\n    vertical-align: baseline;\n    font-family: inherit;\n    font-weight: inherit;\n    color: inherit;\n    -webkit-appearance: none;\n    appearance: none;\n    -webkit-font-smoothing: antialiased;\n    -moz-font-smoothing: antialiased;\n    font-smoothing: antialiased;\n}\n\n.todoapp button,\n.todoapp input[type=\"checkbox\"] {\n    outline: none;\n}\n\n.todoapp input::-webkit-input-placeholder {\n    font-style: italic;\n    font-weight: 300;\n    color: #e6e6e6;\n}\n\n.todoapp input::-moz-placeholder {\n    font-style: italic;\n    font-weight: 300;\n    color: #e6e6e6;\n}\n\n.todoapp input::input-placeholder {\n    font-style: italic;\n    font-weight: 300;\n    color: #e6e6e6;\n}\n\n.todoapp h1 {\n    position: absolute;\n    top: -155px;\n    width: 100%;\n    font-size: 100px;\n    font-weight: 100;\n    text-align: center;\n    color: rgba(175, 47, 47, 0.15);\n    -webkit-text-rendering: optimizeLegibility;\n    -moz-text-rendering: optimizeLegibility;\n    text-rendering: optimizeLegibility;\n}\n\n.todoapp .hidden {\n    display: none !important;\n}\n\n.todoapp .new-todo,\n.todoapp .edit {\n    position: relative;\n    margin: 0;\n    width: 100%;\n    font-size: 24px;\n    font-family: inherit;\n    font-weight: inherit;\n    line-height: 1.4em;\n    border: 0;\n    outline: none;\n    color: inherit;\n    padding: 6px;\n    border: 1px solid #999;\n    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);\n    box-sizing: border-box;\n    -webkit-font-smoothing: antialiased;\n    -moz-font-smoothing: antialiased;\n    font-smoothing: antialiased;\n}\n\n.todoapp .new-todo {\n    padding: 16px 16px 16px 60px;\n    border: none;\n    background: rgba(0, 0, 0, 0.003);\n    box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);\n}\n\n.todoapp .toggle-all {\n    position: absolute;\n    top: 12px;\n    left: 14px;\n    width: 60px;\n    height: 34px;\n    text-align: center;\n    border: none; /* Mobile Safari */\n}\n\n.todoapp .main {\n    position: relative;\n    z-index: 2;\n    border-top: 1px solid #e6e6e6;\n}\n\n.todoapp .todo-list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n}\n\n.todoapp .todo-list li {\n    position: relative;\n    padding-left: 40px;\n    font-size: 24px;\n    border-bottom: 1px solid #ededed;\n}\n\n.todoapp .todo-list li:last-child {\n    border-bottom: none;\n}\n\n.todoapp .todo-list li.editing {\n    display: flex;\n    flex-direction: row;\n    border-bottom: none;\n    padding: 0;\n    height: 44px;\n}\n\n.todoapp .todo-list li.editing .edit {\n    display: flex;\n    flex: 1;\n    padding: 13px 15px 12px 15px;\n    margin: -1px 0 0 43px;\n}\n\n.todoapp .todo-list li .toggle {\n    text-align: center;\n    width: 40px;\n    /* auto, since non-WebKit browsers doesn't support input styling */\n    height: auto;\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    left: 0px;\n    border: none; /* Mobile Safari */\n    -webkit-appearance: none;\n    appearance: none;\n}\n\n.todoapp .todo-list li .toggle:after {\n    content: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"40\" viewBox=\"-10 -18 100 135\"><circle cx=\"50\" cy=\"50\" r=\"50\" fill=\"none\" stroke=\"#ededed\" stroke-width=\"3\"/></svg>');\n}\n\n.todoapp .todo-list li .toggle:checked:after {\n    content: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"40\" viewBox=\"-10 -18 100 135\"><circle cx=\"50\" cy=\"50\" r=\"50\" fill=\"none\" stroke=\"#bddad5\" stroke-width=\"3\"/><path fill=\"#5dc2af\" d=\"M72 25L42 71 27 56l-4 4 20 20 34-52z\"/></svg>');\n}\n\n.todoapp .todo-list li label {\n    white-space: pre-line;\n    word-break: break-all;\n    margin-left: 12px;\n    padding: 8px 6px;\n    display: block;\n    line-height: 1.2;\n    transition: color 0.4s;\n}\n\n.todoapp .todo-list li.completed label {\n    color: #d9d9d9;\n    text-decoration: line-through;\n}\n\n.todoapp .todo-list li .destroy {\n    display: none;\n    position: absolute;\n    top: 0;\n    right: 10px;\n    bottom: 0;\n    width: 40px;\n    height: 40px;\n    margin: auto 0;\n    font-size: 30px;\n    color: #cc9a9a;\n    margin-bottom: 11px;\n    transition: color 0.2s ease-out;\n}\n\n.todoapp .todo-list li .destroy:hover {\n    color: #af5b5e;\n}\n\n.todoapp .todo-list li .destroy:after {\n    position: relative;\n    content: \"×\";\n    top: 8px;\n}\n\n.todoapp .todo-list li:hover .destroy {\n    display: block;\n}\n\n.todoapp footer {\n    color: #777;\n    padding: 10px 15px;\n    height: 41px;\n    text-align: center;\n    border-top: 1px solid #e6e6e6;\n}\n\n.todoapp footer:before {\n    content: '';\n    position: absolute;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    height: 50px;\n    overflow: hidden;\n    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),\n        0 8px 0 -3px #f6f6f6,\n        0 9px 1px -3px rgba(0, 0, 0, 0.2),\n        0 16px 0 -6px #f6f6f6,\n        0 17px 2px -6px rgba(0, 0, 0, 0.2);\n}\n\n.todoapp .todo-count {\n    float: left;\n    text-align: left;\n}\n\n.todoapp .todo-count strong {\n    font-weight: 300;\n}\n\n.todoapp .clear-completed,\nhtml .todoapp .clear-completed:active {\n    float: right;\n    position: relative;\n    line-height: 20px;\n    text-decoration: none;\n    cursor: pointer;\n    position: relative;\n}\n\n.todoapp .clear-completed:hover {\n    text-decoration: underline;\n}\n\n\n.todoapp .filters {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n    position: absolute;\n    right: 0;\n    left: 0;\n}\n\n.todoapp .filters li {\n    display: inline;\n}\n\n.todoapp .filters li a {\n    color: inherit;\n    margin: 3px;\n    padding: 3px 7px;\n    text-decoration: none;\n    border: 1px solid transparent;\n    border-radius: 3px;\n}\n\n.todoapp .filters li a.selected,\n.todoapp .filters li a:hover {\n    border-color: rgba(175, 47, 47, 0.1);\n}\n\n.todoapp .filters li a.selected {\n    border-color: rgba(175, 47, 47, 0.2);\n}\n"
  },
  {
    "path": "assets/css/ide.css",
    "content": ".flex-spacer { flex: 1; }\n.flex-row { display: flex; flex: 1; flex-direction: row; }\n\n@keyframes hide-delayed {\n    from { opacity: 1; }\n    99% { opacity: 1; }\n    to { opacity: 0; }\n}\n\n* { box-sizing: border-box; }\n\n\nhtml, body, __root { height: 100%; }\nbody { background: #f5f5f5; color: #555; }\nbutton { font-family: \"Avenir\", \"Helvetica neue\", sans-serif; }\n/****************************************************************************\\\n * Root\n\\****************************************************************************/\nbody { justify-content: stretch; }\n.application-root { overflow: auto; }\n\n.editor-root { display: flex; flex-direction: row; height: 100%; align-items: stretch; background: #fff; }\n\n.navigator-pane { width: 280px; flex: 0 0 auto; align-self: stretch; overflow-x: hidden; background:#555; color:#eee; transition: width 0.3s ease-in-out, background-color 0.2s ease-in-out; padding-bottom:30px; }\n.navigator-pane.collapsed { width: 20px; background-color: #fff; }\n.navigator-pane.collapsed:hover { background: #555; color: #eee; }\n.navigator-pane .navigator-pane-inner { width: 280px; padding: 0 5px; margin-left: 0; transition: margin-left 0.3s ease-in-out; }\n.navigator-pane.collapsed .navigator-pane-inner { margin-left: -255px; }\n.navigator-pane.collapsed .tree-item { pointer-events: none; }\n\n.editor-pane { position: relative; height: 100%; }\n\n.CodeMirror-sizer > div:first-child { width: 650px; min-height: 100%; }\n\n/****************************************************************************\\\n * Navigator\n\\****************************************************************************/\n.navigator-header { display: flex; flex-direction: column; padding:10px 5px; user-select: none; -webkit-user-select: none; margin-bottom:10px; font-size:18px; color: #aaa; transition:color 0.2s ease-in-out; }\n.navigator-header > .controls { display:flex; flex-direction:row; flex:1; }\n.navigator-header > .controls > * {height: 20px; text-align: center; padding: 0 5px; transition: transform 0.3s; cursor: pointer; }\n.navigator-header > .controls > *:last-child { margin-right: -5px; }\n.navigator-header > .controls > div:not(.disabled):hover { color: #eee; }\n.navigator-pane.collapsed:hover .navigator-header > .controls > div { color: #eee; }\n.navigator-header .collapse-btn { transform: rotate(180deg); color: #999; }\n.navigator-header .up-btn.disabled > .up-icon { opacity: 0; }\n\n.navigator-pane .load-dialog { padding: 10 25; color: #555; margin-top: -10px; margin-bottom: 10px; }\n.navigator-pane .load-dialog input { padding: 5px; outline: none; border-top-left-radius: 2px; border: 1px solid #CCC; border-bottom-left-radius: 2px; }\n.navigator-pane .load-dialog .load-btn { padding: 5px; background: white; border: 1px solid #CCC; border-left-color: transparent; border-top-right-radius: 2px; border-bottom-right-radius: 2px; }\n.navigator-pane .load-dialog .load-btn:before { position: relative; top: 3px; }\n.navigator-pane .load-dialog .load-btn:hover { background: #EEE; }\n.navigator-pane .load-dialog .load-btn:active { background: #DDD; }\n\n.navigator-header .up-btn .label { display: flex; font-size:16px; margin-left: 10px; }\n.navigator-header .label { white-space: pre; display:none; }\n\n.navigator-header .inspector-controls { margin-top: 20px; }\n.navigator-header .inspector-controls button {border:none; color:#eee; outline:none; cursor:pointer; padding:3px 10px; font-size: 10pt; margin-left:20px; background:#666; border-radius:2px; margin-top: 20px; transition:background 0.1s ease-in-out; }\n.navigator-header .inspector-controls button:hover { background: #777; }\n\n\n\n.tree-item { flex-direction: column; align-items: stretch; }\n.tree-item > .tree-items { display: flex; flex-direction: column; }\n\n.item-level-1 > div > .label { font-size:14pt; }\n.item-level-1 > .flex-row { margin-bottom:25px; }\n.tree-item + .item-level-1 > .flex-row { margin-top: 25px; }\n.item-level-2 { margin-bottom:25px; margin-left:3px; }\n.item-level-2 > div > .label { font-weight: bold; margin-bottom:5px; }\n.item-level-3 { margin-top:3px; }\n.item-level-3 > div > .label { color:#bbb; font-size: 11pt; }\n\n.tree-item .label { display: flex; flex: 1 1 auto; margin-left: 20px; user-select: none; -webkit-user-select: none; transition: opacity 0.15s; cursor: pointer;}\n.tree-item .label:before { display: inline-block; margin-left: -15px; margin-right: 5px; height: 15px; width: 15px; text-align: center; }\n.tree-item.branch .label:before { opacity: 0.25; transition: transform 0.15s; }\n.tree-item.branch.root > .flex-row > .label { opacity: 0; pointer-events: none; }\n.tree-item.branch.collapsed .label:before { transform: rotate(-90deg); opacity: 0.5; }\n.tree-item.branch:hover .label:before { opacity: 1; }\n.tree-item .label.no-icon:before { content: \"\"; }\n\n.tree-item .controls { display: flex; flex: 0 0 auto; align-items: center; color:#bbb; margin-left: 5px; opacity: 0; transition: opacity 0.15s; user-select: none; -webkit-user-select: none; }\n.tree-item .controls > * { cursor:pointer; display: inline-block; width: 20px; text-align: center; }\n.tree-item .controls > div:hover { color: #eee; }\n.tree-item:hover > .flex-row > .controls { opacity: 1; }\n\n.tree-item.hidden > .flex-row > .label { opacity: 0.5; }\n\n/****************************************************************************\\\n * Notices\n\\****************************************************************************/\n.main-pane { display: flex; flex-direction: column; justify-content: stretch; overflow-y: hidden; position: relative; }\n.main-pane .notices { display: flex; flex: 0 0 auto; flex-direction: column; border-right: 1px solid #ddd; border-bottom: 1px solid #ddd; }\n\n.main-pane .notices > .notice { padding: 5px 60px; background: #f5f5f5; flex: 0 0 auto; }\n.main-pane .notices > .notice + .notice { border-top: 1px solid #ddd; }\n.main-pane .notices > .notice > .time { margin-right: 10px;  }\n\n.main-pane .notices > .notice > .dismiss-btn { margin-left: 10px; vertical-align: bottom; align-self: center; opacity: 0; transition: opacity 0.2s; }\n.main-pane .notices:hover > .notice > .dismiss-btn { opacity: 1; }\n.main-pane .notices > .notice > .dismiss-btn:hover { background: #eee; }\n\n.main-pane .notices > .notice.error { background: rgb(255, 216, 222); }\n.main-pane .notices > .notice.warning { background: rgb(255, 247, 217); }\n\n/****************************************************************************\\\n * Modals\n\\****************************************************************************/\n.main-pane > .modal-overlay {display: flex;justify-content: center;align-items: center;position: absolute;top: 0;left: 0;bottom: 0;right: 0;z-index: 5;background: rgba(0, 0, 0, 0.1);}\n.main-pane > .modal-overlay .modal-window { display: flex; flex-direction: column; width: 320; padding: 20; background: #FFF; border-radius: 4px; z-index: 10; box-shadow: 0 4px 5px rgba(0, 0, 0, 0.25); }\n.main-pane > .modal-overlay .modal-window h3 { align-self: center; margin-top: 0; }\n.main-pane > .modal-overlay .modal-window .controls { flex: 0 0 auto; }\n.main-pane > .modal-overlay .modal-window .btn { flex: 1; text-align: center; padding: 5 10; border: 1px solid #DDD; border-radius: 4px; -webkit-user-select: none; -moz-webkit-user-select: none; }\n.main-pane > .modal-overlay .modal-window .btn + .btn { margin-left: 10; }\n.main-pane > .modal-overlay .modal-window .btn:hover { background: #EEE; }\n.main-pane > .modal-overlay .modal-window .btn:active {background: #DDD; }\n.main-pane > .modal-overlay .modal-window .btn.danger { background: #EE5555; color: white; border: #CC3333; }\n.main-pane > .modal-overlay .modal-window .btn.danger:hover { background: #DD4444; }\n.main-pane > .modal-overlay .modal-window .btn.danger:active { background: #CC3333; }\n\n/****************************************************************************\\\n * Editor\n\\****************************************************************************/\n.editor-pane { margin-left: 50px; }\n\n.editor-pane > .controls { position: absolute; top: 10px; right: 30px; z-index: 10; background: rgba(255, 255, 255, 1); }\n.editor-pane > .controls > div { padding: 5px 10px; font-size:22px; color: #999; border-radius:2px; }\n.editor-pane > .controls > div:hover { background: #eee; color: #555; }\n\n.CodeMirror-lines { padding-top:30px; }\n\n.CodeMirror { background:none; color: inherit; height:100%; font-size:11pt;  }\n.CodeMirror-scroll, .CodeMirror-gutters { height:100%; }\n.CodeMirror-cursor { background: #555; border-color:#555; }\n\n.CodeMirror { color: #555; line-height:25px; font-family: Avenir, \"Helvetica Neue\", sans-serif; }\n.CodeMirror pre { padding-left: 8px; padding-right:50px; }\n.CodeMirror-scroll { scroll-behavior: smooth; }\n.CodeMirror-gutters { background: #303030; border-right: none; padding-right: 5px; }\n.CodeMirror-selected { background: rgba(0,117,255,0.1); }\n.CodeMirror-focused .CodeMirror-selected { background: rgba(0,117,255,0.2); }\n\n.CodeMirror-simplescroll-vertical { width:14px; background: #fff; border-left:1px solid #ddd; border-right:1px solid #ddd; }\n.CodeMirror-simplescroll-vertical div { border:none; border-radius:0; background: #f4f4f4; border-top:1px solid #ddd; border-bottom:1px solid #ddd; }\n.CodeMirror .scrollbar-annotation { width: 8px !important; min-height:3px !important; height:3px !important; right: 3px !important; }\n\n.CodeMirror .STRONG { font-weight:bold; }\n.CodeMirror .EMPH { font-style:italic; }\n.CodeMirror .CODE { margin-left: 10px; margin-right:50px; border-left:5px solid #eaeaea; background: #f4f4f4; font-family: \"Inconsolata\", \"Monaco\", \"Consolas\", \"Ubuntu Mono\", monospace;  }\n.CodeMirror .CODE.css { background: #f0f5ff; border-left-color: #e1ebff;}\n.CodeMirror .CODE-TEXT { line-height:19px; padding-left:30px; transition: opacity 0.3s; }\n.CodeMirror .CODE-TEXT.CODE-DISABLED { opacity: 0.6; }\n.CodeMirror span.CODE { color: #0076ce; background:none; margin:0; padding:0; border:none; }\n\n.CodeMirror .CODE_BLOCK { font-family: \"Inconsolata\", \"Monaco\", \"Consolas\", \"Ubuntu Mono\", monospace; }\n.CodeMirror .CODE-TOP { border-radius:3px 3px 0 0; padding-top:8px; }\n.CodeMirror .CODE-BOTTOM { border-radius:0 0 3px 3px; padding-bottom:8px; }\n\n.CodeMirror .CodeMirror-linewidget { overflow: visible; z-index: 3; }\n.CodeMirror .code-controls-widget { display: flex; flex-direction: row; justify-content: flex-end; margin-right: 50px; padding: 3px 6px; padding-bottom: 0; height: 20px; font-size: 1.2rem; transition: opacity 0.3s; }\n.CodeMirror .code-controls-widget .enable-btn { cursor:pointer; opacity: 0.2; }\n.CodeMirror .CodeMirror-linewidget .code-controls-widget .enable-btn:hover { opacity: 1; }\n.CodeMirror .CodeMirror-linewidget .code-controls-widget .code-language-label {font-size: 1.0rem; margin-top: -2px; margin-right: 5px; opacity: 0.5;}\n\n.CodeMirror .code-footer-widget { height: 15px; }\n\n.CodeMirror .DOC { color: #d8d8d8; }\n.CodeMirror .HEADING1 { font-size:30px; padding-top:20px; padding-bottom: 30px;}\n.CodeMirror .HEADING2 { font-size:20px; padding-top:20px; padding-bottom: 10px; font-weight: bold; }\n.CodeMirror .HEADING3 { font-size:16px; padding-top:20px; padding-bottom: 10px; font-weight: bold; }\n.CodeMirror .HEADING4 { font-size:12pt;}\n\n.CodeMirror .link { color: #0079d3; }\n.CodeMirror .link-widget { margin-right: 8px; }\n\n/* Syntax Styles */\n\n.CodeMirror .COMMENT { color: #747474; }\n\n/* Variables/Attributes */\n.CodeMirror .ALIAS { }\n.CodeMirror .TAG, .CodeMirror .TAG + .IDENTIFIER { color: #50edf6; color: #4bcbd3; color: #0076ce; }\n.CodeMirror .NAME, .CodeMirror .NAME + .IDENTIFIER { color: #50edf6; color: #4bcbd3; color: #0076ce; }\n/* Phases */\n.CodeMirror .ACTION, .SEARCH { color: black; }\n/* Statements */\n.CodeMirror .IF, .ELSE, .THEN { color: black; }\n/* Literals */\n.CodeMirror .STRING, .QUOTE, .BOOL, .NUM { color: #00a588; }\n/* Brackets */\n.CodeMirror .OPEN-BRACKET, .CodeMirror .CLOSE-BRACKET, .CodeMirror .OPEN-PAREN, .CodeMirror .CLOSE-PAREN { color: gray; }\n.CodeMirror .STRING-EMBED-OPEN, .CodeMirror .STRING-EMBED-CLOSE { color: gray; }\n.CodeMirror .DOT, .CodeMirror .COMMA { color: gray; }\n/* Binding/Action Operators */\n.CodeMirror .EQUALITY, .CodeMirror .MERGE, .CodeMirror .SET, .CodeMirror .MUTATE { color: gray; }\n/* Infix Operators */\n.CodeMirror .INFIX, .CodeMirror .COMPARISON { color: gray; }\n\n/****************************************************************************\\\n * Elision\n\\****************************************************************************/\n\n.elision { border-top:1px solid #ddd; }\n\n/****************************************************************************\\\n * Format Bars\n\\****************************************************************************/\n.editor-pane .new-block-bar { position: absolute; top: 0; left: 0; margin-left: -28px; display: flex; flex-direction: row; width: 28px; overflow: hidden; transition: width 0.15s; z-index: 5; color: #666; border-radius:5px; border:1px solid #ddd; font-size:10pt; background:white; }\n.editor-pane .new-block-bar.active { width: 267px; }\n\n\n.editor-pane .new-block-bar > .new-block-bar-toggle,\n.editor-pane .new-block-bar > .controls > div { display:flex; padding: 3px 10px; align-items:center; cursor:pointer; }\n\n.editor-pane .new-block-bar > .new-block-bar-toggle { padding: 3px 8px; }\n\n.editor-pane > .controls > div { cursor:pointer; }\n.editor-pane .new-block-bar > .new-block-bar-toggle:hover,\n.editor-pane .new-block-bar > .controls > div:hover{ background: #e4e4e4; }\n.editor-pane .new-block-bar > .new-block-bar-toggle:before { transition: transform 0.15s; }\n.editor-pane .new-block-bar.active > .new-block-bar-toggle:before { transform: rotate(45deg); }\n\n.editor-pane .format-bar { position: absolute; top: 0; left: 0; display: flex; flex-direction: row; background: #666; color: #eee; z-index: 9; border-radius:2px; overflow:hidden; }\n.editor-pane .format-bar > * { padding: 5px 10px; cursor:pointer; }\n.editor-pane .format-bar > div:hover { background: #888; color: #fff; }\n\n\n/****************************************************************************\\\n * Comments\n\\****************************************************************************/\n\n.comment-widget { padding: 2px 0; }\n.code-comment-widget { padding-left: 30px; margin-top: 2px; margin-left: 10px; margin-right: 50px; border-left: 5px solid #ff7e92; }\n\n.comment-widget.error { background: rgb(255, 216, 222); color: #b7003e; }\n\n.comment-widget .comment { font-size: 10pt; }\n\n.comment-widget .comment-inner { display: block; }\n.comment-inner { display: none; }\n\n.comment-widget .quick-actions { display: flex; flex-direction: row; flex-wrap: wrap; opacity: 0.5; transition: opacity 0.15s; }\n.comment-widget:hover .quick-actions { opacity: 1; }\n.comment-widget .quick-actions > .comment-action { flex: 1 0 auto; max-width: 50%; padding: 2px 5px; margin-top: 5px; text-align: center; background: #404040; }\n.comment-widget .quick-actions > .comment-action + .comment-action { margin-left: 1px; }\n.comment-widget .quick-actions > .comment-action:hover { background: #505050; }\n\n.comment.error { border-color: #F06060; color: #b7003e; }\n.comment.warning { border-color: #C0C060; }\n\n.CodeMirror .document_comment { }\n.CodeMirror .document_comment.error { border-bottom: 1px solid #ff7e92; }\n.CodeMirror .document_comment.warning { border-bottom: 1px solid #C0C060; }\n\n.CodeMirror .scrollbar-annotation { background: #AAA; min-height: 5px; opacity: 1; transition: opacity 0.15s; z-index: 2; }\n.CodeMirror .scrollbar-annotation:hover { opacity: 0.5; }\n.CodeMirror .scrollbar-annotation.error { background: #ff7e92; }\n.CodeMirror .scrollbar-annotation.warning { background: #C0C060; }\n.CodeMirror .scrollbar-annotation.affector, .CodeMirror .scrollbar-annotation.source { background: #66b1e9; }\n\n/* .CodeMirror .COMMENT_error { border-color: #ff7e92; } */\n\n/****************************************************************************\\\n * Views\n\\****************************************************************************/\n.program > .view { display: none !important; }\n\n.view-container { margin:10px 50px 0px 10px; padding-left: 20px; border-left:5px solid #96e4d7; background: #d1f5eb; }\n.CodeMirror-linewidget + .CodeMirror-linewidget .view-container { margin-top:0; }\n\ntable.view { border-collapse: collapse; }\ntable.view thead { border: 1px solid #ccc; }\ntable.view td { padding: 0 5px; border: 1px solid #ccc; border-top: none; border-bottom: none; }\n\n.view.kv-table { display: table; border-collapse: collapse; border: 1px solid #202020; }\n.view.kv-table .kv-row { display: table-row; }\n.view.kv-table .kv-row + .kv-row { border-top: 1px solid #606060; }\n.view.kv-table .kv-row > div { display: table-cell; padding: 0 5px; }\n.view.kv-table .kv-values { border-left: 1px solid #202020; }\n\n.view.bar-graph { position: relative; display: flex; flex-direction: row; align-items: flex-end; }\n.view.bar-graph .bar-graph-bar { display: flex; align-items: flex-end; margin:10px 0; justify-content: center; min-height: 1px; background: #6ed2c1; margin-right: 2px; transition: 0.3s width, 0.3s height; }\n\n/****************************************************************************\\\n * Inspector\n\\****************************************************************************/\n.editor-pane .controls .inspector-button.waiting { color: #ff0028; background: #ffdde2; }\n.editor-pane .controls .inspector-button.inspecting { color: #ff0028; background: #ffdde2; }\n\n.CodeMirror .code.annotated { border-left-color: #66b1e9; }\n.CodeMirror .code.annotated.annotated_performance-red { border-left-color: #c65555; }\n.CodeMirror .code.annotated.annotated_performance-orange { border-left-color: #e9c070; }\n.CodeMirror .scrollbar-annotation.performance-red { background: #c65555; min-height: 5px; opacity: 1; transition: opacity 0.15s; z-index: 2; }\n.CodeMirror .scrollbar-annotation.performance-orange { background: #e9c070; min-height: 5px; opacity: 1; transition: opacity 0.15s; z-index: 2; }\n\n.code-comment-widget.performance-green { white-space: pre; background: #e0e0e0; border-left: 5px solid #EEEEAA; }\n.code-comment-widget.performance-orange { white-space: pre; background: #ffe9c0; border-left: 5px solid #e9c070; }\n.code-comment-widget.performance-red { white-space: pre; background: #f9cccc; border-left: 5px solid #c65555; }\n\n.CodeMirror .shadow { color: #999 !important; }\n.CodeMirror .highlight { background: rgba(127, 192, 255, 0.4); }\n.CodeMirror .badge {  }\n.CodeMirror .badge-widget { display: inline-block; margin-left: 2px; font-size: 10pt; color: #E91E63; padding: 0 3px; }\n.CodeMirror .badge-widget:before { content: \"( \"; }\n.CodeMirror .badge-widget:after { content: \" )\"; }\n.CodeMirror .cause-of-failure { background: rgb(255, 200, 215); }\n\n.inspector-pane { overflow:hidden; display:flex; width:250px; background: #666; flex-direction: column; color: #eee; border-radius:2px; box-shadow: 0 2px 8px #aaa; z-index: 10; }\n.inspector-pane .buttons { display:flex; flex-direction:column; }\n.inspector-pane .buttons button { background:none; border:none; color: #eee; font-size: 10pt; width: 100%; padding: 8px 15px; text-align:left; }\n.inspector-pane .buttons button + button { border-top:1px solid #555; }\n\n.inspector-pane .kv-table { border:none; width:100%; background: #777; border-radius: 2px 2px 0 0; }\n.inspector-pane .kv-key { color: #ddd; }\n.inspector-pane .kv-table .kv-values { border-left: 1px solid #555; }\n.inspector-pane .kv-table .kv-row > div { padding: 4px 15px; font-size:11pt; }\n.inspector-pane .kv-table .kv-row { border:none; border-bottom:1px solid #555; }\n\n.inspector-pane div[is-entity=\"true\"] { color: #A6D0FF; }\n"
  },
  {
    "path": "assets/css/simplescrollbars.css",
    "content": ".CodeMirror-simplescroll-horizontal div, .CodeMirror-simplescroll-vertical div {\n  position: absolute;\n  background: #ccc;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box;\n  border: 1px solid #bbb;\n  border-radius: 2px;\n}\n\n.CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical {\n  position: absolute;\n  z-index: 6;\n  background: #eee;\n}\n\n.CodeMirror-simplescroll-horizontal {\n  bottom: 0; left: 0;\n  height: 8px;\n}\n.CodeMirror-simplescroll-horizontal div {\n  bottom: 0;\n  height: 100%;\n}\n\n.CodeMirror-simplescroll-vertical {\n  right: 0; top: 0;\n  width: 8px;\n}\n.CodeMirror-simplescroll-vertical div {\n  right: 0;\n  width: 100%;\n}\n\n\n.CodeMirror-overlayscroll .CodeMirror-scrollbar-filler, .CodeMirror-overlayscroll .CodeMirror-gutter-filler {\n  display: none;\n}\n\n.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div {\n  position: absolute;\n  background: #bcd;\n  border-radius: 3px;\n}\n\n.CodeMirror-overlayscroll-horizontal, .CodeMirror-overlayscroll-vertical {\n  position: absolute;\n  z-index: 6;\n}\n\n.CodeMirror-overlayscroll-horizontal {\n  bottom: 0; left: 0;\n  height: 6px;\n}\n.CodeMirror-overlayscroll-horizontal div {\n  bottom: 0;\n  height: 100%;\n}\n\n.CodeMirror-overlayscroll-vertical {\n  right: 0; top: 0;\n  width: 6px;\n}\n.CodeMirror-overlayscroll-vertical div {\n  right: 0;\n  width: 100%;\n}\n"
  },
  {
    "path": "assets/css/trace.css",
    "content": ".trace { position:absolute; bottom:20px; right:20px; width:75vw; background: #202020; color: #ccc;  height:70vh; font-family: inconsolata; }\n.trace .searcher { overflow:auto; padding:10px; flex:none; }\n.trace button { flex:none; text-align:left; border:none; background: #444; margin-top:5px; cursor:pointer; color: #ccc; }\n.trace .change-link { flex:none; }\n\n.trace .vis { overflow:auto; }\n.trace .vis row { flex:none; }\n.trace .vis column { flex:none; }\n.trace .node { padding:10px; border:1px solid #777; flex:none; margin:5px; }\n\n.trace .block { margin:15px 0; }\n"
  },
  {
    "path": "bin/eve.js",
    "content": "#!/usr/bin/env node\n\"use strict\";\n\nvar path = require(\"path\");\nvar fs = require(\"fs\");\nvar minimist = require(\"minimist\");\n\nvar config = require(\"../build/src/config\");\nvar Owner = config.Owner;\nvar Mode = config.Mode;\nvar server = require(\"../build/src/runtime/server\");\n\nconst argv = minimist(process.argv.slice(2), {boolean: [\"help\", \"version\", \"localControl\", \"server\", \"editor\", \"multiDoc\"]});\n\n// Since our current development pattern uses npm as its package repository, we treat the nearest ancestor directory with a package.json (inclusive) as the directory's \"root\".\nfunction findRoot(root) {\n  var pkg;\n  root = root.split(path.sep);\n  while(!pkg && root.length > 1) {\n    var cur = root.join(path.sep);\n    if(fs.existsSync(path.join(cur, \"package.json\"))) {\n      return cur;\n    }\n    root.pop();\n  }\n}\n\n\nvar port = argv[\"port\"] || process.env.PORT || 8080;\nvar runtimeOwner = argv[\"server\"] ? Owner.server : Owner.client;\nvar controlOwner = argv[\"localControl\"] ? Owner.client : Owner.server;\nvar editor = argv[\"editor\"] || false;\nvar multiDoc = argv[\"multiDoc\"] || false;\nvar filepath = argv[\"_\"][0];\nvar internal = false;\n\nvar root = findRoot(process.cwd());\nvar eveRoot = findRoot(__dirname);\n\nif(argv[\"help\"]) {\n  let pkg = require(path.join(eveRoot, \"package.json\"));\n  console.log(`\n    Eve ${pkg.version}\n\n    Usage: eve [flags] [file]\n\n    --help          Display this message.\n    --version       Display installed version and exit.\n    --server        Execute code on the server rather than the client.\n    --editor        Display the editor (default if no file is specified).\n    --port <number> Change the port the Eve server listens to (default 8080).\n    --localControl  Entirely disable server interaction. File changes will be\n                    stored in localStorage.\n\n    If the Eve binary is run in a project directory (a directory containing a\n    package.json file), it will use that directory as your workspace. Otherwise\n    Eve will use the built-in examples workspace.\n\n    If a file is provided, Eve will run it in application-only mode unless the\n    --editor flag is supplied.\n\n    Please refer questions and comments to the mailing list:\n    https://groups.google.com/forum/#!forum/eve-talk\n\n    Please report bugs via GH issues:\n    https://github.com/witheve/eve/issues\n`);\n  process.exit(0);\n}\nif(argv[\"version\"]) {\n  let pkg = require(path.join(eveRoot, \"package.json\"));\n  console.log(pkg.version);\n  process.exit(0);\n}\n\n\n// If we're executing within the eve module/repo, we're running internally and should expose our examples, src, etc.\n// This should be handled down the road by some sort of a manifest in conjunction with the `root` rather than hardcoding.\nif(root === eveRoot) internal = true;\nelse if(!root) {\n  internal = true;\n  // We shouldn't (and when globally installed, *can't*) taint the internal examples when running as an installed binary.\n  // @TODO: In the future we should have a more flexible solution that can copy out the examples into your current workspace when edited.\n  controlOwner = Owner.client;\n}\n\n\n// If we're not given an explicit filepath to run, assume the user wanted the editor (rather than a blank page).\n// Similarly, if we're running internally, send the user over to the quickstart, since they're likely testing the waters.\nif(!filepath) {\n  editor = true;\n  if(internal) filepath = eveRoot + \"/\" + \"examples/quickstart.eve\";\n} else {\n  filepath = path.resolve(filepath);\n  if(process.platform.indexOf(\"win\") === 0) {\n    filepath = filepath.replace(/\\\\/g, \"/\");\n  }\n}\n\nlet mode = Mode.workspace;\nif(filepath && !editor) mode = Mode.file\n\nvar opts = {internal: internal, runtimeOwner: runtimeOwner, controlOwner: controlOwner, editor: editor, port: port, path: filepath, internal: internal, root: root, eveRoot: eveRoot, mode: mode, multiDoc: multiDoc};\nconfig.init(opts);\n\nserver.run(opts);\n"
  },
  {
    "path": "circle.yml",
    "content": "machine:\n  node:\n    version: 7.4.0\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "eve:\n  build: .\n  ports:\n    - 18080:8080"
  },
  {
    "path": "index.html",
    "content": "<html>\n  <head>\n    <title>Eve</title>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"assets/css/codemirror.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"assets/css/simplescrollbars.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"assets/css/ionicons.min.css\">\n\n    <style id=\"app-styles\"></style>\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"assets/css/base.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"assets/css/trace.css\">\n    <link href=\"assets/favicon.png\" rel=\"icon\" type=\"image/png\" />\n  </head>\n  <body>\n    <script type=\"text/javascript\" src=\"build/watchers.js\"></script>\n    <script type=\"text/javascript\" src=\"src/system.js\"></script>\n    <script type=\"text/javascript\" src=\"src/systemJSConfig.js\"></script>\n\n    <script>\n      SystemJS.import(\"/build/src/bootstrap\");\n    </script>\n\n    <!-- PRODUCTION ANALYTICS -->\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"witheve\",\n  \"version\": \"0.3.0-preview5\",\n  \"description\": \"Programming designed for humans\",\n  \"keywords\": [\n    \"language\",\n    \"ide\",\n    \"relational\",\n    \"database\",\n    \"dataflow\"\n  ],\n  \"homepage\": \"http://witheve.com\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/witheve/Eve\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/witheve/Eve/issues\"\n  },\n  \"license\": \"Apache-2.0\",\n  \"engines\": {\n    \"node\": \">=7.4.0\"\n  },\n  \"main\": \"build/src/index.js\",\n  \"types\": \"build/src/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"postinstall\": \"tsc\",\n    \"prepublish\": \"tsc\",\n    \"test\": \"node build/test/all.js | faucet\",\n    \"start\": \"echo 'Please download the eve-starter repository to begin using Eve <https://github.com/witheve/eve-starter>.'\"\n  },\n  \"dependencies\": {\n    \"@types/commonmark\": \"^0.22.29\",\n    \"@types/dateformat\": \"^1.0.1\",\n    \"@types/node\": \"^6.0.41\",\n    \"@types/tape\": \"^4.2.28\",\n    \"@types/uuid\": \"^2.0.29\",\n    \"chevrotain\": \"0.28.2\",\n    \"commonmark\": \"^0.27.0\",\n    \"dateformat\": \"^2.0.0\",\n    \"express\": \"^4.14.0\",\n    \"falafel\": \"^2.0.0\",\n    \"javascript-natural-sort\": \"^0.7.1\",\n    \"setimmediate\": \"^1.0.5\",\n    \"typescript\": \"^2.1.4\",\n    \"uuid\": \"^3.0.1\"\n  },\n  \"devDependencies\": {\n    \"faucet\": \"0.0.1\",\n    \"tape\": \"^4.6.0\"\n  }\n}\n"
  },
  {
    "path": "src/bootstrap.ts",
    "content": "import \"setimmediate\";\nimport {Program} from \"./runtime/dsl2\";\nimport * as testUtil from \"../test/util\";\nimport \"./parser/parser\";\n\n// let assert = {};\n// function verify(assert:any, prog:Program, ins:any[], outs:any[]) {\n//   prog.test(prog.nextTransactionId, ins);\n// }\n\n// function verifyIO(assert:any, progName:string, inputString:string, expecteds:testUtil.EAVRCTuple[][]) {\n//   let inputs = testUtil.createInputs(inputString);\n//   for(let input of inputs) {\n//     prog.test(prog.nextTransactionId, input);\n//     console.groupCollapsed(\"Expected\");\n//     console.info(testUtil.pprint(expecteds));\n//     console.groupEnd();\n//   }\n// }\n\n// let prog = new Program(\"test\");\n\n// import \"./programs/flappy\";\n// import \"./programs/compiler\";\n// import \"./programs/hover\";\n// import \"./programs/canvas-demo\";\n// import \"./programs/shape-demo\";\n// import \"./programs/ui-demo\";\n// import \"./programs/editor-demo\";\n"
  },
  {
    "path": "src/index.ts",
    "content": "export {Watcher, Program, appendAsEAVs, RawEAV, RawEAVC, RawValue, RawMap, RawRecord, createId, EAVDiffs, Diffs} from \"./watchers/watcher\";\nexport {parseDoc} from \"./parser/parser\";\n\nexport var watcherPath = \"./build/src/watchers\";\nimport * as watchers from \"./watchers/index\";\nexport {watchers};\n"
  },
  {
    "path": "src/loadWorker.js",
    "content": "//-------------------------------------------------------------------\n// Web worker initialization\n//-------------------------------------------------------------------\n\n// We need to import uuid and commonmark before systemJS since the config tries\n// to read their global objects\nimportScripts(\"/build/src/uuid.js\", \"/build/src/commonmark.js\", \"/build/src/system.js\", \"/build/src/systemJSConfig.js\");\n\n// while we're loading stuff in using systemJS we need to queue any messages\n// we may get from the browser\nlet queue = [];\n\n// We set an initial onmessage here that just adds messages to the queue,\n// we'll override this with a real on message once the webworker code is loaded\nonmessage = function(event) {\n  queue.push(event);\n}\n\nSystemJS.import(\"runtime/webworker\").then(function(worker) {\n  onmessage = worker.onmessage;\n  for(let queued of queue) {\n    onmessage(queued);\n  }\n});\n\n"
  },
  {
    "path": "src/microReact.ts",
    "content": "declare var Velocity:any;\n\nexport interface Handler<T extends Event> {\n  (evt:T, elem:uElement): void\n}\nexport interface RenderHandler {\n  (node:HTMLElement, elem:uElement): void\n}\n\nexport interface uElement {\n  t?:string\n  c?:string\n  id?:string\n  parent?:string\n  children?:uElement[]\n  ix?:number\n  key?:string\n  dirty?:boolean\n  semantic?:string\n  tween?: any\n  enter?: any\n  leave?: any\n  debug?:any\n\n  // Content\n  contentEditable?:boolean\n  checked?:boolean\n  draggable?:boolean\n  spellcheck?:boolean\n  href?:string\n  src?:string\n  data?:any\n  download?:string\n  allowfullscreen?:boolean\n  placeholder?:string\n  selected?:boolean\n  tabindex?:number\n  text?:string\n  strictText?: boolean\n  type?:string\n  value?:string\n  dangerouslySetInnerHTML?:string\n  target?:string\n\n  style?: string,\n\n  // Styles (Structure)\n  flex?:number|string\n  left?:number|string\n  top?:number|string\n  width?:number|string\n  height?:number|string\n  textAlign?:string\n  transform?:string\n  verticalAlign?:string\n  zIndex?:number\n\n  // Styles (Aesthetic)\n  backgroundColor?:string\n  backgroundImage?:string\n  border?:string\n  borderColor?:string\n  borderWidth?:number|string\n  borderRadius?:number|string\n  color?:string\n  colspan?:number\n  fontFamily?:string\n  fontSize?:string\n\n  opacity?:number\n\n  // Svg\n  svg?:boolean\n  x?:number|string\n  y?:number|string\n  dx?:number|string\n  dy?:number|string\n  cx?:number|string\n  cy?:number|string\n  r?:number|string\n  d?:number|string\n  fill?:string\n  stroke?:string\n  strokeWidth?:string\n  startOffset?:number|string\n  textAnchor?:string\n  viewBox?:string\n  xlinkhref?:string\n\n  // Events\n  dblclick?:Handler<MouseEvent>\n  click?:Handler<MouseEvent>\n  contextmenu?:Handler<MouseEvent>\n  mousedown?:Handler<MouseEvent>\n  mousemove?:Handler<MouseEvent>\n  mouseup?:Handler<MouseEvent>\n  mouseover?:Handler<MouseEvent>\n  mouseout?:Handler<MouseEvent>\n  mouseleave?:Handler<MouseEvent>\n  mousewheel?:Handler<MouseEvent>\n  dragover?:Handler<MouseEvent>\n  dragstart?:Handler<MouseEvent>\n  dragend?:Handler<MouseEvent>\n  drag?:Handler<MouseEvent>\n  drop?:Handler<MouseEvent>\n  scroll?:Handler<MouseEvent>\n  focus?:Handler<FocusEvent>\n  blur?:Handler<FocusEvent>\n  input?:Handler<Event>\n  change?:Handler<Event>\n  keyup?:Handler<KeyboardEvent>\n  keydown?:Handler<KeyboardEvent>\n  cut?:Handler<KeyboardEvent>\n  copy?:Handler<KeyboardEvent>\n  paste?:Handler<KeyboardEvent>\n\n  postRender?:RenderHandler\n\n  [attr:string]: any\n}\n\nfunction now() {\n  if(window.performance) {\n    return window.performance.now();\n  }\n  return (new Date()).getTime();\n}\n\nfunction shallowEquals(a:any, b:any) {\n  if(a === b) return true;\n  if(!a || !b) return false;\n  for(var k in a) {\n    if(a[k] !== b[k]) return false;\n  }\n  for(var k in b) {\n    if(b[k] !== a[k]) return false;\n  }\n  return true;\n}\n\nfunction postAnimationRemove(elements:Element[]) {\n  for(let elem of elements) {\n    if(elem.parentNode) elem.parentNode.removeChild(elem);\n  }\n}\n\nexport class Renderer {\n  // @TODO: A more performant implementation would have a way of rendering subtrees and just have a lambda Renderer to compile into\n  static _compileRenderer:{[id:string]: Renderer} = {};\n  static compile(elem:uElement) {\n    if(!elem.id) throw new Error(\"Cannot compile element with id \" + elem.id);\n    let renderer = Renderer._compileRenderer[elem.id];\n    if(!renderer) renderer = Renderer._compileRenderer[elem.id] = new Renderer();\n    renderer.render([elem]);\n    return renderer.elementCache[elem.id];\n  }\n\n  content: HTMLElement;\n  elementCache: {[id:string]: HTMLElement|undefined};\n  prevTree:{[id:string]: uElement};\n  tree:{[id:string]: uElement};\n  postRenders: uElement[];\n  lastDiff: {adds: string[], updates: {}};\n  queued: boolean;\n  handleEvent: (any);\n  constructor() {\n    this.content = document.createElement(\"div\");\n    this.content.className = \"__root\";\n    this.elementCache = { \"__root\": this.content };\n    this.prevTree = {};\n    this.tree = {};\n    this.postRenders = [];\n    this.lastDiff = {adds: [], updates: {}};\n    var self = this;\n    this.handleEvent = function handleEvent(e: Event) {\n      var id = ((e.currentTarget || e.target) as any)[\"_id\"];\n      var elem = self.tree[id];\n      if (!elem) return;\n      var handler = elem[e.type];\n      if (handler) { handler(e, elem); }\n    };\n  }\n  reset() {\n    this.prevTree = this.tree;\n    this.tree = {};\n    this.postRenders = [];\n  }\n\n  domify() {\n    var fakePrev:uElement = {}; //create an empty object once instead of every instance of the loop\n    var elements = this.tree;\n    var prevElements = this.prevTree;\n    var diff = this.lastDiff;\n    var adds = diff.adds;\n    var updates:any = diff.updates;\n    var elemKeys = Object.keys(updates);\n    var elementCache = this.elementCache;\n    var tempTween:any = {};\n\n    //Create all the new elements to ensure that they're there when they need to be\n    //parented\n    for(var i = 0, len = adds.length; i < len; i++) {\n      var id = adds[i];\n      var cur = elements[id]!;\n      var div: any;\n      if (cur.svg) {\n        div = document.createElementNS(\"http://www.w3.org/2000/svg\", cur.t || \"rect\");\n      } else {\n        div = document.createElement(cur.t || \"div\");\n      }\n      div._id = id;\n      elementCache[id] = div;\n      if(cur.enter) {\n        if(cur.enter.delay) {\n          cur.enter.display = \"auto\";\n          div.style.display = \"none\";\n        }\n\n        Velocity(div, cur.enter, cur.enter);\n\n      }\n    }\n\n    for(var i = 0, len = elemKeys.length; i < len; i++) {\n      var id = elemKeys[i];\n      var cur = elements[id]!;\n      var prev = prevElements[id] || fakePrev;\n      var type:any = updates[id];\n      var div;\n      if(type === \"replaced\") {\n        let me = elementCache[id]!;\n        if (me.parentNode) me.parentNode.removeChild(me);\n        if (cur.svg) {\n          div = document.createElementNS(\"http://www.w3.org/2000/svg\", cur.t || \"rect\");\n        } else {\n          div = document.createElement(cur.t || \"div\");\n        }\n        prev = fakePrev;\n        div._id = id;\n        elementCache[id] = div;\n      } else if (type === \"removed\") {\n        //NOTE: Batching the removes such that you only remove the parent\n        //didn't actually make this faster surprisingly. Given that this\n        //strategy is much simpler and there's no noticable perf difference\n        //we'll just do the dumb thing and remove all the children one by one.\n        let me = elementCache[id]!\n        if(prev.leave) {\n          prev.leave.complete = postAnimationRemove;\n          if(prev.leave.absolute) {\n            me.style.position = \"absolute\";\n          }\n          Velocity(me, prev.leave, prev.leave);\n        }\n        else if(me.parentNode) me.parentNode.removeChild(me);\n        elementCache[id] = undefined;\n        continue;\n      } else {\n        div = elementCache[id];\n      }\n\n      var style = div.style;\n      if(cur.c !== prev.c) div.className = cur.c;\n      if(cur.draggable !== prev.draggable) div.draggable = cur.draggable === undefined ? null : \"true\";\n      if(cur.spellcheck !== prev.spellcheck) div.setAttribute(\"spellcheck\", cur.spellcheck);\n      if(cur.contentEditable !== prev.contentEditable) div.contentEditable = cur.contentEditable !== undefined ? JSON.stringify(cur.contentEditable) : \"inherit\";\n      if(cur.colspan !== prev.colspan) div.colSpan = cur.colspan;\n      if(cur.placeholder !== prev.placeholder) div.setAttribute(\"placeholder\", cur.placeholder);\n      if(cur.selected !== prev.selected) div.selected = cur.selected;\n      if((cur.value !== prev.value || cur.strictText) && div.value !== cur.value) div.value = cur.value;\n      if(cur.t === \"input\" && cur.type !== prev.type) div.type = cur.type;\n      if(cur.t === \"input\" && cur.checked !== prev.checked) div.checked = cur.checked;\n      if((cur.text !== prev.text || cur.strictText) && div.textContent !== cur.text) div.textContent = cur.text === undefined ? \"\" : cur.text;\n      if(cur.tabindex !== prev.tabindex) div.setAttribute(\"tabindex\", cur.tabindex);\n      if(cur.href !== prev.href) div.setAttribute(\"href\", cur.href);\n      if(cur.src !== prev.src) div.setAttribute(\"src\", cur.src);\n      if(cur.target !== prev.target) div.setAttribute(\"target\", cur.target);\n      if(cur.data !== prev.data) div.setAttribute(\"data\", cur.data);\n      if(cur.download !== prev.download) div.setAttribute(\"download\", cur.download);\n      if(cur.allowfullscreen !== prev.allowfullscreen) div.setAttribute(\"allowfullscreen\", cur.allowfullscreen);\n\n      // animateable properties\n      var tween = cur.tween || tempTween;\n      if(cur.flex !== prev.flex) {\n        if(tween.flex) tempTween.flex = cur.flex;\n        else style.flex = cur.flex === undefined ? \"\" : cur.flex;\n      }\n      if(cur.left !== prev.left) {\n          if(tween.left) tempTween.left = cur.left;\n          else style.left = cur.left === undefined ? \"\" : cur.left;\n      }\n      if(cur.top !== prev.top) {\n        if(tween.top) tempTween.top = cur.top;\n        else style.top = cur.top === undefined ? \"\" : cur.top;\n      }\n      if(cur.height !== prev.height) {\n        if(tween.height) tempTween.height = cur.height;\n        else style.height = cur.height === undefined ? \"auto\" : cur.height;\n      }\n      if(cur.width !== prev.width) {\n        if(tween.width) tempTween.width = cur.width;\n        else style.width = cur.width === undefined ? \"auto\" : cur.width;\n      }\n      if(cur.zIndex !== prev.zIndex) {\n        if(tween.zIndex) tempTween.zIndex = cur.zIndex;\n        else style.zIndex = cur.zIndex;\n      }\n      if(cur.backgroundColor !== prev.backgroundColor) {\n        if(tween.backgroundColor) tempTween.backgroundColor = cur.backgroundColor;\n        else style.backgroundColor = cur.backgroundColor || \"transparent\";\n      }\n      if(cur.borderColor !== prev.borderColor) {\n        if(tween.borderColor) tempTween.borderColor = cur.borderColor;\n        else style.borderColor = cur.borderColor || \"none\";\n      }\n      if(cur.borderWidth !== prev.borderWidth) {\n        if(tween.borderWidth) tempTween.borderWidth = cur.borderWidth;\n        else style.borderWidth = cur.borderWidth || 0;\n      }\n      if(cur.borderRadius !== prev.borderRadius) {\n        if(tween.borderRadius) tempTween.borderRadius = cur.borderRadius;\n        else style.borderRadius = (cur.borderRadius || 0) + \"px\";\n      }\n      if(cur.opacity !== prev.opacity) {\n        if(tween.opacity) tempTween.opacity = cur.opacity;\n        else style.opacity = cur.opacity === undefined ? 1 : cur.opacity;\n      }\n      if(cur.fontSize !== prev.fontSize) {\n        if(tween.fontSize) tempTween.fontSize = cur.fontSize;\n        else style.fontSize = cur.fontSize;\n      }\n      if(cur.color !== prev.color) {\n        if(tween.color) tempTween.color = cur.color;\n        else style.color = cur.color || \"inherit\";\n      }\n\n      let animKeys = Object.keys(tempTween);\n      if(animKeys.length) {\n        Velocity(div, tempTween, tween);\n        tempTween = {};\n      }\n\n      // non-animation style properties\n      if(cur.backgroundImage !== prev.backgroundImage) style.backgroundImage = `url('${cur.backgroundImage}')`;\n      if(cur.border !== prev.border) style.border = cur.border || \"none\";\n      if(cur.textAlign !== prev.textAlign) {\n        style.alignItems = cur.textAlign;\n        if(cur.textAlign === \"center\") {\n          style.textAlign = \"center\";\n        } else if(cur.textAlign === \"flex-end\") {\n          style.textAlign = \"right\";\n        } else {\n          style.textAlign = \"left\";\n        }\n      }\n      if(cur.verticalAlign !== prev.verticalAlign) style.justifyContent = cur.verticalAlign;\n      if(cur.fontFamily !== prev.fontFamily) style.fontFamily = cur.fontFamily || \"inherit\";\n      if(cur.transform !== prev.transform) style.transform = cur.transform || \"none\";\n      if(cur.style !== prev.style) div.setAttribute(\"style\", cur.style);\n\n      if(cur.dangerouslySetInnerHTML !== prev.dangerouslySetInnerHTML) div.innerHTML = cur.dangerouslySetInnerHTML;\n\n      // debug/programmatic properties\n      if(cur.semantic !== prev.semantic) div.setAttribute(\"data-semantic\", cur.semantic);\n      if(cur.debug !== prev.debug) div.setAttribute(\"data-debug\", cur.debug);\n\n      // SVG properties\n      if(cur.svg) {\n        if(cur.fill !== prev.fill) div.setAttributeNS(null, \"fill\", cur.fill);\n        if(cur.stroke !== prev.stroke) div.setAttributeNS(null, \"stroke\", cur.stroke);\n        if(cur.strokeWidth !== prev.strokeWidth) div.setAttributeNS(null, \"stroke-width\", cur.strokeWidth);\n        if(cur.d !== prev.d) div.setAttributeNS(null, \"d\", cur.d);\n        if(cur.c !== prev.c) div.setAttributeNS(null, \"class\", cur.c);\n        if(cur.x !== prev.x)  div.setAttributeNS(null, \"x\", cur.x);\n        if(cur.y !== prev.y) div.setAttributeNS(null, \"y\", cur.y);\n        if(cur.dx !== prev.dx)  div.setAttributeNS(null, \"dx\", cur.dx);\n        if(cur.dy !== prev.dy) div.setAttributeNS(null, \"dy\", cur.dy);\n        if(cur.cx !== prev.cx)  div.setAttributeNS(null, \"cx\", cur.cx);\n        if(cur.cy !== prev.cy) div.setAttributeNS(null, \"cy\", cur.cy);\n        if(cur.r !== prev.r) div.setAttributeNS(null, \"r\", cur.r);\n        if(cur.height !== prev.height) div.setAttributeNS(null, \"height\", cur.height);\n        if(cur.width !== prev.width)  div.setAttributeNS(null, \"width\", cur.width);\n        if(cur.xlinkhref !== prev.xlinkhref)  div.setAttributeNS('http://www.w3.org/1999/xlink', \"href\", cur.xlinkhref);\n        if(cur.startOffset !== prev.startOffset) div.setAttributeNS(null, \"startOffset\", cur.startOffset);\n        if(cur.id !== prev.id) div.setAttributeNS(null, \"id\", cur.id);\n        if(cur.viewBox !== prev.viewBox) div.setAttributeNS(null, \"viewBox\", cur.viewBox);\n        if(cur.transform !== prev.transform) div.setAttributeNS(null, \"transform\", cur.transform);\n        if(cur.draggable !== prev.draggable) div.setAttributeNS(null, \"draggable\", cur.draggable);\n        if(cur.textAnchor !== prev.textAnchor) div.setAttributeNS(null, \"text-anchor\", cur.textAnchor);\n      }\n\n      //events\n      if(cur.dblclick !== prev.dblclick) div.ondblclick = cur.dblclick !== undefined ? this.handleEvent : undefined;\n      if(cur.click !== prev.click) div.onclick = cur.click !== undefined ? this.handleEvent : undefined;\n      if(cur.contextmenu !== prev.contextmenu) div.oncontextmenu = cur.contextmenu !== undefined ? this.handleEvent : undefined;\n      if(cur.mousedown !== prev.mousedown) div.onmousedown = cur.mousedown !== undefined ? this.handleEvent : undefined;\n      if(cur.mousemove !== prev.mousemove) div.onmousemove = cur.mousemove !== undefined ? this.handleEvent : undefined;\n      if(cur.mouseup !== prev.mouseup) div.onmouseup = cur.mouseup !== undefined ? this.handleEvent : undefined;\n      if(cur.mouseover !== prev.mouseover) div.onmouseover = cur.mouseover !== undefined ? this.handleEvent : undefined;\n      if(cur.mouseout !== prev.mouseout) div.onmouseout = cur.mouseout !== undefined ? this.handleEvent : undefined;\n      if(cur.mouseleave !== prev.mouseleave) div.onmouseleave = cur.mouseleave !== undefined ? this.handleEvent : undefined;\n      if(cur.mousewheel !== prev.mousewheel) div.onmouseheel = cur.mousewheel !== undefined ? this.handleEvent : undefined;\n      if(cur.dragover !== prev.dragover) div.ondragover = cur.dragover !== undefined ? this.handleEvent : undefined;\n      if(cur.dragstart !== prev.dragstart) div.ondragstart = cur.dragstart !== undefined ? this.handleEvent : undefined;\n      if(cur.dragend !== prev.dragend) div.ondragend = cur.dragend !== undefined ? this.handleEvent : undefined;\n      if(cur.drag !== prev.drag) div.ondrag = cur.drag !== undefined ? this.handleEvent : undefined;\n      if(cur.drop !== prev.drop) div.ondrop = cur.drop !== undefined ? this.handleEvent : undefined;\n      if(cur.scroll !== prev.scroll) div.onscroll = cur.scroll !== undefined ? this.handleEvent : undefined;\n      if(cur.focus !== prev.focus) div.onfocus = cur.focus !== undefined ? this.handleEvent : undefined;\n      if(cur.blur !== prev.blur) div.onblur = cur.blur !== undefined ? this.handleEvent : undefined;\n      if(cur.input !== prev.input) div.oninput = cur.input !== undefined ? this.handleEvent : undefined;\n      if(cur.change !== prev.change) div.onchange = cur.change !== undefined ? this.handleEvent : undefined;\n      if(cur.keyup !== prev.keyup) div.onkeyup = cur.keyup !== undefined ? this.handleEvent : undefined;\n      if(cur.keydown !== prev.keydown) div.onkeydown = cur.keydown !== undefined ? this.handleEvent : undefined;\n      if(cur.cut !== prev.cut) div.oncut = cur.cut !== undefined ? this.handleEvent : undefined;\n      if(cur.copy !== prev.copy) div.oncopy = cur.copy !== undefined ? this.handleEvent : undefined;\n      if(cur.paste !== prev.paste) div.onpaste = cur.paste !== undefined ? this.handleEvent : undefined;\n\n      if(type === \"added\" || type === \"replaced\" || type === \"moved\") {\n        var parentEl:any = elementCache[cur.parent!];\n        if(parentEl) {\n          if(cur.ix! >= parentEl.children.length) {\n            parentEl.appendChild(div);\n          } else {\n            parentEl.insertBefore(div, parentEl.children[cur.ix!]);\n          }\n        }\n      }\n    }\n  }\n\n  diff() {\n    var a = this.prevTree;\n    var b = this.tree;\n    var as = Object.keys(a);\n    var bs = Object.keys(b);\n    var updated:any = {};\n    var adds = [];\n    for(var i = 0, len = as.length; i < len; i++) {\n      var id = as[i];\n      var curA = a[id];\n      var curB = b[id];\n      if(curB === undefined) {\n        updated[id] = \"removed\";\n        continue;\n      }\n      if(curA.t !== curB.t) {\n        updated[id] = \"replaced\";\n        continue;\n      }\n      if(curA.ix !== curB.ix || curA.parent !== curB.parent) {\n        updated[id] = \"moved\";\n        continue;\n      }\n\n      if(!curB.dirty\n          && curA.c === curB.c\n          && curA.key === curB.key\n          && curA.dangerouslySetInnerHTML === curB.dangerouslySetInnerHTML\n          && curA.tabindex === curB.tabindex\n          && curA.href === curB.href\n          && curA.src === curB.src\n          && curA.data === curB.data\n          && curA.download === curB.download\n          && curA.allowfullscreen === curB.allowfullscreen\n          && curA.placeholder === curB.placeholder\n          && curA.selected === curB.selected\n          && curA.draggable === curB.draggable\n          && curA.spellcheck === curB.spellcheck\n          && curA.contentEditable === curB.contentEditable\n          && curA.value === curB.value\n          && curA.target === curB.target\n          && curA.type === curB.type\n          && curA.checked === curB.checked\n          && curA.text === curB.text\n          && curA.top === curB.top\n          && curA.flex === curB.flex\n          && curA.left === curB.left\n          && curA.width === curB.width\n          && curA.height === curB.height\n          && curA.zIndex === curB.zIndex\n          && curA.backgroundColor === curB.backgroundColor\n          && curA.backgroundImage === curB.backgroundImage\n          && curA.color === curB.color\n          && curA.colspan === curB.colspan\n          && curA.border === curB.border\n          && curA.borderColor === curB.borderColor\n          && curA.borderWidth === curB.borderWidth\n          && curA.borderRadius === curB.borderRadius\n          && curA.opacity === curB.opacity\n          && curA.fontFamily === curB.fontFamily\n          && curA.fontSize === curB.fontSize\n          && curA.textAlign === curB.textAlign\n          && curA.transform === curB.transform\n          && curA.verticalAlign === curB.verticalAlign\n          && curA.semantic === curB.semantic\n          && curA.debug === curB.debug\n          && curA.style === curB.style\n          && (curB.svg === undefined || (\n              curA.x === curB.x\n              && curA.y === curB.y\n              && curA.dx === curB.dx\n              && curA.dy === curB.dy\n              && curA.cx === curB.cx\n              && curA.cy === curB.cy\n              && curA.r === curB.r\n              && curA.d === curB.d\n              && curA.fill === curB.fill\n              && curA.stroke === curB.stroke\n              && curA.strokeWidth === curB.strokeWidth\n              && curA.startOffset === curB.startOffset\n              && curA.textAnchor === curB.textAnchor\n              && curA.viewBox === curB.viewBox\n              && curA.xlinkhref === curB.xlinkhref))\n              ) {\n        continue;\n      }\n      updated[id] = \"updated\";\n    }\n    for(var i = 0, len = bs.length; i < len; i++) {\n      var id = bs[i];\n      var curA = a[id];\n      if(curA === undefined) {\n        adds.push(id);\n        updated[id] = \"added\";\n        continue;\n      }\n    }\n    this.lastDiff = {adds: adds, updates: updated};\n    return this.lastDiff;\n  }\n\n  prepare(root:uElement) {\n    var elemLen = 1;\n    var tree = this.tree;\n    var elements = [root];\n    var elem:uElement;\n    for(var elemIx = 0; elemIx < elemLen; elemIx++) {\n      elem = elements[elemIx];\n      if(elem.parent === undefined) elem.parent = \"__root\";\n      if(elem.id === undefined) elem.id = \"__root__\" + elemIx;\n      tree[elem.id] = elem;\n      if(elem.postRender !== undefined) {\n        this.postRenders.push(elem);\n      }\n      var children = elem.children;\n      if(children !== undefined) {\n        for(var childIx = 0, len = children.length; childIx < len; childIx++) {\n          var child = children[childIx];\n          if(child === undefined) continue;\n          if(child.id === undefined) { child.id = elem.id + \"__\" + childIx; }\n          if(child.ix === undefined) { child.ix = childIx; }\n          if(child.parent === undefined) { child.parent = elem.id; }\n          elements.push(child);\n          elemLen++;\n        }\n      }\n    }\n    return tree;\n  }\n\n  postDomify() {\n    var postRenders:any = this.postRenders;\n    var diff:any = this.lastDiff.updates;\n    var elementCache = this.elementCache;\n    for(var i = 0, len = postRenders.length; i < len; i++) {\n      var elem = postRenders[i];\n      var id = elem.id!;\n      if(diff[id] === \"updated\" || diff[id] === \"added\" || diff[id] === \"replaced\" || elem.dirty || diff[id] === \"moved\") {\n        elem.postRender(elementCache[id]!, elem);\n      }\n    }\n  }\n\n  render(elems:uElement[]) {\n      this.reset();\n    // We sort elements by depth to allow them to be self referential.\n    elems.sort((a, b) => (a.parent ? a.parent.split(\"__\").length : 0) - (b.parent ? b.parent.split(\"__\").length : 0));\n    let start = now();\n    for(let elem of elems) {\n      let post = this.prepare(elem);\n\n    }\n    let prepare = now();\n    let d = this.diff();\n    let diff = now();\n    this.domify();\n    let domify = now();\n    this.postDomify();\n    let postDomify = now();\n    let time = now() - start;\n    if(time > 5) {\n      console.log(\"slow render (> 5ms): \", time, {\n        prepare: prepare - start,\n        diff: diff - prepare,\n        domify: domify - diff,\n        postDomify: postDomify - domify\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "src/parser/errors.ts",
    "content": "//--------------------------------------------------------------\n// Errors\n//--------------------------------------------------------------\n\nimport {exceptions, Token, EOF} from \"chevrotain\";\nimport * as parser from \"./parser\";\n\nconst SPAN_TYPE = \"document_comment\";\n\n//--------------------------------------------------------------\n// EveError\n//--------------------------------------------------------------\n\nexport class EveError {\n  static ID = 0;\n\n  type = \"error\";\n  id: string;\n  blockId: string;\n  message: string;\n  start: number;\n  stop: number;\n  context?: any;\n  spanId: string;\n\n  constructor(blockId:string, start:number, stop:number, message:string, context?:any) {\n    this.blockId = blockId;\n    this.id = `${blockId}|error|${EveError.ID++}`;\n    this.start = start;\n    this.stop = stop;\n    this.message = message;\n    this.context = context;\n  }\n\n  injectSpan(spans:any, extraInfo:any) {\n    spans.push(this.start, this.stop, SPAN_TYPE, this.id);\n    extraInfo[this.id] = this;\n  }\n}\n\n//--------------------------------------------------------------\n// Parse error utils\n//--------------------------------------------------------------\n\nfunction regexGroup(str:string, regex:RegExp, group = 1) {\n  var matches = [];\n  var match;\n  while (match = regex.exec(str)) {\n    matches.push(match[group]);\n  }\n  return matches;\n}\n\nfunction className(thing:any) {\n   var funcNameRegex = /function (.{1,})\\(/;\n   var results = (funcNameRegex).exec((thing).constructor.toString());\n   return (results && results.length > 1) ? results[1] : \"\";\n};\n\nfunction lastTokenWithType(tokens:any, type:any) {\n  let ix = tokens.length - 1;\n  while(ix >= 0) {\n    let cur = tokens[ix];\n    if(cur instanceof type) {\n      return cur;\n    }\n    ix--;\n  }\n}\n\n\n//--------------------------------------------------------------\n// Parse errors\n//--------------------------------------------------------------\n\nexport function parserErrors(errors: any[], parseInfo: {blockId: string, blockStart: number, spans: any[], extraInfo: any, tokens: Token[]}) {\n  let {blockId, blockStart, spans, extraInfo} = parseInfo;\n  let normalized = [];\n  let errorIx = 1;\n\n  for(let error of errors) {\n    let {token, context, message, resyncedTokens, name} = error;\n\n    let eveError: EveError;\n    if(name === \"MismatchedTokenException\") {\n      eveError = mismatchedToken(error, parseInfo);\n    } else if(name === \"NotAllInputParsedException\") {\n      eveError = notAllInputParsed(error, parseInfo);\n    } else {\n      // console.log(\"UNHANDLED ERROR TYPE\", name);\n      let start = token.startOffset;\n      let stop = token.startOffset + token.image.length;\n      eveError = new EveError(blockId, start, stop, message, context);\n    }\n\n    eveError.injectSpan(spans, extraInfo);\n    normalized.push(eveError);\n  }\n  return normalized;\n}\n\n//--------------------------------------------------------------\n// MismatchedToken parse error\n//--------------------------------------------------------------\n\nconst MismatchRegex = /-->\\s*(.*?)\\s*<--/gi;\n\nfunction mismatchedToken(error:any, parseInfo:any) {\n  const Pairs:any = {\n    \"CloseString\": parser.OpenString,\n    \"CloseBracket\": parser.OpenBracket,\n    \"CloseParen\": parser.OpenParen,\n  };\n\n  let {blockId, blockStart, spans, extraInfo, tokens} = parseInfo;\n  let {token, context, message, resyncedTokens, name} = error;\n\n  let blockEnd = tokens[tokens.length - 1].endOffset + 1;\n\n  let [expectedType, foundType] = regexGroup(message, MismatchRegex);\n\n  let start, stop;\n\n  if(token instanceof EOF) {\n    let pair = Pairs[expectedType] as any;\n    if(pair) {\n      token = lastTokenWithType(tokens, pair);\n      message = messages.unclosedPair(expectedType);\n    } else {\n      token = tokens[tokens.length - 1];\n    }\n    stop = blockEnd;\n  }\n\n  // We didn't find a matching pair, check if we're some other mistmatched bit of syntax.\n  if(stop === undefined) {\n    if(expectedType === \"Tag\") {\n      if(token.label === \"identifier\") {\n        message = messages.actionRawIdentifier(token.image);\n      } else {\n        message = messages.actionNonTag(token.image);\n      }\n    }\n  }\n\n  if(start === undefined) start = token.startOffset;\n  if(stop === undefined) stop = token.startOffset + token.image.length;\n\n  return new EveError(blockId, start, stop, message, context);\n}\n\n//--------------------------------------------------------------\n// NotAllInputParsed parse error\n//--------------------------------------------------------------\n\nconst NotAllInputRegex = /found:\\s*([^\\s]+)/gi;\nconst CloseChars:any = {\")\": true, \"]\": true};\n\nfunction notAllInputParsed(error:any, parseInfo:any) {\n  let {blockId, blockStart, spans, extraInfo, tokens} = parseInfo;\n  let {token, context, message, resyncedTokens, name} = error;\n\n  let blockEnd = tokens[tokens.length - 1].endOffset + 1;\n\n  let [foundChar] = regexGroup(message, NotAllInputRegex);\n\n  let start, stop;\n\n  if(CloseChars[foundChar]) {\n    message = messages.extraCloseChar(foundChar);\n  } else {\n    console.log(\"WEIRD STUFF AT THE END\", context);\n  }\n\n  if(start === undefined) start = token.startOffset;\n  if(stop === undefined) stop = token.startOffset + token.image.length;\n\n  return new EveError(blockId, start, stop, message, context);\n}\n\n//--------------------------------------------------------------\n// Build errors\n//--------------------------------------------------------------\n\nexport function unprovidedVariableGroup(block:any, variables:any) {\n  let {id, start: blockStart} = block;\n  let found;\n  for(let variable of variables) {\n    if(!variable.generated) {\n      found = variable;\n      break;\n    }\n  }\n  if(!found) {\n    found = variables[0];\n  }\n  let [start, stop] = parser.nodeToBoundaries(found, blockStart);\n  return new EveError(id, start, stop, messages.unprovidedVariable(found.name));\n}\n\nexport function blankScan(block:any, scan:any) {\n  let {id, start: blockStart} = block;\n  let [start, stop] = parser.nodeToBoundaries(scan, blockStart);\n  return new EveError(id, start, stop, messages.blankScan());\n}\n\nexport function invalidLookupAction(block:any, action:any) {\n  let {id, start: blockStart} = block;\n  let [start, stop] = parser.nodeToBoundaries(action, blockStart);\n  let missing = [];\n  if(action.entity === undefined) missing.push(\"record\");\n  if(action.attribute === undefined) missing.push(\"attribute\");\n  if(action.value === undefined) missing.push(\"value\");\n  return new EveError(id, start, stop, messages.invalidLookupAction(missing));\n}\n\nexport function unimplementedExpression(block:any, expression:any) {\n  let {id, start: blockStart} = block;\n  let [start, stop] = parser.nodeToBoundaries(expression, blockStart);\n  return new EveError(id, start, stop, messages.unimplementedExpression(expression.op));\n}\n\nexport function incompatabileConstantEquality(block:any, left:any, right:any) {\n  let {id, start: blockStart} = block;\n  let [start] = parser.nodeToBoundaries(left, blockStart);\n  let [_, stop] = parser.nodeToBoundaries(right, blockStart);\n  return new EveError(id, start, stop, messages.neverEqual(left.value, right.value));\n}\n\nexport function incompatabileVariableToConstantEquality(block:any, variable:any, variableValue:any, constant:any) {\n  let {id, start: blockStart} = block;\n  let [start] = parser.nodeToBoundaries(variable, blockStart);\n  let [_, stop] = parser.nodeToBoundaries(constant, blockStart);\n  return new EveError(id, start, stop, messages.variableNeverEqual(variable, variableValue, constant.value));\n}\n\nexport function incompatabileTransitiveEquality(block:any, variable:any, value:any) {\n  let {id, start: blockStart} = block;\n  let [start, stop] = parser.nodeToBoundaries(variable, blockStart);\n  return new EveError(id, start, stop, messages.variableNeverEqual(variable, variable.constant, value));\n}\n\nexport function unrecognisedFunctionAttribute(block:any, expression:any, attribute:any) {\n  let {id, start: blockStart} = block;\n  return new EveError(id, attribute.startOffset , attribute.endOffset, messages.unrecognisedFunctionAttribute(attribute.attribute, expression.op));\n}\n\n//--------------------------------------------------------------\n// Messages\n//--------------------------------------------------------------\n\nconst PairToName:any = {\n  \"CloseString\": \"quote\",\n  \"CloseBracket\": \"bracket\",\n  \"CloseParen\": \"paren\",\n  \"]\": \"bracket\",\n  \")\": \"paren\",\n  \"\\\"\": \"quote\",\n}\n\nexport var messages = {\n\n  unclosedPair: (type:string) => `Looks like a close ${PairToName[type]} is missing`,\n\n  extraCloseChar: (char:string) => `This close ${PairToName[char]} is missing an open ${PairToName[char]}`,\n\n  unprovidedVariable: (varName:string) => `Nothing is providing a value for ${varName}`,\n  unrecognisedFunctionAttribute: (attributeName:string, functionName:string) => `${attributeName} is not a recognised attribute for ${functionName}.`,\n\n  unimplementedExpression: (op:string) => `There's no definition for the function ${op}`,\n\n  blankScan: () => 'Lookup requires at least one attribute: record, attribute, value, or node',\n  invalidLookupAction: (missing:string[]) => `Updating a lookup requires that record, attribute, and value all be provided. Looks like ${missing.join(\"and\")} ${missing.length > 1 ? \"are\" : \"is\"} missing.`,\n\n  neverEqual: (left:string, right:string) => `${left} can never equal ${right}`,\n  variableNeverEqual: (variable:any, value:string, right:string) => `${variable.name} is equivalent to ${value}, which can't be equal to ${right}`,\n\n  actionNonTag: (found:string) => `Looks like this should be a tag, try changing the ${found} to #${found}`,\n  actionRawIdentifier: (found:string) => `I can only add/remove tags directly on a record. If you meant to add ${found} as an attribute to the record, try 'my-record.${found} += ${found}'; if you meant to add the #${found} tag, add #.`\n};\n"
  },
  {
    "path": "src/parser/parser.ts",
    "content": "//-----------------------------------------------------------\n// Parser\n//-----------------------------------------------------------\n\nimport * as commonmark from \"commonmark\";\nimport * as chev from \"chevrotain\";\nimport {parserErrors, EveError} from \"./errors\";\nvar {Lexer, tokenMatcher} = chev;\nexport var Token = chev.Token;\nimport * as uuid from \"uuid\";\n\n//-----------------------------------------------------------\n// Utils\n//-----------------------------------------------------------\n\nfunction cleanString(str:string) {\n  let cleaned = str\n    .replace(/\\\\n/g, \"\\n\")\n    .replace(/\\\\t/g, \"\\t\")\n    .replace(/\\\\r/g, \"\\r\")\n    .replace(/\\\\\"/g, \"\\\"\")\n    .replace(/\\\\{/g, \"{\")\n    .replace(/\\\\}/g, \"}\");\n  return cleaned;\n}\n\nfunction toEnd(node:any) {\n  if(node && node.tokenType !== undefined) {\n    return node.endOffset! + 1;\n  }\n  return node.endOffset;\n}\n\n//-----------------------------------------------------------\n// Markdown\n//-----------------------------------------------------------\n\nlet markdownParser = new commonmark.Parser();\n\nfunction parseMarkdown(markdown: string, docId: string) {\n  let parsed = markdownParser.parse(markdown);\n  let walker = parsed.walker();\n  var cur;\n  let tokenId = 0;\n  var text = [];\n  var extraInfo:any = {};\n  var pos = 0;\n  var lastLine = 1;\n  var spans = [];\n  var context = [];\n  var blocks = [];\n  while(cur = walker.next()) {\n    let node = cur.node as any;\n    if(cur.entering) {\n      while(node.sourcepos && node.sourcepos[0][0] > lastLine) {\n        lastLine++;\n        pos++;\n        text.push(\"\\n\");\n      }\n      if(node.type !== \"text\") {\n        context.push({node, start: pos});\n      }\n      if(node.type == \"text\" || node.type === \"code_block\" || node.type == \"code\") {\n        text.push(node.literal);\n        pos += node.literal.length;\n      }\n      if(node.type == \"softbreak\") {\n        text.push(\"\\n\");\n        pos += 1;\n        lastLine++;\n        context.pop();\n      }\n      if(node.type == \"code_block\") {\n        let spanId = `${docId}|block|${tokenId++}`;\n        let start = context.pop()!.start;\n        node.id = spanId;\n        node.startOffset = start;\n        let type = node.type;\n        if(!(node as any)._isFenced) {\n          type = \"indented_code_block\";\n        } else {\n          blocks.push(node);\n        }\n        spans.push(start, pos, node.type, spanId);\n        lastLine = node.sourcepos[1][0] + 1;\n      }\n      if(node.type == \"code\") {\n        let spanId = `${docId}|${tokenId++}`;\n        let start = context.pop()!.start;\n        spans.push(start, pos, node.type, spanId);\n      }\n    } else {\n      let info = context.pop()!;\n      if(node !== info.node) {\n        throw new Error(\"Common mark is exiting a node that doesn't agree with the context stack\");\n      }\n      if(node.type == \"emph\" || node.type == \"strong\" || node.type == \"link\") {\n        let spanId = `${docId}|${tokenId++}`;\n        spans.push(info.start, pos, node.type, spanId);\n        if(node.type === \"link\") {\n          extraInfo[spanId] = {destination: node._destination};\n        }\n      } else if(node.type == \"heading\" || node.type == \"item\") {\n        let spanId = `${docId}|${tokenId++}`;\n        spans.push(info.start, info.start, node.type, spanId);\n        extraInfo[spanId] = {level: node._level, listData: node._listData};\n      }\n    }\n  }\n  return {text: text.join(\"\"), spans, blocks, extraInfo};\n}\n\n//-----------------------------------------------------------\n// Tokens\n//-----------------------------------------------------------\n\nconst breakChars = \"@#\\\\.,\\\\(\\\\)\\\\[\\\\]\\\\{\\\\}⦑⦒:\\\\\\\"\";\n\n// Markdown\nexport class DocContent extends Token { static PATTERN = /[^\\n]+/; }\nexport class Fence extends Token {\n  static PATTERN = /```|~~~/;\n  static PUSH_MODE = \"code\";\n}\nexport class CloseFence extends Token {\n  static PATTERN = /```|~~~/;\n  static POP_MODE = true;\n}\n\n// Comments\nexport class CommentLine extends Token { static PATTERN = /\\/\\/.*\\n/; label = \"comment\"; static GROUP = \"comments\"; }\n\n// Operators\nexport class Equality extends Token { static PATTERN = /:|=/; label = \"equality\"; }\nexport class Comparison extends Token { static PATTERN = />=|<=|!=|>|</; label = \"comparison\"; }\nexport class AddInfix extends Token { static PATTERN = /\\+|-/; label = \"infix\"; }\nexport class MultInfix extends Token { static PATTERN = /\\*|\\//; label = \"infix\"; }\nexport class Merge extends Token { static PATTERN = /<-/; label = \"merge\"; }\nexport class Set extends Token { static PATTERN = /:=/; label = \"set\"; }\nexport class Mutate extends Token { static PATTERN = /\\+=|-=/; label = \"mutate\"; }\nexport class Dot extends Token { static PATTERN = /\\./; label = \"dot\"; }\nexport class Pipe extends Token { static PATTERN = /\\|/; label = \"pipe\"; }\n\n// Identifier\nexport class Identifier extends Token { static PATTERN = new RegExp(`([\\\\+-/\\\\*][^\\\\s${breakChars}]+|[^\\\\d${breakChars}\\\\+-/\\\\*][^\\\\s${breakChars}]*)(?=[^\\\\[])`); label = \"identifier\"; }\nexport class FunctionIdentifier extends Token { static PATTERN = new RegExp(`([\\\\+-/\\\\*][^\\\\s${breakChars}]+|[^\\\\d${breakChars}\\\\+-/\\\\*][^\\\\s${breakChars}]*)(?=\\\\[)`); label = \"functionIdentifier\"; }\n\n// Keywords\nexport class Keyword extends Token {\n    static PATTERN = Lexer.NA;\n    static LONGER_ALT = Identifier;\n}\nexport class Lookup extends Keyword { static PATTERN = /lookup(?=\\[)/; label = \"lookup\"; }\nexport class Action extends Keyword { static PATTERN = /bind|commit/; label = \"action\"; }\nexport class Search extends Keyword { static PATTERN = /search/; label = \"search\"; }\nexport class If extends Keyword { static PATTERN = /if/; label = \"if\"; }\nexport class Else extends Keyword { static PATTERN = /else/; label = \"else\"; }\nexport class Then extends Keyword { static PATTERN = /then/; label = \"then\"; }\nexport class Not extends Keyword { static PATTERN = /not/; label = \"not\"; }\n\n// Values\nexport class Bool extends Keyword { static PATTERN = /true|false/; label = \"bool\"; }\nexport class Num extends Token { static PATTERN = /-?\\d+(\\.\\d+)?/; label = \"num\"; }\nexport class None extends Keyword { static PATTERN = /none/; label = \"none\"; }\nexport class Name extends Token { static PATTERN = /@/; label = \"name\"; }\nexport class Tag extends Token { static PATTERN = /#/; label = \"tag\"; }\n\n// Delimiters\nexport class OpenBracket extends Token { static PATTERN = /\\[/; label = \"open-bracket\"; }\nexport class CloseBracket extends Token { static PATTERN = /\\]/; label = \"close-bracket\"; }\nexport class OpenParen extends Token { static PATTERN = /\\(/; label = \"open-paren\"; }\nexport class CloseParen extends Token { static PATTERN = /\\)/; label = \"close-paren\"; }\n\n// Strings\nexport class StringChars extends Token { static PATTERN = /(\\\\.|{(?=[^{])|[^\"\\\\{])+/; label = \"string\"; }\nexport class OpenString extends Token {\n  static PATTERN = /\"/;\n  static PUSH_MODE = \"string\";\n  label = \"quote\";\n}\nexport class CloseString extends Token {\n  static PATTERN = /\"/;\n  static POP_MODE = true;\n  label = \"quote\";\n}\n\n// String Embeds\nexport class StringEmbedOpen extends Token {\n  static PATTERN = /{{/;\n  static PUSH_MODE = \"code\";\n  label = \"string-embed-open\";\n}\nexport class StringEmbedClose extends Token {\n  static PATTERN = /}}/;\n  static POP_MODE = true;\n  label = \"string-embed-close\";\n}\n\n// Whitespace\nexport class WhiteSpace extends Token {\n  static PATTERN = /\\s+|,/;\n  static GROUP = Lexer.SKIPPED;\n}\n\n//-----------------------------------------------------------\n// Lexers\n//-----------------------------------------------------------\n\nlet codeTokens: any[] = [\n  CloseFence, WhiteSpace, CommentLine, OpenBracket, CloseBracket, OpenParen,\n  CloseParen, StringEmbedClose, OpenString, Bool, Action, Set, Equality, Dot, Pipe, Merge,\n  Mutate, Comparison, Num,  Search, Lookup, If, Else, Then,\n  Not, None, Name, Tag, FunctionIdentifier, Identifier, AddInfix, MultInfix\n];\n\nlet stringEmbedTokens: any[] = [StringEmbedClose].concat(codeTokens);\n\nlet LexerModes:any = {\n  \"doc\": [WhiteSpace, Fence, DocContent],\n  \"code\": codeTokens,\n  \"string\": [CloseString, StringEmbedOpen, StringChars],\n  // \"stringEmbed\": stringEmbedTokens,\n};\n\nlet allTokens: any[] = codeTokens.concat([Fence, DocContent, CloseString, StringEmbedOpen, StringEmbedClose, StringChars]);\n\nlet EveDocLexer = new Lexer({modes: LexerModes, defaultMode: \"doc\"});\nlet EveBlockLexer = new Lexer({modes: LexerModes, defaultMode: \"code\"});\n\n//-----------------------------------------------------------\n// Parse Nodes\n//-----------------------------------------------------------\n\nexport type NodeDependent = chev.IToken | ParseNode;\n\nexport interface ParseNode {\n  type?: string\n  id?: string\n  startOffset?: number,\n  endOffset?: number,\n  from: NodeDependent[]\n  [property: string]: any\n}\n\nexport class ParseBlock {\n  id: string;\n  start: number;\n  nodeId = 0;\n  variables: {[name: string]: ParseNode} = {};\n  equalities: any[] = [];\n  scanLike: ParseNode[] = [];\n  expressions: ParseNode[] = [];\n  binds: ParseNode[] = [];\n  commits: ParseNode[] = [];\n  variableLookup: {[name: string]: ParseNode};\n  links: string[] = [];\n  tokens: chev.Token[];\n  searchScopes: string[] = [];\n  parent: ParseBlock | undefined;\n\n  constructor(id:string, variableLookup?:any) {\n    this.id = id;\n    this.variableLookup = variableLookup || {};\n  }\n\n  toVariable(name:string, generated = false) {\n    let variable = this.variableLookup[name];\n    if(!variable) {\n      this.variableLookup[name] = this.makeNode(\"variable\", {name, from: [], generated});\n    }\n    variable = this.variables[name] = this.variableLookup[name];\n    return {id: variable.id, type: \"variable\", name, from: [], generated};\n  }\n\n  addUsage(variable:any, usage:any) {\n    let global = this.variableLookup[variable.name];\n    global.from.push(usage)\n    if(global.from.length === 1) {\n      global.startOffset = usage.startOffset;\n      global.endOffset = toEnd(usage);\n    }\n    variable.from.push(usage);\n    variable.startOffset = usage.startOffset;\n    variable.endOffset = toEnd(usage);\n    this.links.push(variable.id, usage.id);\n  }\n\n  equality(a:any, b:any) {\n    this.equalities.push([a, b]);\n  }\n\n  commit(node: ParseNode) {\n    this.commits.push(node);\n  }\n\n  bind(node: ParseNode) {\n    this.binds.push(node);\n  }\n\n  expression(node: ParseNode) {\n    this.expressions.push(node);\n  }\n\n  scan(node: ParseNode) {\n    this.scanLike.push(node);\n  }\n\n  makeNode(type:any, node: ParseNode) {\n    if(!node.id) {\n      node.id = `${this.id}|node|${this.nodeId++}`;\n    }\n    for(let from of node.from as any[]) {\n      this.links.push(node.id, from.id);\n    }\n    if(node.from.length) {\n      node.startOffset = node.from[0].startOffset;\n      node.endOffset = toEnd(node.from[node.from.length - 1]);\n    }\n    node.type = type;\n    return node;\n  }\n\n  addSearchScopes(scopes: string[]) {\n    for(let scope of scopes) {\n      if(this.searchScopes.indexOf(scope) === -1) {\n        this.searchScopes.push(scope);\n      }\n    }\n  }\n\n  subBlock() {\n    let neue = new ParseBlock(`${this.id}|sub${this.nodeId++}`, this.variableLookup);\n    neue.parent = this;\n    return neue;\n  }\n}\n\n\n//-----------------------------------------------------------\n// Parser\n//-----------------------------------------------------------\n\nexport class Parser extends chev.Parser {\n  customErrors: any[];\n  block: ParseBlock;\n  activeScopes: string[];\n  currentAction: string;\n\n  // Parser patterns\n  doc: any;\n  codeBlock: any;\n  fencedBlock: any;\n  section: any;\n  searchSection: any;\n  actionSection: any;\n  value: any;\n  bool: any;\n  num: any;\n  scopeDeclaration: any;\n  name: any;\n  statement: any;\n  expression: any;\n  attribute: any;\n  attributeEquality: any;\n  attributeComparison: any;\n  attributeNot: any;\n  attributeOperation: any;\n  record: any;\n  tag: any;\n  functionRecord: any;\n  notStatement: any;\n  comparison: any;\n  infix: any;\n  attributeAccess: any;\n  actionStatement: any;\n  actionEqualityRecord: any;\n  actionAttributeExpression: any;\n  actionOperation: any;\n  actionLookup: any;\n  variable: any;\n  recordOperation: any;\n  ifExpression: any;\n  ifBranch: any;\n  elseIfBranch: any;\n  elseBranch: any;\n  multiplication: any;\n  addition: any;\n  infixValue: any;\n  parenthesis: any;\n  attributeMutator: any;\n  singularAttribute: any;\n  stringInterpolation: any;\n\n\n  constructor(input:any) {\n    super(input, allTokens, {});\n    let self = this;\n    let asValue = (node:any) => {\n      if(node.type === \"constant\" || node.type === \"variable\" || node.type === \"parenthesis\") {\n        return node;\n      } else if(node.variable) {\n        return node.variable;\n      }\n      throw new Error(\"Tried to get value of a node that is neither a constant nor a variable.\\n\\n\" + JSON.stringify(node));\n    }\n    let ifOutputs = (expression:any) => {\n      let outputs = [];\n      if(expression.type === \"parenthesis\") {\n        for(let item of expression.items) {\n          outputs.push(asValue(item));\n        }\n      } else {\n        outputs.push(asValue(expression));\n      }\n      return outputs;\n    }\n\n    let makeNode = (type:string, node:any) => {\n      return self.block.makeNode(type, node);\n    }\n\n    let blockStack:any[] = [];\n    let pushBlock = (blockId?:string) => {\n      let block;\n      let prev = blockStack[blockStack.length - 1];\n      if(prev) {\n        block = prev.subBlock();\n      } else {\n        block = new ParseBlock(blockId || \"block\");\n      }\n      blockStack.push(block);\n      self.block = block;\n      return block;\n    }\n\n    let popBlock = () => {\n      let popped = blockStack.pop();\n      self.block = blockStack[blockStack.length - 1];\n      return popped;\n    }\n\n    //-----------------------------------------------------------\n    // Doc rules\n    //-----------------------------------------------------------\n\n    self.RULE(\"doc\", () => {\n      let doc = {\n        full: [] as any[],\n        content: [] as any[],\n        blocks: [] as any[],\n      }\n      self.MANY(() => {\n        self.OR([\n          {ALT: () => {\n            let content = self.CONSUME(DocContent);\n            doc.full.push(content);\n            doc.content.push(content);\n          }},\n          {ALT: () => {\n            let block : any = self.SUBRULE(self.fencedBlock);\n            if(doc.content.length) {\n              block.name = doc.content[doc.content.length - 1].image;\n            } else {\n              block.name = \"Unnamed block\";\n            }\n            doc.full.push(block);\n            doc.blocks.push(block);\n          }},\n        ])\n      });\n      return doc;\n    });\n\n    self.RULE(\"fencedBlock\", () => {\n      self.CONSUME(Fence);\n      let block = self.SUBRULE(self.codeBlock);\n      let fence = self.CONSUME(CloseFence);\n      return block;\n    });\n\n    //-----------------------------------------------------------\n    // Blocks\n    //-----------------------------------------------------------\n\n    self.RULE(\"codeBlock\", (blockId = \"block\") => {\n      blockStack = [];\n      let block = pushBlock(blockId);\n      self.MANY(() => { self.SUBRULE(self.section) })\n      return popBlock();\n    })\n\n    self.RULE(\"section\", () => {\n      return self.OR([\n        {ALT: () => { return self.SUBRULE(self.searchSection) }},\n        {ALT: () => { return self.SUBRULE(self.actionSection) }},\n        {ALT: () => { return self.CONSUME(CommentLine); }},\n      ]);\n    });\n\n\n    //-----------------------------------------------------------\n    // Scope declaration\n    //-----------------------------------------------------------\n\n    self.RULE(\"scopeDeclaration\", () => {\n      let scopes:any[] = [];\n      self.OR([\n        {ALT: () => {\n          self.CONSUME(OpenParen);\n          self.AT_LEAST_ONE(() => {\n            let name: any = self.SUBRULE(self.name);\n            scopes.push(name.name);\n          })\n          self.CONSUME(CloseParen);\n        }},\n        {ALT: () => {\n          self.AT_LEAST_ONE2(() => {\n            let name: any = self.SUBRULE2(self.name);\n            scopes.push(name.name);\n          })\n        }},\n      ]);\n      return scopes;\n    });\n\n\n    //-----------------------------------------------------------\n    // Search section\n    //-----------------------------------------------------------\n\n    self.RULE(\"searchSection\", () => {\n      // @TODO fill in from\n      let from:any[] = [];\n      self.CONSUME(Search);\n      let scopes:any = [\"session\"];\n      self.OPTION(() => { scopes = self.SUBRULE(self.scopeDeclaration) })\n      self.activeScopes = scopes;\n      self.currentAction = \"match\";\n      self.block.addSearchScopes(scopes);\n      let statements:any[] = [];\n      self.MANY(() => {\n        let statement: any = self.SUBRULE(self.statement);\n        if(statement) {\n          statements.push(statement);\n          statement.scopes = scopes;\n        }\n      });\n      return makeNode(\"searchSection\", {statements, scopes, from});\n    });\n\n    self.RULE(\"statement\", () => {\n      return self.OR([\n        {ALT: () => { return self.SUBRULE(self.comparison); }},\n        {ALT: () => { return self.SUBRULE(self.notStatement); }},\n      ])\n    });\n\n    //-----------------------------------------------------------\n    // Action section\n    //-----------------------------------------------------------\n\n    self.RULE(\"actionSection\", () => {\n      // @TODO fill in from\n      let from:any[] = [];\n      let action = self.CONSUME(Action).image;\n      let actionKey = action;\n      let scopes:any = [\"session\"];\n      self.OPTION(() => { scopes = self.SUBRULE(self.scopeDeclaration) })\n      self.activeScopes = scopes;\n      self.currentAction = action!;\n      let statements:any[] = [];\n      self.MANY(() => {\n        let statement = self.SUBRULE(self.actionStatement, [actionKey]) as any;\n        if(statement) {\n          statements.push(statement);\n          statement.scopes = scopes;\n        }\n      });\n      return makeNode(\"actionSection\", {statements, scopes, from});\n    });\n\n\n    self.RULE(\"actionStatement\", (actionKey) => {\n      return self.OR([\n        {ALT: () => {\n          let record = self.SUBRULE(self.record, [false, actionKey, \"+=\"]);\n          return record;\n        }},\n        {ALT: () => { return self.SUBRULE(self.actionEqualityRecord, [actionKey]); }},\n        {ALT: () => {\n          let record = self.SUBRULE(self.actionOperation, [actionKey]);\n          (self.block as any)[actionKey](record);\n          return record;\n        }},\n        {ALT: () => { return self.SUBRULE(self.actionLookup, [actionKey]); }},\n      ])\n    });\n\n    //-----------------------------------------------------------\n    // Action operations\n    //-----------------------------------------------------------\n\n    self.RULE(\"actionOperation\", (actionKey) => {\n      return self.OR([\n        {ALT: () => { return self.SUBRULE(self.recordOperation, [actionKey]) }},\n        {ALT: () => { return self.SUBRULE(self.attributeOperation, [actionKey]) }},\n      ]);\n    });\n\n    self.RULE(\"attributeOperation\", (actionKey) => {\n      let mutator = self.SUBRULE(self.attributeMutator) as any;\n      let {attribute, parent} = mutator;\n      return self.OR([\n        {ALT: () => {\n          let variable = self.block.toVariable(`${attribute.image}|${attribute.startLine}|${attribute.startColumn}`, true);\n          let scan = makeNode(\"scan\", {entity: parent, attribute: makeNode(\"constant\", {value: attribute.image, from: [attribute]}), value: variable, scopes: self.activeScopes, from: [mutator]});\n          self.block.addUsage(variable, scan);\n          self.block.scan(scan);\n          self.CONSUME(Merge);\n          let record = self.SUBRULE(self.record, [true, actionKey, \"+=\", undefined, variable]) as any;\n          record.variable = variable;\n          record.action = \"<-\";\n          return record;\n        }},\n        {ALT: () => {\n          let op = self.CONSUME(Set);\n          let none = self.CONSUME(None);\n          return makeNode(\"action\", {action: \"erase\", entity: asValue(parent), attribute: attribute.image, from: [mutator, op, none]});\n        }},\n        {ALT: () => {\n          let op = self.CONSUME2(Set);\n          let value = self.SUBRULE(self.infix);\n          return makeNode(\"action\", {action: op.image, entity: asValue(parent), attribute: attribute.image, value: asValue(value), from: [mutator, op, value]});\n        }},\n        {ALT: () => {\n          let op = self.CONSUME3(Set);\n          let value = self.SUBRULE2(self.record, [false, actionKey, \"+=\", parent]);\n          return makeNode(\"action\", {action: op.image, entity: asValue(parent), attribute: attribute.image, value: asValue(value), from: [mutator, op, value]});\n        }},\n        {ALT: () => {\n          let variable = self.block.toVariable(`${attribute.image}|${attribute.startLine}|${attribute.startColumn}`, true);\n          let scan = makeNode(\"scan\", {entity: parent, attribute: makeNode(\"constant\", {value: attribute.image, from: [attribute]}), value: variable, scopes: self.activeScopes, from: [mutator]});\n          self.block.addUsage(variable, scan);\n          self.block.scan(scan);\n          let op = self.CONSUME(Mutate);\n          let tag : any = self.SUBRULE(self.tag);\n          return makeNode(\"action\", {action: op.image, entity: variable, attribute: \"tag\", value: makeNode(\"constant\", {value: tag.tag, from: [tag]}), from: [mutator, op, tag]});\n        }},\n        {ALT: () => {\n          let op = self.CONSUME2(Mutate);\n          let value: any = self.SUBRULE2(self.actionAttributeExpression, [actionKey, op.image, parent]);\n          if(value.type === \"record\" && !value.extraProjection) {\n            value.extraProjection = [parent];\n          }\n          if(value.type === \"parenthesis\") {\n            let autoIndex = 0;\n            for(let item of value.items) {\n              if(item.type === \"record\" && !value.extraProjection) {\n                item.extraProjection = [parent];\n              }\n              if(item.from[0] && item.from[0].type === \"record\") {\n                let record = item.from[0];\n                record.attributes.push(makeNode(\"attribute\", {attribute: \"eve-auto-index\", value: makeNode(\"constant\", {value: autoIndex, from: [record]}), from: [record]}));\n                autoIndex++;\n              }\n            }\n          }\n          return makeNode(\"action\", {action: op.image, entity: asValue(parent), attribute: attribute.image, value: asValue(value), from: [mutator, op, value]});\n        }},\n      ])\n    });\n\n    self.RULE(\"recordOperation\", (actionKey) => {\n      let variable = self.SUBRULE(self.variable) as any;\n      return self.OR([\n        {ALT: () => {\n          let set = self.CONSUME(Set);\n          let none = self.CONSUME(None);\n          return makeNode(\"action\", {action: \"erase\", entity: asValue(variable), from: [variable, set, none]});\n        }},\n        {ALT: () => {\n          self.CONSUME(Merge);\n          let record = self.SUBRULE(self.record, [true, actionKey, \"+=\", undefined, variable]) as any;\n          record.needsEntity = true;\n          record.action = \"<-\";\n          return record;\n        }},\n        {ALT: () => {\n          let op = self.CONSUME(Mutate);\n          let tag : any = self.SUBRULE(self.tag);\n          return makeNode(\"action\", {action: op.image, entity: asValue(variable), attribute: \"tag\", value: makeNode(\"constant\", {value: tag.tag, from: [tag]}), from: [variable, op, tag]});\n        }},\n      ])\n    });\n\n    self.RULE(\"actionLookup\", (actionKey) => {\n      let lookup = self.CONSUME(Lookup);\n      let record: any = self.SUBRULE(self.record, [true]);\n      let info: any = {};\n      for(let attribute of record.attributes) {\n        info[attribute.attribute] = attribute.value;\n      }\n      let actionType = \"+=\";\n      self.OPTION(() => {\n        self.CONSUME(Set);\n        self.CONSUME(None);\n        if(info[\"value\"] !== undefined) {\n          actionType = \"-=\";\n        } else {\n          actionType = \"erase\";\n        }\n      })\n      let action = makeNode(\"action\", {action: actionType, entity: info.record, attribute: info.attribute, value: info.value, node: info.node, scopes: self.activeScopes, from: [lookup, record]});\n      (self.block as any)[actionKey](action);\n      return action;\n    });\n\n    self.RULE(\"actionAttributeExpression\", (actionKey, action, parent) => {\n      return self.OR([\n        {ALT: () => { return self.SUBRULE(self.record, [false, actionKey, action, parent]); }},\n        {ALT: () => { return self.SUBRULE(self.infix); }},\n      ])\n    })\n\n    self.RULE(\"actionEqualityRecord\", (actionKey) => {\n      let variable = self.SUBRULE(self.variable);\n      self.CONSUME(Equality);\n      let record : any = self.SUBRULE(self.record, [true, actionKey, \"+=\"]);\n      record.variable = variable;\n      (self.block as any)[actionKey](record);\n      return record;\n    });\n\n    //-----------------------------------------------------------\n    // Record + attribute\n    //-----------------------------------------------------------\n\n    self.RULE(\"record\", (noVar = false, blockKey = \"scan\", action = false, parent?, passedVariable?) => {\n      let attributes:any[] = [];\n      let start = self.CONSUME(OpenBracket);\n      let from: NodeDependent[] = [start];\n      let info: any = {attributes, action, scopes: self.activeScopes, from};\n      if(parent) {\n        info.extraProjection = [parent];\n      }\n      if(passedVariable) {\n        info.variable = passedVariable;\n        info.variable.nonProjecting = true;\n      } else if(!noVar) {\n        info.variable = self.block.toVariable(`record|${start.startLine}|${start.startColumn}`, true);\n        info.variable.nonProjecting = true;\n      }\n      let nonProjecting = false;\n      self.MANY(() => {\n        self.OR([\n          {ALT: () => {\n            let attribute: any = self.SUBRULE(self.attribute, [false, blockKey, action, info.variable]);\n            // Inline handles attributes itself and so won't return any attribute for us to add\n            // to this object\n            if(!attribute) return;\n\n            if(attribute.constructor === Array) {\n              for(let attr of attribute as any[]) {\n                attr.nonProjecting = nonProjecting;\n                attributes.push(attr);\n                from.push(attr);\n              }\n            } else {\n              attribute.nonProjecting = nonProjecting;\n              attributes.push(attribute);\n              from.push(attribute);\n            }\n          }},\n          {ALT: () => {\n            nonProjecting = true;\n            let pipe = self.CONSUME(Pipe);\n            from.push(pipe);\n            return pipe;\n          }},\n        ]);\n      })\n      from.push(self.CONSUME(CloseBracket));\n      let record : any = makeNode(\"record\", info);\n      if(!noVar) {\n        self.block.addUsage(info.variable, record);\n        (self.block as any)[blockKey](record);\n      }\n      return record;\n    });\n\n    self.RULE(\"attribute\", (noVar, blockKey, action, recordVariable) => {\n      return self.OR([\n        {ALT: () => { return self.SUBRULE(self.attributeEquality, [noVar, blockKey, action, recordVariable]); }},\n        {ALT: () => { return self.SUBRULE(self.attributeComparison); }},\n        {ALT: () => { return self.SUBRULE(self.attributeNot, [recordVariable]); }},\n        {ALT: () => { return self.SUBRULE(self.singularAttribute); }},\n        {ALT: () => {\n          let value: any = self.SUBRULE(self.value);\n          let token = value.from[0];\n\n          let message = \"Value missing attribute\";\n\n          if (value.hasOwnProperty(\"value\")) {\n            message = `\"${value.value}\" needs to be labeled with an attribute`;\n          }\n\n          self.customErrors.push({\n            message,\n            name: \"Unlabeled value\",\n            resyncedTokens: [],\n            context: {\n              ruleOccurrenceStack: [],\n              ruleStack: []\n            },\n            token\n          });\n        }},\n      ]);\n    });\n\n    self.RULE(\"singularAttribute\", (forceGenerate) => {\n      return self.OR([\n        {ALT: () => {\n          let tag : any = self.SUBRULE(self.tag);\n          return makeNode(\"attribute\", {attribute: \"tag\", value: makeNode(\"constant\", {value: tag.tag, from: [tag]}), from: [tag]});\n        }},\n        {ALT: () => {\n          let variable : any = self.SUBRULE(self.variable, [forceGenerate]);\n          return makeNode(\"attribute\", {attribute: variable.from[0].image, value: variable, from: [variable]});\n        }},\n      ]);\n    });\n\n    self.RULE(\"attributeMutator\", () => {\n      let scans:any[] = [];\n      let entity:any, attribute:any, value:any;\n      let needsEntity = true;\n      let from:any[] = [];\n      entity = self.SUBRULE(self.variable);\n      let dot = self.CONSUME(Dot);\n      from.push(entity, dot);\n      self.MANY(() => {\n        attribute = self.CONSUME(Identifier);\n        from.push(attribute);\n        from.push(self.CONSUME2(Dot));\n        value = self.block.toVariable(`${attribute.image}|${attribute.startLine}|${attribute.startColumn}`, true);\n        self.block.addUsage(value, attribute);\n        let scopes = self.activeScopes;\n        if(self.currentAction !== \"match\") {\n          scopes = self.block.searchScopes;\n        }\n        let scan = makeNode(\"scan\", {entity, attribute: makeNode(\"constant\", {value: attribute.image, from: [value]}), value, needsEntity, scopes, from: [entity, dot, attribute]});\n        self.block.scan(scan);\n        needsEntity = false;\n        entity = value;\n      });\n      attribute = self.CONSUME2(Identifier);\n      from.push(attribute);\n      return makeNode(\"attributeMutator\", {attribute: attribute, parent: entity, from});\n    });\n\n    self.RULE(\"attributeAccess\", () => {\n      let scans:any[] = [];\n      let entity:any, attribute:any, value:any;\n      let needsEntity = true;\n      entity = self.SUBRULE(self.variable);\n      let parentId = entity.name;\n      self.AT_LEAST_ONE(() => {\n        let dot = self.CONSUME(Dot);\n        attribute = self.CONSUME(Identifier);\n        parentId = `${parentId}|${attribute.image}`;\n        value = self.block.toVariable(parentId, true);\n        self.block.addUsage(value, attribute);\n        let scopes = self.activeScopes;\n        if(self.currentAction !== \"match\") {\n          scopes = self.block.searchScopes;\n        }\n        let scan = makeNode(\"scan\", {entity, attribute: makeNode(\"constant\", {value: attribute.image, from: [attribute]}), value, needsEntity, scopes, from: [entity, dot, attribute]});\n        self.block.scan(scan);\n        needsEntity = false;\n        entity = value;\n      });\n      return value;\n    });\n\n    self.RULE(\"attributeEquality\", (noVar, blockKey, action, parent) => {\n      let attributes:any[] = [];\n      let autoIndex = 1;\n      let attributeNode:any;\n      let attribute: any = self.OR([\n        {ALT: () => {\n          attributeNode = self.CONSUME(Identifier);\n          return attributeNode.image;\n        }},\n        {ALT: () => {\n          attributeNode = self.CONSUME(Num);\n          return parseFloat(attributeNode.image) as any;\n        }}\n      ]);\n      let equality = self.CONSUME(Equality);\n      let result : any;\n      self.OR2([\n        {ALT: () => {\n          result = self.SUBRULE(self.infix);\n          // if the result is a parenthesis, we have to make sure that if there are sub-records\n          // inside that they get eve-auto-index set on them and they also have the parent transfered\n          // down to them. If we don't do this, we'll end up with children that are shared between\n          // the parents instead of one child per parent.\n          if(result.type === \"parenthesis\") {\n            for(let item of result.items) {\n              // this is a bit sad, but by the time we see the parenthesis, the records have been replaced\n              // with their variables. Those variables are created from the record object though, so we can\n              // check the from of the variable for a reference to the record.\n              if(item.type === \"variable\" && item.from[0] && item.from[0].type === \"record\") {\n                let record = item.from[0];\n                // if we have a parent, we need to make sure it ends up part of our extraProjection set\n                if(parent && !item.extraProjection) {\n                  record.extraProjection = [parent];\n                } else if(parent) {\n                  record.extraProjection.push(parent);\n                }\n                // Lastly we need to add the eve-auto-index attribute to make sure this is consistent with the case\n                // where we leave the parenthesis off and just put records one after another.\n                record.attributes.push(makeNode(\"attribute\", {attribute: \"eve-auto-index\", value: makeNode(\"constant\", {value: autoIndex, from: [record]}), from: [record]}));\n                autoIndex++;\n              }\n            }\n          }\n        }},\n        {ALT: () => {\n          result = self.SUBRULE(self.record, [noVar, blockKey, action, parent]);\n          self.MANY(() => {\n            autoIndex++;\n            let record : any = self.SUBRULE2(self.record, [noVar, blockKey, action, parent]);\n            record.attributes.push(makeNode(\"attribute\", {attribute: \"eve-auto-index\", value: makeNode(\"constant\", {value: autoIndex, from: [record]}), from: [record]}));\n            attributes.push(makeNode(\"attribute\", {attribute, value: asValue(record), from: [attributeNode, equality, record]}));\n          })\n          if(autoIndex > 1) {\n            result.attributes.push(makeNode(\"attribute\", {attribute: \"eve-auto-index\", value: makeNode(\"constant\", {value: 1, from: [result]}), from: [result]}));\n          }\n        }},\n      ]);\n      attributes.push(makeNode(\"attribute\", {attribute, value: asValue(result), from: [attributeNode, equality, result]}))\n      return attributes;\n    });\n\n    self.RULE(\"attributeComparison\", () => {\n      let attribute = self.CONSUME(Identifier);\n      let comparator = self.CONSUME(Comparison);\n      let result = self.SUBRULE(self.expression);\n      let variable = self.block.toVariable(`attribute|${attribute.startLine}|${attribute.startColumn}`, true);\n      let expression = makeNode(\"expression\", {op: `compare/${comparator.image}`, args: [asValue(variable), asValue(result)], from: [attribute, comparator, result]})\n      self.block.addUsage(variable, expression);\n      self.block.expression(expression);\n      return makeNode(\"attribute\", {attribute: attribute.image, value: variable, from: [attribute, comparator, expression]});\n    });\n\n    self.RULE(\"attributeNot\", (recordVariable) => {\n      let block = pushBlock();\n      block.type = \"not\";\n      let not = self.CONSUME(Not);\n      let start = self.CONSUME(OpenParen);\n      let attribute: any = self.OR([\n        {ALT: () => { return self.SUBRULE(self.attributeComparison); }},\n        {ALT: () => { return self.SUBRULE(self.singularAttribute, [true]); }},\n      ]);\n      let end = self.CONSUME(CloseParen);\n      // we have to add a record for this guy\n      let scan : any = makeNode(\"scan\", {entity: recordVariable, attribute: makeNode(\"constant\", {value: attribute.attribute, from: [attribute]}), value: attribute.value, needsEntity: true, scopes: self.activeScopes, from: [attribute]});\n      block.variables[recordVariable.name] = recordVariable;\n      block.scan(scan);\n      block.from = [not, start, attribute, end];\n      block.startOffset = not.startOffset;\n      block.endOffset = toEnd(end);\n      popBlock();\n      self.block.scan(block);\n      return;\n    });\n\n    //-----------------------------------------------------------\n    // Name and tag\n    //-----------------------------------------------------------\n\n    self.RULE(\"name\", () => {\n      let at = self.CONSUME(Name);\n      let name = self.CONSUME(Identifier);\n      self.customErrors.push({message: `Databases have been deprecated, so @${name.image} has no meaning here`, name: \"Database deprecation\", resyncedTokens: [], context:{ruleOccurrenceStack: [], ruleStack: []}, token:name})\n      return makeNode(\"name\", {name: name.image, from: [at, name]});\n    });\n\n    self.RULE(\"tag\", () => {\n      let hash = self.CONSUME(Tag);\n      let tag = self.CONSUME(Identifier);\n      return makeNode(\"tag\", {tag: tag.image, from: [hash, tag]});\n    });\n\n    //-----------------------------------------------------------\n    // Function\n    //-----------------------------------------------------------\n\n    self.RULE(\"functionRecord\", (): any => {\n      let name = self.OR([\n          {ALT: () => { return self.CONSUME(FunctionIdentifier); }},\n          {ALT: () => { return self.CONSUME(Lookup); }}\n      ]);\n      let record: any = self.SUBRULE(self.record, [true]);\n      if(name.image === \"lookup\") {\n        let info: any = {};\n        for(let attribute of record.attributes) {\n          info[attribute.attribute] = attribute.value;\n        }\n        let scan = makeNode(\"scan\", {entity: info.record, attribute: info.attribute, value: info.value, node: info.node, scopes: self.activeScopes, from: [name, record]});\n        self.block.scan(scan);\n        return scan;\n      } else {\n        let variable = self.block.toVariable(`return|${name.startLine}|${name.startColumn}`, true);\n        let functionRecord = makeNode(\"functionRecord\", {op: name.image, record, variable, from: [name, record]});\n        self.block.addUsage(variable, functionRecord);\n        self.block.expression(functionRecord);\n        return functionRecord;\n      }\n    });\n\n    //-----------------------------------------------------------\n    // Comparison\n    //-----------------------------------------------------------\n\n    self.RULE(\"comparison\", (nonFiltering) : any => {\n      let left = self.SUBRULE(self.expression);\n      let from = [left];\n      let rights:any[] = [];\n      self.MANY(() => {\n        let comparator = self.OR([\n          {ALT: () => { return self.CONSUME(Comparison); }},\n          {ALT: () => { return self.CONSUME(Equality); }}\n        ]);\n        let value = self.OR2([\n          {ALT: () => { return self.SUBRULE2(self.expression); }},\n          {ALT: () => { return self.SUBRULE(self.ifExpression); }}\n        ]);\n        from.push(comparator, value);\n        rights.push({comparator, value});\n      })\n      if(rights.length) {\n        let expressions = [];\n        let curLeft: any = left;\n        for(let pair of rights) {\n          let {comparator, value} = pair;\n          let expression = null;\n          // if this is a nonFiltering comparison, then we return an expression\n          // with a variable for its return value\n          if(nonFiltering) {\n            let variable = self.block.toVariable(`comparison|${comparator.startLine}|${comparator.startColumn}`, true);\n            expression = makeNode(\"expression\", {variable, op: `compare/${comparator.image}`, args: [asValue(curLeft), asValue(value)], from: [curLeft, comparator, value]});\n            self.block.addUsage(variable, expression);\n            self.block.expression(expression);\n          } else if(tokenMatcher(comparator, Equality)) {\n            if(value.type === \"choose\" || value.type === \"union\") {\n              value.outputs = ifOutputs(left);\n              self.block.scan(value);\n            } else if(value.type === \"functionRecord\" && curLeft.type === \"parenthesis\") {\n              value.returns = curLeft.items.map(asValue);\n              self.block.equality(asValue(value.returns[0]), asValue(value));\n            } else if(curLeft.type === \"parenthesis\") {\n              throw new Error(\"Left hand parenthesis without an if or function on the right\");\n            } else {\n              self.block.equality(asValue(curLeft), asValue(value));\n            }\n          } else {\n            expression = makeNode(\"expression\", {op: `compare/${comparator.image}`, args: [asValue(curLeft), asValue(value)], from: [curLeft, comparator, value]});\n            self.block.expression(expression);\n          }\n          curLeft = value;\n          if(expression) {\n            expressions.push(expression);\n          }\n        }\n        return makeNode(\"comparison\", {expressions, from});\n      };\n      return left;\n    });\n\n    //-----------------------------------------------------------\n    // Special Forms\n    //-----------------------------------------------------------\n\n    self.RULE(\"notStatement\", () => {\n      let block = pushBlock();\n      block.type = \"not\";\n      let from: NodeDependent[] = [\n        self.CONSUME(Not),\n        self.CONSUME(OpenParen),\n      ];\n      self.MANY(() => {\n        from.push(self.SUBRULE(self.statement) as ParseNode);\n      });\n      from.push(self.CONSUME(CloseParen));\n      popBlock();\n      block.from = from;\n      block.startOffset = from[0].startOffset;\n      block.endOffset = toEnd(from[from.length - 1]);\n      self.block.scan(block);\n      return;\n    });\n\n    //-----------------------------------------------------------\n    // If ... then\n    //-----------------------------------------------------------\n\n    self.RULE(\"ifExpression\", () => {\n      let branches:any[] = [];\n      let exclusive = false;\n      let from = branches;\n      branches.push(self.SUBRULE(self.ifBranch));\n      self.MANY(() => {\n        branches.push(self.OR([\n          {ALT: () => { return self.SUBRULE2(self.ifBranch); }},\n          {ALT: () => {\n            exclusive = true;\n            return self.SUBRULE(self.elseIfBranch);\n          }},\n        ]));\n      });\n      self.OPTION(() => {\n        exclusive = true;\n        branches.push(self.SUBRULE(self.elseBranch));\n      });\n      let expressionType = exclusive ? \"choose\" : \"union\";\n      return makeNode(expressionType, {branches, from});\n    });\n\n    self.RULE(\"ifBranch\", () => {\n      let block = pushBlock();\n      let from: NodeDependent[] = [\n        self.CONSUME(If)\n      ]\n      self.AT_LEAST_ONE(() => {\n        let statement = self.SUBRULE(self.statement) as ParseNode;\n        if(statement) {\n          from.push(statement);\n        }\n      })\n      from.push(self.CONSUME(Then));\n      let expression = self.SUBRULE(self.expression) as ParseNode;\n      from.push(expression);\n      block.startOffset = from[0].startOffset;\n      block.endOffset = toEnd(from[from.length - 1]);\n      popBlock();\n      return makeNode(\"ifBranch\", {block, outputs: ifOutputs(expression), exclusive: false, from});\n    });\n\n    self.RULE(\"elseIfBranch\", () => {\n      let block = pushBlock();\n      let from: NodeDependent[] = [\n        self.CONSUME(Else),\n        self.CONSUME(If),\n      ]\n      self.AT_LEAST_ONE(() => {\n        let statement = self.SUBRULE(self.statement) as ParseNode;\n        if(statement) {\n          from.push(statement);\n        }\n      })\n      from.push(self.CONSUME(Then));\n      let expression = self.SUBRULE(self.expression) as ParseNode;\n      from.push(expression);\n      block.startOffset = from[0].startOffset;\n      block.endOffset = toEnd(from[from.length - 1]);\n      popBlock();\n      return makeNode(\"ifBranch\", {block, outputs: ifOutputs(expression), exclusive: true, from});\n    });\n\n    self.RULE(\"elseBranch\", () => {\n      let block = pushBlock();\n      let from: NodeDependent[] = [self.CONSUME(Else)];\n      let expression = self.SUBRULE(self.expression) as ParseNode;\n      from.push(expression);\n      block.startOffset = from[0].startOffset;\n      block.endOffset = toEnd(from[from.length - 1]);\n      popBlock();\n      return makeNode(\"ifBranch\", {block, outputs: ifOutputs(expression), exclusive: true, from});\n    });\n\n    //-----------------------------------------------------------\n    // Infix and operator precedence\n    //-----------------------------------------------------------\n\n    self.RULE(\"infix\", () => {\n      return self.SUBRULE(self.addition);\n    });\n\n    self.RULE(\"addition\", () : any => {\n      let left = self.SUBRULE(self.multiplication);\n      let from = [left];\n      let ops:any[] = [];\n      self.MANY(function() {\n        let op = self.CONSUME(AddInfix);\n        let right = self.SUBRULE2(self.multiplication);\n        from.push(op, right);\n        ops.push({op, right})\n      });\n      if(!ops.length) {\n        return left;\n      } else {\n        let expressions = [];\n        let curVar;\n        let curLeft = left;\n        for(let pair of ops) {\n          let {op, right} = pair;\n          curVar = self.block.toVariable(`addition|${op.startLine}|${op.startColumn}`, true);\n          let expression = makeNode(\"expression\", {op: `math/${op.image}`, args: [asValue(curLeft), asValue(right)], variable: curVar, from: [curLeft, op, right]});\n          expressions.push(expression);\n          self.block.addUsage(curVar, expression);\n          self.block.expression(expression)\n          curLeft = expression;\n        }\n        return makeNode(\"addition\", {expressions, variable: curVar, from});\n      }\n    });\n\n    self.RULE(\"multiplication\", () : any => {\n      let left = self.SUBRULE(self.infixValue);\n      let from = [left];\n      let ops:any = [];\n      self.MANY(function() {\n        let op = self.CONSUME(MultInfix);\n        let right = self.SUBRULE2(self.infixValue);\n        from.push(op, right);\n        ops.push({op, right})\n      });\n      if(!ops.length) {\n        return left;\n      } else {\n        let expressions = [];\n        let curVar;\n        let curLeft = left;\n        for(let pair of ops) {\n          let {op, right} = pair;\n          curVar = self.block.toVariable(`addition|${op.startLine}|${op.startColumn}`, true);\n          let expression = makeNode(\"expression\", {op: `math/${op.image}`, args: [asValue(curLeft), asValue(right)], variable: curVar, from: [curLeft, op, right]});\n          expressions.push(expression);\n          self.block.addUsage(curVar, expression);\n          self.block.expression(expression)\n          curLeft = expression;\n        }\n        return makeNode(\"multiplication\", {expressions, variable: curVar, from});\n      }\n    });\n\n    self.RULE(\"parenthesis\", () => {\n      let items:any[] = [];\n      let from:any[] = [];\n      from.push(self.CONSUME(OpenParen));\n      self.AT_LEAST_ONE(() => {\n        let item = self.SUBRULE(self.expression);\n        items.push(asValue(item));\n        from.push(item);\n      })\n      from.push(self.CONSUME(CloseParen));\n      if(items.length === 1) {\n        return items[0];\n      }\n      return makeNode(\"parenthesis\", {items, from});\n    });\n\n    self.RULE(\"infixValue\", () => {\n      return self.OR([\n        {ALT: () => { return self.SUBRULE(self.attributeAccess); }},\n        {ALT: () => { return self.SUBRULE(self.functionRecord); }},\n        {ALT: () => { return self.SUBRULE(self.variable); }},\n        {ALT: () => { return self.SUBRULE(self.value); }},\n        {ALT: () => { return self.SUBRULE(self.parenthesis); }},\n      ]);\n    })\n\n    //-----------------------------------------------------------\n    // Expression\n    //-----------------------------------------------------------\n\n    self.RULE(\"expression\", () => {\n      let blockKey:any, action:any;\n      if(self.currentAction !== \"match\") {\n        blockKey = self.currentAction;\n        action = \"+=\";\n      }\n      return self.OR([\n        {ALT: () => { return self.SUBRULE(self.infix); }},\n        {ALT: () => { return self.SUBRULE(self.record, [false, blockKey, action]); }},\n      ]);\n    });\n\n    //-----------------------------------------------------------\n    // Variable\n    //-----------------------------------------------------------\n\n    self.RULE(\"variable\", (forceGenerate = false) => {\n      let token = self.CONSUME(Identifier);\n      let name = token.image;\n      if(forceGenerate) {\n        name = `${token.image}-${token.startLine}-${token.startColumn}`;\n      }\n      let variable = self.block.toVariable(name!, forceGenerate);\n      self.block.addUsage(variable, token);\n      return variable;\n    });\n\n    //-----------------------------------------------------------\n    // Values\n    //-----------------------------------------------------------\n\n    self.RULE(\"stringInterpolation\", () : any => {\n      let args:any[] = [];\n      let start = self.CONSUME(OpenString);\n      let from: NodeDependent[] = [start];\n      self.MANY(() => {\n        let arg = self.OR([\n          {ALT: () => {\n            let str = self.CONSUME(StringChars)!;\n            return makeNode(\"constant\", {value: cleanString(str.image!), from: [str]});\n          }},\n          {ALT: () => {\n            self.CONSUME(StringEmbedOpen);\n            let expression = self.SUBRULE(self.infix);\n            self.CONSUME(StringEmbedClose);\n            return expression;\n          }},\n        ]);\n        args.push(asValue(arg));\n        from.push(arg as ParseNode);\n      });\n      from.push(self.CONSUME(CloseString));\n      if(args.length === 1 && args[0].type === \"constant\") {\n        return args[0];\n      } else if(args.length === 0) {\n        return makeNode(\"constant\", {value: \"\", from});\n      }\n      let variable = self.block.toVariable(`concat|${start.startLine}|${start.startColumn}`, true);\n      let expression = makeNode(\"expression\", {op: \"eve/internal/concat\", args, variable, from});\n      self.block.addUsage(variable, expression);\n      self.block.expression(expression);\n      return expression;\n    });\n\n    self.RULE(\"value\", () => {\n      return self.OR([\n        {ALT: () => { return self.SUBRULE(self.stringInterpolation) }},\n        {ALT: () => { return self.SUBRULE(self.num) }},\n        {ALT: () => { return self.SUBRULE(self.bool) }},\n      ])\n    })\n\n    self.RULE(\"bool\", () => {\n      let value = self.CONSUME(Bool);\n      return makeNode(\"constant\", {value: value.image === \"true\", from: [value]});\n    })\n\n    self.RULE(\"num\", () => {\n      let num = self.CONSUME(Num);\n      return makeNode(\"constant\", {value: parseFloat(num.image!), from: [num]}) ;\n    });\n\n    //-----------------------------------------------------------\n    // Chevrotain analysis\n    //-----------------------------------------------------------\n\n    Parser.performSelfAnalysis(this);\n  }\n}\n\n//-----------------------------------------------------------\n// Public API\n//-----------------------------------------------------------\n\nexport function nodeToBoundaries(node:any, offset = 0) {\n  return [node.startOffset, toEnd(node)];\n}\n\nlet eveParser = new Parser([]);\n\nexport function parseBlock(block:any, blockId:string, offset = 0, spans:any[] = [], extraInfo:any = {}) {\n  let lex: any = EveBlockLexer.tokenize(block);\n  let token: any;\n  let tokenIx = 0;\n  for(token of lex.tokens) {\n    let tokenId = `${blockId}|token|${tokenIx++}`;\n    token.id = tokenId;\n    token.startOffset += offset;\n    spans.push(token.startOffset, token.startOffset + token.image.length, token.label, tokenId);\n  }\n  for(token of lex.groups.comments) {\n    let tokenId = `${blockId}|token|${tokenIx++}`;\n    token.id = tokenId;\n    token.startOffset += offset;\n    spans.push(token.startOffset, token.startOffset + token.image.length, token.label, tokenId);\n  }\n  eveParser.input = lex.tokens;\n  let results;\n  try {\n    eveParser.customErrors = [];\n    // The parameters here are a strange quirk of how Chevrotain works, I believe the\n    // 1 tells chevrotain what level the rule is starting at, we then pass our params\n    // to the codeBlock parser function as an array\n    results = eveParser.codeBlock(1, [blockId]);\n  } catch(e) {\n    console.error(\"The parser threw an error: \" + e);\n  }\n  if(results) {\n    results.start = offset;\n    results.startOffset = offset;\n    results.tokens = lex.tokens;\n    for(let scan of results.scanLike) {\n      let type = \"scan-boundary\";\n      if(scan.type === \"record\") {\n        type = \"record-boundary\";\n      }\n      spans.push(scan.startOffset, scan.endOffset, type, scan.id);\n    }\n    for(let action of results.binds) {\n      let type = \"action-boundary\";\n      if(action.type === \"record\") {\n        type = \"action-record-boundary\";\n      }\n      spans.push(action.startOffset, action.endOffset, type, action.id);\n      extraInfo[action.id] = {kind: \"bind\"};\n    }\n    for(let action of results.commits) {\n      let type = \"action-boundary\";\n      if(action.type === \"record\") {\n        type = \"action-record-boundary\";\n      }\n      spans.push(action.startOffset, action.endOffset, type, action.id);\n      extraInfo[action.id] = {kind: \"commits\"};\n    }\n  }\n  let errors = parserErrors(eveParser.errors.concat(eveParser.customErrors), {blockId, blockStart: offset, spans, extraInfo, tokens: lex.tokens});\n  lex.groups.comments.length = 0;\n  return {\n    results,\n    lex,\n    errors,\n  }\n}\n\nlet docIx = 0;\nexport function parseDoc(doc:string, docId = `doc|${docIx++}`) {\n  let {text, spans, blocks, extraInfo} = parseMarkdown(doc, docId);\n  let parsedBlocks = [];\n  let allErrors = [];\n  for(let block of blocks) {\n    extraInfo[block.id] = {info: block.info, block};\n    if(block.info.indexOf(\"disabled\") > -1) {\n      extraInfo[block.id].disabled = true;\n    }\n    if(block.info !== \"\" && block.info.indexOf(\"eve\") === -1) continue;\n    let {results, lex, errors} = parseBlock(block.literal, block.id, block.startOffset, spans, extraInfo);\n    // if this block is disabled, we want the parsed spans and such, but we don't want\n    // the block to be in the set sent to the builder\n    if(!extraInfo[block.id].disabled) {\n      if(errors.length) {\n        allErrors.push(errors);\n      } else if(results) {\n        results.endOffset = block.endOffset;\n        parsedBlocks.push(results);\n      }\n    }\n  }\n\n  let eavs:any[] = [];\n  for(let block of parsedBlocks) {\n    //if(typeof process === \"undefined\") console.log(block);\n    toFacts(eavs, block);\n  }\n\n  for(let errorSet of allErrors) {\n    for(let error of errorSet) {\n      errorToFacts(eavs, error, extraInfo[error.blockId].block);\n    }\n  }\n\n  return {\n    results: {blocks: parsedBlocks, text, spans, extraInfo, eavs},\n    errors: allErrors,\n  }\n}\n\nexport function errorToFacts(eavs:any[], error:EveError, block:any) {\n  let text = block.literal;\n  let offset = block.startOffset;\n  let blockStartLine = block.sourcepos[0][0];\n  let blockLines = text.split(\"\\n\");\n  let pos = 0;\n  let start = error.start - offset;\n  let stop = error.stop - offset;\n  if(isNaN(stop)) stop = text.length + offset;\n  if(isNaN(start)) start = offset;\n  let curLine = 0;\n  let startLine = 0;\n  let startChar = 0;\n  let stopLine = 0;\n  let stopChar = 0;\n  while(curLine < blockLines.length && pos < start) {\n    pos += blockLines[curLine++].length + 1;\n  }\n  startLine = blockStartLine + curLine;\n  startChar = start - (pos - (blockLines[curLine - 1] || \"\").length) + 2;\n  while(curLine < blockLines.length && pos < stop) {\n    pos += (blockLines[curLine++] || \"\").length + 1;\n  }\n  stopLine = blockStartLine + curLine;\n  stopChar = stop - (pos - (blockLines[curLine - 1] || \"\").length) + 2;\n\n  let sampleText = [];\n  let relativeStart = startLine - blockStartLine;\n  let relativeStop = stopLine - blockStartLine;\n  if(relativeStart != 0) {\n    sampleText.push(blockLines[relativeStart - 1]);\n    sampleText.push(blockLines[relativeStart]);\n  }\n\n  if(relativeStop > relativeStart) {\n    let cur = relativeStart;\n    while(cur <= relativeStop) {\n      sampleText.push(blockLines[cur]);\n      cur++;\n    }\n  }\n\n  if(relativeStop < blockLines.length && blockLines[relativeStop + 1]) {\n    sampleText.push(blockLines[relativeStop + 1]);\n  }\n\n  let errorId = uuid();\n  let startId = uuid();\n  let stopId = uuid();\n  eavs.push([errorId, \"tag\", \"eve/compiler/error\"]);\n  eavs.push([errorId, \"message\", error.message]);\n  eavs.push([errorId, \"start\", startId]);\n  eavs.push([startId, \"line\", startLine]);\n  eavs.push([startId, \"char\", startChar]);\n  eavs.push([errorId, \"stop\", stopId]);\n  eavs.push([stopId, \"line\", stopLine]);\n  eavs.push([stopId, \"char\", stopChar]);\n  eavs.push([errorId, \"sample\", sampleText.join(\"\\n\")])\n}\n\nexport function recordToFacts(eavs:any[], vars:any, scanLike:any) {\n  let rec = uuid();\n  eavs.push([rec, \"tag\", \"eve/compiler/record\"]);\n  eavs.push([rec, \"record\", vars[scanLike.variable.name]]);\n\n  for(let attr of scanLike.attributes) {\n    if(attr.type === \"attribute\") {\n      let values;\n      if(attr.value && attr.value.type === \"parenthesis\") {\n        values = attr.value.items;\n      } else {\n        values = [attr.value];\n      }\n      for(let value of values) {\n        let attrId = uuid();\n        eavs.push([attrId, \"attribute\", attr.attribute]);\n        eavs.push([attrId, \"value\", asFactValue(vars, value)]);\n        eavs.push([rec, \"attribute\", attrId]);\n      }\n    }\n  }\n\n  return rec;\n}\n\nfunction asFactValue(vars:any, value:any) {\n  if(typeof value !== \"object\") return value;\n  return value.type == \"constant\" ? value.value : vars[value.name];\n}\n\nexport function outputToFacts(eavs:any[], vars:any, scanLike:any, blockId:string) {\n  let rec = uuid();\n  eavs.push([rec, \"tag\", \"eve/compiler/output\"]);\n  eavs.push([rec, \"record\", vars[scanLike.variable.name]]);\n  if(scanLike.action === \"-=\" || scanLike.action === \"erase\") {\n    eavs.push([rec, \"tag\", \"eve/compiler/remove\"]);\n  } else if(scanLike.action === \":=\" || scanLike.action === \"<-\") {\n    let attrs = [];\n    for(let attribute of scanLike.attributes) {\n      attribute.nonProjecting = true;\n      if(attribute.type === \"attribute\") {\n        if(scanLike.action === \":=\" || (attribute.attribute !== \"tag\")) {\n          attrs.push({type: \"attribute\", attribute: attribute.attribute, nonProjecting:true});\n        }\n      }\n    }\n    outputToFacts(eavs, vars, {variable:scanLike.variable, action: \"erase\", attributes:attrs}, blockId);\n  }\n\n  for(let attr of scanLike.attributes) {\n    if(attr.type === \"attribute\") {\n      let values;\n      if(attr.value && attr.value.type === \"parenthesis\") {\n        values = attr.value.items;\n      } else {\n        values = [attr.value];\n      }\n      for(let value of values) {\n        let attrId = uuid();\n        eavs.push([attrId, \"attribute\", asFactValue(vars, attr.attribute)]);\n        if(value) {\n          eavs.push([attrId, \"value\", asFactValue(vars, value)]);\n        }\n        if(attr.nonProjecting) {\n          eavs.push([attrId, \"tag\", \"eve/compiler/attribute/non-identity\"]);\n        }\n        eavs.push([rec, \"attribute\", attrId]);\n      }\n    }\n  }\n  eavs.push([blockId, \"constraint\", rec]);\n  return rec;\n}\n\nfunction subBlockToFacts(eavs:any[], vars:any, blockId: string, block:any) {\n  for(let [left, right] of block.equalities) {\n    let eqId = uuid();\n    eavs.push([eqId, \"tag\", \"eve/compiler/equality\"]);\n    eavs.push([eqId, \"left\", asFactValue(vars, left)]);\n    eavs.push([eqId, \"right\", asFactValue(vars, right)]);\n    eavs.push([blockId, \"constraint\", eqId]);\n  }\n\n  for(let scanLike of block.scanLike) {\n    switch(scanLike.type) {\n      case \"record\":\n        let constraint = recordToFacts(eavs, vars, scanLike);\n        eavs.push([blockId, \"constraint\", constraint]);\n        break;\n      case \"scan\":\n        let lookupId = uuid();\n        eavs.push([lookupId, \"tag\", \"eve/compiler/lookup\"]);\n        eavs.push([lookupId, \"record\", asFactValue(vars, scanLike.entity)]);\n        eavs.push([lookupId, \"attribute\", asFactValue(vars, scanLike.attribute)]);\n        eavs.push([lookupId, \"value\", asFactValue(vars, scanLike.value)]);\n        eavs.push([blockId, \"constraint\", lookupId]);\n        break;\n      case \"not\":\n        let notId = uuid();\n        eavs.push([notId, \"tag\", \"eve/compiler/not\"]);\n        eavs.push([notId, \"tag\", \"eve/compiler/block\"]);\n        eavs.push([blockId, \"constraint\", notId]);\n        subBlockToFacts(eavs, vars, notId, scanLike);\n        break;\n      case \"choose\":\n      case \"union\":\n        let chooseId = uuid();\n        if(scanLike.type === \"choose\") {\n          eavs.push([chooseId, \"tag\", \"eve/compiler/choose\"]);\n        } else {\n          eavs.push([chooseId, \"tag\", \"eve/compiler/union\"]);\n        }\n        eavs.push([chooseId, \"tag\", \"eve/compiler/branch-set\"]);\n        eavs.push([blockId, \"constraint\", chooseId]);\n        for(let branch of scanLike.branches) {\n          let branchId = uuid();\n          eavs.push([chooseId, \"branch\", branchId]);\n          eavs.push([branchId, \"tag\", \"eve/compiler/block\"]);\n          subBlockToFacts(eavs, vars, branchId, branch.block);\n          let ix = 1;\n          for(let output of branch.outputs) {\n            let outputId = uuid();\n            eavs.push([branchId, \"output\", outputId]);\n            eavs.push([outputId, \"value\", asFactValue(vars, output)]);\n            eavs.push([outputId, \"index\", ix]);\n            ix++;\n          }\n        }\n        let ix = 1;\n        for(let output of scanLike.outputs) {\n          let outputId = uuid();\n          eavs.push([chooseId, \"output\", outputId]);\n          eavs.push([outputId, \"value\", asFactValue(vars, output)]);\n          eavs.push([outputId, \"index\", ix]);\n          ix++;\n        }\n        break;\n    }\n  }\n\n  for(let expr of block.expressions) {\n    let exprId = uuid();\n    let isAggregate = expr.op.indexOf(\"gather/\") === 0;\n    eavs.push([blockId, \"constraint\", exprId]);\n    eavs.push([exprId, \"tag\", \"eve/compiler/expression\"]);\n    if(isAggregate) {\n      eavs.push([exprId, \"tag\", \"eve/compiler/aggregate\"]);\n    }\n    eavs.push([exprId, \"op\", expr.op]);\n    if(expr.type === \"expression\") {\n      let ix = 1;\n      for(let arg of expr.args) {\n        let argId = uuid();\n        eavs.push([exprId, \"arg\", argId]);\n        eavs.push([argId, \"index\", ix]);\n        eavs.push([argId, \"value\", asFactValue(vars, arg)]);\n        ix++;\n      }\n      if(expr.variable) {\n        let returnId = uuid();\n        eavs.push([exprId, \"return\", returnId]);\n        eavs.push([returnId, \"index\", 1]);\n        eavs.push([returnId, \"value\", asFactValue(vars, expr.variable)]);\n      }\n    } else if(expr.type === \"functionRecord\") {\n      for(let arg of expr.record.attributes) {\n        let ix = 1;\n        if(arg.value.type === \"parenthesis\") {\n          for(let value of arg.value.items) {\n            let argId = uuid();\n            eavs.push([exprId, \"arg\", argId]);\n            eavs.push([argId, \"name\", arg.attribute]);\n\n            eavs.push([argId, \"value\", asFactValue(vars, value)]);\n            eavs.push([argId, \"index\", ix]);\n            ix++;\n          }\n        } else {\n          let argId = uuid();\n          eavs.push([exprId, \"arg\", argId]);\n          eavs.push([argId, \"name\", arg.attribute]);\n\n          eavs.push([argId, \"value\", asFactValue(vars, arg.value)]);\n          eavs.push([argId, \"index\", ix]);\n        }\n      }\n      if(expr.returns) {\n        let ix = 1;\n        for(let ret of expr.returns) {\n          let returnId = uuid();\n          eavs.push([exprId, \"return\", returnId]);\n          eavs.push([returnId, \"index\", ix]);\n          eavs.push([returnId, \"value\", asFactValue(vars, ret.value)]);\n          ix++;\n        }\n      } else if(expr.variable) {\n        let returnId = uuid();\n        eavs.push([exprId, \"return\", returnId]);\n        eavs.push([returnId, \"index\", 1]);\n        eavs.push([returnId, \"value\", asFactValue(vars, expr.variable)]);\n      }\n    }\n  }\n}\n\nexport function toFacts(eavs:any[], block:any) {\n  let blockId = uuid();\n  eavs.push([blockId, \"tag\", \"eve/compiler/rule\"]);\n  eavs.push([blockId, \"tag\", \"eve/compiler/block\"]);\n  eavs.push([blockId, \"name\", block.id]);\n  let blockType = \"bind\";\n  if(block.commits.length) { blockType = \"commit\"; }\n  eavs.push([blockId, \"type\", blockType]);\n\n  let vars:any = {};\n  for(let variable in block.variableLookup) {\n    let varId = uuid();\n    vars[variable] = varId;\n    eavs.push([varId, \"tag\", \"eve/compiler/var\"]);\n  }\n\n  subBlockToFacts(eavs, vars, blockId, block);\n\n  let outputs = block.binds.concat(block.commits);\n  for(let output of outputs) {\n    switch(output.type) {\n      case \"record\":\n        outputToFacts(eavs, vars, output, blockId);\n      break;\n      case \"action\":\n        outputToFacts(eavs, vars, {\n          action: output.action,\n          variable: output.entity,\n          attributes: [{type: \"attribute\", attribute: output.attribute, value: output.value, nonProjecting: true}]\n        }, blockId)\n        break;\n    }\n  }\n\n  return eavs;\n\n  // let lookup = find(\"eve/compiler/lookup\");\n  // let {record:rec, attribute, value} = lookup;\n\n}\n"
  },
  {
    "path": "src/programs/perfReport.ts",
    "content": "//---------------------------------------------------------------------\n// Performance Report\n//---------------------------------------------------------------------\nimport {v4 as uuid} from \"uuid\";\nimport {Program} from \"../watchers/watcher\";\nimport {PerformanceTracker} from \"../runtime/performance\";\n\n\nexport class PerformanceReporter {\n  constructor() { }\n\n  blocksToEAVs(blocks:any, eavs:any[] = []) {\n    for(let name in blocks) {\n      let {counts, times} = blocks[name];\n      let blockId = uuid();\n      eavs.push(\n        [blockId, \"tag\", \"block\"],\n        [blockId, \"name\", name],\n      );\n      for(let property in counts) {\n        let count = counts[property];\n        let time = times[property];\n        let propertyId = uuid();\n        eavs.push(\n          [blockId, \"property\", propertyId],\n          [propertyId, \"name\", property],\n          [propertyId, \"count\", count],\n          [propertyId, \"time\", time]\n        );\n      }\n    }\n    return eavs as any[];\n  }\n\n  totalsToEAVs(counts:any, times:any, eavs:any[] = []) {\n    let programId = uuid();\n    eavs.push(\n      [programId, \"tag\", \"total\"],\n    );\n    for(let property in counts) {\n      let count = counts[property];\n      let time = times[property];\n      let propertyId = uuid();\n      eavs.push(\n        [programId, \"property\", propertyId],\n        [propertyId, \"name\", property],\n        [propertyId, \"count\", count],\n        [propertyId, \"time\", time]\n      );\n    }\n  }\n\n  report(perf:PerformanceTracker) {\n    let eavs = this.blocksToEAVs(perf.blocks);\n    this.totalsToEAVs(perf.counts, perf.times, eavs);\n    let me = new Program(\"Performance report\");\n    me.attach(\"ui\");\n\n    me.bind(\"calculate block percentage\", ({find, record, lib: {math}}) => {\n      let total = find(\"total\");\n      total.property.name == \"transaction\"\n      let block = find(\"block\");\n      let property = block.property;\n      property.name == \"block\";\n      let percent = math.round(property.time * 100 / total.property.time);\n      return [\n        block.add(\"percent\", percent)\n      ]\n    });\n\n    me.bind(\"draw total\", ({find, record, lib: {math}}) => {\n      let container = find(\"block-times\")\n      let total = find(\"total\");\n      let property = total.property;\n      let avg = math.toFixed(property.time / property.count, 4);\n      let timeStr = math.toFixed(property.time, 2)\n      return [\n        container.add(\"children\", [\n          record(\"ui/row\", {total})\n          .add(\"style\", record({flex: \"none\", padding: \"10px 20px\", \"margin-bottom\":\"10px\", background: \"#ccc\"}))\n          .add(\"sort\", -10000).add(\"children\", [\n            record(\"ui/column\", {total, sort:0}).add(\"children\", [\n              record(\"ui/text\", {text:\"Total\", sort:0, style: record({\"font-size\": \"16pt\"})}),\n              record(\"ui/row\", {total, sort:1, style:record({\"font-size\":\"14pt\", margin:\"10px 0\"})}).add(\"children\", [\n                record(\"ui/text\", {text:property.count, sort:0}),\n                record(\"ui/text\", {text:timeStr, sort:1, style: record({margin: \"0 10px\"})}),\n                record(\"ui/text\", {text:avg, sort:2}),\n              ]),\n            ]),\n            record(\"ui/spacer\", {sort:1}),\n            record(\"ui/text\", {text:\"100%\", sort:1, style:record({\"font-size\":\"20pt\"})})\n          ])\n        ])\n      ]\n    });\n\n    me.bind(\"draw blocks 2\", ({find, record, lib: {math}}) => {\n      let block = find(\"block\");\n      let {name, property} = block;\n      property.name == \"block\"\n      let rev = -1;\n      let avg = math.toFixed(property.time / property.count, 4);\n      let timeStr = math.toFixed(property.time, 2)\n      let foo = record({\"font-size\":\"14pt\", margin: \"10px 0\"});\n      return [\n        record(\"ui/column\", \"block-times\", {style:record({\"max-width\":500}), sort:2}).add(\"children\", [\n          record(\"ui/row\", {block})\n            .add(\"style\", record({flex: \"none\", padding: \"10px 20px\", \"margin-bottom\":\"10px\", background: \"#ccc\"}))\n            .add(\"sort\", rev * property.time).add(\"children\", [\n              record(\"ui/column\", {block, sort:0}).add(\"children\", [\n                record(\"ui/text\", {text:block.name, sort:0, style: record({\"font-size\": \"16pt\"})}),\n                record(\"ui/row\", {block, sort:1, style:foo}).add(\"children\", [\n                  record(\"ui/text\", {text:property.count, sort:0}),\n                  record(\"ui/text\", {text:timeStr, sort:1, style: record({margin: \"0 10px\"})}),\n                  record(\"ui/text\", {text:avg, sort:2}),\n                ]),\n                record(\"ui/column\", \"props\", {block, sort:2})\n              ]),\n              record(\"ui/spacer\", {sort:1}),\n              record(\"ui/text\", {text:block.percent + \"%\", sort:1, style:record({\"font-size\":\"20pt\"})})\n            ])\n        ])\n      ]\n    });\n\n    me.bind(\"draw props\", ({find, record, lib: {math}}) => {\n      let container = find(\"props\");\n      let {block} = container;\n      let property = block.property;\n      property.name != \"block\"\n      let avg = math.toFixed(property.time / property.count, 4);\n      let timeStr = math.toFixed(property.time, 2)\n      return [\n        container.add(\"children\", [\n          record(\"ui/row\", {block, property, sort:property.name}).add(\"children\", [\n            record(\"ui/text\", {sort:0, text:property.name, style:record({\"margin-right\":\"15px\", width:\"120px\"})}),\n            record(\"ui/text\", {sort:1, text:property.count, style:record({width:50})}),\n            record(\"ui/text\", {sort:2, text:timeStr, style:record({margin: \"0 10px\", width:50})}),\n            record(\"ui/text\", {sort:3, text:avg, style:record({width:50})}),\n          ])\n        ])\n      ]\n    });\n\n    me.bind(\"Translate elements into html\", ({find, record, union}) => {\n      let elem = find(\"html/div\");\n      return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"div\")];\n    });\n\n    // console.profile(\"perf\");\n    me.inputEAVs(eavs);\n    // console.profileEnd();\n  }\n\n}\n\nlet baseline = {\"times\":{\"transaction\":1559.8550000000002},\"counts\":{\"transaction\":155},\"blocks\":{\"setup timers\":{\"counts\":{\"block\":5022,\"PresolveCheck\":0,\"GenericJoin\":0},\"times\":{\"block\":14.034999999994398,\"PresolveCheck\":0,\"GenericJoin\":0}},\"Remove click events!\":{\"counts\":{\"block\":10044,\"PresolveCheck\":110,\"GenericJoin\":0},\"times\":{\"block\":13.43499999997448,\"PresolveCheck\":0.23000000000092768,\"GenericJoin\":0}},\"Elements with no parents are roots.\":{\"counts\":{\"block\":10044,\"PresolveCheck\":1486,\"GenericJoin\":190},\"times\":{\"block\":42.510000000021364,\"PresolveCheck\":3.640000000006694,\"GenericJoin\":3.845000000001164}},\"Create an instance for each child of a rooted parent.\":{\"counts\":{\"block\":10044,\"PresolveCheck\":2230,\"GenericJoin\":268},\"times\":{\"block\":58.76499999999328,\"PresolveCheck\":12.61500000000592,\"GenericJoin\":9.5949999999998}},\"Export all instances.\":{\"counts\":{\"block\":10044,\"PresolveCheck\":1302,\"GenericJoin\":186},\"times\":{\"block\":32.044999999980746,\"PresolveCheck\":3.1350000000034015,\"GenericJoin\":4.429999999996198}},\"Export roots.\":{\"counts\":{\"block\":10044,\"PresolveCheck\":2,\"GenericJoin\":2},\"times\":{\"block\":10.035000000010314,\"PresolveCheck\":0.015000000000100044,\"GenericJoin\":0.03499999999962711}},\"Export instance parents.\":{\"counts\":{\"block\":10044,\"PresolveCheck\":554,\"GenericJoin\":184},\"times\":{\"block\":23.02999999999588,\"PresolveCheck\":1.8199999999999363,\"GenericJoin\":3.1999999999986812}},\"Export element styles.\":{\"counts\":{\"block\":10044,\"PresolveCheck\":10418,\"GenericJoin\":1},\"times\":{\"block\":70.72999999996205,\"PresolveCheck\":38.89999999996758,\"GenericJoin\":0.2949999999998454}},\"Export element attributes.\":{\"counts\":{\"block\":10044,\"PresolveCheck\":10788,\"GenericJoin\":4086},\"times\":{\"block\":133.43500000000154,\"PresolveCheck\":42.17999999998483,\"GenericJoin\":43.32000000000221}},\"draw the game world\":{\"counts\":{\"block\":5022,\"PresolveCheck\":1,\"GenericJoin\":1},\"times\":{\"block\":11.345000000002983,\"PresolveCheck\":0.044999999999845386,\"GenericJoin\":2.7650000000003274}},\"draw the main menu\":{\"counts\":{\"block\":5022,\"PresolveCheck\":4,\"GenericJoin\":2},\"times\":{\"block\":9.915000000004056,\"PresolveCheck\":0.08999999999991815,\"GenericJoin\":0.19000000000028194}},\"draw the game over menu\":{\"counts\":{\"block\":5022,\"PresolveCheck\":10,\"GenericJoin\":0},\"times\":{\"block\":15.044999999990296,\"PresolveCheck\":0.09000000000105501,\"GenericJoin\":0}},\"calculate the score\":{\"counts\":{\"block\":5022,\"PresolveCheck\":200,\"GenericJoin\":199},\"times\":{\"block\":14.614999999993188,\"PresolveCheck\":0.6000000000005912,\"GenericJoin\":2.8449999999988904}},\"clicking starts the game\":{\"counts\":{\"block\":5022,\"PresolveCheck\":127,\"GenericJoin\":110},\"times\":{\"block\":39.78500000001327,\"PresolveCheck\":1.9649999999994634,\"GenericJoin\":7.139999999999873}},\"draw the player\":{\"counts\":{\"block\":5022,\"PresolveCheck\":1428,\"GenericJoin\":197},\"times\":{\"block\":40.729999999991605,\"PresolveCheck\":9.180000000000518,\"GenericJoin\":11.09999999999468}},\"draw obstacles\":{\"counts\":{\"block\":5022,\"PresolveCheck\":1080,\"GenericJoin\":378},\"times\":{\"block\":162.57000000000403,\"PresolveCheck\":7.26999999999407,\"GenericJoin\":99.70999999999572}},\"every 2 distance, a wild obstacle appears\":{\"counts\":{\"block\":5022,\"PresolveCheck\":204,\"GenericJoin\":200},\"times\":{\"block\":40.62999999998988,\"PresolveCheck\":2.460000000001628,\"GenericJoin\":22.080000000002656}},\"adjust the height of the gap\":{\"counts\":{\"block\":5022,\"PresolveCheck\":2044,\"GenericJoin\":440},\"times\":{\"block\":133.72000000002367,\"PresolveCheck\":64.90000000000441,\"GenericJoin\":47.21999999999548}},\"apply a velocity when you click\":{\"counts\":{\"block\":5022,\"PresolveCheck\":224,\"GenericJoin\":110},\"times\":{\"block\":18.65500000000452,\"PresolveCheck\":2.624999999998181,\"GenericJoin\":3.0799999999994725}},\"scroll the world\":{\"counts\":{\"block\":5022,\"PresolveCheck\":2384,\"GenericJoin\":1095},\"times\":{\"block\":482.3849999999866,\"PresolveCheck\":261.6300000000017,\"GenericJoin\":173.37500000000523}},\"Translate elements into html\":{\"counts\":{\"block\":5022,\"PresolveCheck\":1,\"GenericJoin\":0},\"times\":{\"block\":5.629999999994197,\"PresolveCheck\":0.010000000000218279,\"GenericJoin\":0}},\"Translate elements into svg\":{\"counts\":{\"block\":20088,\"PresolveCheck\":185,\"GenericJoin\":0},\"times\":{\"block\":21.100000000014916,\"PresolveCheck\":0.4450000000001637,\"GenericJoin\":0}}}}\n"
  },
  {
    "path": "src/runtime/dsl2.ts",
    "content": "//@FIXME: This doesn't currently handle chooses/unions that rely on eachother.\n\n//--------------------------------------------------------------------\n// Javascript DSL for writing Eve programs\n//--------------------------------------------------------------------\n\nimport {RawValue, Change, RawEAV, RawEAVC, Register, isRegister, GlobalInterner, ID, concatArray} from \"./runtime\";\nimport * as Runtime from \"./runtime\";\nimport * as indexes from \"./indexes\";\nimport {Watcher, Exporter, DiffConsumer, ObjectConsumer, RawRecord} from \"../watchers/watcher\";\nimport * as Parser from \"../parser/parser\";\nimport \"./stdlib\";\nimport {SumAggregate} from \"./stdlib\";\nimport {v4 as uuid} from \"uuid\";\nimport * as falafel from \"falafel\";\n\nconst UNASSIGNED = -1;\nconst operators:any = {\n  \"+\": \"math['+']\",\n  \"-\": \"math['-']\",\n  \"*\": \"math['*']\",\n  \"/\": \"math['/']\",\n  \">\": \"compare['>']\",\n  \">=\": \"compare['>=']\",\n  \"<\": \"compare['<']\",\n  \"<=\": \"compare['<=']\",\n  \"!=\": \"compare['!=']\",\n  \"==\": \"compare['==']\",\n  \"concat\": \"eve.internal.concat\",\n}\n\n// There don't seem to be TypeScript definitions for these by default,\n// so here we are.\ndeclare var Proxy:new (obj:any, proxy:any) => any;\n\nfunction isArray<T>(v:any): v is Array<T> {\n  return v && v.constructor === Array;\n}\n\nfunction isASTString(thing:any) {\n  return thing.value && typeof thing.value === \"string\";\n}\n\nfunction macro<FuncType extends Function>(func:FuncType, transform:(code:string, args:string[], name:string) => string):FuncType {\n  let code = func.toString();\n  // trim the function(...) { from the start and capture the arg names\n\n  let name:string = \"\";\n  code = code.replace(/function\\s*(\\w*)\\s*/, (str:string, funcName:string) => {\n    name = funcName;\n    return \"\";\n  })\n\n  let functionArgs:string[] = [];\n  code = code.replace(/\\((.*)\\)\\s*\\{/m, function(str:string, args:string) {\n    functionArgs.push.apply(functionArgs, args.split(\",\").map((str) => str.trim()));\n    return \"\";\n  });\n  // trim the final } since we removed the function bit\n  code = code.substring(0, code.length - 1);\n  code = transform(code, functionArgs, name);\n\n  let neueFunc = (new Function(`\n    return function ${name}(${functionArgs.join(\", \")}) {\n      ${code}\n    };\n  `))() as FuncType;\n\n  return neueFunc;\n}\n\n//--------------------------------------------------------------------\n// Reference\n//--------------------------------------------------------------------\n\nexport type Value = Reference|RawValue;\nexport type ProxyReference = any;\n\nfunction isRawValue(v:any): v is RawValue {\n  return (typeof v === \"string\" || typeof v === \"number\");\n}\n\nfunction isReference(v:any): v is Reference {\n  return (v instanceof Reference);\n}\n\nexport type Owner = any;\n\nexport class Reference {\n  static ReferenceID = 0;\n  static create(context:ReferenceContext, value?:Owner|RawValue) {\n    if(typeof value !== \"object\") {\n      let neue = new Reference(context);\n      if(value !== undefined) context.equality(neue, value);\n      return neue;\n    }\n    return new Reference(context, value);\n  }\n\n  __ID = Reference.ReferenceID++;\n  __forceRegister = false;\n\n  constructor(public __context:ReferenceContext, public __owner?:Owner) {\n    let proxied = this.__proxy();\n    __context.register(proxied);\n    this.__owner = __owner || null;\n    return proxied;\n  }\n\n  add(attrMap:{[attr:string]:Value|Value[]}):Reference;\n  add(attribute:Value, value:Value|Value[]):Reference;\n  add(attrMapOrAttr:Value|{[attr:string]:Value|Value[]}, value?:Value|Value[]):Reference {\n    if(!this.__owner) {\n      this.__owner = new Record(this.__context, [], {}, this);\n    }\n\n    if(this.__owner instanceof Record) {\n      // we only allow you to call add at the root context\n      if(this.__context.parent) throw new Error(\"Add can't be called in a sub-block\");\n      if(isRawValue(attrMapOrAttr) || isReference(attrMapOrAttr)) {\n        let attribute = attrMapOrAttr;\n        if(value === undefined) throw new Error(\"Can't call add without a value.\");\n        this.__owner.add(this.__context, attribute, value);\n      } else {\n        for(let attribute of Object.keys(attrMapOrAttr)) {\n          let value = attrMapOrAttr[attribute];\n          this.__owner.add(this.__context, attribute, value);\n        }\n      }\n\n      return this;\n    } else {\n      throw new Error(\"Can't call add on a non-record\");\n    }\n  }\n\n  // @TODO: allow free A's and V's here\n  remove(attribute?:Value, value?:Value|Value[]):Reference {\n    if(!this.__owner) {\n      this.__owner = new Record(this.__context, [], {}, this);\n    }\n\n    if(this.__owner instanceof Record) {\n      // we only allow you to call remove at the root context\n      if(this.__context.parent) throw new Error(\"Add can't be called in a sub-block\");\n      this.__owner.remove(this.__context, attribute as any, value as any);\n      return this;\n    } else {\n      throw new Error(\"Can't call add on a non-record\");\n    }\n  }\n\n  toString() {\n    return `${this.__context.ID}.${this.__ID}`;\n  }\n\n  __proxy() {\n    return new Proxy(this, {\n      get: (obj:any, prop:string) => {\n        if(obj[prop] !== undefined) return obj[prop];\n        if(typeof prop === \"symbol\") return () => {\n          return \"uh oh\";\n        }\n\n        let active = this.__context.getActive();\n        if(!active) {\n          return;\n        }\n\n        if(!this.__owner) {\n          this.__owner = new Record(active, [], {}, this);\n        }\n\n        return this.__owner.access(this.__context, active, prop);\n      },\n\n      set: (obj:any, prop:string, value:any) => {\n        if(obj[prop] !== undefined) {\n          obj[prop] = value;\n          return true;\n        }\n        throw new Error(\"Cannot set a value on a reference\")\n      }\n    });\n  }\n}\n\n//--------------------------------------------------------------------\n// ReferenceContext\n//--------------------------------------------------------------------\n\nexport class ReferenceContext {\n  static IDs = 0;\n  static stack:ReferenceContext[] = [];\n  static push(context:ReferenceContext) {\n    ReferenceContext.stack.push(context);\n  }\n  static pop() {\n    ReferenceContext.stack.pop();\n  }\n\n  ID:number;\n  flow: LinearFlow;\n  references:Reference[] = [];\n  equalities:Value[][] = [];\n  forcedMoves:Value[][] = [];\n  referenceValues: (Register|RawValue)[] = [];\n  totalRegisters = 0;\n  maxRegisters = 0;\n\n  constructor(public parent?:ReferenceContext, flow?:LinearFlow) {\n    this.ID = ReferenceContext.IDs++;\n    this.flow = flow || new LinearFlow((x:LinearFlow) => []);\n  }\n\n  register(ref:Reference) {\n    // if this reference is not owned by this context, we have to walk up the context\n    // stack and register this for our parents until we find a layer that *does* own it\n    // so that it can be considered an input to that context.\n    let {parent} = this;\n    if(!this.owns(ref) && parent) {\n      while(parent && parent !== ref.__context) {\n        parent.register(ref);\n        parent = parent.parent;\n      }\n    }\n    if(!this.owns(ref) && parent !== ref.__context) {\n      console.error(\"Reference with no owner in the parent stack: \", ref);\n      throw new Error(\"Reference with no owner in the parent stack\")\n    }\n\n    if(!this.references[ref.__ID]) this.references[ref.__ID] = ref;\n  }\n\n  equality(a:Value, b:Value) {\n    if(a instanceof Reference) {\n      this.register(a);\n    }\n    if(b instanceof Reference) {\n      this.register(b);\n    }\n    this.equalities.push([a,b]);\n  }\n\n  getActive():ReferenceContext {\n    return ReferenceContext.stack[ReferenceContext.stack.length - 1];\n  }\n\n  owns(ref:Reference) {\n    return ref.__context === this;\n  }\n\n  getValue(ref:Reference|RawValue, orGenerateRegister?:boolean):Register|RawValue {\n    if(isRawValue(ref)) return ref;\n    let val = this.referenceValues[ref.__ID];\n    if(val === undefined) {\n      if(!this.owns(ref) && this.parent) return this.parent.getValue(ref);\n      if(orGenerateRegister) {\n        val = new Register(UNASSIGNED);\n        this.referenceValues[ref.__ID] = val;\n      }\n    }\n    if(val === undefined) throw new Error(\"Unable to resolve reference: \" + ref.__ID);\n    return val;\n  }\n\n  interned(ref:Reference|RawValue):Register|ID {\n    let value = this.getValue(ref);\n    if(isRawValue(value)) return GlobalInterner.intern(value);\n    return value;\n  }\n\n  maybeInterned(ref:Reference|RawValue|undefined):Register|ID|undefined {\n    if(ref === undefined) return;\n    let value = this.getValue(ref);\n    if(isRawValue(value)) return GlobalInterner.intern(value);\n    return value;\n  }\n\n  selectReference(refIds:any, ref:Reference, ref2:Reference) {\n    let refID = refIds[ref.__ID] || ref.__ID;\n    let ref2ID = refIds[ref2.__ID] || ref2.__ID;\n    if(!this.owns(ref) && !this.owns(ref2)) {\n      if(ref2ID < refID) return ref2;\n      return ref;\n    }\n    if(!this.owns(ref)) return ref;\n    if(!this.owns(ref2)) return ref2;\n    if(ref2ID < refID) return ref2;\n    return ref;\n  }\n\n  unify() {\n    let {equalities} = this;\n    let values:(Register | RawValue)[] = this.referenceValues;\n    let forcedMoves = [];\n    let changed = equalities.length > 0;\n    let refIds:any = {};\n\n    let round = 0;\n    let maxRound = Math.pow(this.equalities.length + 1, 2);\n    for(let ref of this.references) {\n      if(!ref) continue;\n      this.getValue(ref, true);\n    }\n    while(changed && round < maxRound) {\n      round++;\n      changed = false;\n      for(let [a, b] of equalities) {\n        let aValue = isReference(a) ? this.getValue(a, true) : a;\n        let bValue = isReference(b) ? this.getValue(b, true) : b;\n        let neueA = aValue;\n        let neueB = bValue;\n\n\n        if((a as Reference).__forceRegister || (b as Reference).__forceRegister) {\n          forcedMoves.push([a,b]);\n        } else if(!isRegister(neueB) && !isRegister(neueA) && neueA != neueB) {\n          throw new Error(`Attempting to unify two disparate static values: \\`${neueA}\\` and \\`${neueB}\\``);\n        } else if(isReference(a) && !isRegister(neueB)) {\n          neueA = bValue;\n        } else if(isReference(b) && !isRegister(neueA)) {\n          neueB = aValue;\n        } else if(isReference(a) && isReference(b)) {\n          if(this.selectReference(refIds, a, b) === b) {\n            neueA = bValue;\n            refIds[a.__ID] = refIds[b.__ID] || b.__ID\n          } else {\n            neueB = aValue;\n            refIds[b.__ID] = refIds[a.__ID] || a.__ID\n          }\n        } else if(isReference(a)) {\n          neueA = bValue;\n        } else if(isReference(b)) {\n          neueB = aValue;\n        } else if(a !== b) {\n          throw new Error(`Attempting to unify two disparate static values: \\`${a}\\` and \\`${b}\\``);\n        }\n\n        if(aValue !== neueA) {\n          // console.log(\"Unifying A\", a.toString(), b.toString(), neueA, neueB);\n          values[(a as Reference).__ID] = neueA;\n          changed = true;\n        }\n        if(bValue !== neueB) {\n          // console.log(\"Unifying B\", a.toString(), b.toString(), neueA, neueB);\n          values[(b as Reference).__ID] = neueB;\n          changed = true;\n        }\n      }\n    }\n    if(round >= maxRound) {\n      throw new Error(\"Unable to unify variables. This is almost certainly an implementation error.\");\n    }\n    this.assignRegisters()\n    this.forcedMoves = forcedMoves;\n  }\n\n  getMoves() {\n    let moves = [];\n    for(let [a,b] of this.forcedMoves) {\n      let to = isReference(a) ? a : b;\n      let from = to === a ? b : a;\n      moves.push(new Move(this, from, to as Reference));\n    }\n    for(let ref of this.references) {\n      if(ref === undefined || this.owns(ref)) continue;\n      let local = this.getValue(ref);\n      let parent = ref.__context.getValue(ref);\n      if(local !== parent) {\n        moves.push(new Move(this, ref));\n      }\n    }\n    return moves;\n  }\n\n  getInputReferences():Reference[] {\n    let refs = [];\n    for(let reference of this.references) {\n      if(!reference || this.owns(reference)) continue;\n      refs.push(reference);\n    }\n    return refs;\n  }\n\n  getInputRegisters():Register[] {\n    return this.getInputReferences().map((v) => this.getValue(v)).filter(isRegister) as Register[];\n  }\n\n  updateMaxRegisters(maybeMax:number) {\n    let parent:ReferenceContext|undefined = this;\n    while(parent) {\n      parent.maxRegisters = Math.max(parent.maxRegisters, maybeMax);\n      parent = parent.parent;\n    }\n  }\n\n  assignRegisters() {\n    let startIx = this.parent ? this.parent.totalRegisters : 0;\n    for(let ref of this.references) {\n      if(ref === undefined) continue;\n      let local = this.getValue(ref);\n      if(isRegister(local)) {\n        if(local.offset >= startIx) throw new Error(\"Trying to assign a register that already has a higher offset\");\n        if(local.offset === UNASSIGNED) {\n          local.offset = startIx++;\n        }\n      }\n    }\n    this.totalRegisters = startIx;\n    this.updateMaxRegisters(startIx);\n  }\n}\n\n//--------------------------------------------------------------------\n// Linear Flow\n//--------------------------------------------------------------------\n\nexport type Node = Record | Insert | Fn | Not | Choose | Union | Aggregate | Lookup | Move;\nexport type LinearFlowFunction = (self:LinearFlow) => (Value|Value[])\nexport type RecordAttributes = {[key:string]:Value|Value[]}\nexport type FlowRecordArg = string | RecordAttributes\n\nexport class FlowLevel {\n  records:Record[] = [];\n  lookups:Lookup[] = [];\n  functions:Fn[] = [];\n  aggregates:Aggregate[] = [];\n  inserts:Insert[] = [];\n  nots:Not[] = [];\n  chooses:Choose[] = [];\n  unions:Union[] = [];\n  moves:Move[] = [];\n\n  collect(node:Node) {\n    if(node instanceof Insert) {\n      this.inserts.push(node);\n    } else if(node instanceof Record) {\n      this.records.push(node);\n    } else if(node instanceof Lookup) {\n      this.lookups.push(node);\n    } else if(node instanceof Fn) {\n      this.functions.push(node);\n    } else if(node instanceof Aggregate) {\n      this.aggregates.push(node);\n    } else if(node instanceof Not) {\n      this.nots.push(node);\n    } else if(node instanceof Choose) {\n      this.chooses.push(node);\n    } else if(node instanceof Union) {\n      this.unions.push(node);\n    } else if(node instanceof Move) {\n      if(!this.moves.some((v) => v.toKey() == node.toKey())) {\n        this.moves.push(node);\n      }\n    } else {\n      console.error(\"Don't know how to collect this type of node: \", node);\n      throw new Error(\"Unknown node type sent to collect\");\n    }\n  }\n\n  findReference(node:any) {\n    let ref = node.reference();\n    let items;\n    if(node instanceof Record) {\n      items = this.records;\n    } else {\n      console.error(\"Don't know how to lookup a: \", node);\n      throw new Error(\"Unknown node type sent to findReference\");\n    }\n    for(let item of items) {\n      if(item.reference() === ref) return item;\n    }\n  }\n\n  toConstraints(injections:Node[]) {\n    let items:(Record|Fn|Lookup)[] = [];\n    concatArray(items, injections);\n    concatArray(items, this.functions);\n    concatArray(items, this.records);\n    concatArray(items, this.lookups);\n    concatArray(items, this.moves);\n    return items;\n  }\n\n  compile(nodes:Runtime.Node[], injections:Node[], toPass:Node[]):Runtime.Node[] {\n    let items = this.toConstraints(injections);\n    let seen:any = {};\n    let constraints:Runtime.Constraint[] = [];\n    for(let toCompile of items) {\n      let compiled = toCompile.compile();\n      for(let item of compiled) {\n        if(!(item instanceof Runtime.Scan)) {\n          constraints.push(item as Runtime.Constraint);\n        } else {\n          let key = item.toKey();\n          if(!seen[key]) {\n            seen[key] = true;\n            constraints.push(item as Runtime.Constraint);\n          }\n        }\n      }\n    }\n\n    let join:Runtime.Node;\n    if(!nodes.length && constraints.length) {\n      join = new Runtime.JoinNode(constraints);\n      if(!this.records.length && !this.lookups.length && !this.moves.length) {\n        (join as Runtime.JoinNode).isStatic = true;\n      }\n    } else if(constraints.length) {\n      join = new Runtime.DownstreamJoinNode(constraints);\n    } else if(nodes.length) {\n      join = nodes.pop() as Runtime.Node;\n    } else {\n      join = new Runtime.NoopJoinNode([]);\n    }\n\n    // @NOTE: We need to unify all of our sub-blocks along with ourselves\n    //        before the root node can allocate registers.\n    for(let not of this.nots) {\n      // All sub blocks take their parents' items and embed them into\n      // the sub block. This is to make sure that the sub only computes the\n      // results that might actually join with the parent instead of the possibly\n      // very large set of unjoined results. This isn't guaranteed to be optimal\n      // and may very well cause us to do more work than necessary. For example if\n      // the results of the inner join with many outers, we'll still enumerate the\n      // whole set. This *may* be necessary for getting the correct multiplicities\n      // anyways, so this is what we're doing.\n      let notNodes = not.compile(toPass.concat(items));\n      // @TODO: once we have multiple nodes in a not (e.g. aggs, or recursive not/choose/union)\n      // this won't be sufficient.\n      let notJoinNode = notNodes[0];\n      join = new Runtime.AntiJoin(join, notJoinNode, not.getInputRegisters())\n    }\n\n    for(let choose of this.chooses) {\n      // For why we pass items down, see the comment about not\n      join = choose.compile(join);\n    }\n\n    for(let union of this.unions) {\n      // For why we pass items down, see the comment about not\n      join = union.compile(join);\n    }\n\n    for(let aggregate of this.aggregates) {\n      let aggregateNode = aggregate.compile();\n      join = new Runtime.MergeAggregateFlow(join, aggregateNode, aggregate.getJoinRegisters(), aggregate.getOutputRegisters());\n    }\n\n    nodes.push(join);\n    return nodes;\n  }\n\n  split(context:ReferenceContext):FlowLevel[] {\n    let maxLevel = 0;\n    // if a register can be filled from the database, it doesn't need to be up-leveled,\n    // since we always have a value for it from the beginning. Let's find all of those\n    // registers so we can ignore them in our functions\n    let databaseSupported = concatArray([], this.records);\n    concatArray(databaseSupported, this.lookups);\n    let supported:boolean[] = [];\n    for(let item of this.records) {\n      let registers = item.getRegisters();\n      for(let register of registers) {\n        supported[register.offset] = true;\n      }\n    }\n\n    // Every register that's an input to this level is supported by the outer context,\n    // so we don't need to level its output.\n    for(let register of context.getInputRegisters()) {\n      supported[register.offset] = true;\n    }\n\n    // Any move whose `from` is supported, has their `to` supported as well\n    for(let move of this.moves) {\n      let from = move.getInputRegisters()[0];\n      let to = move.getOutputRegisters()[0];\n      if(from && supported[from.offset]) {\n        supported[to.offset] = true;\n      }\n    }\n\n    // choose, union, and aggregates can cause us to need multiple levels\n    // if there's something that relies on an output from one of those, it\n    // has to come in a level after that thing is computed.\n    let changed = false;\n    let leveledRegisters:{[offset:number]: {level:number, providers:any[]}} = {};\n    let providerToLevel:{[id:number]: number} = {};\n    let items = concatArray([], this.chooses);\n    concatArray(items, this.unions);\n    concatArray(items, this.aggregates);\n    for(let item of items) {\n      for(let result of item.getOutputRegisters()) {\n        let offset = result.offset;\n        if(!supported[offset]) {\n          let found = leveledRegisters[offset];\n          if(!found) {\n            found = leveledRegisters[offset] = {level: 1, providers: []};\n          }\n          leveledRegisters[offset].providers.push(item);\n          providerToLevel[item.ID] = 0;\n          changed = true;\n          maxLevel = 1;\n        }\n      }\n    }\n    // go through all the functions, nots, chooses, and unions to see if they rely on\n    // a register that has been leveled, if so, they need to move to a level after\n    // the provider's heighest\n    concatArray(items, this.functions);\n    concatArray(items, this.nots);\n    concatArray(items, this.moves);\n    let remaining = items.length * items.length;\n    while(changed && remaining > -1) {\n      changed = false;\n      for(let item of items) {\n        remaining--;\n        if(!item) continue;\n\n        let changedProvider = false;\n        let providerLevel = providerToLevel[item.ID] || 0;\n        let regs = item.getInputRegisters();\n\n        for(let input of regs) {\n          let inputInfo = leveledRegisters[input.offset];\n          if(inputInfo && inputInfo.level > providerLevel) {\n            changedProvider = true;\n            providerLevel = inputInfo.level;\n          }\n        }\n\n        if(changedProvider) {\n          providerToLevel[item.ID] = providerLevel;\n          // level my outputs\n          if(item.getOutputRegisters) {\n            for(let output of item.getOutputRegisters()) {\n              if(supported[output.offset]) continue;\n              let outputInfo = leveledRegisters[output.offset];\n              if(!outputInfo) {\n                outputInfo = leveledRegisters[output.offset] = {level: 0, providers: []};\n              }\n              if(outputInfo.providers.indexOf(item) === -1) {\n                outputInfo.providers.push(item);\n              }\n              if(outputInfo.level <= providerLevel) {\n                outputInfo.level = providerLevel + 1;\n              }\n            }\n          }\n          maxLevel = Math.max(maxLevel, providerLevel);\n          changed = true;\n        }\n      }\n    }\n\n    if(remaining === -1) {\n      // we couldn't stratify\n      throw new Error(\"Unstratifiable program: cyclic dependency\");\n    }\n\n    // now we put all our children into a series of objects that\n    // represent each level\n    let levels:FlowLevel[] = [];\n    for(let ix = 0; ix <= maxLevel; ix++) {\n      levels[ix] = new FlowLevel();\n    }\n\n    // @FIXME: An implementation issue in 0.3.0 causes stratified subblocks (nots, unions, and chooses) to\n    // break in several edge cases. To prevent foot-shootiness until we've resolved the problem, we disallow\n    // any orderings which are potentially dangerous.\n    let dangerousOrdering = false;\n\n    // all database scans are at the first level\n    for(let record of this.records) {\n      levels[0].records.push(record);\n    }\n    for(let lookup of this.lookups) {\n      levels[0].lookups.push(lookup);\n    }\n\n    // functions/nots/chooses/unions can all be in different levels\n    for(let func of this.functions) {\n      if(!func) continue;\n      let level = providerToLevel[func.ID] || 0;\n      levels[level].functions.push(func);\n    }\n\n    for(let not of this.nots) {\n      let level = providerToLevel[not.ID] || 0;\n      if(level > 0) dangerousOrdering = true;\n      levels[level].nots.push(not);\n    }\n\n    for(let choose of this.chooses) {\n      let level = providerToLevel[choose.ID] || 0;\n      if(level > 0) dangerousOrdering = true;\n      levels[level].chooses.push(choose);\n    }\n\n    for(let union of this.unions) {\n      let level = providerToLevel[union.ID] || 0;\n      if(level > 0) dangerousOrdering = true;\n      levels[level].unions.push(union);\n    }\n\n    for(let aggregate of this.aggregates) {\n      let level = providerToLevel[aggregate.ID] || 0;\n      levels[level].aggregates.push(aggregate);\n    }\n\n    for(let move of this.moves) {\n      let level = providerToLevel[move.ID] || 0;\n      levels[level].moves.push(move);\n    }\n\n    if(dangerousOrdering) throw new Error(`Refusing to compile potentially dangerous ordering with stratified subblock(s). This is avoids an implementation issue in the 0.3 runtime. Until it's lifted, you can work around it by splitting any subblocks (nots, unions, or chooses) that depend directly on other subblocks into separate blocks. Please contact the Eve team on the mailing list for further assistance.`);\n\n    return levels;\n  }\n}\n\nexport class DSLBase {\n  static CurrentID = 1;\n  ID = DSLBase.CurrentID++;\n}\n\nexport class LinearFlow extends DSLBase {\n  context:ReferenceContext;\n  collector:FlowLevel = new FlowLevel();\n  levels:FlowLevel[] = [];\n  results:Value[];\n  parent:LinearFlow|undefined;\n\n  constructor(func:LinearFlowFunction, parent?:LinearFlow) {\n    super();\n    let parentContext = parent ? parent.context : undefined;\n    this.parent = parent;\n    this.createLib();\n    this.context = new ReferenceContext(parentContext, this);\n    let transformed = func;\n    if(!parent) {\n      transformed = this.transform(func);\n    }\n    ReferenceContext.push(this.context);\n    let results = transformed.call(func, this) as Value[];\n    if(isArray(results)) this.results = results;\n    else if(results === undefined) this.results = [];\n    else this.results = [results];\n    for(let result of this.results) {\n      if(isReference(result)) {\n        this.context.register(result);\n      }\n    }\n    ReferenceContext.pop();\n  }\n\n  //------------------------------------------------------------------\n  // Create lib\n  //------------------------------------------------------------------\n\n  lib: any;\n  createLib() {\n    let lib:any = {};\n    let registered = Runtime.FunctionConstraint.registered;\n    for(let name in registered) {\n      let parts = name.replace(/\\/\\//gi, \"/slash\").split(\"/\").map((v) => v === \"slash\" ? \"/\" : v);\n      let final = parts.pop();\n      let found = lib;\n      for(let part of parts) {\n        let next = found[part];\n        if(!next) next = found[part] = {};\n        found = next;\n      }\n      found[final!] = (...args:any[]) => {\n        let fn = new Fn(this.context.getActive(), name, args);\n        return fn.reference();\n      }\n\n    }\n    lib.compare[\"==\"] = (a:any, b:any) => {\n      this.context.getActive().equality(a, b);\n      return b;\n    }\n    this.lib = lib;\n  }\n\n  //------------------------------------------------------------------\n  // Collector interactions\n  //------------------------------------------------------------------\n\n  collect(node:Node) {\n    this.collector.collect(node);\n  }\n\n  findReference(node:Node) {\n    this.collector.findReference(node);\n  }\n\n  //------------------------------------------------------------------\n  // Inputs/outputs\n  //------------------------------------------------------------------\n\n  getInputRegisters() {\n    return this.context.getInputRegisters();\n  }\n\n  //------------------------------------------------------------------\n  // End user API\n  //------------------------------------------------------------------\n\n  find = (...args:FlowRecordArg[]):ProxyReference => {\n    let tags = args;\n    let attributes = tags.pop();\n    if(typeof attributes === \"string\") {\n      tags.push(attributes);\n      attributes = undefined;\n    }\n    let active = this.context.getActive();\n    let record = new Record(active, tags as string[], attributes);\n    return record.reference();\n  }\n\n  lookup = (record:Value): {attribute:ProxyReference, value:ProxyReference} => {\n    let active = this.context.getActive();\n    let lookup = new Lookup(active, record);\n    return lookup.output();\n  }\n\n  record = (...args:FlowRecordArg[]):ProxyReference => {\n    let tags = args;\n    let attributes = tags.pop();\n    if(typeof attributes === \"string\") {\n      tags.push(attributes);\n      attributes = undefined;\n    }\n    let active = this.context.getActive();\n    let insert = new Insert(active, tags as string[], attributes);\n    return insert.reference();\n  }\n\n  not = (func:Function):void => {\n    let active = this.context.getActive();\n    let not = new Not(func as LinearFlowFunction, active.flow.parent || this);\n    return;\n  }\n\n  union = (...branches:Function[]):ProxyReference[] => {\n    let active = this.context.getActive();\n    let union = new Union(active, branches, active.flow.parent || this);\n    return union.results.slice();\n  }\n\n  choose = (...branches:Function[]):ProxyReference[] => {\n    let active = this.context.getActive();\n    let choose = new Choose(active, branches, active.flow.parent || this);\n    return choose.results.slice();\n  }\n\n  gather = (...projection:Reference[]) => {\n    let active = this.context.getActive();\n    return new AggregateBuilder(active, projection);\n  }\n\n  //------------------------------------------------------------------\n  // Compile\n  //------------------------------------------------------------------\n\n  unify() {\n    this.context.unify();\n  }\n\n  compile(_:Node[] = []):Runtime.Node[] {\n    let items:Node[] = []\n    this.unify();\n    let nodes:Runtime.Node[] = [];\n\n    for(let move of this.context.getMoves()) {\n      this.collect(move);\n    }\n\n    // Split our collector into levels\n    let levels = this.collector.split(this.context);\n    let localItems = items.slice();\n    for(let level of levels) {\n      nodes = level.compile(nodes, items, localItems);\n      concatArray(localItems, level.toConstraints([]));\n    }\n\n    // all the inputs end up at the end\n    let outputs:Runtime.OutputNode[] = [];\n    for(let record of this.collector.inserts) {\n      let compiled = record.compile();\n      for(let node of compiled) {\n        if(node instanceof Runtime.WatchNode) {\n          nodes.push(node);\n        } else {\n          outputs.push(node as any); // @FIXME: types\n        }\n      }\n    }\n    if(outputs.length) {\n      nodes.push(new Runtime.OutputWrapperNode(outputs));\n    }\n\n    this.levels = levels;\n    return nodes;\n  }\n\n  //------------------------------------------------------------------\n  // Function transformation\n  //------------------------------------------------------------------\n\n  transform(func:LinearFlowFunction) {\n    return macro(func, this.transformCode);\n  }\n\n  transformCode = (code:string, functionArgs:string[]):string => {\n    if(!functionArgs[0]) throw new Error(`Trying to create a block that has no args with code:\\n\\n\\`\\`\\`\\n${code}\\n\\`\\`\\``);\n    var output = falafel(`function f() { var $___eve_block$ = ${functionArgs[0]}; ${code} }`, function (node:any) {\n      if (node.type === 'BinaryExpression') {\n        let func = operators[node.operator] as string;\n        if(node.operator === \"+\" && (isASTString(node.left) || isASTString(node.right))) {\n          func = operators[\"concat\"];\n        }\n        if(func) {\n          node.update(`$___eve_block$.lib.${func}(${node.left.source()}, ${node.right.source()})`)\n        }\n      }\n    });\n    let updated = output.toString();\n    updated = updated.replace(\"function f() {\", \"\");\n    updated = updated.substring(0, updated.length - 1);\n    return updated;\n  }\n}\n\n//--------------------------------------------------------------------\n// WatchFlow\n//--------------------------------------------------------------------\n\nexport class WatchFlow extends LinearFlow {\n  collect(node:Node) {\n    if(!(node instanceof Watch) && node instanceof Insert) {\n      node = node.toWatch();\n      return;\n    }\n    super.collect(node);\n  }\n}\n\n//--------------------------------------------------------------------\n// CommitFlow\n//--------------------------------------------------------------------\n\nexport class CommitFlow extends LinearFlow {\n  collect(node:Node) {\n    if(!(node instanceof CommitInsert) && !(node instanceof CommitRemove) && node instanceof Insert) {\n      node = node.toCommit();\n      return;\n    }\n    super.collect(node);\n  }\n}\n\n//--------------------------------------------------------------------\n// DSL runtime types\n//--------------------------------------------------------------------\n\n//--------------------------------------------------------------------\n// Record\n//--------------------------------------------------------------------\n\nexport class Record extends DSLBase {\n  attributes:Value[];\n  constructor(public context:ReferenceContext, tags:string[] = [], attributes:RecordAttributes = {}, public record?:Reference) {\n    super();\n    if(!record) {\n      this.record = this.createReference();\n    } else {\n      context.register(record);\n    }\n    let attrs:Value[] = [];\n    for(let tag of tags) {\n      attrs.push(\"tag\", tag);\n    }\n    let keys = Object.keys(attributes).sort();\n    for(let attr of keys) {\n      let value = attributes[attr];\n      if(isArray(value)) {\n        for(let current of value) {\n          if(isReference(current)) context.register(current as Reference);\n          attrs.push(attr, current);\n        }\n      } else {\n        if(isReference(value)) context.register(value as Reference);\n        attrs.push(attr, value);\n      }\n    }\n    this.attributes = attrs;\n    context.flow.collect(this);\n  }\n\n  createReference() {\n    return Reference.create(this.context, this);\n  }\n\n  createSub(context:ReferenceContext, record?:Reference):Record {\n    return new Record(context, undefined, undefined, record);\n  }\n\n  reference() {\n    return this.record!;\n  }\n\n  add(context:ReferenceContext, attribute:Value, value:Value|Value[]) {\n    let insert = new Insert(context, [], {}, this.reference());\n    if(!isArray(value)) {\n      insert.add(context, attribute, value);\n    } else {\n      for(let v of value) {\n        insert.add(context, attribute, v);\n      }\n    }\n  }\n\n  remove(context:ReferenceContext, attribute:Value, value:Value|Value[]) {\n    let insert = new Insert(context, [], {}, this.reference());\n    if(!isArray(value)) {\n      insert.remove(context, attribute, value);\n    } else {\n      for(let v of value) {\n        insert.remove(context, attribute, v);\n      }\n    }\n  }\n\n  copyToContext(activeContext:ReferenceContext) {\n    let found = activeContext.flow.findReference(this);\n    if(found) return found;\n\n    let neue = this.createSub(activeContext, this.record);\n    activeContext.register(this.record!);\n    return neue;\n  }\n\n  findAttribute(name:string):Reference|undefined {\n    let {attributes} = this;\n    for(let ix = 0, len = attributes.length; ix < len; ix += 2) {\n      let attrName = attributes[ix];\n      let value = attributes[ix + 1];\n      if(attrName === name && isReference(value)) return value;\n    }\n  }\n\n  access(refContext:ReferenceContext, activeContext:ReferenceContext, prop:string) {\n    let record:Record = this;\n    if(refContext !== activeContext) {\n      record = this.copyToContext(activeContext);\n    }\n    let found = record.findAttribute(prop);\n    if(found) return found;\n\n    // we need to add this attribute to us and return that\n    let attrRecord = this.createSub(activeContext);\n    let attrRef = attrRecord.reference();\n    record.attributes.push(prop, attrRef);\n    return attrRef;\n  }\n\n  getRegisters():Register[] {\n    let values:Value[] = [this.record!];\n    let {attributes} = this;\n    for(let ix = 0, len = attributes.length; ix < len; ix += 2) {\n      let a = attributes[ix];\n      let v = attributes[ix + 1];\n      values.push(a,v);\n    }\n    return values.map((v) => this.context.getValue(v)).filter(isRegister) as Register[];\n  }\n\n  compile():(Runtime.Node|Runtime.Scan)[] {\n    let {attributes, context} = this;\n    let constraints = [];\n    let e = context.interned(this.record!);\n    for(let ix = 0, len = attributes.length; ix < len; ix += 2) {\n      let a = attributes[ix];\n      let v = attributes[ix + 1];\n      // @TODO: get a real node id\n      constraints.push(new Runtime.Scan(e, context.interned(a), context.interned(v), Runtime.IGNORE_REG))\n    }\n    return constraints;\n  }\n}\n\n//--------------------------------------------------------------------\n// Lookup\n//--------------------------------------------------------------------\n\nexport class Lookup extends DSLBase {\n  attribute:Reference;\n  value:Reference;\n\n  constructor(public context:ReferenceContext, public record:Value) {\n    super();\n    if(isReference(record)) {\n      context.register(record);\n    }\n    let attribute = new Record(context);\n    let value = new Record(context);\n    this.attribute = attribute.reference();\n    this.value = value.reference();\n    context.flow.collect(this);\n  }\n\n  reference():Reference {\n    return this.record as Reference;\n  }\n\n  output() {\n    return {attribute: this.attribute, value: this.value};\n  }\n\n  compile():Runtime.Scan[] {\n    let scans = [];\n    let {context} = this;\n    scans.push(new Runtime.Scan(context.interned(this.record), context.interned(this.attribute), context.interned(this.value), Runtime.IGNORE_REG));\n    return scans;\n  }\n}\n\n//--------------------------------------------------------------------\n// Move\n//--------------------------------------------------------------------\n\nexport class Move extends DSLBase {\n  public to:Reference;\n  constructor(public context:ReferenceContext, public from:Value, to?:Reference) {\n    super();\n    if(isReference(from)) {\n      context.register(from);\n    }\n    if(!to) {\n      if(!isReference(from)) throw new Error(\"Move where the to is not a reference\");\n      this.to = from;\n    } else {\n      this.to = to;\n    }\n  }\n\n  toKey() {\n    let from:any = isReference(this.from) ? `[${this.from.__ID}]` : this.from;\n    let to:any = isReference(this.to) ? `[${this.to.__ID}]` : this.to;\n    return `${from}|${to}`\n  }\n\n  getInputRegisters():Register[] {\n    let value = this.context.getValue(this.from);\n    if(isRegister(value)) {\n      return [value];\n    }\n    return [];\n  }\n\n  getOutputRegisters():Register[] {\n    let {to} = this;\n    let parent = to.__context.getValue(to) as Register;\n    return [parent];\n  }\n\n  compile():Runtime.Constraint[] {\n    let {from, to} = this;\n    let local = this.context.interned(from);\n    let parent = to.__context.getValue(to) as Register;\n    return [new Runtime.MoveConstraint(local, parent)];\n  }\n}\n\n//--------------------------------------------------------------------\n// Insert\n//--------------------------------------------------------------------\n\nexport class Insert extends Record {\n\n  constructor(public context:ReferenceContext, tags:string[] = [], attributes:RecordAttributes = {}, record?:Reference) {\n    super(context, tags, attributes, record);\n\n    if(!record) {\n      // we have to make our ID generation function\n      let args = [];\n      for(let ix = 0, len = this.attributes.length; ix < len; ix += 2) {\n        let a = this.attributes[ix];\n        let v = this.attributes[ix + 1];\n        args.push(a,v);\n      }\n\n      let genId = new Fn(context, \"eve/internal/gen-id\", args, this.reference());\n    }\n  }\n\n  createReference() {\n    // @TODO: create an InsertReference type and return that here\n    return Reference.create(this.context, this);\n  }\n\n  createSub(context:ReferenceContext, record?:Reference):Record {\n    return new Insert(context, undefined, undefined, record);\n  }\n\n  add(context:ReferenceContext, attribute:Value, value:Value|Value[]) {\n    if(!isArray(value)) {\n      this.attributes.push(attribute, value);\n    } else {\n      for(let v of value) {\n        this.attributes.push(attribute, v);\n      }\n    }\n    return this.reference();\n  }\n\n  remove(context:ReferenceContext, attribute:Value, value:Value|Value[]) {\n    let remove = new Remove(context, [], {}, this.record);\n    if(!isArray(value)) {\n      remove.attributes.push(attribute, value);\n    } else {\n      for(let v of value) {\n        remove.attributes.push(attribute, v);\n      }\n    }\n    return this.reference();\n  }\n\n  toWatch() {\n    let watch = new Watch(this.context, [], {}, this.record);\n    watch.attributes = this.attributes;\n    return watch;\n  }\n\n  toCommit() {\n    let commit = new CommitInsert(this.context, [], {}, this.record);\n    commit.attributes = this.attributes;\n    return commit;\n  }\n\n  compile():any[] {\n    let {attributes, context} = this;\n    let nodes = [];\n    let e = context.interned(this.record!);\n    for(let ix = 0, len = attributes.length; ix < len; ix += 2) {\n      let a = attributes[ix];\n      let v = attributes[ix + 1];\n      // @TODO: get a real node id\n      let n = uuid();\n      nodes.push(new Runtime.InsertNode(e, context.interned(a), context.interned(v), context.interned(n)))\n    }\n    return nodes;\n  }\n}\n\n//--------------------------------------------------------------------\n// Remove\n//--------------------------------------------------------------------\n\nexport class Remove extends Insert {\n  toCommit() {\n    let commit = new CommitRemove(this.context, [], {}, this.record);\n    commit.attributes = this.attributes;\n    return commit;\n  }\n\n  compile():any[] {\n    let {attributes, context} = this;\n    let nodes = [];\n    let e = context.interned(this.record!);\n    for(let ix = 0, len = attributes.length; ix < len; ix += 2) {\n      let a = attributes[ix];\n      let v = attributes[ix + 1];\n\n      // @TODO: get a real node id\n      let n = uuid();\n      let internedV:any = context.maybeInterned(v); // @FIXME\n      internedV = internedV !== undefined ? internedV : Runtime.IGNORE_REG;\n      let internedA:any = context.maybeInterned(a); // @FIXME\n      internedA = internedA !== undefined ? internedA : Runtime.IGNORE_REG;\n      nodes.push(new Runtime.RemoveNode(e, internedA, internedV, context.interned(n)));\n    }\n    return nodes;\n  }\n}\n\n\n//--------------------------------------------------------------------\n// Watch\n//--------------------------------------------------------------------\n\nexport class Watch extends Insert {\n  compile():(Runtime.Node|Runtime.Scan)[] {\n    let {attributes, context} = this;\n    let nodes = [];\n    let e = context.interned(this.record!);\n    for(let ix = 0, len = attributes.length; ix < len; ix += 2) {\n      let a = attributes[ix];\n      let v = attributes[ix + 1];\n      // @TODO: get a real node id\n      let n = uuid();\n      nodes.push(new Runtime.WatchNode(e, context.interned(a), context.interned(v), context.interned(n), context.flow.ID));\n    }\n    return nodes;\n  }\n}\n\n//--------------------------------------------------------------------\n// CommitInsert\n//--------------------------------------------------------------------\n\nexport class CommitInsert extends Insert {\n  compile():any[] {\n    let {attributes, context} = this;\n    let nodes = [];\n    let e = context.interned(this.record!);\n    for(let ix = 0, len = attributes.length; ix < len; ix += 2) {\n      let a = attributes[ix];\n      let v = attributes[ix + 1];\n      // @TODO: get a real node id\n      let n = uuid();\n      nodes.push(new Runtime.CommitInsertNode(e, context.interned(a), context.interned(v), context.interned(n)));\n    }\n    return nodes;\n  }\n}\n\n//--------------------------------------------------------------------\n// CommitRemove\n//--------------------------------------------------------------------\n\nexport class CommitRemove extends Remove {\n  compile():any[] {\n    let {attributes, context} = this;\n    let nodes = [];\n    let e = context.interned(this.record!);\n    for(let ix = 0, len = attributes.length; ix < len; ix += 2) {\n      let a = attributes[ix];\n      let v = attributes[ix + 1];\n      // @TODO: get a real node id\n      let n = uuid();\n      let internedV:any = context.maybeInterned(v); // @FIXME\n      internedV = internedV !== undefined ? internedV : Runtime.IGNORE_REG;\n      let internedA:any = context.maybeInterned(a); // @FIXME\n      internedA = internedA !== undefined ? internedA : Runtime.IGNORE_REG;\n      nodes.push(new Runtime.CommitRemoveNode(e, internedA, internedV, context.interned(n)));\n    }\n    return nodes;\n  }\n}\n\n//--------------------------------------------------------------------\n// Fn\n//--------------------------------------------------------------------\n\nexport class Fn extends DSLBase {\n  output:Value;\n  constructor(public context:ReferenceContext, public name:string, public args:Value[], output?:Reference) {\n    super();\n    let {filter} = Runtime.FunctionConstraint.fetchInfo(name)\n    if(output) {\n      this.output = output;\n    } else if(filter) {\n      this.output = args[args.length - 1];\n    } else {\n      this.output = Reference.create(context, this);\n    }\n    for(let arg of args) {\n      if(isReference(arg)) {\n        context.register(arg);\n      }\n    }\n    context.flow.collect(this);\n  }\n\n  reference():Value {\n    return this.output;\n  }\n\n  access(refContext:ReferenceContext, activeContext:ReferenceContext, prop:string) {\n    throw new Error(\"Implement me!\");\n  }\n\n  getInputRegisters() {\n    let {context} = this;\n    return this.args.map((v) => context.getValue(v)).filter(isRegister);\n  }\n\n  getOutputRegisters() {\n    let registers = [];\n    let value = this.context.getValue(this.output);\n    if(isRegister(value)) {\n      registers.push(value);\n    }\n    return registers;\n  }\n\n  compile():Runtime.Constraint[] {\n    let constraints:Runtime.FunctionConstraint[] = [];\n    let {context, name} = this;\n    let values = this.args.map((v) => context.interned(v))\n    let {variadic, filter} = Runtime.FunctionConstraint.fetchInfo(name)\n    let returns:any = {};\n    if(!filter) {\n      returns.result = context.interned(this.output);\n    }\n    let constraint;\n    if(variadic) {\n      constraint = Runtime.FunctionConstraint.create(name, returns, values)!\n    } else {\n      constraint = Runtime.FunctionConstraint.create(name, returns, [])!\n      let ix = 0;\n      for(let arg of constraint.argNames) {\n        constraint.fields[arg] = values[ix];\n        ix++;\n      }\n    }\n    constraints.push(constraint);\n    return constraints;\n  }\n}\n\n//--------------------------------------------------------------------\n// Aggregate\n//--------------------------------------------------------------------\n\nexport class Aggregate extends DSLBase {\n  output: Reference;\n\n  constructor(public context:ReferenceContext, public aggregate:any, public projection:Reference[], public group:Reference[], public args:Value[], output?:Reference) {\n    super();\n    if(output) {\n      this.output = output;\n    } else {\n      this.output = Reference.create(context, this);\n    }\n    this.output.__forceRegister = true;\n\n    // add all of our args to our projection\n    for(let arg of args) {\n      if(isReference(arg)) {\n        projection.push(arg);\n      }\n    }\n    context.flow.collect(this);\n  }\n\n  getJoinRegisters():Register[] {\n    let {context} = this;\n    let items = [] as any[]; //concatArray([], this.args);\n    if(this.aggregate === Runtime.SortNode) {\n      concatArray(items, this.projection);\n    }\n    concatArray(items, this.group);\n    return items.map((v) => context.getValue(v)).filter(isRegister) as Register[];\n  }\n\n  getInputRegisters():Register[] {\n    let {context} = this;\n    let items = concatArray([], this.args);\n    concatArray(items, this.projection);\n    concatArray(items, this.group);\n    return items.map((v) => context.getValue(v)).filter(isRegister) as Register[];\n  }\n\n  getOutputRegisters():Register[] {\n    // @TODO: should this blow up if it doesn't resolve to a register?\n    let value = this.context.getValue(this.output);\n    return [value as Register];\n  }\n\n  compile() {\n    let {context} = this;\n    let groupRegisters = this.group.map((v) => context.getValue(v)).filter(isRegister);\n    let projectRegisters = this.projection.map((v) => context.getValue(v)).filter(isRegister);\n    let inputs = this.args.map((v) => context.interned(v));\n    let agg = new this.aggregate(groupRegisters, projectRegisters, inputs, this.getOutputRegisters());\n    return agg;\n  }\n\n  reference():Reference {\n    return this.output;\n  }\n\n  per(...args:Reference[]) {\n    for(let arg of args) {\n      this.group.push(arg);\n    }\n  }\n}\n\nexport class AggregateBuilder {\n\n  group:Reference[] = [];\n\n  constructor(public context:ReferenceContext, public projection:Reference[]) {\n  }\n\n  per(...args:Reference[]) {\n    for(let arg of args) {\n      this.group.push(arg);\n    }\n    return this;\n  }\n\n  checkBlock() {\n    let active = this.context.getActive();\n    if(active !== this.context) throw new Error(\"Cannot gather in one scope and aggregate in another\");\n  }\n\n  sum(value:Reference):any {\n    this.checkBlock();\n    let agg = new Aggregate(this.context, SumAggregate, this.projection, this.group, [value]);\n    return agg.reference();\n  }\n\n  count():any {\n    this.checkBlock();\n    let agg = new Aggregate(this.context, SumAggregate, this.projection, this.group, [1]);\n    return agg.reference();\n  }\n\n  sort(...directions:Value[]):any {\n    this.checkBlock();\n    let agg = new Aggregate(this.context, Runtime.SortNode, this.projection, this.group, directions);\n    return agg.reference();\n  }\n\n}\n\n//--------------------------------------------------------------------\n// Not\n//--------------------------------------------------------------------\n\nexport class Not extends LinearFlow {\n  constructor(func:LinearFlowFunction, parent:LinearFlow) {\n    super(func, parent);\n    parent.collect(this);\n  }\n}\n\n//--------------------------------------------------------------------\n// Union\n//--------------------------------------------------------------------\n\nexport class Union extends DSLBase {\n  branches:LinearFlow[] = [];\n  results:Reference[] = [];\n  inputs:Reference[] = [];\n  branchInputs:Reference[][] = [];\n\n  constructor(public context:ReferenceContext, branchFunctions: Function[], parent:LinearFlow, existingResults?:Reference[]) {\n    super();\n    let {branches, results} = this;\n    let resultCount:number|undefined;\n    if(existingResults) {\n      resultCount = existingResults.length;\n      this.results = existingResults.slice();\n      results = this.results;\n      for(let result of results) {\n        if(isReference(result)) {\n          result.__forceRegister = true;\n        } else {\n          throw new Error(\"Non-reference choose/union result\");\n        }\n      }\n    }\n    let ix = 0;\n    for(let branch of branchFunctions) {\n      let flow = new LinearFlow(branch as LinearFlowFunction, parent);\n      let branchResultCount = this.resultCount(flow.results);\n      if(resultCount === undefined) {\n        resultCount = branchResultCount;\n        for(let resultIx = 0; resultIx < resultCount; resultIx++) {\n          let ref = Reference.create(context);\n          ref.__forceRegister = true;\n          results.push(ref);\n        }\n      } else if(resultCount !== branchResultCount) {\n        throw new Error(`Choose branch ${ix} doesn't have the right number of returns. I expected ${resultCount}, but got ${branchResultCount}`);\n      }\n      this.setBranchInputs(ix, flow.context.getInputReferences());\n      let resultIx = 0;\n      for(let result of results) {\n        flow.collect(new Move(flow.context, flow.results[resultIx], result));\n        resultIx++;\n      }\n      branches.push(flow);\n      ix++;\n    }\n    context.flow.collect(this);\n  }\n\n  setBranchInputs(branchIx:number, inputs:Reference[]) {\n    let branchInputs:Reference[] = this.branchInputs[branchIx] = [];\n    for(let ref of inputs) {\n      if(this.inputs.indexOf(ref) === -1) {\n        this.inputs.push(ref);\n      }\n      branchInputs.push(ref);\n    }\n  }\n\n  getInputRegisters() {\n    let {context} = this;\n    let inputs = this.inputs.map((v) => context.getValue(v)).filter(isRegister) as Register[];\n    return inputs;\n  }\n\n  getOutputRegisters() {\n    let {context} = this;\n    return this.results.map((v) => context.getValue(v)).filter(isRegister) as Register[];\n  }\n\n  resultCount(result:any):number {\n    if(result && result.constructor === Array) {\n      return result.length;\n    } else if(result) {\n      return 1;\n    }\n    return 0;\n  }\n\n  build(left: Runtime.Node, nodes:Runtime.Node[], inputs:Register[][], outputs:Register[], extraOuterJoins:Register[]):Runtime.Node {\n    return new Runtime.UnionFlow(left, nodes, inputs, outputs, extraOuterJoins);\n  }\n\n  compile(join:Runtime.Node) {\n    let {context, branchInputs} = this;\n    let nodes:Runtime.Node[] = [];\n    let inputs:Register[][] = [];\n    let outputs = this.getOutputRegisters();\n    let ix = 0;\n    for(let flow of this.branches) {\n      if(flow.collector.chooses.length > 0 || flow.collector.unions.length > 0) {\n        throw new Error(\"Nested chooses and unions are not currently supported\");\n      }\n      let compiled = flow.compile();\n      // @NOTE: Not sure why TS isn't correctly pegging this as filtered to only Registers already.\n      let flowInputs = branchInputs[ix].map((v) => context.getValue(v)).filter(isRegister) as Register[];\n      if(compiled.length > 1) {\n        // if this branch has an aggregate in it, we need to do some surgery to make sure that\n        // the JoinNode gets wrapped in an AggregateOuterLookup. For the motivation see the\n        // comment about the definition for AggregateOuterLookup.\n        if(flow.collector.aggregates.length > 0) {\n          let aggMerge = compiled[0] as Runtime.MergeAggregateFlow;\n          // we have to make sure that the outer lookup fields are actually used in the join node for\n          // the aggregate merge otherwise they will never join. For example if you have:\n          //\n          // board = [#board size: N, not(winner)]\n          // winner = if N = gather/count[for: cell, per: col]\n          //             cell = [#bar board col player]\n          //             then player\n          //\n          // N is both the input to the branch as well as the output of the aggregate, but it won't\n          // be available when doing the join *before* the aggregate. If it's included in the outer\n          // lookup we'll join the board size with undefined every time and always fail.\n          let outerLookupInputs = flowInputs.filter((v:Register) => {\n            let next = aggMerge.left;\n            if(next instanceof Runtime.JoinNode) {\n              return next.registerLookup[v.offset];\n            } else {\n              throw new Error(\"Don't know how to handle multi-node aggregates that don't start with a join\");\n            }\n          });\n          aggMerge.left = new Runtime.AggregateOuterLookup(join, aggMerge.left, outerLookupInputs)\n        }\n        nodes.push(new Runtime.LinearFlow(compiled));\n      } else {\n        nodes.push(compiled[0]);\n      }\n      inputs.push(flowInputs);\n      ix++;\n    }\n    let extraJoins:Register[] = [];\n    for(let result of this.results) {\n      if(result.__owner && result.__owner instanceof Record && result.__owner.attributes.length) {\n        let maybeReg = context.getValue(result);\n        if(isRegister(maybeReg)) {\n          extraJoins.push(maybeReg);\n        }\n      }\n    }\n    return this.build(join, nodes, inputs, this.getOutputRegisters(), extraJoins);\n  }\n}\n\n//--------------------------------------------------------------------\n// Choose\n//--------------------------------------------------------------------\n\nexport class Choose extends Union {\n  build(left: Runtime.Node, nodes:Runtime.Node[], inputs:Register[][], outputs:Register[], extraOuterJoins:Register[]):Runtime.Node {\n    return new Runtime.ChooseFlow(left, nodes, inputs, outputs, extraOuterJoins);\n  }\n}\n\n//--------------------------------------------------------------------\n// Program\n//--------------------------------------------------------------------\n\n// You can specify changes as either [e,a,v] or [e,a,v,round,count];\nexport type EAVTuple = [RawValue, RawValue, RawValue];\nexport type EAVRCTuple = [RawValue, RawValue, RawValue, number, number];\nexport type TestChange =  EAVTuple | EAVRCTuple;\n\nexport class Program {\n  context:Runtime.EvaluationContext;\n  blocks:Runtime.Block[] = [];\n  flows:LinearFlow[] = [];\n  index:indexes.Index;\n  nodeCount = 0;\n  nextTransactionId = 0;\n\n  protected exporter = new Exporter();\n  protected lastWatch?:number;\n  protected watchers:{[id:string]: Watcher|undefined} = {};\n  protected _constants?:{[key:string]: RawValue};\n\n  constructor(public name:string) {\n    this.index = new indexes.HashIndex();\n    this.context = new Runtime.EvaluationContext(this.index);\n  }\n\n  constants(obj:{[key:string]: RawValue}) {\n    if(!this._constants) this._constants = {};\n    for(let constant in obj) {\n      if(this._constants[constant] && this._constants[constant] !== obj[constant]) {\n        // throw new Error(\"Unable to rebind existing constant\");\n      }\n      this._constants[constant] = obj[constant];\n    }\n\n    return this;\n  }\n\n  injectConstants(func:LinearFlowFunction):LinearFlowFunction {\n    if(!this._constants) return func;\n    return macro(func, (code) => {\n      let constants = this._constants!;\n      for(let constant in constants) {\n        code = code.replace(new RegExp(`{{${constant}}}`, \"gm\"), \"\" + constants[constant]);\n      }\n      return code;\n    });\n  }\n\n  clear() {\n    this.index = new indexes.HashIndex();\n    this.context = new Runtime.EvaluationContext(this.index);\n  }\n\n  _bind(name:string, flow:LinearFlow) {\n    let nodes = flow.compile();\n    let block = new Runtime.Block(name, nodes, flow.context.maxRegisters);\n    this.flows.push(flow);\n    this.blocks.push(block);\n    return block;\n  }\n\n  bind(name:string, func:LinearFlowFunction) {\n    let flow = new LinearFlow(this.injectConstants(func));\n    this._bind(name, flow);\n    return this;\n  }\n\n  blockChangeTransaction(added:Runtime.Block[], removed:Runtime.Block[]) {\n    for(let remove of removed) {\n      let ix = this.blocks.indexOf(remove)\n      this.blocks.splice(ix, 1);\n    }\n    // console.time(\"input\");\n    let trans = new Runtime.BlockChangeTransaction(this.context, this.nextTransactionId++, added, removed, this.blocks, this.lastWatch ? this.exporter.handle : undefined);\n    trans.exec(this.context);\n    // console.timeEnd(\"input\");\n    // console.info(trans.changes.map((change, ix) => `    <- ${change}`).join(\"\\n\"));\n    return trans;\n  }\n\n  input(changes:Runtime.Change[]) {\n    // console.time(\"input\");\n    if(changes[0].transaction >= this.nextTransactionId) this.nextTransactionId = changes[0].transaction + 1;\n    let trans = new Runtime.Transaction(this.context, changes[0].transaction, this.blocks, this.lastWatch ? this.exporter.handle : undefined);\n    for(let change of changes) {\n      trans.output(this.context, change);\n    }\n    trans.exec(this.context);\n    // console.timeEnd(\"input\");\n    // console.info(trans.changes.map((change, ix) => `    <- ${change}`).join(\"\\n\"));\n\n    // let g:any = global;\n    // let filterPrefix = \"eve/compiler/\";\n    // let filteredIds = g.filteredIds = g.filteredIds || [];\n    // for(let change of trans.changes) {\n    //   if(change.a == GlobalInterner.get(\"tag\") && (\"\"+GlobalInterner.reverse(change.v)).indexOf(filterPrefix) == 0) {\n    //     filteredIds.push(change.e);\n    //   }\n    // }\n\n    // let filtered = trans.changes.filter((c) => filteredIds.indexOf(c.e) !== -1);\n    // if(filtered.length) {\n    //   console.log(\"---------------INPUT-----------\")\n    //   console.log(filtered.map((change, ix) => `    <- ${change}`).join(\"\\n\"));\n    // }\n\n    return trans;\n  }\n\n  inputEAVs(eavcs:(RawEAVC|RawEAV)[]) {\n    let changes:Change[] = [];\n    let transactionId = this.nextTransactionId++;\n    for(let [e, a, v, c = 1] of eavcs as RawEAVC[]) {\n      changes.push(Change.fromValues(e, a, v, \"input\", transactionId, 0, c));\n    }\n    return this.input(changes);\n  }\n\n  test(transaction:number, eavns:TestChange[]) {\n    if(\"group\" in console) console.group(this.name + \" test \" + transaction);\n    if(transaction >= this.nextTransactionId) this.nextTransactionId = transaction + 1;\n    let trans = new Runtime.Transaction(this.context, transaction, this.blocks, this.lastWatch ? this.exporter.handle : undefined);\n    for(let [e, a, v, round = 0, count = 1] of eavns as EAVRCTuple[]) {\n      let change = Runtime.Change.fromValues(e, a, v, \"input\", transaction, round, count);\n      trans.output(this.context, change);\n    }\n    trans.exec(this.context);\n    console.info(trans.changes.map((change, ix) => `    <- ${change}`).join(\"\\n\"));\n    if(\"group\" in console) console.groupEnd();\n    return this;\n  }\n\n  _commit(name:string, flow:LinearFlow) {\n    let nodes = flow.compile();\n    let block = new Runtime.Block(name, nodes, flow.context.maxRegisters);\n    this.flows.push(flow);\n    this.blocks.push(block);\n    return block;\n  }\n\n  commit(name:string, func:LinearFlowFunction) {\n    let flow = new CommitFlow(this.injectConstants(func));\n    this._commit(name, flow);\n    return this;\n  }\n\n  attach(id:string) {\n    let WatcherConstructor = Watcher.get(id);\n    if(!WatcherConstructor) throw new Error(`Unable to attach unknown watcher '${id}'.`);\n    if(this.watchers[id]) return this.watchers[id];\n    let watcher = new WatcherConstructor(this);\n    this.watchers[id] = watcher;\n    return watcher;\n  }\n\n  _watch(name:string, flow:WatchFlow) {\n    let nodes = flow.compile();\n    let block = new Runtime.Block(name, nodes, flow.context.maxRegisters);\n    this.lastWatch = flow.ID;\n    this.flows.push(flow);\n    this.blocks.push(block);\n    return block;\n  }\n\n  watch(name:string, func:LinearFlowFunction) {\n    let flow = new WatchFlow(this.injectConstants(func));\n    this._watch(name, flow);\n    return this;\n  }\n\n  asDiffs(handler:DiffConsumer) {\n    if(!this.lastWatch) throw new Error(\"Must have at least one watch block to export as diffs.\");\n    this.exporter.triggerOnDiffs(this.lastWatch, handler);\n\n    return this;\n  }\n\n  asObjects<Pattern extends RawRecord>(handler:ObjectConsumer<Pattern>) {\n    if(!this.exporter || !this.lastWatch) throw new Error(\"Must have at least one watch block to export as diffs.\");\n    this.exporter.triggerOnObjects(this.lastWatch, handler);\n\n    return this;\n  }\n\n  load(str:string) {\n    let results = Parser.parseDoc(str);\n    this.attach(\"ui\");\n    this.attach(\"html\");\n    this.attach(\"compiler\");\n    if(results.results.eavs.length) {\n      this.inputEAVs(results.results.eavs);\n    }\n  }\n}\n"
  },
  {
    "path": "src/runtime/indexes.ts",
    "content": "import {Proposal, Change, ResolvedValue, createArray, createHash, IGNORE_REG, ID, EAVN, EAVNField,\n        Iterator, Register, Constraint, ALLOCATION_COUNT, RoundArray} from \"./runtime\";\n\n//------------------------------------------------------------------------\n// Utils\n//------------------------------------------------------------------------\n\nfunction isResolved(field:ResolvedValue): field is ID {\n  return !!field || field === 0;\n}\n\nfunction sumTimes(roundArray:RoundArray, transaction:number, round:number) {\n  if(!roundArray) return 0;\n  let total = 0;\n  for(let cur of roundArray) {\n    if(Math.abs(cur) - 1 <= round) {\n      total += cur > 0 ? 1 : -1;\n    }\n  }\n  return total;\n}\n\n//------------------------------------------------------------------------\n// Indexes\n//------------------------------------------------------------------------\n\nexport interface Index {\n  insert(change:Change):void;\n  propose(proposal:Proposal, e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue, transaction:number, round:number):Proposal;\n  resolveProposal(proposal:Proposal):any[][];\n  check(e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue, transaction:number, round:number):boolean;\n  getDiffs(e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue):RoundArray;\n  get(e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue, transaction:number, round:number):EAVN[];\n}\n\nexport class HashIndex implements Index {\n  eavIndex = createHash();\n  aveIndex = createHash();\n  cardinality = 0;\n\n  getOrCreateHash(parent:any, key:any) {\n    let found = parent[key];\n    if(!found) {\n      found = parent[key] = createHash(\"hashLevel\");\n    }\n    return found;\n  }\n\n  getOrCreateArray(parent:any, key:any) {\n    let found = parent[key];\n    if(!found) {\n      found = parent[key] = createArray(\"hashVix\");\n    }\n    return found;\n  }\n\n  roundArrayInsert(arr:RoundArray, change:Change) {\n    let round = change.round + 1;\n    let neue =  round * change.count;\n    let ix = 0;\n    let handled = false;\n    for(let cur of arr) {\n      let curRound = Math.abs(cur);\n      if(curRound === round) {\n        let updated = cur + neue;\n        if(updated === 0) {\n          arr.splice(ix,1);\n        } else {\n          arr[ix] = updated;\n        }\n        handled = true;\n        break;\n      } else if(curRound > round) {\n        arr.splice(ix, 0, neue);\n        handled = true;\n        break;\n      }\n      ix++;\n    }\n    if(!handled) arr.push(neue)\n  }\n\n  insert(change:Change) {\n    let {getOrCreateHash, getOrCreateArray} = this;\n    let eIx = getOrCreateHash(this.eavIndex, change.e);\n    let aIx = getOrCreateHash(eIx, change.a);\n    let vIx = getOrCreateArray(aIx, change.v);\n    this.roundArrayInsert(vIx, change);\n    let shouldRemove = false;\n    if(!vIx.length) {\n      this.cardinality--;\n      delete aIx[change.v];\n      if(!Object.keys(aIx).length) {\n        delete eIx[change.a];\n        if(!Object.keys(eIx).length) {\n          delete this.eavIndex[change.e];\n        }\n      }\n      shouldRemove = true;\n    }\n\n    aIx = getOrCreateHash(this.aveIndex, change.a);\n    vIx = getOrCreateHash(aIx, change.v);\n    eIx = getOrCreateArray(vIx, change.e);\n    if(shouldRemove) {\n      delete vIx[change.e];\n      if(!Object.keys(vIx).length) {\n        delete aIx[change.v];\n        if(!Object.keys(aIx).length) {\n          delete this.aveIndex[change.a];\n        }\n      }\n    } else {\n      this.roundArrayInsert(eIx, change)\n      this.cardinality++;\n    }\n\n  }\n\n  resolveProposal(proposal:Proposal) {\n    return proposal.info;\n  }\n\n  propose(proposal:Proposal, e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue, transaction:number, round:number) {\n    let forFields = proposal.forFields;\n    forFields.clear();\n    if(isResolved(e)) {\n      return this.walkPropose(proposal, this.eavIndex, e, a, v, n, \"a\", \"v\", transaction, round);\n    } else if(isResolved(a)) {\n      return this.walkPropose(proposal, this.aveIndex, a, v, e, n, \"v\", \"e\", transaction, round);\n    } else {\n      // propose for attribute since that's likely to be the smallest\n      forFields.push(\"a\");\n      proposal.info = Object.keys(this.aveIndex);\n      proposal.cardinality = proposal.info.length;\n    }\n    return proposal;\n  }\n\n  walkPropose(proposal:Proposal, index:any, a:ResolvedValue, b:ResolvedValue, c:ResolvedValue, n:ResolvedValue,\n              fieldB:EAVNField, fieldC:EAVNField, transaction:number, round:number):Proposal {\n    let {forFields} = proposal;\n    forFields.clear();\n    let bIx = index[a as ID];\n    if(!bIx) {\n      proposal.cardinality = 0;\n      return proposal;\n    }\n    if(isResolved(b)) {\n      let cIx = bIx[b];\n      if(!cIx) {\n        proposal.cardinality = 0;\n        return proposal;\n      }\n      if(isResolved(c)) {\n        let roundArray = cIx[c];\n        if(roundArray) {\n          proposal.skip = true;\n          return proposal;\n        }\n        proposal.cardinality = 0;\n        return proposal;\n      } else {\n        forFields.push(fieldC);\n        proposal.info = Object.keys(cIx);\n        proposal.cardinality = proposal.info.length;\n        return proposal;\n      }\n    } else {\n      forFields.push(fieldB);\n      proposal.info = Object.keys(bIx);\n      proposal.cardinality = proposal.info.length;\n      return proposal;\n    }\n  }\n\n  // This function checks that there is at least one value in the index that matches the\n  // given pattern. If a level is free, we have to run through the potential values\n  // until we come across one that could match or we run out of values to check.\n  walkCheck(index:any, a:ResolvedValue, b:ResolvedValue, c:ResolvedValue, n:ResolvedValue, transaction:number, round:number):boolean {\n    let bIx = index[a as ID];\n    if(!bIx) return false;\n    if(isResolved(b)) {\n      let cIx = bIx[b];\n      if(!cIx) return false;\n      if(isResolved(c)) {\n        let roundArray = cIx[c];\n        if(roundArray) {\n          return true;\n        }\n        return false;\n      } else {\n        return Object.keys(cIx).length !== 0;\n      }\n    } else {\n      for(let key of Object.keys(bIx)) {\n        let cIx = bIx[key];\n        if(!cIx) continue;\n        if(isResolved(c)) {\n          let roundArray = cIx[c];\n          if(roundArray) {\n            return true;\n          }\n          return false;\n        } else if(Object.keys(cIx).length !== 0) {\n          return true;\n        }\n      }\n    }\n    return false;\n  }\n\n  check(e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue, transaction:number, round:number):boolean {\n    if(isResolved(e)) {\n      return this.walkCheck(this.eavIndex, e, a, v, n, transaction, round);\n    } else if(isResolved(a)) {\n      return this.walkCheck(this.aveIndex, a, v, e, n, transaction, round);\n    }\n    return true;\n  }\n\n  // This function finds all EAVs in the index that match the given\n  // pattern at the stated time. If a level is free, we have to run\n  // through the potential values until we come across one that could\n  // match or we run out of values to check.\n  walkGet(index:any, a:ResolvedValue, b:ResolvedValue, c:ResolvedValue, n:ResolvedValue, fieldB:EAVNField, fieldC:EAVNField, transaction:number, round:number):EAVN[] {\n    let fieldA:EAVNField = \"e\";\n    if(fieldB === \"e\") fieldA = \"a\";\n\n    let results:EAVN[] = createArray(\"IndexWalkGet\");\n\n    let bIx = index[a as ID];\n    if(!bIx) return results;\n    if(isResolved(b)) {\n      let cIx = bIx[b];\n      if(!cIx) return results;\n      if(isResolved(c)) { // ABC\n        if(sumTimes(cIx[c], transaction, round) > 0) {\n          results.push({[fieldA]: +a!, [fieldB]: +b, [fieldC]: +c, n} as any);\n        }\n        return results;\n\n      } else { // ABc\n        for(let c of Object.keys(cIx)) {\n          if(sumTimes(cIx[c], transaction, round) > 0) {\n            results.push({[fieldA]: +a!, [fieldB]: +b, [fieldC]: +c, n} as any);\n          }\n        }\n        return results;\n      }\n    } else {\n      for(let b of Object.keys(bIx)) {\n        let cIx = bIx[b];\n        if(!cIx) continue;\n        if(isResolved(c)) {  // AbC\n          if(sumTimes(cIx[c], transaction, round) > 0) {\n            results.push({[fieldA]: +a!, [fieldB]: +b, [fieldC]: +c, n} as any);\n          }\n        } else { // Abc\n          for(let c of Object.keys(cIx)) {\n            if(sumTimes(cIx[c], transaction, round) > 0) {\n              results.push({[fieldA]: +a!, [fieldB]: +b, [fieldC]: +c, n} as any);\n            }\n          }\n        }\n      }\n      return results;\n    }\n\n    // throw new Error(\"HashIndex.walkGet eav not implemented.\");\n  }\n\n  get(e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue, transaction:number, round:number):EAVN[] {\n    if(isResolved(e)) {\n      return this.walkGet(this.eavIndex, e, a, v, n, \"a\", \"v\", transaction, round);\n    } else if(isResolved(a)) {\n      return this.walkGet(this.aveIndex, a, v, e, n, \"v\", \"e\", transaction, round);\n    } else throw new Error(\"HashIndex.get eaV not implemented.\");\n  }\n\n  getDiffs(e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue):RoundArray {\n    let aIx = this.eavIndex[e!];\n    if(aIx) {\n      let vIx = aIx[a!];\n      if(vIx && vIx[v!]) {\n        return vIx[v!];\n      }\n    }\n    return createArray();\n  }\n}\n\n//------------------------------------------------------------------------\n// Bit Matrix index\n//------------------------------------------------------------------------\n\nexport class BitMatrixIndex {\n\n  insert(change:Change):void {\n    throw new Error(\"not implemented\")\n  }\n\n  propose(proposal:Proposal, e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue, transaction:number, round:number):Proposal {\n    throw new Error(\"not implemented\")\n  }\n\n  resolveProposal(proposal:Proposal):any[][] {\n    throw new Error(\"not implemented\")\n  }\n\n  check(e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue, transaction:number, round:number):boolean {\n    throw new Error(\"not implemented\")\n  }\n\n  getDiffs(e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue):RoundArray {\n    throw new Error(\"not implemented\")\n  }\n\n  get(e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue, transaction:number, round:number):EAVN[] {\n    throw new Error(\"not implemented\")\n  }\n}\n\n\n//------------------------------------------------------------------------\n// DistinctIndex\n//------------------------------------------------------------------------\n\nexport class DistinctIndex {\n  index:{[key:string]: (number|undefined)[]|undefined} = {};\n\n  getDelta(last:number, next:number) {\n    let delta = 0;\n    if(last == 0 && next > 0) delta = 1;\n    if(last > 0 && next == 0) delta = -1;\n    if(last > 0 && next < 0) delta = -1;\n    if(last < 0 && next > 0) delta = 1;\n    return delta;\n  }\n\n  shouldOutput(key:string, prefixRound:number, prefixCount:number):number[] {\n    let {index} = this;\n    let roundCounts = index[key] || createArray(\"Insert intermediate counts\");\n    index[key] = roundCounts;\n\n    let curCount = 0;\n    let startingCount = roundCounts[prefixRound] = roundCounts[prefixRound] || 0;\n    let minRound = Math.min(roundCounts.length, prefixRound + 1);\n    for(let roundIx = 0; roundIx < minRound; roundIx++) {\n      let prevCount = roundCounts[roundIx];\n      if(!prevCount) continue;\n      curCount += prevCount;\n    }\n\n    let deltas = [];\n\n    // We only need delta changed here because if there's a round delta, it\n    // would have been applied already.\n\n    if(prefixCount === -Infinity) {\n      if(curCount === Infinity) {\n        curCount = 1;\n        startingCount = 1;\n      }\n      prefixCount = -curCount;\n    }\n    let nextCount = curCount + prefixCount;\n    let delta = this.getDelta(curCount, nextCount);\n    if(delta) {\n      deltas.push(prefixRound, delta);\n    }\n    curCount = nextCount;\n    roundCounts[prefixRound] = startingCount + prefixCount;\n\n    for(let roundIx = prefixRound + 1; roundIx < roundCounts.length; roundIx++) {\n      let roundCount = roundCounts[roundIx];\n      if(roundCount === undefined || roundCount === 0) continue;\n\n      let lastCount = curCount - prefixCount;\n      let nextCount = lastCount + roundCount;\n\n      let delta = this.getDelta(lastCount, nextCount);\n\n      let lastCountChanged = curCount;\n      let nextCountChanged = curCount + roundCount;\n\n      let deltaChanged = this.getDelta(lastCountChanged, nextCountChanged);\n\n      // let finalDelta = deltaChanged - delta;\n      let finalDelta = 0;\n      if(delta && delta !== deltaChanged) {\n        // undo delta\n        finalDelta = -delta;\n      } else if(delta !== deltaChanged) {\n        finalDelta = deltaChanged;\n      }\n\n      if(finalDelta) {\n        deltas.push(roundIx, finalDelta);\n      }\n\n      curCount = nextCountChanged;\n    }\n\n    return deltas;\n  }\n\n  distinct(input:Change, results:Iterator<Change>):boolean {\n    let {e, a, v, n, round, count} = input;\n    // @FIXME: When we start to unintern, we'll have invalid keys left in this index,\n    // so we'll need to delete the keys themselves from the index\n    let key = `${e}|${a}|${v}`;\n    let deltas = this.shouldOutput(key, round, count);\n    for(let deltaIx = 0; deltaIx < deltas.length; deltaIx += 2) {\n      let deltaRound = deltas[deltaIx];\n      let delta = deltas[deltaIx + 1];\n      let change = new Change(e!, a!, v!, n!, input.transaction, deltaRound, delta);\n      results.push(change)\n    }\n    return deltas.length > 0;\n  }\n\n  distinctKey(key:string, round:number, count:number, results:Iterator<[number, number]>):boolean {\n    let deltas = this.shouldOutput(key, round, count);\n    for(let deltaIx = 0; deltaIx < deltas.length; deltaIx += 2) {\n      let deltaRound = deltas[deltaIx];\n      let delta = deltas[deltaIx + 1];\n      results.push([deltaRound, delta]);\n    }\n    return deltas.length > 0;\n  }\n\n  getCounts(change:Change) {\n    let {e, a, v} = change;\n    let key = `${e}|${a}|${v}`;\n    return this.index[key];\n  }\n\n  sanityCheck() {\n    let failed = false;\n    let {index} = this;\n    for(let key in index) {\n      let counts = index[key]!;\n      let sum = 0;\n      for(let c of counts) {\n        if(!c) continue;\n        sum += c;\n        if(sum < 0) {\n          failed = true;\n          console.error(\"# Negative postDistinct: \", key, counts.slice())\n        }\n      }\n    }\n    if(failed) throw new Error(\"Distinct sanity check failed.\");\n  }\n}\n"
  },
  {
    "path": "src/runtime/performance.ts",
    "content": "//---------------------------------------------------------------------\n// Performance\n//---------------------------------------------------------------------\nimport {v4 as uuid} from \"uuid\";\n// import {Program} from \"./dsl2\";\n\nvar globalsToTrack = [\"transaction\"];\nvar propertiesToTrack = [\"block\", \"PresolveCheck\", \"GenericJoin\"];\n\nexport type TimeReturn = number;\n\nexport class PerformanceTracker {\n\n  blocks:{[block:string]: {\n    times: {[property:string]: number},\n    counts: {[property:string]: number},\n  }};\n  activeBlock:string;\n  activeProperties:{[property:string]: TimeReturn};\n  times: {[property:string]: number};\n  counts: {[property:string]: number};\n\n  now: () => TimeReturn;\n  elapsed: (start:TimeReturn) => TimeReturn;\n\n  _makePropertyHolder(props = propertiesToTrack) {\n    let neue:any = {};\n    for(let property of props) {\n      neue[property] = 0;\n    }\n    return neue;\n  }\n\n  constructor() {\n    this.reset();\n    this.now = now;\n    this.elapsed = elapsed;\n  }\n\n  reset() {\n    this.activeBlock = \"\";\n    this.activeProperties = {};\n    this.times = this._makePropertyHolder(globalsToTrack);\n    this.counts = this._makePropertyHolder(globalsToTrack);\n    this.blocks = {};\n  }\n\n  block(name:string) {\n    let {blocks} = this;\n    let found = blocks[name];\n    if(!found) {\n      found = blocks[name] = {counts: this._makePropertyHolder(), times: this._makePropertyHolder()};\n    }\n    this.activeBlock = name;\n    this.activeProperties[\"block\"] = this.now();\n    found.counts[\"block\"]++;\n  }\n\n  blockEnd(name:string) {\n    let {blocks, activeBlock} = this;\n    blocks[activeBlock].times[\"block\"] += this.elapsed(this.activeProperties[\"block\"])\n    this.activeBlock = \"\";\n  }\n\n  blockTime(property:string) {\n    let {blocks, activeBlock} = this;\n    let found = blocks[activeBlock];\n    this.activeProperties[property] = this.now();\n    found.counts[property]++;\n  }\n\n  blockTimeEnd(property:string) {\n    let {blocks, activeBlock} = this;\n    let found = blocks[activeBlock];\n    found.times[property] += this.elapsed(this.activeProperties[property]);\n  }\n\n  time(property:string) {\n    let {counts} = this;\n    this.activeProperties[property] = this.now();\n    counts[property]++;\n  }\n  timeEnd(property:string) {\n    let {times} = this;\n    times[property] += this.elapsed(this.activeProperties[property]);\n  }\n\n  serialize() {\n    return JSON.stringify({\n      times: this.times,\n      counts: this.counts,\n      blocks: this.blocks\n    })\n  }\n}\n\nexport class NoopPerformanceTracker extends PerformanceTracker {\n  blocks:{[block:string]: {\n    times: {[property:string]: number},\n    counts: {[property:string]: number},\n  }};\n  times: {[property:string]: number};\n  counts: {[property:string]: number};\n\n  now: () => TimeReturn;\n  elapsed: (start:TimeReturn) => TimeReturn;\n\n  constructor() {\n    super();\n    this.now = () => 0;\n    this.elapsed = (start:any) => 0;\n  }\n  reset() { }\n\n  time(property:string) {}\n  timeEnd(property:string) {}\n\n  block(name:string) {  this.activeBlock = name; }\n  blockEnd(name:string) { this.activeBlock = \"\";  }\n\n  blockTime(property:string) {}\n  blockTimeEnd(property:string) {}\n}\n\nexport var now: () => any;\nexport var elapsed: (start:any) => any;\nif(global.process) {\n  now = function(start?): any {\n    return process.hrtime();\n  }\n  elapsed = function(start:any): any {\n    let end = process.hrtime(start);\n    return ((end[0]*1000) + (end[1]/1000000));\n  }\n} else {\n  now = function(start?): any {\n    return performance.now();\n  }\n  elapsed = function(start:any): any {\n    let end = performance.now();\n    return end - start;\n  }\n}\n"
  },
  {
    "path": "src/runtime/runtime.ts",
    "content": "import {Index, HashIndex, DistinctIndex} from \"./indexes\";\nimport {Tracer, NoopTracer, TraceNode, TraceFrameType} from \"./trace\";\n\n//------------------------------------------------------------------------\n// debugging utilities\n//------------------------------------------------------------------------\n\nconst TRACE = false;\n\n// Turning this on causes all of the debug(.*) statements to print to the\n// console.  This is useful to see exactly what the runtime is doing as it\n// evaluates a transaction, but it incurs both a pretty serious performance\n// cost and prints a lot of stuff.\nconst DEBUG = false;\n\nexport var debug:Function = () => {};\nif(DEBUG) {\n  debug = function() {\n    console.log.apply(console, arguments);\n  }\n}\n\nfunction indent(text:string, level:number) {\n  let padding = new Array(level + 1).join(\" \");\n  return text.split(\"\\n\").join(\"\\n\" + padding);\n}\n\nexport function printField(field:ScanField) {\n  if(isRegister(field)) return \"[\" + field.offset + \"]\";\n  if(field === undefined || field === null) return field;\n  let raw = maybeReverse(field);\n  return typeof raw === \"string\" ? `\"${raw}\"` : raw;\n}\n\nexport function printFieldArray(fields:ScanField[]) {\n  return \"[\" + fields.map(printField).join(\", \") + \"]\";\n}\n\nexport function printPrefix(prefix:Prefix) {\n  return prefix.map((v) => GlobalInterner.reverse(v));\n}\n\nfunction toString(x:any):string {\n  if(x && x.toString) return x.toString();\n  console.warn(\"No toString specified for\", x);\n  return \"\";\n}\n\nexport function printBlock(block:Block):string {\n  return block.toString();\n}\n(global as any).printBlock = printBlock;\n\nexport function maybeReverse(value?:ID):ID|RawValue|undefined {\n  if(value === undefined) return value;\n  let raw = GlobalInterner.reverse(value);\n  return (\"\"+raw).indexOf(\"|\") === -1 ? raw : value;\n}\n\n//------------------------------------------------------------------------\n// Allocations\n//------------------------------------------------------------------------\n\n// As this is a language runtime, we want to get insight into how we're using\n// memory and what allocation costs we're eating as we run. To track that, we\n// use createHash and createArray to give us some rough numbers. The JIT will\n// inline these functions, so the cost over just using {} or [], is fairly\n// negligible. In a release build we can also strip the allocation tracking.\n\nexport var ALLOCATION_COUNT:any = {};\n\nexport function createHash(place = \"unknown-hash\") {\n  if(!ALLOCATION_COUNT[place]) ALLOCATION_COUNT[place] = 0;\n  ALLOCATION_COUNT[place]++;\n  return Object.create(null);\n}\n\nexport function createArray(place = \"unknown\") {\n  if(!ALLOCATION_COUNT[place]) ALLOCATION_COUNT[place] = 0;\n  ALLOCATION_COUNT[place]++;\n  return [];\n}\n\nexport function copyArray(arr:any[], place = \"unknown\") {\n  if(!ALLOCATION_COUNT[place]) ALLOCATION_COUNT[place] = 0;\n  ALLOCATION_COUNT[place]++;\n  return arr.slice();\n}\n\nexport function copyHash(hash:any, place = \"unknown\") {\n  if(!ALLOCATION_COUNT[place]) ALLOCATION_COUNT[place] = 0;\n  ALLOCATION_COUNT[place]++;\n  let neue:any = {};\n  for(let key of Object.keys(hash)) {\n    neue[key] = hash[key];\n  }\n  return neue;\n}\n\n// given two arrays, append the second's items on to the first\nexport function concatArray(arr:any[], arr2:any[]) {\n  let ix = arr.length;\n  for(let elem of arr2) {\n    arr[ix] = elem;\n    ix++;\n  }\n  return arr;\n}\n\n// overwrite the first array with the values of the second array\n// and fix the length if it's different\nexport function moveArray(arr:any[], arr2:any[]) {\n  let ix = 0;\n  for(let elem of arr) {\n    arr2[ix] = arr[ix];\n  }\n  if(arr2.length !== arr.length) arr2.length = arr.length;\n  return arr2;\n}\n\n//------------------------------------------------------------------------\n// Iterator\n//------------------------------------------------------------------------\n\n// To reduce allocations as much as possible, we want to reuse arrays as much\n// as possible. If we reused the array by setting its length to 0 or to some\n// new size that is smaller than its current length, we eat the cost of\n// deallocating some chunk of memory as well as the potential cost in\n// fragmentation. Instead, the Iterator class never changes the size of its\n// backing array, and instead keeps its own length. You iterate through the\n// array using the next() method:\n//\n// let current;\n// while((current = iterator.next()) !== undefined) {\n//   ...\n// }\n//\n// Through the magic of the JIT, this has no performance penalty over using a\n// standard for loop. You can get some of those \"zero-cost abstractions\" in JS\n// too!\n\nexport class Iterator<T> {\n  array:T[] = [];\n  length:number = 0;\n  ix:number = 0;\n\n  push(value:T) {\n    this.array[this.length++] = value;\n  }\n\n  clear() {\n    this.length = 0;\n    this.reset();\n  }\n\n  reset() {\n    this.ix = 0;\n  }\n\n  next():T|undefined {\n    if(this.ix < this.length) return this.array[this.ix++];\n    return;\n  }\n\n  iter():ReadOnlyIterator<T> {\n    return new ReadOnlyIterator(this.array, this.length);\n  }\n}\n\nexport class ReadOnlyIterator<T> extends Iterator<T> {\n  constructor(arr:T[], length:number) {\n    super();\n    this.array = arr;\n    this.length = length;\n  }\n\n  push(value:T) {\n    throw new Error(\"Cannot write to a readonly iterator\");\n  }\n}\n\n//------------------------------------------------------------------------\n// Interning\n//------------------------------------------------------------------------\n\n// Every value that touches the runtime is interned. While that may seem kind\n// of crazy, there are lots of good reasons for this. The first is that it\n// turns an infinite space of values into a bounded space of integers. This\n// gives us a lot more options in how we index values and dramatically improves\n// our memory layout. On top of that, every lookup and equality is now on\n// fixed-size integers, which computers can do near instantly.  Similarly,\n// nearly every function in the runtime is now monomorphic, giving the JIT free\n// reign to compile our loops into very fast native code.\n//\n// This is of course a tradeoff. It means that when we need to do operations on\n// the actual values, we have to look them up. In practice all of the above\n// benefits have greatly outweighed the lookup cost, the cache-line savings\n// alone makes that pretty irrelevant.  The main cost is that as values flow\n// out of the system, if we don't clean them up, we'll end up leaking ids.\n// Also, at current you can have a maximum of a 32bit integer's worth of unique\n// values in your program. Chances are that doesn't matter in practice on the\n// client side, but could be a problem in the server at some point. To combat\n// this, our intener keeps a ref-count, but we're not freeing any of the IDs at\n// the moment.\n//\n// @TODO: we don't ever release IDs in the current runtime because we're not\n// sure who might be holding onto a transaction, which contain references to\n// IDs. At some point we should probably reference count transactions as well\n// and when they are released, that gives us an opportunity to release any\n// associated IDs that are no longer in use.\n\n/** The union of value types we support in Eve. */\nexport type RawValue = string|number;\n/**  An interned value's ID. */\nexport type ID = number;\n\nfunction isNumber(thing:any): thing is number {\n  return typeof thing === \"number\";\n}\n\nexport class Interner {\n  // IDs are only positive integers so that they can be used as array indexes\n  // for efficient lookup.\n  currentID: number = 0;\n\n  // We currently only have two value types in Eve at the moment, strings and\n  // numbers.  Because keys in a javascript object are always converted to\n  // strings, we have to keep dictionaries for the two types separate,\n  // otherwise the number 1 and the string \"1\" would end up being the same\n  // value;\n  strings: {[value:string]: ID|undefined} = createHash(); numbers:\n    {[value:number]: ID|undefined} = createHash();\n\n  // We use this array as a lookup from an integer ID to a RawValue since the\n  // IDs are guaranteed to be densely packed, this gives us much better\n  // performance than using another hash.\n  IDs: RawValue[] = createArray();\n\n  // This is used as another lookup from ID to the number of references this ID\n  // has in the system. As the ref count goes to zero, we can add the ID to the\n  // free list so that it can be reused.\n  IDRefCount: number[] = createArray(); IDFreeList: number[] = createArray();\n\n  // During the course of evaluation, we might allocate a bunch of intermediate\n  // IDs whose values might just be thrown away. For example if we generate a\n  // value just to use as a filter, there's no sense in us keeping the value in\n  // the interned space.  Arenas are named groups of allocations that we may\n  // want to dereference all together.  Note that just because we may\n  // dereference it once, that doesn't mean the ID should be released - other\n  // uses of the ID may exist.\n  arenas: {[arena:string]: Iterator<ID>} = createHash();\n\n  constructor() {\n    // The only arena we *know* we want from the beginning is for the output of functions.\n    this.arenas[\"functionOutput\"] = new Iterator<ID>();\n  }\n\n  _getFreeID() {\n    return this.IDFreeList.pop() || this.currentID++;\n  }\n\n  reference(id:ID) {\n    this.IDRefCount[id]++;\n  }\n\n  // Intern takes a value and gives you the ID associated with it. If there isn't an\n  // ID it should create one for this value and in either case it should add a reference.\n  intern(value: RawValue): ID {\n    let coll;\n    if(isNumber(value)) {\n      coll = this.numbers;\n    } else {\n      coll = this.strings;\n    }\n    let found = coll[value];\n    if(found === undefined) {\n      found = this._getFreeID();\n      coll[value] = found;\n      this.IDs[found] = value;\n      this.IDRefCount[found] = 1;\n    } else {\n      this.IDRefCount[found]++;\n    }\n    return found;\n  }\n\n  // Get neither creates an ID nor adds a reference to the ID, it only looks up the\n  // ID for a value if it exists.\n  get(value: RawValue): ID|undefined {\n    let coll;\n    if(isNumber(value)) {\n      coll = this.numbers;\n    } else {\n      coll = this.strings;\n    }\n    return coll[value];\n  }\n\n  // Go from an ID to the RawValue\n  reverse(id: ID): RawValue {\n    return this.IDs[id];\n  }\n\n  // Dereference an ID and if there are no remaining references, add it to the freelist.\n  release(id: ID|undefined) {\n    if(id === undefined) return;\n\n    this.IDRefCount[id]--;\n    if(!this.IDRefCount[id]) {\n      let value = this.IDs[id];\n      let coll;\n      if(isNumber(value)) {\n        coll = this.numbers;\n      } else {\n        coll = this.strings;\n      }\n      coll[value] = undefined;\n      this.IDs[id] = undefined as any;\n      this.IDFreeList.push(id);\n    }\n  }\n\n  arenaIntern(arenaName:string, value:RawValue):ID {\n    // @FIXME: Unfortunately we can't use arena intern at the moment due to the\n    // fact that while we can know what values end up in the primary indexes,\n    // we don't know what values might be hiding in intermediate indexes that\n    // runtime nodes sometimes need to keep. If we *did* deallocate an arena\n    // and the value didn't make it to a primary index, but ended up in an\n    // intermediate one, we'd have effectively corrupted our program. The ID\n    // would be freed, and then used for some completely different value. Until\n    // we can find an accurate (and cheap!) way to track what values are still\n    // hanging around, we'll just have to eat the cost of interning all the\n    // values we've seen. Keep in mind that this isn't as bad as it sounds, as\n    // the only values that would actually be freed this way are values that\n    // are calculated but never end up touching the primary indexes. This is\n    // rare enough that in practice, this probably isn't a big deal.\n    throw new Error(\"Arena interning isn't ready for primetime yet.\")\n\n    // let arena = this.arenas[arenaName];\n    // if(!arena) {\n    //   arena = this.arenas[arenaName] = new Iterator<ID>();\n    // }\n    // // @NOTE: for performance reasons it might make more sense to prevent duplicates\n    // // from ending up in the list. If that's the case, we could either keep a seen\n    // // hash or do a get and only intern if it hasn't been seen. This is (probably?)\n    // // a pretty big performance gain in the case where a bunch of rows might repeat\n    // // the same function output over and over.\n    // let id = this.intern(value);\n    // arena.push(id);\n    // return id;\n  }\n\n  releaseArena(arenaName:string) {\n    let arena = this.arenas[arenaName];\n    if(!arena) {\n      console.warn(\"Trying to release unknown arena: \" + arenaName)\n      return;\n    }\n\n    let id;\n    while((id = arena.next()) !== undefined) {\n      this.release(id);\n    }\n    arena.clear();\n  }\n}\n\n// The runtime uses a single global interner so that all values remain comparable.\nexport var GlobalInterner = new Interner();\n(global as any)[\"GlobalInterner\"] = GlobalInterner;\n\n//------------------------------------------------------------------------\n// Changes\n//------------------------------------------------------------------------\n\n// Because Eve's runtime is incremental from the ground up, the primary unit of\n// information in the runtime is a Change. The content of a change is in the\n// form of \"triples,\" a tuple of entity, attribute, and value (or in the RDF\n// world, subject, object, predicate). For example, if we wanted to talk about\n// my age, we might have a triple of (\"chris\", \"age\", 30). Beyond the content\n// of the change, we also want to know who created this change and what\n// transaction it came from. This gives us enough information to work out the\n// provenance of this information, which is very useful for debugging as well\n// as doing clever things around verification and trust. The final two pieces\n// of information in a change are the round and count, which are used to help\n// us maintain our program incrementally. Because Eve runs all blocks to\n// fixedpoint, a single change may cause multiple \"rounds\" of evaluation which\n// introduce more changes. By tracking what round these changes happened in, we\n// can do some clever reconciling to handle removal inside recursive rules\n// efficiently, which we'll go into more depth later. Count tells us how many\n// of these triples we are adding or, if the number is negative, removing from\n// the system.\n\n// We track counts as Multiplicities, which are just signed integers.\nexport type Multiplicity = number;\n\n// It's often useful to know just the sign of a multiplicity\nfunction sign (x:number) {\n  return typeof x === 'number' ? x ? x < 0 ? -1 : 1 : x === x ? 0 : NaN : NaN;\n}\n\n\n// In a change entity, attribute, value, and node are stored as e, a, v, and n\n// respectively.  We often need to look these up in loops or pass around\n// information about what property we might currently be talking about, so we\n// have a type representing those fields.\nexport type EAVNField = \"e\"|\"a\"|\"v\"|\"n\";\n\nexport class Change {\n  // Change expects that all values have already been interned.\n  constructor(public e: ID, public a: ID, public v: ID, public n: ID, public transaction:number, public round:number, public count:Multiplicity) {}\n\n  // As a convenience, you can generate a change from values that haven't been\n  // interned yet.\n  static fromValues(e: any, a: any, v: any, n: any, transaction: number, round: number, count:Multiplicity) {\n    return new Change(GlobalInterner.intern(e), GlobalInterner.intern(a), GlobalInterner.intern(v),\n                      GlobalInterner.intern(n), transaction, round, count);\n  }\n\n  toString() {\n    // let e = GlobalInterner.reverse(this.e);\n    let e = this.e;\n    return `Change(${e}, ${GlobalInterner.reverse(this.a)}, ${maybeReverse(this.v)}, ${this.n}, ${this.transaction}, ${this.round}, ${this.count})`;\n  }\n\n  // For testing purposes, you often want to compare two Changes ignoring their\n  // node, as you don't know exactly what node will generate a value when you\n  // run. withoutE is also used in testing to check if a triple whose entity\n  // may have been generated by the program *could* match this change.\n  equal(other:Change, withoutNode?:boolean, withoutE?:boolean) {\n   return (withoutE || this.e == other.e) &&\n          this.a == other.a &&\n          this.v == other.v &&\n          (withoutNode || this.n == other.n) &&\n          this.transaction == other.transaction &&\n          this.round == other.round &&\n          this.count == other.count;\n  }\n\n  reverse(interner:Interner = GlobalInterner) {\n    let {e, a, v, n, transaction, round, count} = this;\n    return new RawChange(interner.reverse(e), interner.reverse(a), interner.reverse(v), interner.reverse(n), transaction, round, count);\n  }\n\n  toRawEAV(interner:Interner = GlobalInterner):RawEAV {\n    let {e, a, v} = this;\n    return [interner.reverse(e), interner.reverse(a), interner.reverse(v)];\n  }\n\n  clone() {\n    let {e, a, v, n, transaction, round, count} = this;\n    return new Change(e, a, v, n, transaction, round, count);\n  }\n}\n\nconst BLOCK_REMOVE = new Change(0,0,0,0,0,0,-1);\nconst BLOCK_ADD = new Change(0,0,0,0,0,0,1);\n\nexport class RemoveChange extends Change {\n  toString() {\n    // let e = GlobalInterner.reverse(this.e);\n    let e = this.e;\n    return `RemoveChange(${e}, ${GlobalInterner.reverse(this.a)}, ${maybeReverse(this.v)}, ${this.n}, ${this.transaction}, ${this.round}, ${this.count})`;\n  }\n  clone() {\n    let {e, a, v, n, transaction, round, count} = this;\n    return new RemoveChange(e, a, v, n, transaction, round, count);\n  }\n}\n\nexport class RemoveVsChange extends RemoveChange {\n  toRemoveChanges(context:EvaluationContext, changes:Change[]) {\n    let {e,a,v,n} = this;\n    let {index, distinctIndex} = context;\n    let matches = index.get(e, a, IGNORE_REG, IGNORE_REG, this.transaction, Infinity);\n    for(let {v} of matches) {\n      let rounds = index.getDiffs(e, a, v, IGNORE_REG);\n      for(let round of rounds) {\n        let count = this.count * (round > 0 ? 1 : -1);\n        let changeRound = Math.max(this.round, Math.abs(round) - 1);\n        let change = new RemoveChange(e!, a!, v!, n!, this.transaction, changeRound, count);\n        changes.push(change);\n      }\n    }\n  }\n  clone() {\n    let {e, a, v, n, transaction, round, count} = this;\n    return new RemoveVsChange(e, a, v, n, transaction, round, count);\n  }\n}\n\nexport class RemoveAVsChange extends RemoveVsChange {\n  toRemoveChanges(context:EvaluationContext, changes:Change[]) {\n    let {e,a,v,n} = this;\n    let {index, distinctIndex} = context;\n    let matches = index.get(e, IGNORE_REG, IGNORE_REG, IGNORE_REG, this.transaction, Infinity);\n    for(let {a, v} of matches) {\n      let rounds = index.getDiffs(e, a, v, IGNORE_REG);\n      for(let round of rounds) {\n        let count = this.count * (round > 0 ? 1 : -1);\n        let changeRound = Math.max(this.round, Math.abs(round) - 1);\n        let change = new RemoveChange(e!, a!, v!, n!, this.transaction, changeRound, count);\n        changes.push(change);\n      }\n    }\n  }\n\n  clone() {\n    let {e, a, v, n, transaction, round, count} = this;\n    return new RemoveAVsChange(e, a, v, n, transaction, round, count);\n  }\n}\n\n// When interacting with the outside world, we need to pass changes around that\n// are no longer interned. A RawChange is the same as Change, but all the\n// information in the triple has been converted back into RawValues instead of\n// interned IDs.\nexport class RawChange {\n  constructor(public e: RawValue, public a: RawValue, public v: RawValue, public n: RawValue,\n              public transaction:number, public round:number, public count:Multiplicity) {}\n\n  toString() {\n    let {e, a, v, n, transaction, round, count} = this;\n    let internedE = GlobalInterner.get(e);\n    let internedV = GlobalInterner.get(v);\n    return `RawChange(${internedE}, ${a}, ${maybeReverse(internedV) || v}, ${n}, ${transaction}, ${round}, ${count})`;\n  }\n}\n\n//------------------------------------------------------------------------\n// Joins\n//------------------------------------------------------------------------\n\n// Buckle up, we're going for a ride.\n//\n// Now that we have a change representation, we need to actually do something\n// with it. Eve is a relational language, which means the primary action in\n// the language is to join tuples together. Unlike in most relational databases\n// where we might do joins by looking at full relations pair-wise and joining\n// them together, we need to operate on changes and we want to sidestep the\n// cost of figuring out a good query plan for the pair-wise joins. Both of\n// these properties require us to look at joins very differently than we\n// normally would in say Postgres. Instead, we're going to use a magical join\n// algorithm called Generic Join [1] and extend it to work on incremental\n// changes instead of just fully realized relations.\n//\n// The core idea behind Generic Join is that instead of breaking a query down\n// into a set of binary joins on relations, we look at each unique variable in\n// the query and have all of the relations that might say something about that\n// variable do an intersection. Let's look at an example:\n//\n//  people(person-id, name)\n//  dogs(person-id, dog-name, dog-age)\n//\n// Here we have two relations we want to join together: \"people\" and \"dogs\".\n// The people relation has two fields that are represented by the variables\n// \"person-id\" and \"name.\" The dogs relation has three fields: \"person-id\",\n// \"dog-name\", and \"dog-age.\" In postgres, we'd take these two relations and do\n// a hash or merge join based on the first column of each. In generic join we\n// look at all the variables we need to solve for, in this case four of them,\n// and then ask each relation which variable they could propose values for.\n// These proposals include not just what variable this relation could solve\n// for, but also an estimate of how many values the variable would have. In the\n// interest of doing the least amount of work possible, we select the proposal\n// with the smallest estimate and then for each proposed value of the variable,\n// we ask all the other relations if they \"accept\" the value.  If they do, we\n// recursively solve for the rest of the variables in depth-first fashion.\n//\n// In this algorithm, each relation acts as a constraint on the space of\n// valid solutions. We don't just look at every row in the people table or\n// every row in the dogs table, but instead look at the unique values per\n// variable given some set of already solved variables. We call that\n// solved set of variables a \"prefix\". So when we ask a constraint to propose\n// values, we hand it the prefix and ask it which variable it would solve for\n// next. We then ask each constraint if they accept the new prefix and continue\n// to solve for the rest of the variables. By selecting the proposal with the\n// smallest estimate, we can make some interesting guarantees about the upper\n// bound [2] of the work we will do to satisfy our join and we side step the\n// need for the insanely complex query planners that exist in most commercial\n// databases. An interesting aspect of this algorithm is that it's basically\n// making planning decisions for every unique value of a variable, which means\n// that it is resilient to the high skew you often end up with in real-world\n// data.\n//\n// So the key parts of Generic Join are prefixes, constraints, and proposals,\n// which we'll start to layout below. We'll talk more about the ways we have\n// to change Generic Join to make it work incrementally later.\n//\n// [1]: Generic Join is presented in \"Skew Strikes Back: New Developments in\n//      the Theory of Join Algorithms\" https://arxiv.org/abs/1310.3314\n// [2]: \"Worst-case Optimal Join Algorithms \"https://arxiv.org/abs/1203.1952\n\n//------------------------------------------------------------------------\n// Prefixes and registers\n//------------------------------------------------------------------------\n\nexport type Prefix = ID[];\n\n// A register is a numerical offset into a prefix. We can't just make this a\n// type alias to number because we need to be able to tell the difference between\n// IDs which represent static values and registers which represent dynamic values\n// in the prefix. For example I might have a constraint that looks for the\n// pattern (register1, \"tag\", \"person\"), which if we treated Registers as numbers\n// might just look like (1, 2, 3) after the values have been interned. Instead\n// we make Register a class.\n\nexport class Register {\n  constructor(public offset:number) {}\n}\n\nexport function isRegister(x: any): x is Register {\n  return x && x.constructor === Register;\n}\n\n// In some cases we have a constraint whose value we may want to ignore.\n// IGNORE_REG is a sentinel value that tells us we don't care what the value of\n// something is when we're solving.\nexport var IGNORE_REG = null;\nexport type IgnoreRegister = typeof IGNORE_REG;\n\n//------------------------------------------------------------------------\n// Proposal\n//------------------------------------------------------------------------\n\nexport interface Proposal {\n  cardinality:number,\n  forFields:Iterator<EAVNField>,\n  forRegisters:Iterator<Register>,\n  proposer:Constraint,\n  skip?:boolean,\n  info?:any,\n}\n\n//------------------------------------------------------------------------\n// Constraints\n//------------------------------------------------------------------------\n\nexport type RoundArray = number[];\n\nexport enum ApplyInputState {\n  pass,\n  fail,\n  none,\n}\n\nexport interface Constraint {\n  isInput:boolean;\n  toString():string;\n  setup():void;\n  getRegisters():Register[];\n  applyInput(input:Change, prefix:Prefix):ApplyInputState;\n  isAffected(input:Change):ApplyInputState;\n  propose(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:any[]):Proposal;\n  resolveProposal(context:EvaluationContext, prefix:Prefix, proposal:Proposal, transaction:number, round:number, results:any[]):ID[][];\n  accept(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, solvingFor:Register[]):boolean;\n  acceptInput(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number):boolean;\n  getDiffs(context:EvaluationContext, prefix:Prefix):RoundArray;\n}\n\n//------------------------------------------------------------------------\n// Resolved values\n//------------------------------------------------------------------------\n\n/** A scan field may contain a register, a static interned value, or the IGNORE_REG sentinel value. */\nexport type ScanField = Register|ID|IgnoreRegister;\n/** A resolved value is a scan field that, if it contained a register, now contains the register's resolved value. */\nexport type ResolvedValue = ID|undefined|IgnoreRegister;\n\nexport type ResolvedEAVN = {e:ResolvedValue, a:ResolvedValue, v:ResolvedValue, n:ResolvedValue};\n\nexport class EAVN {\n  constructor(public e:ID, public a:ID, public v:ID, public n:ID) {}\n};\n\nexport type EAV = [ID, ID, ID];\nexport type RawEAV = [RawValue, RawValue, RawValue];\nexport type RawEAVC = [RawValue, RawValue, RawValue, number];\n\n//------------------------------------------------------------------------\n// Move Constraint\n//------------------------------------------------------------------------\n\nexport class MoveConstraint {\n\n  constructor(public from:Register|ID, public to:Register) { }\n\n  shouldApplyInput = false;\n  proposal:Proposal = {cardinality: 1, forFields: new Iterator<EAVNField>(), forRegisters: new Iterator<Register>(), proposer: this};\n  registers:Register[] = createArray(\"MoveConstriantRegisters\");\n  resolved:(ID|undefined)[] = createArray(\"MoveConstraintResolved\");\n\n  isInput = false;\n  isStatic = true;\n\n  toString() {\n    return `Move(${printField(this.from)}, ${printField(this.to)})`;\n  }\n\n  setup():void {\n    if(isRegister(this.from)) {\n      this.isStatic = false;\n    }\n    this.registers.push(this.to);\n\n    // we are always only proposing for our to register\n    this.proposal.forRegisters.clear();\n    this.proposal.forRegisters.push(this.to);\n  }\n\n  resolve(prefix:Prefix) {\n    if(isRegister(this.from)) {\n      this.resolved[0] = prefix[this.from.offset];\n    } else {\n      this.resolved[0] = this.from;\n    }\n    this.resolved[1] = prefix[this.to.offset];\n    return this.resolved;\n  }\n\n  getRegisters():Register[] {\n    return this.registers;\n  }\n\n  isAffected(input:Change):ApplyInputState {\n    if(this.shouldApplyInput) {\n      return ApplyInputState.pass;\n    }\n    return ApplyInputState.none;\n  }\n\n  applyInput(input:Change, prefix:Prefix):ApplyInputState {\n    if(this.shouldApplyInput) {\n      let value;\n      if(isRegister(this.from)) {\n        value = prefix[this.from.offset];\n      } else {\n        value = this.from;\n      }\n      let current = prefix[this.to.offset];\n      if(value !== undefined && (current === undefined || current == value)) {\n        prefix[this.to.offset] = value;\n      } else {\n        return ApplyInputState.fail;\n      }\n      return ApplyInputState.pass;\n    }\n    return ApplyInputState.none;\n  }\n\n  propose(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:any[]):Proposal {\n    let [from, to] = this.resolve(prefix);\n    this.proposal.skip = true;\n    if(from !== undefined && to === undefined) {\n      this.proposal.skip = false;\n    }\n    return this.proposal;\n  }\n\n  resolveProposal(context:EvaluationContext, prefix:Prefix, proposal:Proposal, transaction:number, round:number, results:any[]):ID[][] {\n    let [from, to] = this.resolve(prefix);\n    let arr = createArray(\"MoveResult\") as Prefix;\n    arr[0] = from!;\n    return arr as any;\n  }\n\n  accept(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, solvingFor:Register[]):boolean {\n    let [from, to] = this.resolve(prefix);\n    if(from !== undefined && to !== undefined) {\n      return from == to;\n    }\n    return true;\n  }\n\n  acceptInput(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number):boolean {\n    return this.accept(context, prefix, transaction, round, this.registers);\n  }\n\n  getDiffs(context:EvaluationContext, prefix:Prefix):RoundArray {\n    throw new Error(\"Asking for Diffs from MoveConstraint\");\n  }\n}\n\n//------------------------------------------------------------------------\n// Scans\n//------------------------------------------------------------------------\n\n/**\n * A scan maps a set of bound variables to unbound variables.\n */\n\nexport class Scan implements Constraint {\n  constructor(public e:ScanField,\n              public a:ScanField,\n              public v:ScanField,\n              public n:ScanField) {}\n\n  protected resolved:ResolvedEAVN = {e: undefined, a: undefined, v:undefined, n: undefined};\n  protected registers:Register[] = createArray();\n  protected registerLookup:boolean[] = createArray();\n\n  isInput:boolean = false;\n  proposal:Proposal = {cardinality: 0, forFields: new Iterator<EAVNField>(), forRegisters: new Iterator<Register>(), proposer: this};\n\n  toString() {\n    return `Scan(${printField(this.e)} ${printField(this.a)} ${printField(this.v)} ${printField(this.n)})`;\n  }\n\n  toKey() {\n    let e = isRegister(this.e) ? `$reg(${this.e.offset})` : this.e;\n    let a = isRegister(this.a) ? `$reg(${this.a.offset})` : this.a;\n    let v = isRegister(this.v) ? `$reg(${this.v.offset})` : this.v;\n    return `${e}|${a}|${v}`\n  }\n\n  /**\n   * Resolve each scan field.\n   * The resolved object may contain one of three possible value types:\n   * - IGNORE_REG -- this field is entirely ignored by the scan.\n   * - undefined -- this field is a register that hasn't been filled in yet.\n   *                We'll fill it if possible.\n   * - ID -- this field contains a static or already solved value.\n   */\n  resolve(prefix:Prefix) {\n    let resolved = this.resolved;\n    if(isRegister(this.e)) {\n      resolved.e = prefix[this.e.offset];\n    } else {\n      resolved.e = this.e;\n    }\n\n    if(isRegister(this.a)) {\n      resolved.a = prefix[this.a.offset];\n    } else {\n      resolved.a = this.a;\n    }\n\n    if(isRegister(this.v)) {\n      resolved.v = prefix[this.v.offset];\n    } else {\n      resolved.v = this.v;\n    }\n\n    if(isRegister(this.n)) {\n      resolved.n = prefix[this.n.offset];\n    } else {\n      resolved.n = this.n;\n    }\n\n    return resolved;\n  }\n\n  /**\n   * A field is unresolved if it is completely ignored by the scan or\n   * is an output of the scan.\n   */\n  fieldUnresolved(resolved:ResolvedEAVN, key: keyof ResolvedEAVN) {\n    return resolved[key] === IGNORE_REG || resolved[key] === undefined;\n  }\n\n  /**\n   * A field is not a static match if it is ignored, not a static\n   * field, or the input value does not match the static value.\n   */\n  notStaticMatch(input:Change, key: \"e\"|\"a\"|\"v\"|\"n\") {\n    return this[key] !== IGNORE_REG && !isRegister(this[key]) && this[key] !== input[key];\n  }\n\n  isAffected(input:Change):ApplyInputState {\n    // If this change isn't relevant to this scan, skip it.\n    if(this.notStaticMatch(input, \"e\")) return ApplyInputState.none;\n    if(this.notStaticMatch(input, \"a\")) return ApplyInputState.none;\n    if(this.notStaticMatch(input, \"v\")) return ApplyInputState.none;\n    if(this.notStaticMatch(input, \"n\")) return ApplyInputState.none;\n    return ApplyInputState.pass;\n  }\n\n  /**\n   * Apply new changes that may affect this scan to the prefix to\n   * derive only the results affected by this change.  If the change\n   * was successfully applied or irrelevant we'll return true. If the\n   * change was relevant but invalid (i.e., this scan could not be\n   * satisfied due to proposals from previous scans) we'll return\n   * false.\n   */\n  applyInput(input:Change, prefix:Prefix):ApplyInputState {\n    // For each register field of this scan:\n    //   if the required value is impossible fail,\n    //   else add this new value to the appropriate prefix register.\n    // @NOTE: Technically, we republish existing values here too.\n    //   In practice, that's harmless and eliminates the need for a branch.\n    if(isRegister(this.e)) {\n      if(prefix[this.e.offset] !== undefined && prefix[this.e.offset] !== input.e) return ApplyInputState.fail;\n      prefix[this.e.offset] = input.e;\n    }\n\n    if(isRegister(this.a)) {\n      if(prefix[this.a.offset] !== undefined && prefix[this.a.offset] !== input.a) return ApplyInputState.fail;\n      prefix[this.a.offset] = input.a;\n    }\n\n    if(isRegister(this.v)) {\n      if(prefix[this.v.offset] !== undefined && prefix[this.v.offset] !== input.v) return ApplyInputState.fail;\n      prefix[this.v.offset] = input.v;\n    }\n\n    if(isRegister(this.n)) {\n      if(prefix[this.n.offset] !== undefined && prefix[this.n.offset] !== input.n) return ApplyInputState.fail;\n      prefix[this.n.offset] = input.n;\n    }\n\n    return ApplyInputState.pass;\n  }\n\n  propose(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:any[]):Proposal {\n    let {index} = context;\n    let {e,a,v,n} = this.resolve(prefix);\n    this.proposal.skip = false;\n    let proposal = index.propose(this.proposal, e, a, v, n, transaction, round);\n    let {forRegisters, forFields} = proposal;\n\n    forRegisters.clear();\n    let field;\n    while((field = forFields.next()) !== undefined) {\n      forRegisters.push(this[field as EAVNField] as Register);\n    }\n    if(proposal.forFields.length === 0) proposal.skip = true;\n    return proposal;\n  }\n\n  resolveProposal(context:EvaluationContext, prefix:Prefix, proposal:Proposal, transaction:number, round:number, results:any[]):ID[][] {\n    let {index} = context;\n    return index.resolveProposal(proposal);\n  }\n\n  accept(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, solvingFor:Register[]):boolean {\n    // Before we start trying to accept, we check if we care about the\n    // registers we are currently solving.\n    let solving = false;\n    for(let register of solvingFor) {\n      if(this.registerLookup[register.offset]) {\n        solving = true;\n        break;\n      }\n    }\n    // If we aren't looking at any of these registers, then we just\n    // say we accept.\n    if(!solving) return true;\n    let {e,a,v,n} = this.resolve(prefix);\n    return context.index.check(e, a, v, n, transaction, round);\n  }\n\n  acceptInput(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number):boolean {\n    let {e,a,v,n} = this.resolve(prefix);\n    if((e === IGNORE_REG || input.e === e) &&\n       (a === IGNORE_REG || input.a === a) &&\n       (v === IGNORE_REG || input.v === v) &&\n       (n === IGNORE_REG || input.n === n)) {\n      return true;\n    } else {\n      return this.accept(context, prefix, transaction, round, this.registers);\n    }\n  }\n\n  setupRegister(field:EAVNField, parts:string[]) {\n    let value = this[field];\n    if(isRegister(value)) {\n      this.registers.push(value);\n      parts.push(`resolved.${field} = prefix[${value.offset}];`);\n    } else {\n      this.resolved[field] = value;\n    }\n  }\n\n  setupIsAffected() {\n    let fields:EAVNField[] = [\"e\", \"a\", \"v\", \"n\"];\n    let parts:string[] = [];\n    for(let field of fields) {\n      let value = this[field];\n      if(!isRegister(value) && value !== IGNORE_REG) {\n        parts.push(`if(${value} !== change[\"${field}\"]) return ${ApplyInputState.none};`)\n      }\n    }\n    this.isAffected = new Function(\"change\", parts.join(\"\\n\")) as (change:Change) => ApplyInputState;\n  }\n\n  setupApplyInput() {\n    let fields:EAVNField[] = [\"e\", \"a\", \"v\", \"n\"];\n    let parts:string[] = [];\n    for(let field of fields) {\n      let value = this[field];\n      if(isRegister(value)) {\n        parts.push(`if(prefix[${value.offset}] !== undefined && prefix[${value.offset}] !== input.${field}) return ${ApplyInputState.fail};\n                    prefix[${value.offset}] = input.${field};`);\n\n      }\n    }\n    parts.push(`return ${ApplyInputState.pass}`)\n    this.applyInput = new Function(\"input\", \"prefix\", parts.join(\"\\n\")) as (change:Change, prefix:Prefix) => ApplyInputState;\n  }\n\n  // We precompute the registers we're interested in for fast accepts.\n  setup() {\n    let parts = [\"var resolved = this.resolved;\"];\n    this.setupRegister(\"e\", parts);\n    this.setupRegister(\"a\", parts);\n    this.setupRegister(\"v\", parts);\n    this.setupRegister(\"n\", parts);\n    parts.push(\"return resolved\");\n    this.resolve = new Function(\"prefix\", parts.join(\"\\n\")) as (prefix:Prefix) => ResolvedEAVN;\n\n    this.setupIsAffected();\n    this.setupApplyInput();\n    for(let register of this.registers) {\n      this.registerLookup[register.offset] = true;\n    }\n  }\n\n  getRegisters():Register[] {\n    return this.registers;\n  }\n\n  getDiffs(context:EvaluationContext, prefix:Prefix):RoundArray {\n    let {e,a,v,n} = this.resolve(prefix);\n    return context.index.getDiffs(e,a,v,n);\n  }\n\n}\n\n//------------------------------------------------------------------------\n// Function constraint\n//------------------------------------------------------------------------\n\nexport type ConstraintFieldMap = {[name:string]: ScanField};\nexport type ResolvedFields = {[fieldName:string]: ResolvedValue};\n\nexport class FunctionConstraint implements Constraint {\n  static registered: {[name:string]: typeof FunctionConstraint} = {};\n  static register(name:string, klass: typeof FunctionConstraint) {\n    FunctionConstraint.registered[name] = klass;\n  }\n\n  static filter = false;\n  static variadic = false;\n  static argNames:string[];\n  static returnNames:string[];\n  static fetchInfo(name:string):typeof FunctionConstraint {\n    let info = FunctionConstraint.registered[name];\n    if(!info) throw new Error(\"No function info for: \" + name);\n    return info;\n  }\n\n  static create(name:string, fields:ConstraintFieldMap, restFields:(ID|Register)[] = createArray()):FunctionConstraint|undefined {\n    let cur = FunctionConstraint.registered[name];\n    if(!cur) {\n      throw new Error(`No function named ${name} is registered.`);\n    }\n\n    if(restFields.length && !cur.variadic) {\n      console.error(`The ${name} function is not variadic, so may not accept restFields.`);\n      restFields = createArray();\n    }\n\n    let created = new cur(fields, restFields);\n    return created;\n  }\n\n  constructor(public fields:ConstraintFieldMap, public restFields:(ID|Register)[]) {}\n\n  name:string;\n  args:{[name:string]: string};\n  returns:{[name:string]: string};\n  argNames:string[];\n  returnNames:string[];\n  apply: (this: FunctionConstraint, ... things: any[]) => undefined|(number|string)[]|(number|string)[][];\n  estimate?:(context:EvaluationContext, prefix:Prefix, transaction:number, round:number) => number\n  state?: any;\n  multi:boolean = false;\n  isInput:boolean = false;\n\n  fieldNames:string[];\n  proposal:Proposal = {cardinality:0, forFields: new Iterator<EAVNField>(), forRegisters: new Iterator<Register>(), proposer: this};\n  protected resolved:ResolvedFields = {};\n  protected resolvedRest:(number|undefined)[] = createArray();\n  protected registers:Register[] = createArray();\n  protected registerLookup:boolean[] = createArray();\n  protected applyInputs:(RawValue|RawValue[])[] = createArray();\n  protected applyRestInputs:RawValue[] = createArray();\n\n  toString() {\n    let params = this.fieldNames.map((v) => v + \": \" + printField(this.fields[v])).join(\", \");\n    let restParams = this.restFields.map(printField).join(\", \");\n    return `FunctionConstraint(\"${this.name}\", ${params} ${restParams ? `, [${restParams}]` : \"\"})`;\n  }\n\n  // We precompute the registers we're interested in for fast accepts.\n  setup() {\n    this.fieldNames = Object.keys(this.fields);\n\n    for(let fieldName of this.fieldNames) {\n      let field = this.fields[fieldName];\n      if(isRegister(field)) this.registers.push(field);\n    }\n\n    for(let field of this.restFields) {\n      if(isRegister(field)) this.registers.push(field);\n    }\n\n    for(let register of this.registers) {\n      this.registerLookup[register.offset] = true;\n    }\n\n    this.setupResolve();\n    this.setupResolveRest();\n  }\n\n  setupResolve() {\n    let {resolved} = this;\n    let parts = [\"var resolved = this.resolved;\"];\n    for(let fieldName of this.fieldNames) {\n      let field = this.fields[fieldName];\n      if(isRegister(field)) {\n        parts.push(`resolved[\"${fieldName}\"] = prefix[${field.offset}];`);\n      } else {\n        resolved[fieldName] = field;\n      }\n    }\n    parts.push(\"return resolved\");\n    this.resolve = new Function(\"prefix\", parts.join(\"\\n\")) as (prefix:Prefix) => ResolvedEAVN;\n  }\n\n  setupResolveRest() {\n    let {resolvedRest} = this;\n    let parts = [\"var resolvedRest = this.resolvedRest;\"];\n    let ix = 0;\n    for(let field of this.restFields) {\n      if(isRegister(field)) {\n        parts.push(`resolvedRest[${ix}] = prefix[${field.offset}]`);\n      } else {\n        resolvedRest[ix] = field;\n      }\n      ix++;\n    }\n    parts.push(\"return resolvedRest;\");\n    this.resolveRest = new Function(\"prefix\", parts.join(\"\\n\")) as (prefix:Prefix) => number[];\n  }\n\n  getRegisters() {\n    return this.registers;\n  }\n\n  /**\n   * Similar to `Scan.resolve`, but resolving a map of the function's\n   * fields rather than an EAVN.\n   */\n  resolve(prefix:Prefix) {\n    let resolved = this.resolved;\n\n    for(let fieldName of this.fieldNames) {\n      let field = this.fields[fieldName];\n      if(isRegister(field)) {\n        resolved[fieldName] = prefix[field.offset];\n      } else {\n        resolved[fieldName] = field;\n      }\n    }\n\n    return resolved;\n  }\n\n  /**\n   * If a function is variadic, we need to resolve its rest fields as well.\n   */\n  resolveRest(prefix:Prefix) {\n    let resolvedRest = this.resolvedRest;\n\n    let ix = 0;\n    for(let field of this.restFields) {\n      if(isRegister(field)) {\n        resolvedRest[ix] = prefix[field.offset];\n      } else {\n        resolvedRest[ix] = field;\n      }\n      ix++;\n    }\n\n    return resolvedRest;\n  }\n\n  // Function constraints have nothing to apply to the input, so they\n  // always return ApplyInputState.none\n  isAffected(input:Change):ApplyInputState { return ApplyInputState.none; }\n  applyInput(input:Change, prefix:Prefix):ApplyInputState { return ApplyInputState.none; }\n\n  propose(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:any[]):Proposal {\n    let proposal = this.proposal;\n    proposal.forRegisters.clear();\n    let resolved = this.resolve(prefix);\n\n    // If none of our returns are unbound\n    // @NOTE: We don't need to worry about the filter case here, since he'll be\n    let unresolvedOutput = false;\n    for(let output of this.returnNames) {\n      if(resolved[output] === undefined) {\n        unresolvedOutput = true;\n        let field = this.fields[output];\n        if(isRegister(field)) {\n          proposal.forRegisters.push(field);\n        }\n      }\n    }\n    if(!unresolvedOutput) {\n      proposal.skip = true;\n      return proposal;\n    }\n\n    // If any of our args aren't resolved yet, we can't compute results either.\n    // @NOTE: This'll need to be touched up when we add optional support if they\n    //   co-inhabit the args object.\n    for(let input of this.argNames) {\n      if(resolved[input] === undefined) {\n        proposal.skip = true;\n        return proposal;\n      }\n    }\n\n    // Similarly, if we're variadic we need to check that all of our\n    // variadic inputs bound to registers are resolved too.\n    // @NOTE: We really need to bend over backwards at the moment to\n    //   convince TS to check a static member of the current class...\n    if((this.constructor as (typeof FunctionConstraint)).variadic) {\n      let resolvedRest = this.resolveRest(prefix);\n      for(let field of resolvedRest) {\n        if(field === undefined) {\n          proposal.skip = true;\n          return proposal;\n        }\n      }\n    }\n\n    // Otherwise, we're ready to propose.\n    proposal.skip = false;\n\n    if(this.estimate) {\n      // If the function provides a cardinality estimator, invoke that.\n      proposal.cardinality = this.estimate(context, prefix, transaction, round);\n\n    } else {\n      // Otherwise, we'll just return 1 for now, since computing a\n      // function is almost always cheaper than a scan.\n      // @NOTE: If this is an issue, we can just behave like scans and\n      //   compute ourselves here, caching the results.\n      proposal.cardinality = 1;\n    }\n\n    return proposal;\n  }\n\n  /**\n   * Pack the resolved register values for the functions argument\n   * fields into an array.\n   */\n  packInputs(prefix:Prefix) {\n    let resolved = this.resolve(prefix);\n    let inputs = this.applyInputs;\n    let argIx = 0;\n    for(let argName of this.argNames) {\n      // If we're asked to resolve the propoal we know that we've\n      // proposed, and we'll only propose if these are resolved.\n      inputs[argIx] = GlobalInterner.reverse(resolved[argName]!);\n      argIx++;\n    }\n\n    // If we're variadic, we also need to pack our var-args up and\n    // attach them as the last argument.\n    if((this.constructor as (typeof FunctionConstraint)).variadic) {\n      let resolvedRest = this.resolveRest(prefix);\n      let restInputs = this.applyRestInputs;\n      restInputs.length = 0;\n      let ix = 0;\n      for(let value of resolvedRest) {\n        if(value !== undefined) {\n          restInputs[ix] = GlobalInterner.reverse(value);\n        }\n        ix++;\n      }\n\n      inputs[argIx] = restInputs;\n    }\n    return inputs;\n  }\n\n  unpackOutputs(outputs:RawValue[]) {\n    for(let ix = 0; ix < outputs.length; ix++) {\n      // @NOTE: we'd like to use arenaIntern here, but because of intermediate values\n      // that's not currently a possibility. We should revisit this if a practical solution\n      // for arenas surfaces.\n      outputs[ix] = GlobalInterner.intern(outputs[ix]);\n    }\n    return outputs as Prefix;\n  }\n\n  getResult(prefix:Prefix, outputs:ID[]) {\n    // Finally, if we had results, we create the result prefixes and pass them along.\n    let result = createArray(\"functionResult\") as Prefix;\n    let ix = 0;\n    for(let returnName of this.returnNames) {\n      let field = this.fields[returnName];\n      if(isRegister(field) && !prefix[field.offset]) {\n        result[ix] = outputs[ix];\n      }\n      ix++;\n    }\n    return result;\n  }\n\n  checkResult(prefix:Prefix, outputs:ID[]) {\n    // Finally, we make sure every return register matches up with our outputs.\n    // @NOTE: If we just use solvingFor then we don't know the offsets into the outputs array,\n    // so we check everything...\n    let ix = 0;\n    for(let returnName of this.returnNames) {\n      let field = this.fields[returnName];\n      let value = isRegister(field) ? prefix[field.offset] : field;\n      if(value !== outputs[ix]) {\n        return false;\n      }\n      ix++;\n    }\n    return true;\n  }\n\n  resolveProposal(context:EvaluationContext, prefix:Prefix, proposal:Proposal, transaction:number, round:number, results:any[]):ID[][] {\n    // First we build the args array to provide the apply function.\n    let inputs = this.packInputs(prefix);\n\n    // Then we actually apply it and then unpack the outputs.\n    let computed = this.apply.apply(this, inputs);\n    if(!computed) return results;\n    if(!this.multi) {\n      // If it's not a multi-returning function, it has a single result.\n      let outputs = this.unpackOutputs(computed);\n      let result = this.getResult(prefix, outputs);\n      results.push(result);\n    } else {\n      for(let row of computed) {\n        // Otherwise it has N results.\n        let outputs = this.unpackOutputs(row);\n        let result = this.getResult(prefix, outputs);\n        results.push(result);\n      }\n    }\n\n    return results;\n  }\n\n  accept(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, solvingFor:Register[]):boolean {\n    // If none of the registers we're solving for intersect our inputs\n    // or outputs, we're not relevant to the solution.\n    let isRelevant = false;\n    for(let register of solvingFor) {\n      if(this.registerLookup[register.offset]) {\n        isRelevant = true;\n        break;\n      }\n    }\n    if(!isRelevant) return true;\n\n    // If we're missing a field, we can't verify our output yet so we preliminarily accept.\n    for(let fieldName of this.fieldNames) {\n      let field = this.fields[fieldName];\n      if(isRegister(field) && prefix[field.offset] === undefined) return true;\n    }\n\n    // First we build the args array to provide the apply function.\n    let inputs = this.packInputs(prefix);\n\n    // Then we actually apply it and then unpack the outputs.\n    let computed = this.apply.apply(this, inputs);\n    if(!computed) return false;\n    if(!this.multi) {\n      // If it's not a multi-returning function we only need to check against the single result.\n      let outputs = this.unpackOutputs(computed);\n      return this.checkResult(prefix, outputs);\n    } else {\n      // Otherwise we match against any of the results.\n      for(let row of computed) {\n        let outputs = this.unpackOutputs(row);\n        if(this.checkResult(prefix, outputs)) return true;\n      }\n      return false;\n    }\n  }\n\n  acceptInput(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number):boolean {\n    return this.accept(context, prefix, transaction, round, this.registers);\n  }\n\n  getDiffs(context:EvaluationContext, prefix:Prefix):RoundArray {\n    return [];\n  }\n}\n\nexport interface FunctionSetup {\n  name:string,\n  variadic?: boolean,\n  args:{[argName:string]: string},\n  returns:{[argName:string]: string},\n  apply:(this: FunctionConstraint, ... things: any[]) => undefined|(number|string)[]|(number|string)[][],\n  estimate?:(index:Index, prefix:Prefix, transaction:number, round:number) => number,\n  initialState?:any,\n  multi?: true|false;\n}\nexport interface SingleFunctionSetup extends FunctionSetup {\n  apply:(this: FunctionConstraint, ... things: any[]) => undefined|(number|string)[],\n  multi?: false\n}\nexport interface MultiFunctionSetup extends FunctionSetup {\n  apply:(this: FunctionConstraint, ... things: any[]) => undefined|(number|string)[][],\n  multi?: true\n}\n\nfunction _makeFunction({name, variadic = false, args, returns, apply, estimate, initialState, multi = false}:FunctionSetup) {\n  class NewFunctionConstraint extends FunctionConstraint {\n    static variadic = variadic;\n    static filter = Object.keys(returns).length === 0;\n    static argNames = Object.keys(args);\n    static returnNames = Object.keys(returns);\n    name = name;\n    args = args;\n    argNames = Object.keys(args);\n    returnNames = Object.keys(returns);\n    returns = returns;\n    apply = apply;\n    state = initialState;\n    multi = multi\n  }\n  FunctionConstraint.register(name, NewFunctionConstraint);\n}\n\nexport function makeFunction(args:SingleFunctionSetup) {\n  return _makeFunction(args);\n}\nexport function makeMultiFunction(args:MultiFunctionSetup) {\n  args.multi = true;\n  return _makeFunction(args);\n}\n\n//------------------------------------------------------------------------\n// Nodes\n//------------------------------------------------------------------------\n\n/**\n * Base class for nodes, the building blocks of blocks.\n */\nexport abstract class Node {\n  static NodeID = 0;\n  ID = Node.NodeID++;\n  traceType:TraceNode;\n  results = new Iterator<Prefix>();\n\n  toBranchString():string {\n    return this.toString();\n  }\n\n  /**\n   * Evaluate the node in the context of the currently solved prefix,\n   * returning a set of valid prefixes to continue the query as\n   * results.\n   */\n  abstract exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, changes:Transaction):boolean;\n}\n\n/**\n * The JoinNode implements generic join across multiple constraints.\n * Since our system is incremental, we need to do something slightly\n * fancier than we did in the previous runtime.  For each new change\n * that enters the system, we ask each of our constraints whether they\n * are capable of producing a new result. In the case where a single\n * constraint can, we presolve that constraint and then run the rest\n * normally, limited to only producing results that match the first\n * constraint. However, if multiple constraints might apply the input,\n * we need to run for each *combination* of heads. E.g.:\n *\n * Given a join node with constraints [A, B, C, and D], where A and D\n * can both apply the input, we must combine the results of the\n * following computations to get the full result set:\n *\n * Apply {A} -> Do {B, C, D}\n * Apply {A, D} -> Do {B, C}\n * Apply {D} -> Do {A, B, C}\n *\n * We calculate this using the power set in exec.\n *\n * We then apply each of these combinations by running a genericJoin\n * over the remaining unresolved registers.  We ask each un-applied\n * constraint to propose a register to be solved. If a constraint is\n * capable of resolving one, it returns the set of registers it can\n * resolve and an estimate of the result set's cardinality. Generic\n * Join chooses the cheapest proposal, which the winning constraint\n * then fully computes (or retrieves from cache and returns). Next it\n * asks each other constraint to accept or reject the proposal. If the\n * constraint doesn't apply to the solved registers, it accepts.  If\n * the solution contains results that match the output of the\n * constraint, it also accepts. Otherwise, it must reject the solution\n * and that particular run yields no results.\n */\n\nexport class JoinNode extends Node {\n  traceType = TraceNode.Join;\n  isStatic = false;\n  dormant = false;\n  registerLength = 0;\n  registerLookup:boolean[];\n  registerArrays:Register[][];\n  proposedResultsArrays:ID[][];\n  emptyProposal:Proposal = {cardinality: Infinity, forFields: new Iterator<EAVNField>(), forRegisters: new Iterator<Register>(), skip: true, proposer: {} as Constraint};\n  inputCount:Multiplicity;\n  protected affectedConstraints = new Iterator<Constraint>();\n\n  constructor(public constraints:Constraint[]) {\n    super();\n    // We need to find all the registers contained in our scans so that\n    // we know how many rounds of Generic Join we need to do.\n    let registerLength = 0;\n    let registerLookup = [];\n    let registers = createArray() as Register[][];\n    let proposedResultsArrays = createArray() as ID[][];\n    let hasOnlyMoves = true;\n    let hasNoScans = true;\n    let onlyStatic = true;\n    for(let constraint of constraints) {\n      constraint.setup();\n      if(!(constraint instanceof MoveConstraint)) hasOnlyMoves = false;\n      else if(!constraint.isStatic) onlyStatic = false;\n\n      if(constraint instanceof Scan) hasNoScans = false;\n\n      for(let register of constraint.getRegisters()) {\n        if(!registerLookup[register.offset]) {\n          registers.push(createArray() as Register[]);\n          proposedResultsArrays.push(createArray() as Prefix);\n          registerLookup[register.offset] = true;\n          registerLength++;\n        }\n      }\n    }\n\n    if(hasOnlyMoves) {\n      for(let constraint of constraints as MoveConstraint[]) {\n        constraint.shouldApplyInput = true;\n      }\n      this.isStatic = onlyStatic;\n    }\n\n    if(hasNoScans) {\n      this.exec = JoinNode.prototype.downStreamExec;\n    }\n\n    this.registerLookup = registerLookup;\n    this.registerArrays = registers;\n    this.registerLength = registerLength;\n    this.proposedResultsArrays = proposedResultsArrays;\n  }\n\n  toString() {\n    return \"JoinNode([\\n  \" + this.constraints.map(toString).join(\"\\n  \") + \"\\n])\";\n  }\n\n  findAffectedConstraints(input:Change, prefix:Prefix):Iterator<Constraint> {\n    // @TODO: Hoist me out.\n    let affectedConstraints = this.affectedConstraints;\n    affectedConstraints.clear();\n    for(let ix = 0, len = this.constraints.length; ix < len; ix++) {\n      let constraint = this.constraints[ix];\n      let result = constraint.isAffected(input);\n\n      if(result !== ApplyInputState.none) {\n        affectedConstraints.push(constraint);\n      }\n    }\n\n    return affectedConstraints;\n  }\n\n  applyCombination(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>) {\n    // debug(\"        Join combo:\", prefix.slice());\n    let countOfSolved = 0;\n    for(let ix = 0; ix < this.registerLookup.length; ix++) {\n      if(!this.registerLookup[ix]) continue;\n      if(prefix[ix] !== undefined) countOfSolved++;\n    }\n    let remainingToSolve = this.registerLength - countOfSolved;\n    // context.tracer.tracker.blockTime(\"PresolveCheck\");\n    let valid = this.presolveCheck(context, input, prefix, transaction, round);\n    // context.tracer.tracker.blockTimeEnd(\"PresolveCheck\");\n    // debug(\"        Join combo valid:\", valid, remainingToSolve, countOfSolved, this.registerLength);\n    if(!valid) {\n      // do nothing\n      return false;\n\n    } else if(!remainingToSolve) {\n      // if it is valid and there's nothing left to solve, then we've found\n      // a full result and we should just continue\n      this.prefixToResults(context, this.constraints, prefix, round, results);\n      // debug(\"        Join combo result:\", results);\n      return true;\n\n    } else {\n      // debug(\"              GJ:\", remainingToSolve, this.constraints);\n      // For each node, find the new results that match the prefix.\n      this.genericJoin(context, prefix, transaction, round, results, remainingToSolve);\n      // context.tracer.tracker.blockTimeEnd(\"GenericJoin\");\n      return true;\n    }\n  }\n\n  unapplyConstraint(constraint:Constraint, prefix:Prefix) {\n    for(let register of constraint.getRegisters()) {\n      prefix[register.offset] = undefined as any;\n    }\n  }\n\n  presolveCheck(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number):boolean {\n    let {constraints} = this;\n    // window[\"counts\"][window[\"active\"]]++;\n\n    for(let constraint of constraints) {\n      let valid = constraint.acceptInput(context, input, prefix, transaction, round);\n      if(!valid) {\n        // debug(\"          INVALID:\", constraint);\n        return false;\n      }\n    }\n\n    return true;\n  }\n\n  computeMultiplicities(context: EvaluationContext, results:Iterator<Prefix>, prefix:Prefix, currentRound:number, diffs: RoundArray[], diffIndex:number = -1) {\n    if(diffIndex === -1) {\n      prefix[prefix.length - 2] = currentRound;\n      prefix[prefix.length - 1] = this.inputCount;\n      this.computeMultiplicities(context, results, prefix, currentRound, diffs, diffIndex + 1);\n      prefix[prefix.length - 2] = undefined as any;\n      prefix[prefix.length - 1] = undefined as any;\n    } else if(diffIndex === diffs.length) {\n      let result = copyArray(prefix, \"gjResultsArray\");\n      context.tracer.capturePrefix(result);\n      //debug(\"          GJ -> \", result);\n      results.push(result);\n    } else {\n      let startingRound = prefix[prefix.length - 2];\n      let startingMultiplicity = prefix[prefix.length - 1];\n      let rounds = diffs[diffIndex];\n      let roundToMultiplicity:{[round:number]: number} = {};\n      let maxRound = currentRound;\n      let ix = 0;\n      let currentRoundCount = 0;\n      for(let round of rounds) {\n        if(Math.abs(round) - 1 > currentRound) {\n          break;\n        }\n        currentRoundCount += round > 0 ? 1 : -1;\n        ix++;\n      }\n      if(currentRoundCount) {\n        prefix[prefix.length - 2] = Math.max(currentRound, startingRound);\n        prefix[prefix.length - 1] = startingMultiplicity * currentRoundCount;\n        this.computeMultiplicities(context, results, prefix, currentRound, diffs, diffIndex + 1);\n      }\n      for(; ix < rounds.length; ix++) {\n        let round = rounds[ix];\n        let count = round > 0 ? 1 : -1;\n        prefix[prefix.length - 2] = Math.max(Math.abs(round) - 1, startingRound);\n        prefix[prefix.length - 1] = startingMultiplicity * count;\n        this.computeMultiplicities(context, results, prefix, currentRound, diffs, diffIndex + 1);\n      }\n      prefix[prefix.length - 2] = startingRound;\n      prefix[prefix.length - 1] = startingMultiplicity;\n    }\n    return results;\n  }\n\n  prefixToResults(context:EvaluationContext, constraints:Constraint[], prefix:Prefix, round:number, results:Iterator<Prefix>) {\n    let diffs = [];\n    for(let constraint of constraints) {\n      if(constraint.isInput || !(constraint instanceof Scan)) continue;\n      let cur = constraint.getDiffs(context, prefix);\n      diffs.push(cur);\n    }\n    this.computeMultiplicities(context, results, prefix, round, diffs);\n  }\n\n  genericJoin(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, roundIx:number = this.registerLength):Iterator<Prefix> {\n    let {constraints, emptyProposal} = this;\n    let proposedResults = this.proposedResultsArrays[roundIx - 1];\n    let forRegisters:Register[] = this.registerArrays[roundIx - 1];\n    proposedResults.length = 0;\n\n    let bestProposal:Proposal = emptyProposal;\n\n    for(let constraint of constraints) {\n      let current = constraint.propose(context, prefix, transaction, round, proposedResults);\n      if(!current.skip && current.cardinality === 0) {\n        return results;\n      } else if(current.cardinality < bestProposal.cardinality && !current.skip) {\n        bestProposal = current;\n      }\n    }\n\n    if(bestProposal.skip) {\n      // debug(\"             BAILING\", bestProposal);\n      return results;\n    }\n\n\n    let {proposer} = bestProposal;\n    // We have to copy here because we need to keep a reference to this even if later\n    // rounds might overwrite the proposal\n    moveArray(bestProposal.forRegisters.array, forRegisters);\n    let resolved:any[] = proposer.resolveProposal(context, prefix, bestProposal, transaction, round, proposedResults);\n    if(resolved[0] && resolved[0].constructor === Array) {\n      resultLoop: for(let result of resolved) {\n        let ix = 0;\n        for(let register of forRegisters) {\n          prefix[register.offset] = +result[ix];\n          ix++;\n        }\n        for(let constraint of constraints) {\n          if(constraint === proposer) continue;\n          if(!constraint.accept(context, prefix, transaction, round, forRegisters)) {\n            // debug(\"             BAILING\", printConstraint(constraint));\n            continue resultLoop;\n          }\n        }\n        if(roundIx === 1) {\n          this.prefixToResults(context, constraints, prefix, round, results);\n        } else {\n          this.genericJoin(context, prefix, transaction, round, results, roundIx - 1);\n        }\n      }\n    } else {\n      let register = forRegisters[0];\n      resultLoop: for(let result of resolved) {\n        prefix[register.offset] = +result as ID;\n        for(let constraint of constraints) {\n          if(constraint === proposer) continue;\n          if(!constraint.accept(context, prefix, transaction, round, forRegisters)) {\n            // debug(\"             BAILING\", printConstraint(constraint));\n            continue resultLoop;\n          }\n        }\n        if(roundIx === 1) {\n          this.prefixToResults(context, constraints, prefix, round, results);\n        } else {\n          this.genericJoin(context, prefix, transaction, round, results, roundIx - 1);\n        }\n      }\n    }\n    for(let register of forRegisters) {\n      // @NOTE: marking this as any is spoopy at best, but since we should never\n      // iterate over the prefix, but instead use it almost like a hash, this\n      // should be fine.\n      prefix[register.offset] = undefined as any;\n    }\n\n    return results;\n  }\n\n  downStreamExec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>) {\n    if(this.isStatic && this.dormant) return false;\n\n    this.inputCount = prefix[prefix.length - 1] !== undefined ? prefix[prefix.length - 1] : input.count;\n    let inputRound = prefix[prefix.length - 2] !== undefined ? prefix[prefix.length - 2] : input.round;\n    let didSomething = this.applyCombination(context, input, prefix, transaction, inputRound, results);\n\n    if(this.isStatic && didSomething) {\n      this.dormant = true;\n    }\n    return didSomething;\n  }\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>):boolean {\n    let didSomething = false;\n    this.inputCount = input.count;\n\n    // if we are removing the block, we need the join to compute all the results currently\n    // being generated by this block and undo them. We do this by sending a blank prefix\n    // through with an input count of -1.\n    if(input === BLOCK_REMOVE) {\n      didSomething = this.applyCombination(context, input, prefix, transaction, round, results);\n    } else if(this.isStatic && this.dormant) {\n      return false;\n    } else if(input === BLOCK_ADD) {\n      didSomething = this.applyCombination(context, input, prefix, transaction, round, results);\n    } else {\n      this.inputCount = input.count;\n      let affectedConstraints = this.findAffectedConstraints(input, prefix);\n\n      let combinationCount = Math.pow(2, affectedConstraints.length);\n      for(let comboIx = combinationCount - 1; comboIx > 0; comboIx--) {\n        //console.log(\"  Combo:\", comboIx);\n\n        let shouldApply = true;\n\n        for(let constraintIx = 0; constraintIx < affectedConstraints.length; constraintIx++) {\n          let mask = 1 << constraintIx;\n          let isIncluded = (comboIx & mask) !== 0;\n          let constraint = affectedConstraints.array[constraintIx];\n          constraint.isInput = isIncluded;\n\n          if(isIncluded) {\n            let valid = constraint.applyInput(input, prefix);\n            // debug(\"        included\", printConstraint(constraint));\n            // If any member of the input constraints fails, this whole combination is doomed.\n            if(valid === ApplyInputState.fail) {\n              shouldApply = false;\n              break;\n            }\n            //console.log(\"    \" + printConstraint(constraint));\n          }\n        }\n\n        //console.log(\"    \", printPrefix(prefix));\n        if(shouldApply) {\n          didSomething = this.applyCombination(context, input, prefix, transaction, round, results) || didSomething;\n        }\n\n        let constraint;\n        affectedConstraints.reset();\n        while((constraint = affectedConstraints.next()) !== undefined) {\n          this.unapplyConstraint(constraint, prefix);\n        }\n      }\n\n      affectedConstraints.reset();\n      let constraint;\n      while((constraint = affectedConstraints.next()) !== undefined) {\n        constraint.isInput = false;\n      }\n    }\n\n    if(this.isStatic && didSomething) {\n      this.dormant = true;\n    }\n\n    return didSomething;\n  }\n}\n\nexport class DownstreamJoinNode extends JoinNode {\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>):boolean {\n    return this.downStreamExec(context, input, prefix, transaction, round, results);\n  }\n}\n\nexport class NoopJoinNode extends JoinNode {\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>):boolean {\n    if(!this.dormant) {\n      this.prefixToResults(context, this.constraints, prefix, round, results);\n      this.dormant = true;\n      return true\n    }\n    return false;\n  }\n}\n\nexport class WatchNode extends Node {\n  traceType = TraceNode.Watch;\n  constructor(public e:ID|Register,\n              public a:ID|Register,\n              public v:ID|Register,\n              public n:ID|Register,\n              public blockId:number) {\n                super();\n  }\n\n  protected resolved:ResolvedFields = {};\n  resolve = Scan.prototype.resolve;\n\n  toString() {\n    return `WatchNode(${printField(this.e)}, ${printField(this.a)}, ${printField(this.v)}, ${printField(this.n)})`;\n  }\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transactionId:number, round:number, results:Iterator<Prefix>, transaction:Transaction):boolean {\n    let resolved = this.resolve(prefix);\n    let {e,a,v,n} = resolved;\n\n    // @NOTE: This is wasteful.\n    results.push(prefix);\n\n    if(e === undefined || a === undefined || v === undefined || n === undefined) {\n      throw new Error(`Unable to produce an output with an undefined EAVN field [${e}, ${a}, ${v}, ${n}]`);\n    }\n\n    let prefixRound = prefix[prefix.length - 2];\n    let prefixCount = prefix[prefix.length - 1];\n\n    // @FIXME: Make sure I still work now that I'm sending all my deltas. I think I still need to use local intermediates.\n    let change = new Change(e!, a!, v!, n!, transactionId, prefixRound + 1, prefixCount);\n    transaction.export(context, this.blockId, change);\n    return true;\n  }\n}\n\nexport class OutputWrapperNode extends Node {\n  traceType = TraceNode.Output;\n  constructor(public nodes:OutputNode[]) {\n    super();\n  }\n\n  toString() {\n    return `OutputWrapper([${this.nodes.length ? \"\\n  \" : \"\"}${indent(this.nodes.map(toString).join(\"\\n\"), 2)}])`;\n  }\n\n  binds = new Iterator<Change>();\n  commits = new Iterator<Change>();\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transactionId:number, round:number, results:Iterator<Prefix>, transaction:Transaction):boolean {\n    let {tracer} = context;\n    let {binds, commits} = this;\n    binds.clear();\n    commits.clear();\n    for(let node of this.nodes) {\n      node.exec(context, input, prefix, transactionId, round, binds, commits);\n    }\n\n    binds.reset();\n    let change;\n    while(change = binds.next()) {\n      transaction.output(context, change);\n    }\n\n    commits.reset();\n    while(change = commits.next()) {\n      transaction.commit(context, change);\n    }\n\n    return true;\n  }\n}\n\nexport interface OutputNode {\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transactionId:number, round:number, binds:Iterator<Change>, commits:Iterator<Change>):void;\n}\n\nexport class InsertNode implements OutputNode {\n  multiplier:number = 1;\n  resolve: (prefix:Prefix) => ResolvedEAVN;\n\n  constructor(public e:ID|Register, public a:ID|Register, public v:ID|Register, public n:ID|Register) {\n    let parts = [\"var resolved = this.resolved;\"];\n    this.setupRegister(\"e\", parts);\n    this.setupRegister(\"a\", parts);\n    this.setupRegister(\"v\", parts);\n    this.setupRegister(\"n\", parts);\n    parts.push(\"return resolved\");\n    this.resolve = new Function(\"prefix\", parts.join(\"\\n\")) as (prefix:Prefix) => ResolvedEAVN;\n  }\n\n  toString() {\n    return `InsertNode(${printField(this.e)}, ${printField(this.a)}, ${printField(this.v)}, ${printField(this.n)})`;\n  }\n\n  setupRegister(field:EAVNField, parts:string[]) {\n    let value = this[field];\n    if(isRegister(value)) {\n      parts.push(`resolved.${field} = prefix[this.${field}.offset];`);\n    } else {\n      this.resolved[field] = value;\n    }\n  }\n\n  // We precompute the registers we're interested in for fast accepts.\n  protected resolved:ResolvedEAVN = {e: undefined, a: undefined, v:undefined, n: undefined};\n\n  output(change:Change, binds:Iterator<Change>, commits:Iterator<Change>) {\n    binds.push(change);\n  }\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transactionId:number, round:number, binds:Iterator<Change>, commits:Iterator<Change>):boolean {\n    let resolved = this.resolve(prefix);\n    let {e,a,v,n} = resolved;\n\n    if(e === undefined || a === undefined || v === undefined || n === undefined) {\n      throw new Error(`Unable to produce an output with an undefined EAVN field [${e}, ${a}, ${v}, ${n}]`);\n    }\n\n    let prefixRound = prefix[prefix.length - 2];\n    let prefixCount = prefix[prefix.length - 1];\n\n    let change = new Change(e!, a!, v!, n!, transactionId, prefixRound + 1, prefixCount * this.multiplier);\n    this.output(change, binds, commits);\n    return true;\n  }\n}\n\nexport class CommitInsertNode extends InsertNode {\n  toString() {\n    return `CommitInsertNode(${printField(this.e)}, ${printField(this.a)}, ${printField(this.v)}, ${printField(this.n)})`;\n  }\n\n  output(change:Change, binds:Iterator<Change>, commits:Iterator<Change>) {\n    commits.push(change);\n  }\n}\n\nexport class RemoveNode extends InsertNode {\n  multiplier:number = -1;\n\n  toString() {\n    return `RemoveNode(${printField(this.e)}, ${printField(this.a)}, ${printField(this.v)}, ${printField(this.n)})`;\n  }\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transactionId:number, round:number, binds:Iterator<Change>, commits:Iterator<Change>):boolean {\n    let resolved = this.resolve(prefix);\n    let {e,a,v,n} = resolved;\n\n    if(e === undefined || a === undefined || (v === undefined && this.v !== IGNORE_REG) || n === undefined) {\n      return false;\n    }\n\n    let prefixRound = prefix[prefix.length - 2];\n    let prefixCount = prefix[prefix.length - 1];\n\n    if(this.v !== IGNORE_REG) {\n      let change = new RemoveChange(e!, a!, v!, n!, transactionId, prefixRound + 1, prefixCount * this.multiplier);\n      this.output(change, binds, commits);\n    } else if(this.a !== IGNORE_REG) {\n      let change = new RemoveVsChange(e!, a!, v!, n!, transactionId, prefixRound + 1, prefixCount * this.multiplier);\n      this.output(change, binds, commits);\n    } else {\n      let change = new RemoveAVsChange(e!, a!, v!, n!, transactionId, prefixRound + 1, prefixCount * this.multiplier);\n      this.output(change, binds, commits);\n    }\n    return true;\n  }\n}\n\nexport class CommitRemoveNode extends CommitInsertNode {\n  toString() {\n    return `CommitRemoveNode(${printField(this.e)}, ${printField(this.a)}, ${printField(this.v)}, ${printField(this.n)})`;\n  }\n\n  multiplier = -1;\n\n  protected _exec = RemoveNode.prototype.exec;\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transactionId:number, round:number, binds:Iterator<Change>, commits:Iterator<Change>):boolean {\n    return this._exec(context, input, prefix, transactionId, round, binds, commits);\n  }\n}\n\n//------------------------------------------------------------------------------\n// LinearFlow\n//------------------------------------------------------------------------------\n\nexport class LinearFlow extends Node {\n  traceType = TraceNode.LinearFlow;\n  results = new Iterator<Prefix>();\n  initialResults = new Iterator<Prefix>();\n\n  constructor(public nodes:Node[]) {\n    super();\n  }\n\n  toString() {\n    let content = this.nodes.map(toString).join(\",\\n\");\n    return `LinearFlow([\\n  ${indent(content, 2)}\\n])`;\n  }\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, changes:Transaction):boolean {\n    this.initialResults.clear();\n    this.initialResults.push(prefix);\n    // We populate the prefix with values from the input change so we only derive the\n    // results affected by it.\n    let curPrefix;\n    let iter = this.initialResults;\n    for(let node of this.nodes) {\n      if(iter.length === 0) return false;\n      node.results.clear();\n      while(curPrefix = iter.next()) {\n        context.tracer.node(node, curPrefix);\n        let valid = node.exec(context, input, curPrefix, transaction, round, node.results, changes);\n        context.tracer.pop(TraceFrameType.Node);\n      }\n      iter = node.results.iter();\n    }\n\n    while(curPrefix = iter.next()) {\n      results.push(curPrefix);\n    }\n\n    return true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// BinaryFlow\n//------------------------------------------------------------------------------\n\nexport type KeyFunction = (prefix:Prefix) => string;\n\nexport class IntermediateIndexIterator {\n  values:{[value:string]: any[]};\n  valueKeys:string[];\n  currentCounts: any[];\n\n  valueIx = 0;\n  countIx = 0;\n\n  round = 0;\n  count = 0;\n  minRound = 0;\n\n  reset(values:{[value:string]: any[]}, minRound = 0) {\n    this.values = values;\n    this.valueIx = 0;\n    this.countIx = 0;\n    this.round = 0;\n    this.count = 0;\n    this.minRound = minRound + 1;\n    this.valueKeys = Object.keys(values);\n    this.currentCounts = values[this.valueKeys[0]];\n    return this;\n  }\n\n  next():Prefix|undefined {\n    let {currentCounts, countIx, minRound} = this;\n    if(!currentCounts) return;\n    countIx++;\n    if(countIx >= currentCounts.length) {\n      this.valueIx++;\n      let nextValue = this.valueKeys[this.valueIx];\n      currentCounts = this.currentCounts = this.values[nextValue];\n      if(!currentCounts) return;\n      countIx = 1;\n    }\n    let count = 0;\n    if(countIx < minRound) {\n      let total = 0;\n      for(; countIx <= minRound; countIx++) {\n        let cur = currentCounts[countIx];\n        if(!cur) continue;\n        total += cur;\n      }\n      count = total;\n      countIx = minRound;\n    } else {\n      for(; countIx < currentCounts.length; countIx++) {\n        let cur = currentCounts[countIx];\n        if(cur) {\n          count = cur;\n          break;\n        }\n      }\n    }\n    this.round = countIx - 1;\n    this.count = count;\n    this.countIx = countIx;\n    if(count == 0) return this.next();\n    return currentCounts[0];\n  }\n}\n\nexport class IntermediateIndex {\n  static CreateKeyFunction(registers:Register[]):KeyFunction {\n    let items = registers.map((reg) => {\n      return `prefix[${reg.offset}]`;\n    })\n    let code = `\n      return \"\" ${items.length ? \"+\" : \"\"} ${items.join(' + \"|\" + ')};\n      `;\n    return new Function(\"prefix\", code) as KeyFunction;\n  }\n\n  index:{[key:string]: {[value:string]: any[]}} = {};\n  iterator = new IntermediateIndexIterator();\n\n  insert(key:string, prefix:Prefix) {\n    let values = this.index[key];\n    if(!values) values = this.index[key] = createHash(\"intermediateIndexValues\");\n    let valueKey = this.hashPrefix(prefix);\n    let counts = values[valueKey];\n    if(!counts) {\n      counts = values[valueKey] = createArray(\"intermediateIndexCounts\");\n      counts[0] = prefix;\n    }\n    let round = prefix[prefix.length - 2] + 1;\n    let count = prefix[prefix.length - 1];\n    counts[round] = count + (counts[round] || 0);\n    if(!counts[round]) {\n      let shouldRemove = true;\n      for(let ix = 1, len = counts.length; ix < len; ix++) {\n        if(counts[ix]) {\n          shouldRemove = false;\n          break;\n        }\n      }\n      if(shouldRemove) {\n        delete values[valueKey];\n      }\n    }\n  }\n\n  iter(key:string, round:number):IntermediateIndexIterator|undefined {\n    let values = this.index[key];\n    if(values) return this.iterator.reset(values, round);\n  }\n\n  hashPrefix(prefix:Prefix) {\n    let round = prefix[prefix.length - 2];\n    let count = prefix[prefix.length - 1];\n    prefix[prefix.length - 2] = undefined as any;\n    prefix[prefix.length - 1] = undefined as any;\n    let key = prefix.join(\"|\");\n    prefix[prefix.length - 2] = round;\n    prefix[prefix.length - 1] = count;\n    return key;\n  }\n}\n\nexport class ZeroingIterator {\n  counts:Multiplicity[];\n  roundIx = -1;\n  countSum = 0;\n  minRound = 0;\n  count = 1;\n\n  reset(counts:Multiplicity[], minRound:number = 0) {\n    this.counts = counts;\n    this.minRound = minRound;\n    this.roundIx = -1;\n    this.countSum = 0;\n    this.count = 1;\n    return this;\n  }\n\n  next():number|undefined {\n    let {roundIx, counts, countSum, minRound} = this;\n    let countsLength = counts.length;\n    roundIx++;\n    if(roundIx >= countsLength) return;\n    let final;\n    if(roundIx <= minRound) {\n      if(minRound >= countsLength) countsLength = minRound + 1;\n      for(; roundIx < countsLength; roundIx++) {\n        let cur = counts[roundIx];\n        if(cur) {\n          countSum += cur;\n        }\n        if(roundIx >= minRound && countSum === 0) {\n          final = roundIx;\n          break;\n        }\n      }\n    } else {\n      for(; roundIx <= countsLength; roundIx++) {\n        let cur = counts[roundIx];\n        if(!cur) continue;\n        countSum += cur;\n        if((this.countSum === 0 && countSum > 0) ||\n           (this.countSum > 0 && countSum === 0)) {\n          final = roundIx;\n          break;\n        }\n      }\n    }\n\n    this.roundIx = roundIx;\n    this.countSum = countSum;\n    this.count = 1;\n    if(countSum !== 0) {\n      this.count = -1;\n    }\n    return final;\n  }\n}\n\nexport class KeyOnlyIntermediateIndex {\n  index:{[key:string]: Multiplicity[]} = {};\n  iterator = new ZeroingIterator();\n\n  insert(key:string, prefix:Prefix) {\n    let counts = this.index[key];\n    if(!counts) counts = this.index[key] = createArray(\"KeyOnlyIntermediateIndex\");\n    let round = prefix[prefix.length - 2];\n    let count = prefix[prefix.length - 1];\n    let prev = counts[round] || 0;\n    counts[round] = count + prev;\n    if(!counts[round]) {\n      let shouldRemove = true;\n      for(let ix = 0, len = counts.length; ix < len; ix++) {\n        if(counts[ix]) {\n          shouldRemove = false;\n          break;\n        }\n      }\n      if(shouldRemove) {\n        delete this.index[key];\n      }\n    }\n  }\n\n  has(key:string) {\n    return this.index[key] ? true : false;\n  }\n\n  iter(key:string, round:number):ZeroingIterator|undefined {\n    let values = this.index[key];\n    if(values) return this.iterator.reset(values, round);\n  }\n}\n\nexport abstract class BinaryFlow extends Node {\n  traceType = TraceNode.BinaryJoin;\n\n  constructor(public left:Node, public right:Node) {\n    super();\n  }\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, changes:Transaction):boolean {\n    let {left, right} = this;\n    left.results.clear();\n    context.tracer.node(left, prefix);\n    left.exec(context, input, prefix, transaction, round, left.results, changes);\n    context.tracer.pop(TraceFrameType.Node);\n    right.results.clear();\n    context.tracer.node(right, prefix);\n    right.exec(context, input, prefix, transaction, round, right.results, changes);\n    context.tracer.pop(TraceFrameType.Node);\n    let result;\n    let leftResults = left.results.iter();\n    let rightResults = right.results.iter();\n    while((result = leftResults.next()) !== undefined) {\n      this.onLeft(context, result, transaction, round, results);\n    }\n    while((result = rightResults.next()) !== undefined) {\n      this.onRight(context, result, transaction, round, results);\n    }\n    return true;\n  }\n\n  abstract onLeft(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>):void;\n  abstract onRight(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>):void;\n}\n\nexport class BinaryJoinRight extends BinaryFlow {\n  leftIndex = new IntermediateIndex();\n  rightIndex = new IntermediateIndex();\n  keyFunc:KeyFunction;\n\n  constructor(public left:Node, public right:Node, public keyRegisters:Register[], public registersToMerge:Register[]) {\n    super(left, right);\n    this.keyFunc = IntermediateIndex.CreateKeyFunction(keyRegisters);\n  }\n\n  _nodeName:string = \"BinaryJoinRight\";\n\n  toString() {\n    let keys = \"[\" + this.keyRegisters.map((r) => `[${r.offset}]`).join(\", \") + \"]\";\n    let merge = \"[\" + this.registersToMerge.map((r) => `[${r.offset}]`).join(\", \") + \"]\";\n    return `${this._nodeName}({\n  keys: ${keys},\n  merge: ${merge},\n  left: ${indent(toString(this.left), 2)},\n  right: ${indent(toString(this.right), 2)}\n})`;\n  }\n  toBranchString() {\n    let keys = \"[\" + this.keyRegisters.map((r) => `[${r.offset}]`).join(\", \") + \"]\";\n    let merge = \"[\" + this.registersToMerge.map((r) => `[${r.offset}]`).join(\", \") + \"]\";\n\n    return `${this._nodeName}({\n  keys: ${keys},\n  merge: ${merge},\n  right: ${indent(toString(this.right), 2)}\n})`;\n  }\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, changes:Transaction):boolean {\n    // debug(\"    Binary Join Right:\")\n    let {left, right} = this;\n    right.results.clear();\n    context.tracer.node(right, prefix);\n    right.exec(context, input, prefix, transaction, round, right.results, changes);\n    context.tracer.pop(TraceFrameType.Node);\n    let leftResults = left.results.iter();\n    let rightResults = right.results.iter();\n    let result;\n    while((result = leftResults.next()) !== undefined) {\n      this.onLeft(context, result, transaction, round, results);\n    }\n    while((result = rightResults.next()) !== undefined) {\n      this.onRight(context, result, transaction, round, results);\n    }\n    // debug(\"        results:\", results.array.slice());\n    return true;\n  }\n\n  merge(left:Prefix, right:Prefix) {\n    for(let register of this.registersToMerge) {\n      let leftValue = left[register.offset];\n      let rightValue = right[register.offset];\n      if(leftValue === undefined || leftValue === rightValue) {\n        left[register.offset] = rightValue\n      } else {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  onLeft(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>):void {\n    let key = this.keyFunc(prefix);\n    let prefixRound = prefix[prefix.length - 2];\n    let count = prefix[prefix.length - 1];\n    this.leftIndex.insert(key, prefix);\n    let diffs = this.rightIndex.iter(key, round)\n    // debug(\"       join left\", key, printPrefix(prefix), diffs);\n    if(!diffs) return;\n    let rightPrefix;\n    while(rightPrefix = diffs.next()) {\n      let result = copyArray(prefix, \"BinaryJoinResult\");\n      if(this.merge(result, rightPrefix)) {\n        result[result.length - 2] = Math.max(prefixRound, diffs.round);\n        result[result.length - 1] = count * diffs.count;\n        context.tracer.capturePrefix(result);\n        results.push(result);\n        // debug(\"               join left -> \", printPrefix(result), diffs.round, count, diffs.count);\n      }\n    }\n  }\n\n  onRight(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>):void {\n    let key = this.keyFunc(prefix);\n    let prefixRound = prefix[prefix.length - 2];\n    let count = prefix[prefix.length - 1];\n    this.rightIndex.insert(key, prefix);\n    let diffs = this.leftIndex.iter(key, round)\n    // debug(\"       join right\", key, this.rightIndex.index[key]);\n    if(!diffs) return;\n    let leftPrefix;\n    while(leftPrefix = diffs.next()) {\n      let result = copyArray(leftPrefix, \"BinaryJoinResult\");\n      if(this.merge(result, prefix)) {\n        result[result.length - 2] = Math.max(prefixRound, diffs.round);\n        result[result.length - 1] = count * diffs.count;\n        context.tracer.capturePrefix(result);\n        results.push(result);\n        // debug(\"              join right -> \", printPrefix(result.slice()), diffs.round, count, diffs.count);\n      }\n    }\n  }\n}\n\nexport class AntiJoin extends BinaryFlow {\n  traceType = TraceNode.AntiJoin;\n  leftIndex = new IntermediateIndex();\n  rightIndex = new KeyOnlyIntermediateIndex();\n  distinct = new DistinctIndex();\n  keyFunc:KeyFunction;\n\n  constructor(public left:Node, public right:Node, public keyRegisters:Register[]) {\n    super(left, right);\n    this.keyFunc = IntermediateIndex.CreateKeyFunction(keyRegisters);\n  }\n\n  toString() {\n    let left = indent(toString(this.left), 2);\n    let right = indent(toString(this.right), 2);\n    return `Antijoin({\\n  left: ${left},\\n  right: ${right}\\n})`;\n  }\n  toBranchString() {\n    let right = indent(toString(this.right), 2);\n    return `Antijoin({\\n  right: ${right}\\n})`;\n  }\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, changes:Transaction):boolean {\n    // debug(\"            antijoin:\")\n    return super.exec(context,input,prefix,transaction,round,results,changes);\n  }\n\n  onLeft(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>):void {\n    let key = this.keyFunc(prefix);\n    let prefixRound = prefix[prefix.length - 2];\n    let count = prefix[prefix.length - 1];\n    this.leftIndex.insert(key, prefix);\n    let diffs = this.rightIndex.iter(key, prefixRound);\n    // debug(\"                left:\", key, count, this.rightIndex.index[key] && copyArray(this.rightIndex.index[key]), prefix);\n    if(!diffs) {\n      // debug(\"                    left ->\", key, count, diffs)\n      return results.push(prefix);\n    } else {\n      let currentRound;\n      while((currentRound = diffs.next()) !== undefined) {\n        let result = copyArray(prefix, \"AntiJoinResult\");\n        result[result.length - 2] = currentRound;\n        result[result.length - 1] = diffs.count * count;\n        context.tracer.capturePrefix(result);\n        results.push(result);\n        // debug(\"                    left ->\", key, count, currentRound, result[result.length - 1])\n      }\n    }\n  }\n\n  onRight(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>):void {\n    let key = this.keyFunc(prefix);\n    let prefixRound = prefix[prefix.length - 2];\n    let count = prefix[prefix.length - 1];\n    this.rightIndex.insert(key, prefix);\n    let neue = new Iterator<[number, number]>();\n    this.distinct.distinctKey(key, prefixRound, count, neue)\n    let diffs = this.leftIndex.iter(key, prefixRound)\n    let copy = (thing:any) => {\n      let neue = copyHash(thing);\n      for(let key in thing) {\n        neue[key] = thing[key].slice();\n      }\n      return neue;\n    }\n    // debug(\"                right:\", key, count, this.leftIndex.index[key] && copy(this.leftIndex.index[key]));\n    // debug(\"                right distinct: \", this.distinct.index[key], neue);\n    if(!diffs || !neue.length) return;\n    let leftPrefix;\n    let rightDelta;\n    while(rightDelta = neue.next()) {\n      let [rightRound, rightCount] = rightDelta;\n      diffs = this.leftIndex.iter(key, prefixRound)!; // We already checked for this above.\n      while(leftPrefix = diffs.next()) {\n        let result = copyArray(leftPrefix, \"AntiJoinResult\");\n        let maxRound = Math.max(diffs.round, rightRound);\n        result[result.length - 2] = maxRound;\n        result[result.length - 1] = rightCount * diffs.count * -1;\n        context.tracer.capturePrefix(result);\n        results.push(result);\n        // debug(\"                    right ->\", key, maxRound, rightCount, diffs.count, result[result.length - 1])\n      }\n    }\n  }\n}\n\nexport class AntiJoinPresolvedRight extends AntiJoin {\n  toString() {\n    return `AntiJoinPresolvedRight(${toString(this.left)})`;\n  }\n  toBranchString() {\n    return `AntiJoinPresolvedRight(${this.left.toBranchString()})`;\n  }\n\n  traceType = TraceNode.AntiJoinPresolvedRight;\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, changes:Transaction):boolean {\n    let {left, right} = this;\n    left.results.clear();\n    context.tracer.node(left, prefix);\n    left.exec(context, input, prefix, transaction, round, left.results, changes);\n    context.tracer.pop(TraceFrameType.Node);\n    let leftResults = left.results.iter();\n    let rightResults = right.results.iter();\n    let result;\n    while((result = leftResults.next()) !== undefined) {\n      this.onLeft(context, result, transaction, round, results);\n    }\n    while((result = rightResults.next()) !== undefined) {\n      this.onRight(context, result, transaction, round, results);\n    }\n    return true;\n  }\n}\n\nexport class UnionFlow extends Node {\n  traceType = TraceNode.Union;\n  branches:BinaryJoinRight[] = [];\n  emptyResults = new Iterator<Prefix>();\n\n  constructor(public left:Node, branches:Node[], public keyRegisters:Register[][], public registersToMerge:Register[], public extraOuterJoins:Register[]) {\n    super();\n    let ix = 0;\n    for(let branch of branches) {\n      this.branches.push(new BinaryJoinRight(left, branch, keyRegisters[ix].concat(extraOuterJoins), registersToMerge));\n      ix++;\n    }\n  }\n\n  toString() {\n    let name;\n    let branchText = (this.branches as Node[]).map((branch) => indent(branch.toBranchString(), 4)).join(\",\\n    \");\n    return `UnionFlow({\n  left: ${indent(toString(this.left), 2)},\n  branches: [\\n    ${branchText}\\n)}]\n})`;\n  }\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, changes:Transaction):boolean {\n    let {left} = this;\n    let {tracer} = context;\n    let tempLeftResults = left.results;\n\n    left.results.clear();\n    tracer.node(left, prefix);\n    left.exec(context, input, prefix, transaction, round, left.results, changes);\n    tracer.pop(TraceFrameType.Node);\n\n    let leftPrefix;\n    let leftResults = left.results.iter();\n    for(let node of this.branches) {\n      node.results.clear();\n\n      tracer.node(node, prefix);\n      node.exec(context, input, prefix, transaction, round, node.results, changes);\n      tracer.pop(TraceFrameType.Node);\n\n      // Because we've already run this node once, we don't want it to potentially see the left's\n      // results multiple times. As such, we temporarily set the results to an empty iterator\n      // so that downstream nodes see nothing and we set it back once we've gone through\n      // all the left prefixes.\n      left.results = this.emptyResults;\n      leftResults.reset();\n      if(node.keyRegisters.length && input !== BLOCK_ADD && input !== BLOCK_REMOVE) {\n        while((leftPrefix = leftResults.next()) !== undefined) {\n          tracer.node(node, leftPrefix);\n          node.exec(context, input, copyArray(leftPrefix, \"UnionLeftPrefixCopy\"), transaction, round, node.results, changes);\n          tracer.pop(TraceFrameType.Node);\n        }\n      }\n      // set the left results back to the real results\n      left.results = tempLeftResults;\n\n      let branchResults = node.results.iter();\n      let result;\n      while((result = branchResults.next())) {\n        results.push(result);\n      }\n\n    }\n    return true;\n  }\n}\n\nexport class ChooseFlow extends Node {\n  traceType = TraceNode.Choose;\n  leftResults = new Iterator<Prefix>();\n  emptyResults = new Iterator<Prefix>();\n  branches:(BinaryJoinRight|AntiJoinPresolvedRight)[] = [];\n\n  constructor(public left:Node, initialBranches:Node[], public keyRegisters:Register[][], public registersToMerge:Register[], public extraOuterJoins:Register[]) {\n    super();\n    let allKeys:Register[] = []\n    for(let keySet of keyRegisters) {\n      for(let key of keySet) {\n        if(!allKeys.some((r) => r.offset === key.offset)) {\n          allKeys.push(key);\n        }\n      }\n    }\n    let {branches} = this;\n    let prev:Node|undefined;\n    let ix = 0;\n    for(let branch of initialBranches) {\n      let myKeys = keyRegisters[ix].concat(extraOuterJoins);\n      let join;\n      if(prev) {\n        join = new BinaryJoinRight(left, branch, myKeys, registersToMerge);\n        let antijoin = new AntiJoinPresolvedRight(join, this, allKeys);\n        branches.push(antijoin);\n      } else {\n        join = new BinaryJoinRight(left, branch, myKeys, registersToMerge);\n        branches.push(join);\n      }\n      prev = join;\n      ix++;\n    }\n  }\n\n  toString() {\n    let name;\n    let branchText = (this.branches as Node[]).map((branch) => indent(branch.toBranchString(), 4)).join(\",\\n    \");\n    return `ChooseFlow({\n  left: ${indent(toString(this.left), 2)},\n  branches: [\\n    ${branchText}\\n)}]\n})`;\n  }\n\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, changes:Transaction):boolean {\n    let {tracer} = context;\n    let {branches, left} = this;\n    let prev:Iterator<Prefix>|undefined;\n    let ix = 0;\n    let tempResults = this.results;\n    let tempLeftResults = left.results;\n\n    left.results.clear();\n    tracer.node(left, prefix);\n    left.exec(context, input, prefix, transaction, round, left.results, changes);\n    tracer.pop(TraceFrameType.Node);\n\n    let leftResults = left.results.iter();\n    let leftPrefix;\n\n    for(let node of branches) {\n\n      node.results.clear();\n      tracer.node(node, prefix);\n      node.exec(context, input, prefix, transaction, round, node.results, changes);\n      tracer.pop(TraceFrameType.Node);\n\n      // Because we've already run this node once, we don't want it to potentially see our\n      // results multiple times. As such, we temporarily set our results to an empty iterator\n      // so that downstream nodes see nothing and we set it back once we've gone through\n      // all the left prefixes. This ensures that AntiJoinPresolvedRight only sees the previous\n      // branches' results once. We also need to do this for our left's results, since we'll have\n      // seen those as well and would otherwise double count them.\n      this.results = this.emptyResults;\n      left.results = this.emptyResults;\n      leftResults.reset();\n      if(node.keyRegisters.length && input !== BLOCK_ADD && input !== BLOCK_REMOVE) {\n        while((leftPrefix = leftResults.next()) !== undefined) {\n          tracer.node(node, leftPrefix);\n          node.exec(context, input, copyArray(leftPrefix, \"ChooseLeftPrefixCopy\"), transaction, round, node.results, changes);\n          tracer.pop(TraceFrameType.Node);\n        }\n      }\n      // per above, make sure we set our results back to the real iterator\n      this.results = tempResults;\n      left.results = tempLeftResults;\n      let branchResult = node.results.iter();\n      let result;\n      while((result = branchResult.next()) !== undefined) {\n        tracer.capturePrefix(result);\n        results.push(result);\n      }\n    }\n    return true;\n  }\n}\n\nexport class MergeAggregateFlow extends BinaryJoinRight {\n  _nodeName:string = \"MergeAggregateFlow\";\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, changes:Transaction):boolean {\n    // debug(\"        AGG MERGE\");\n    let result;\n    let {left, right} = this;\n    left.results.clear();\n    left.exec(context, input, prefix, transaction, round, left.results, changes);\n    // debug(\"              left results: \", leftResults);\n\n    let leftResults = left.results.iter();\n    // we run the left's results through the aggregate to capture all the aggregate updates\n    right.results.clear();\n    while((result = leftResults.next()) !== undefined) {\n      // debug(\"              left result: \", result.slice());\n      right.exec(context, input, result, transaction, round, right.results, changes);\n    }\n\n    // now we go through all the lefts and rights like normal\n    leftResults.reset();\n    while((result = leftResults.next()) !== undefined) {\n      this.onLeft(context, result, transaction, round, results);\n    }\n    let rightResults = right.results.iter();\n    while((result = rightResults.next()) !== undefined) {\n      this.onRight(context, result, transaction, round, results);\n    }\n    return true;\n  }\n}\n\n// This node is a bit strange, but is required to make sure that aggregates\n// that are inside of a choose don't end up seeing results that wouldn't actually\n// join with the outer scope of the choose. For example if we have the following rule:\n//\n// prog.block(\"count the names of people\", ({find, gather, record, choose}) => {\n//   let person = find(\"person\");\n//   let [sort] = choose(() => {\n//     return gather(person.name).count();\n//   }, () => \"yo yo yo\");\n//   return [person.add(\"next\", sort)];\n// });\n//\n// If we join the choose branch to the outer *after* we've aggregated, then we're\n// going to count everything with a name whether they're a person or not. Instead\n// we need to make sure there is a join with the outer scope before it makes it to\n// the choose. To do that, the AggregateOuterLookup node just keeps track of every\n// value it has seen from the outer and makes sure that each right has a join with\n// it.\nexport class AggregateOuterLookup extends BinaryFlow {\n  _nodeName:string = \"AggregateOuterLookup\";\n  traceType = TraceNode.AggregateOuterLookup;\n  keyFunc:KeyFunction;\n  leftIndex:KeyOnlyIntermediateIndex = new KeyOnlyIntermediateIndex();\n  rightIndex:IntermediateIndex = new IntermediateIndex();\n\n  constructor(public left:Node, public right:Node, public keyRegisters:Register[]) {\n    super(left, right);\n    this.keyFunc = IntermediateIndex.CreateKeyFunction(keyRegisters);\n  }\n\n  toString() {\n    let keys = \"[\" + this.keyRegisters.map((r) => `[${r.offset}]`).join(\", \") + \"]\";\n    //${indent(this.left.toString(), 2)},\n    return `AggregateOuterLookup({\n  keys: ${keys},\n  left: '*snip*',\n  right: ${indent(this.right.toString(), 2)}\n})`;\n  }\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, changes:Transaction):boolean {\n    let {left, right} = this;\n    right.results.clear();\n    context.tracer.node(right, prefix);\n    right.exec(context, input, prefix, transaction, round, right.results, changes);\n    context.tracer.pop(TraceFrameType.Node);\n    let result;\n    let leftResults = left.results.iter();\n    while((result = leftResults.next()) !== undefined) {\n      this.onLeft(context, result, transaction, round, results);\n    }\n    let rightResults = right.results.iter();\n    while((result = rightResults.next()) !== undefined) {\n      this.onRight(context, result, transaction, round, results);\n    }\n    return true;\n  }\n\n  onLeft(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>):void {\n    let key = this.keyFunc(prefix);\n    let exists = this.leftIndex.has(key);\n    this.leftIndex.insert(key, prefix);\n    let afterExists = this.leftIndex.has(key);\n    let diffs = this.rightIndex.iter(key, 0);\n    if(exists && afterExists || !diffs) return;\n\n    let multiplier = 1;\n    if(exists && !afterExists) {\n      // remove\n      multiplier = -1;\n    }\n\n    let rightPrefix;\n    while(rightPrefix = diffs.next()) {\n      let result = copyArray(rightPrefix, \"aggregateLookupResult\");\n      result[result.length - 2] = diffs.round;\n      result[result.length - 1] = diffs.count * multiplier;\n      context.tracer.capturePrefix(result);\n      results.push(result);\n    }\n  }\n\n  onRight(context:EvaluationContext, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>):void {\n    let key = this.keyFunc(prefix);\n    this.rightIndex.insert(key, prefix);\n    if(this.leftIndex.has(key)) {\n      results.push(prefix);\n    }\n  }\n}\n\nexport abstract class AggregateNode extends Node {\n  abstract name:string;\n  traceType = TraceNode.Aggregate;\n  groupKey:Function;\n  projectKey:Function;\n  groups:{[group:string]: {result:any[], [projection:string]: Multiplicity[]}} = {};\n  resolved:RawValue[] = [];\n  registerLookup:boolean[] = [];\n\n  // @TODO: allow for multiple returns\n  constructor(public groupRegisters:Register[], public projectRegisters:Register[], public inputs:(ID|Register)[], public resultRegisters:Register[]) {\n    super();\n    this.groupKey = IntermediateIndex.CreateKeyFunction(groupRegisters);\n    this.projectKey = IntermediateIndex.CreateKeyFunction(projectRegisters);\n    for(let reg of groupRegisters) {\n      this.registerLookup[reg.offset] = true;\n    }\n    for(let reg of resultRegisters) {\n      this.registerLookup[reg.offset] = true;\n    }\n  }\n\n  toString() {\n    let groups = printFieldArray(this.groupRegisters);\n    let projects = printFieldArray(this.projectRegisters);\n    let inputs = printFieldArray(this.inputs);\n    let results = printFieldArray(this.resultRegisters);\n    return `AggregateNode(${this.name}, ${groups}, ${projects}, ${inputs}, ${results})`;\n  }\n\n  groupPrefix(group:string, prefix:Prefix) {\n    let projection = this.projectKey(prefix);\n    let prefixRound = prefix[prefix.length - 2];\n    let prefixCount = prefix[prefix.length - 1];\n    let delta = 0;\n    let found = this.groups[group];\n    if(!found) {\n      found = this.groups[group] = {result: []};\n    }\n    let counts = found[projection] || [];\n    let totalCount = 0;\n\n    let countIx = 0;\n    for(let count of counts) {\n      // we need the total up to our current round\n      if(countIx > prefixRound) break;\n      countIx++;\n      if(!count) continue;\n      totalCount += count;\n    }\n\n    if(totalCount && totalCount + prefixCount <= 0) {\n      // subtract\n      delta = -1;\n    } else if(totalCount === 0 && totalCount + prefixCount > 0) {\n      // add\n      delta = 1;\n    } else if(totalCount + prefixCount < 0) {\n      // we have removed more values than exist?\n      throw new Error(\"Negative total count for an aggregate projection\");\n    } else {\n      // otherwise this change doesn't impact the projected count, we've just added\n      // or removed a support.\n    }\n    counts[prefixRound] = (counts[prefixRound] || 0) + prefixCount;\n    found[projection] = counts;\n    return delta;\n  }\n\n  getResultPrefix(prefix:Prefix, result:ID, count:Multiplicity):Prefix {\n    let neue = copyArray(prefix, \"aggregateResult\");\n    neue[this.resultRegisters[0].offset] = result;\n    neue[neue.length - 1] = count;\n    let ix = 0;\n    while(ix < neue.length - 2) {\n      if(!this.registerLookup[ix]) {\n        neue[ix] = undefined;\n      }\n      ix++;\n    }\n    return neue;\n  }\n\n  resolve(prefix:Prefix):RawValue[] {\n    let resolved = this.resolved;\n    let ix = 0;\n    for(let field of this.inputs) {\n      if(isRegister(field)) {\n        resolved[ix] = GlobalInterner.reverse(prefix[field.offset]);\n      } else {\n        resolved[ix] = GlobalInterner.reverse(field);\n      }\n      ix++;\n    }\n    return resolved;\n  }\n\n  stateToResult(state:any):ID {\n    let current = this.getResult(state);\n    return GlobalInterner.intern(current);\n  }\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, changes:Transaction):boolean {\n    let group = this.groupKey(prefix);\n    let prefixRound = prefix[prefix.length - 2];\n    let delta = this.groupPrefix(group, prefix);\n    let op = this.add;\n    if(!delta) return false;\n    if(delta < 0) op = this.remove;\n\n    let groupStates = this.groups[group].result;\n    let currentState = groupStates[prefixRound];\n    if(!currentState) {\n      // otherwise we have to find the most recent result that we've seen\n      for(let ix = 0, len = Math.min(groupStates.length, prefixRound); ix < len; ix++) {\n        let current = groupStates[ix];\n        if(current === undefined) continue;\n        currentState = copyHash(current, \"AggregateState\");\n      }\n    }\n    let resolved = this.resolve(prefix);\n    let start = prefixRound;\n    groupStates[prefixRound] = currentState;\n    if(!currentState) {\n      currentState = groupStates[prefixRound] = op(this.newResultState(), resolved);\n      let cur = this.getResultPrefix(prefix, this.stateToResult(currentState), 1);\n      results.push(cur);\n      start = prefixRound + 1;\n    }\n    for(let ix = start, len = Math.max(groupStates.length, prefixRound + 1); ix < len; ix++) {\n      let current = groupStates[ix];\n      if(current === undefined) continue;\n\n      let prevResult = this.getResultPrefix(prefix, this.stateToResult(current), -1);\n      current = groupStates[prefixRound] = op(current, resolved);\n      let neueResult = this.getResultPrefix(prefix, this.stateToResult(current), 1);\n      results.push(prevResult);\n      results.push(neueResult);\n    }\n    return true;\n  }\n\n  abstract add(state:any, resolved:RawValue[]):any;\n  abstract remove(state:any, resolved:RawValue[]):any;\n  abstract getResult(state:any):RawValue;\n  abstract newResultState():any;\n\n}\n\n//------------------------------------------------------------------------------\n// SortNode\n//------------------------------------------------------------------------------\n\nexport abstract class SortNode extends Node {\n  name = \"Sort\";\n  traceType = TraceNode.Aggregate;\n  groupKey:Function;\n  projectKey:Function;\n  groups:{[group:string]: {result:any[], [projection:string]: Multiplicity[]}} = {};\n  resolved:ID[] = [];\n  resolvedDirections:RawValue[] = [];\n  sortRegisters:Register[];\n\n  // @TODO: allow for multiple returns\n  constructor(public groupRegisters:Register[], public projectRegisters:Register[], public directions:(ID|Register)[], public resultRegisters:Register[]) {\n    super();\n    this.groupKey = IntermediateIndex.CreateKeyFunction(groupRegisters);\n    this.projectKey = IntermediateIndex.CreateKeyFunction(projectRegisters);\n    this.sortRegisters = groupRegisters.concat(projectRegisters).filter(isRegister);\n  }\n\n  groupPrefix(group:string, prefix:Prefix) {\n    let projection = this.projectKey(prefix);\n    let prefixRound = prefix[prefix.length - 2];\n    let prefixCount = prefix[prefix.length - 1];\n    let delta = 0;\n    let found = this.groups[group];\n    if(!found) {\n      found = this.groups[group] = {result: []};\n    }\n    let counts = found[projection] || [];\n    let totalCount = 0;\n\n    let countIx = 0;\n    for(let count of counts) {\n      // we need the total up to our current round\n      if(countIx > prefixRound) break;\n      countIx++;\n      if(!count) continue;\n      totalCount += count;\n    }\n    if(totalCount && totalCount + prefixCount <= 0) {\n      // subtract\n      delta = -1;\n    } else if(totalCount === 0 && totalCount + prefixCount > 0) {\n      // add\n      delta = 1;\n    } else if(totalCount + prefixCount < 0) {\n      // we have removed more values than exist?\n      throw new Error(\"Negative total count for an aggregate projection\");\n    } else {\n      // otherwise this change doesn't impact the projected count, we've just added\n      // or removed a support.\n    }\n    counts[prefixRound] = (counts[prefixRound] || 0) + prefixCount;\n    found[projection] = counts;\n    return delta;\n  }\n\n  resolve(prefix:Prefix):ID[] {\n    let {resolved, resolvedDirections} = this;\n    if(resolved.length < prefix.length) resolved.length = prefix.length;\n    for(let field of this.sortRegisters) {\n      if(isRegister(field)) {\n        resolved[field.offset] = prefix[field.offset];\n      }\n    }\n    let ix = 0;\n    for(let field of this.directions) {\n      if(isRegister(field)) {\n        resolvedDirections[ix] = GlobalInterner.reverse(prefix[field.offset]);\n      } else {\n        resolvedDirections[ix] = GlobalInterner.reverse(field);\n      }\n      ix++;\n    }\n    return resolved;\n  }\n\n  isGreater(a:Prefix, b:Prefix):string|false {\n    let {resolvedDirections} = this;\n    let dirIx = 0;\n    let dir = resolvedDirections[dirIx] || \"up\";\n    for(let register of this.projectRegisters) {\n      let {offset} = register;\n      let aV = GlobalInterner.reverse(a[offset]);\n      let bV = GlobalInterner.reverse(b[offset]);\n      if((dir === \"up\" &&  aV > bV) ||\n         (dir === \"down\" && aV < bV)) {\n        return dir;\n      } else if(aV !== bV) {\n        return false;\n      }\n      dirIx++;\n      dir = resolvedDirections[dirIx] || dir;\n    }\n    return false;\n  }\n\n  prefixEqual(a:Prefix, b:Prefix) {\n    let ix = -1;\n    for(let field of a) {\n      ix++;\n      if(field !== b[ix]) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  exec(context:EvaluationContext, input:Change, prefix:Prefix, transaction:number, round:number, results:Iterator<Prefix>, changes:Transaction):boolean {\n    let group = this.groupKey(prefix);\n    let prefixRound = prefix[prefix.length - 2];\n    let delta = this.groupPrefix(group, prefix);\n    let op = this.add;\n    if(!delta) return false;\n    if(delta < 0) op = this.remove;\n\n    let groupStates = this.groups[group].result;\n    let currentState = groupStates[prefixRound];\n    if(!currentState) {\n      // otherwise we have to find the most recent result that we've seen\n      for(let ix = 0, len = Math.min(groupStates.length, prefixRound); ix < len; ix++) {\n        let current = groupStates[ix];\n        if(current === undefined) continue;\n        currentState = copyHash(current, \"SortState\");\n      }\n    }\n    let resolved = this.resolve(prefix);\n    let start = prefixRound;\n    groupStates[prefixRound] = currentState;\n    if(!currentState) {\n      currentState = groupStates[prefixRound] = op(this.newResultState(), resolved, prefixRound, results);\n      start = prefixRound + 1;\n    }\n    for(let ix = start, len = Math.max(groupStates.length, prefixRound + 1); ix < len; ix++) {\n      let current = groupStates[ix];\n      if(current === undefined) continue;\n\n      current = groupStates[prefixRound] = op(current, resolved, ix, results);\n    }\n    return true;\n  }\n\n  resultPrefix(prefix:Prefix, outOffset:number, pos:number, round:number, count:Multiplicity) {\n    let item = copyArray(prefix, \"SortResult\");\n    // add one here because we're one indexed\n    item[outOffset] = GlobalInterner.intern(pos + 1);\n    item[item.length - 2] = round;\n    item[item.length - 1] = count;\n    return item;\n  }\n\n  add = (state:any, resolved:ID[], round:number, results:Iterator<Prefix>):any => {\n    let {resultPrefix} = this;\n    let neue = copyArray(resolved, \"SortIntermediate\");\n    let ix = 0;\n    for(let item of state.sorted) {\n      if(this.isGreater(item, resolved)) {\n        break;\n      }\n      ix++;\n    }\n    let outOffset = this.resultRegisters[0].offset;\n    state.sorted.splice(ix, 0, neue);\n    results.push(resultPrefix(neue, outOffset, ix, round, 1));\n    ix++;\n    for(; ix < state.sorted.length; ix++) {\n      let cur = state.sorted[ix];\n      results.push(resultPrefix(cur, outOffset, ix - 1, round, -1));\n      results.push(resultPrefix(cur, outOffset, ix, round, 1));\n    }\n    return state;\n  }\n\n  remove = (state:any, resolved:ID[], round:number, results:Iterator<Prefix>):any => {\n    let {resultPrefix} = this;\n    let ix = 0;\n    let found = false;\n    for(let item of state.sorted) {\n      if(this.prefixEqual(item, resolved)) {\n        break;\n      }\n      ix++;\n    }\n    state.sorted.splice(ix, 1);\n    let outOffset = this.resultRegisters[0].offset;\n    results.push(resultPrefix(resolved, outOffset, ix, round, -1));\n    for(; ix < state.sorted.length; ix++) {\n      let cur = state.sorted[ix];\n      results.push(resultPrefix(cur, outOffset, ix + 1, round, -1));\n      results.push(resultPrefix(cur, outOffset, ix, round, 1));\n    }\n    return state;\n  }\n\n  newResultState():any {\n    return {sorted: [], sortLookup: {}};\n  }\n\n}\n\n//------------------------------------------------------------------------------\n// Block\n//------------------------------------------------------------------------------\n\nexport class Block {\n  constructor(public name:string, public nodes:Node[], public totalRegisters:number) {\n    for(let ix = 0; ix < this.totalRegisters + 2; ix++) {\n      this.initial[ix] = undefined as any;\n    }\n  }\n\n  results = new Iterator<Prefix>();\n  initial:Prefix = createArray();\n\n  toString() {\n    let content = this.nodes.map(toString).join(\",\\n\");\n    return `Block(\"${this.name}\", [\\n  ${indent(content, 2)}\\n])`;\n  }\n\n  exec(context:EvaluationContext, input:Change, transaction:Transaction):boolean {\n    this.results.clear();\n    this.results.push(this.initial.slice());\n\n    let prefix;\n    let iter = this.results;\n    for(let node of this.nodes) {\n      node.results.clear();\n      if(iter.length === 0) {\n        if(node instanceof AntiJoin) {\n          node.exec(context, input, this.initial.slice(), transaction.transaction, transaction.round, node.results, transaction);\n          iter = node.results.iter();\n        }\n      } else {\n        while((prefix = iter.next()) !== undefined) {\n          context.tracer.node(node, prefix);\n          node.exec(context, input, prefix, transaction.transaction, transaction.round, node.results, transaction);\n          context.tracer.pop(TraceFrameType.Node);\n        }\n        iter = node.results.iter();\n      }\n    }\n\n    return true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// EvaluationContext\n//------------------------------------------------------------------------------\n\nexport class EvaluationContext {\n  distinctIndex = new DistinctIndex();\n  intermediates:{[key:string]: IntermediateIndex} = {};\n  exportIndex:{[beav:string]: number} = {};\n  tracer:Tracer;\n\n  constructor(public index:Index) {\n   this.tracer = TRACE ? new Tracer(this) : new NoopTracer(this);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Transaction\n//------------------------------------------------------------------------------\n\nexport type ExportHandler = (blockChanges:{[id:number]: Change[]|undefined}) => void;\n\nexport class Transaction {\n  round = -1;\n  changes:Change[] = []\n  lastFrame = 0;\n  protected outputs = new Iterator<Change>();\n  protected roundChanges:Change[][] = [];\n  protected frameCommits:Change[] = [];\n  protected framePartialCommits:RemoveVsChange[] = [];\n  protected exportedChanges:{[blockId:number]: Change[]} = {};\n  constructor(public context:EvaluationContext, public transaction:number, public blocks:Block[], protected exportHandler?:ExportHandler) {\n    context.tracer.transaction(transaction);\n  }\n\n  output(context:EvaluationContext, change:Change) {\n    // debug(\"        E~\", change.toString(), context.tracker.activeBlock);\n    let {outputs} = this;\n    let {distinctIndex, tracer} = context;\n    tracer.maybeOutput(change);\n    outputs.clear();\n    distinctIndex.distinct(change, outputs);\n    tracer.postDistinct();\n    outputs.reset();\n    let output;\n    while(output = outputs.next()) {\n      tracer.output(output);\n      // debug(\"          <-\", output.toString())\n      let cur = this.roundChanges[output.round] || createArray(\"roundChangesArray\");\n      cur.push(output);\n      this.roundChanges[output.round] = cur;\n    }\n    tracer.pop(TraceFrameType.MaybeOutput);\n  }\n\n  commit(context:EvaluationContext, change:Change) {\n    context.tracer.commit(change);\n    let {outputs} = this;\n    if(change instanceof RemoveVsChange) {\n      this.framePartialCommits.push(change);\n    } else {\n      this.frameCommits.push(change);\n    }\n    // debug(\"          <-!\", change.toString())\n  }\n\n  export(context:EvaluationContext, blockId:number, change:Change) {\n    if(!this.exportedChanges[blockId]) this.exportedChanges[blockId] = [change];\n    else this.exportedChanges[blockId].push(change);\n  }\n\n  protected prepareRound(context:EvaluationContext, changeIx:number) {\n    let {roundChanges, changes} = this;\n    let next = changes[changeIx];\n    let maxRound = roundChanges.length;\n    let oldLength = changes.length;\n    if(!next && this.round < maxRound) {\n      for(let ix = this.round + 1; ix < maxRound; ix++) {\n        let nextRoundChanges = roundChanges[ix];\n        if(nextRoundChanges) {\n          this.collapseMultiplicity(nextRoundChanges, changes);\n\n          // If we've got new changes to go through, we're done\n          if(oldLength < changes.length) return;\n        }\n      }\n    }\n    let {frameCommits, framePartialCommits} = this;\n    if(!next && (frameCommits.length || framePartialCommits.length)) {\n      for(let commit of framePartialCommits) {\n        commit.toRemoveChanges(context, frameCommits);\n      }\n\n      let collapsedCommits:Change[] = [];\n      this.collapseCommits(this.frameCommits, collapsedCommits);\n      let collapsedChanges:Change[] = [];\n      this.collapseMultiplicity(collapsedCommits, collapsedChanges);\n\n      if(collapsedChanges.length) {\n        context.tracer.frame(collapsedChanges);\n        this.lastFrame = this.changes.length;\n        this.round = -1;\n        this.roundChanges = [];\n        this.frameCommits = [];\n        this.framePartialCommits = [];\n        for(let commit of collapsedChanges) {\n          if(commit.count > 0) commit.count = Infinity;\n          else if(commit.count < 0) commit.count = -Infinity;\n          // debug(\"    ->! \", commit.toString())\n          this.output(context, commit);\n        }\n        this.prepareRound(context, changeIx);\n        // debug(\" ---------------- NEW FRAME -------------------\")\n      }\n    }\n  }\n\n  protected collapseCommits(changes:Change[], results:Change[] /* output */) {\n    // We sort the changes to group all the same EAVs together.\n    changes.sort((a,b) => {\n      let nodeDiff = a.n - b.n;\n      if(!nodeDiff) {\n        let eDiff = a.e - b.e;\n        if(!eDiff) {\n          let aDiff = a.a - b.a;\n          if(!aDiff) {\n            let vDiff = a.v - b.v;\n            return vDiff;\n          }\n          return aDiff;\n        }\n        return eDiff;\n      }\n      return nodeDiff;\n    });\n    let changeIx = 0;\n    for(let changeIx = 0; changeIx < changes.length; changeIx++) {\n      let current = changes[changeIx];\n      let currentType = current instanceof RemoveChange ? true : false;\n      if(currentType) {\n        current = new RemoveChange(current.e, current.a, current.v, current.n, current.transaction, current.round, current.count);\n      } else {\n        current = new Change(current.e, current.a, current.v, current.n, current.transaction, current.round, current.count);\n      }\n\n      // Collapse each subsequent matching EAV's multiplicity into the current one's.\n      while(changeIx + 1 < changes.length) {\n        let next = changes[changeIx + 1];\n        if(current.n === next.n && next.e == current.e && next.a == current.a && next.v == current.v) {\n          current.count += next.count;\n          changeIx++;\n        } else {\n          break;\n        }\n      }\n\n      current.round = 0;\n      if(currentType && current.count < 0) {\n        results.push(current);\n      } else if(!currentType && current.count > 0) {\n        results.push(current);\n      }\n    }\n\n    return results;\n  }\n\n  protected collapseMultiplicity(changes:Change[], results:Change[] /* output */, createNew = false) {\n    // We sort the changes to group all the same EAVs together.\n    changes.sort((a,b) => {\n      let eDiff = a.e - b.e;\n      if(!eDiff) {\n        let aDiff = a.a - b.a;\n        if(!aDiff) {\n          return a.v - b.v;\n        }\n        return aDiff;\n      }\n      return eDiff;\n    });\n    let changeIx = 0;\n    for(let changeIx = 0; changeIx < changes.length; changeIx++) {\n      let current = changes[changeIx];\n      if(createNew) {\n        current = new Change(current.e, current.a, current.v, current.n, current.transaction, current.round, current.count);\n      }\n\n      // Collapse each subsequent matching EAV's multiplicity into the current one's.\n      while(changeIx + 1 < changes.length) {\n        let next = changes[changeIx + 1];\n        if(next.e == current.e && next.a == current.a && next.v == current.v) {\n          current.count += next.count;\n          changeIx++;\n        } else {\n          break;\n        }\n      }\n      if(current.count !== 0) results.push(current);\n    }\n\n    return results;\n  }\n\n  exec(context:EvaluationContext) {\n    let {changes, roundChanges} = this;\n    let {index, tracer} = context;\n    tracer.frame([]);\n    let total = 0;\n    let frames = 0;\n    let changeIx = 0;\n    let iterationLimit = 10000;\n    this.prepareRound(context, changeIx);\n    while(changeIx < changes.length) {\n      let change = changes[changeIx];\n      tracer.input(change);\n      total++;\n      if(total > iterationLimit) {\n        console.error(`Error: Program failed to fixpoint after ${iterationLimit} iterations. This is likely due to an unbounded cycle in the program.`);\n        break;\n      }\n      if(this.round !== 0 && change.round === 0) {\n        frames++;\n        if(frames > 10) {\n          console.error(\"Failed to terminate\");\n          break;\n        }\n      }\n      this.round = change.round;\n      // debug(\"Round:\", this.round);\n      // debug(\"  <- \", change.toString())\n      for(let block of this.blocks) {\n        tracer.block(block.name);\n        //debug(\"    \", block.name);\n        block.exec(context, change, this);\n        tracer.pop(TraceFrameType.Block);\n      }\n\n      // debug(\"\");\n      index.insert(change);\n\n      tracer.pop(TraceFrameType.Input);\n      changeIx++;\n      this.prepareRound(context, changeIx);\n    }\n\n    let exportingBlocks = Object.keys(this.exportedChanges);\n    if(exportingBlocks.length) {\n      if(!this.exportHandler) throw new Error(\"Unable to export changes without export handler.\");\n\n      for(let blockId of exportingBlocks) {\n        let rawExports:Change[] = createArray(\"rawExportsArray\");\n        this.collapseMultiplicity(this.exportedChanges[+blockId], rawExports);\n        let exports:Change[] = createArray(\"exportsArray\");\n        for(let change of rawExports) {\n          let {e, a, v, count} = change;\n          let beav = `${blockId}|${e}|${a}|${v}`;\n          let old = context.exportIndex[beav] || 0;\n          let neue = old + count;\n          let delta = 0;\n          context.exportIndex[beav] = neue;\n\n          // Once you go negative you don't go back.\n          if(old === 0 && neue > 0) delta = 1;\n          else if(old > 0 && neue === 0) delta = -1;\n\n          if(delta) {\n            let exportedChange = new Change(e, a, v, change.n, this.transaction, 0, delta);\n            exports.push(exportedChange);\n          }\n        }\n\n        this.exportedChanges[+blockId] = exports;\n      }\n      try {\n        this.exportHandler(this.exportedChanges);\n      } catch(e) {\n        tracer.pop(TraceFrameType.Transaction);\n        throw e;\n      }\n    }\n\n    // Once the transaction is effectively done, we need to clean up after ourselves. We\n    // arena allocated a bunch of IDs related to function call outputs, which we can now\n    // safely release.\n    GlobalInterner.releaseArena(\"functionOutput\");\n    tracer.pop(TraceFrameType.Transaction);\n  }\n}\n\nexport class BlockChangeTransaction extends Transaction {\n  constructor(public context:EvaluationContext, public transaction:number, public added:Block[], public removed:Block[], public blocks:Block[], protected exportHandler?:ExportHandler) {\n    super(context, transaction, blocks, exportHandler);\n  }\n\n  exec(context:EvaluationContext) {\n    // To remove a block, we run a change with negative count through the system that\n    // is meant to compute all the results the block has generated and remove them.\n    for(let remove of this.removed) {\n      remove.exec(context, BLOCK_REMOVE, this);\n    }\n\n    // To add a block, we do the same as remove, but with a positive change that causes\n    // the block to compute all results based on the state of the indexes\n    for(let add of this.added) {\n      add.exec(context, BLOCK_ADD, this);\n    }\n\n    super.exec(context);\n  }\n}\n\n// window[\"counts\"] = {};\n"
  },
  {
    "path": "src/runtime/stdlib.ts",
    "content": "import {makeFunction, makeMultiFunction, RawValue, AggregateNode} from \"./runtime\";\nimport * as dateformat from \"dateformat\";\n\n//--------------------------------------------------------------------\n// Comparisons\n//--------------------------------------------------------------------\n\nmakeFunction({\n  name: \"compare/>\",\n  args: {a: \"number\", b: \"number\"},\n  returns: {},\n  apply: (a:number, b:number) => {\n    return (a > b) ? [] : undefined;\n  }\n});\n\nmakeFunction({\n  name: \"compare/>=\",\n  args: {a: \"number\", b: \"number\"},\n  returns: {},\n  apply: (a:number, b:number) => {\n    return (a >= b) ? [] : undefined;\n  }\n});\n\nmakeFunction({\n  name: \"compare/<\",\n  args: {a: \"number\", b: \"number\"},\n  returns: {},\n  apply: (a:number, b:number) => {\n    return (a < b) ? [] : undefined;\n  }\n});\n\nmakeFunction({\n  name: \"compare/<=\",\n  args: {a: \"number\", b: \"number\"},\n  returns: {},\n  apply: (a:number, b:number) => {\n    return (a <= b) ? [] : undefined;\n  }\n});\n\nmakeFunction({\n  name: \"compare/!=\",\n  args: {a: \"number\", b: \"number\"},\n  returns: {},\n  apply: (a:number, b:number) => {\n    return (a != b) ? [] : undefined;\n  }\n});\n\nmakeFunction({\n  name: \"compare/==\",\n  args: {a: \"number\", b: \"number\"},\n  returns: {},\n  apply: (a:number, b:number) => {\n    return (a == b) ? [] : undefined;\n  }\n});\n\n//--------------------------------------------------------------------\n// Math\n//--------------------------------------------------------------------\n\nmakeFunction({\n  name: \"math/+\",\n  args: {a: \"number\", b: \"number\"},\n  returns: {result: \"number\"},\n  apply: (a:number, b:number) => {\n    return [a + b];\n  }\n});\n\nmakeFunction({\n  name: \"math/-\",\n  args: {a: \"number\", b: \"number\"},\n  returns: {result: \"number\"},\n  apply: (a:number, b:number) => {\n    return [a - b];\n  }\n});\n\nmakeFunction({\n  name: \"math/*\",\n  args: {a: \"number\", b: \"number\"},\n  returns: {result: \"number\"},\n  apply: (a:number, b:number) => {\n    return [a * b];\n  }\n});\n\nmakeFunction({\n  name: \"math//\",\n  args: {a: \"number\", b: \"number\"},\n  returns: {result: \"number\"},\n  apply: (a:number, b:number) => {\n    return [a / b];\n  }\n});\n\nmakeFunction({\n  name: \"math/floor\",\n  args: {value: \"number\"},\n  returns: {result: \"number\"},\n  apply: (value:number) => {\n    return [Math.floor(value)];\n  }\n});\n\nmakeFunction({\n  name: \"math/ceiling\",\n  args: {value: \"number\"},\n  returns: {result: \"number\"},\n  apply: (value:number) => {\n    return [Math.ceil(value)];\n  }\n});\n\nmakeFunction({\n  name: \"math/round\",\n  args: {value: \"number\"},\n  returns: {result: \"number\"},\n  apply: (value:number) => {\n    return [Math.round(value)];\n  }\n});\n\nmakeFunction({\n  name: \"math/sin\",\n  args: {degrees: \"number\"},\n  returns: {result: \"number\"},\n  apply: (degrees:number) => {\n    return [Math.sin(degrees/180 * Math.PI)];\n  }\n});\n\nmakeFunction({\n  name: \"math/cos\",\n  args: {degrees: \"number\"},\n  returns: {result: \"number\"},\n  apply: (degrees:number) => {\n    return [Math.cos(degrees/180 * Math.PI)];\n  }\n});\n\nmakeFunction({\n  name: \"math/tan\",\n  args: {degrees: \"number\"},\n  returns: {result: \"number\"},\n  apply: (degrees:number) => {\n    return [Math.tan(degrees/180 * Math.PI)];\n  }\n});\n\nmakeFunction({\n  name: \"math/max\",\n  args: {a: \"number\", b: \"number\"},\n  returns: {result: \"number\"},\n  apply: (a:number, b:number) => {\n    return [Math.max(a, b)];\n  }\n});\n\nmakeFunction({\n  name: \"math/min\",\n  args: {a: \"number\", b: \"number\"},\n  returns: {result: \"number\"},\n  apply: (a:number, b:number) => {\n    return [Math.min(a, b)];\n  }\n});\n\nmakeFunction({\n  name: \"math/mod\",\n  args: {value: \"number\", by: \"number\"},\n  returns: {result: \"number\"},\n  apply: (value:number, by:number) => {\n    return [value % by];\n  }\n});\n\nmakeFunction({\n  name: \"math/absolute\",\n  args: {value: \"number\"},\n  returns: {result: \"number\"},\n  apply: (value:number) => {\n    return [Math.abs(value)];\n  }\n});\n\nmakeFunction({\n  name: \"math/pow\",\n  args: {value: \"number\", exponent: \"number\"},\n  returns: {result: \"number\"},\n  apply: (value:number, exponent:number) => {\n    return [Math.pow(value, exponent)];\n  }\n});\n\nmakeFunction({\n  name: \"math/ln\",\n  args: {value: \"number\"},\n  returns: {result: \"number\"},\n  apply: (value:number) => {\n    return [Math.log(value)];\n  }\n});\n\nmakeFunction({\n  name: \"math/to-fixed\",\n  args: {value: \"number\", to: \"number\"},\n  returns: {result: \"string\"},\n  apply: (value:number, to:number) => {\n    if(typeof value === \"number\") {\n      return [value.toFixed(to)];\n    }\n  }\n});\n\nmakeFunction({\n  name: \"math/convert-base\",\n  args: {value: \"number\", to: \"number\"},\n  returns: {result: \"string\"},\n  apply: (value:number, to:number) => {\n    if(typeof value === \"number\" && to > 1 && to < 37) {\n      return [value.toString(to)];\n    }\n  }\n});\n\nmakeMultiFunction({\n  name: \"math/range\",\n  args: {start: \"number\", stop: \"number\"},\n  returns: {result: \"string\"},\n  estimate: function(context, prefix) {\n    let {start, stop} = this.resolve(prefix);\n    if(typeof start !== \"number\" || typeof stop !== \"number\") return 0;\n    if(start > stop) {\n      return start - stop;\n    } else {\n      return stop - start;\n    }\n  },\n  apply: (start:number, stop:number, step:number = 1) => {\n    if(typeof start !== \"number\" || typeof stop !== \"number\" || typeof step !== \"number\") return;\n    if(start > stop) {\n      [stop, start] = [start, stop];\n    }\n\n    let outputs = [];\n    for(let ix = start; ix <= stop; ix += step) {\n      outputs.push([ix]);\n    }\n    return outputs;\n  }\n});\n\n//--------------------------------------------------------------------\n// String\n//--------------------------------------------------------------------\n\nmakeFunction({\n  name: \"string/replace\",\n  args: {text: \"string\", replace: \"string\", with: \"string\"},\n  returns: {result: \"string\"},\n  apply: function(text:string, replace:string, _with:string) {\n    let result = text.split(replace).join(_with);\n    return [result];\n  }\n});\n\nmakeFunction({\n  name: \"string/get\",\n  args: {text: \"string\", at: \"number\"},\n  returns: {result: \"string\"},\n  apply: function(text:string, at:number) {\n    if(at > text.length) return;\n    return [text[at - 1]];\n  }\n});\n\nmakeFunction({\n  name: \"string/uppercase\",\n  args: {text: \"string\"},\n  returns: {result: \"string\"},\n  apply: function(text:string) {\n    return [text.toLocaleUpperCase()];\n  }\n});\n\nmakeFunction({\n  name: \"string/lowercase\",\n  args: {text: \"string\"},\n  returns: {result: \"string\"},\n  apply: function(text:string) {\n    return [text.toLocaleLowerCase()];\n  }\n});\n\nmakeFunction({\n  name: \"string/index-of\",\n  args: {text: \"string\", substring: \"string\"},\n  returns: {result: \"number\"},\n  apply: function(text:string, substring:string) {\n    let ix = (\"\"+text).indexOf(substring) + 1;\n    if(ix == 0) return;\n    return [ix];\n  }\n});\n\nmakeFunction({\n  name: \"string/codepoint-length\",\n  args: {text: \"string\"},\n  returns: {result: \"number\"},\n  apply: function(text:string) {\n    if(typeof text !== \"string\") return;\n    return [text.length];\n  }\n});\n\n\n//--------------------------------------------------------------------\n// Random\n//--------------------------------------------------------------------\n\nmakeFunction({\n  name: \"random/number\",\n  args: {seed: \"any\"},\n  returns: {result: \"number\"},\n  initialState: {},\n  apply: function(seed:RawValue) {\n    let state = this.state;\n    let result = state[seed];\n    if(result === undefined) {\n      result = state[seed] = Math.random();\n    }\n    return [result];\n  }\n});\n\n//--------------------------------------------------------------------\n// Logic\n//--------------------------------------------------------------------\n\nmakeFunction({\n  name: \"logic/toggle\",\n  args: {value: \"string\"},\n  returns: {result: \"string\"},\n  apply: (value: string) => {\n    if(value === \"true\") return [\"false\"];\n    if(value === \"false\") return [\"true\"];\n    return [];\n  }\n})\n\n//--------------------------------------------------------------------\n// Date\n//--------------------------------------------------------------------\n\nmakeFunction({\n  name: \"date/format\",\n  args: {timestamp: \"number\", format: \"string\"},\n  returns: {result: \"string\"},\n  apply: (timestamp: number, format: string) => {\n    return [dateformat(timestamp, format)];\n  }\n})\n\n//--------------------------------------------------------------------\n// Eve internal\n//--------------------------------------------------------------------\n\nmakeFunction({\n  name: \"eve/internal/gen-id\",\n  args: {},\n  variadic: true,\n  returns: {result: \"string\"},\n  apply: (values:RawValue[]) => {\n    // @FIXME: This is going to be busted in subtle cases.\n    //   If a record exists with a \"1\" and 1 value for the same\n    //   attribute, they'll collapse for gen-id, but won't join\n    //   elsewhere.  This means aggregate cardinality will disagree with\n    //   action node cardinality.\n    return [values.join(\"|\")];\n  }\n});\n\nmakeFunction({\n  name: \"eve/internal/concat\",\n  args: {},\n  variadic: true,\n  returns: {result: \"string\"},\n  apply: (values:RawValue[]) => {\n    return [values.join(\"\")];\n  }\n});\n\n//------------------------------------------------------------------------\n// Aggregates\n//------------------------------------------------------------------------\n\nexport type SumAggregateState = {total:number};\nexport class SumAggregate extends AggregateNode {\n  name = \"Sum\";\n  add(state:SumAggregateState, resolved:RawValue[]):any {\n    state.total += resolved[0] as number;\n    return state;\n  }\n  remove(state:SumAggregateState, resolved:RawValue[]):any {\n    state.total -= resolved[0] as number;\n    return state;\n  }\n  getResult(state:SumAggregateState):RawValue {\n    return state.total;\n  }\n  newResultState():SumAggregateState {\n    return {total: 0};\n  };\n}\n"
  },
  {
    "path": "src/runtime/trace.ts",
    "content": "import {Change, Prefix, EvaluationContext, GlobalInterner, printPrefix} from \"./runtime\";\nimport * as Runtime from \"./runtime\";\nimport {Renderer} from \"../microReact\";\nimport {PerformanceTracker} from \"./performance\"\n\nfunction isID(v: any) {\n  return typeof v === \"string\" && (v.indexOf(\"|\") > -1 || (v[8] === \"-\" && v.length === 36))\n}\n\n//------------------------------------------------------------------------\n// UI helpers\n//------------------------------------------------------------------------\n\nfunction handleArgs(args:any[]) {\n  if(typeof args[0] === \"object\" && args[0].constructor !== Array) return args;\n  args.unshift({});\n  return args;\n}\n\nfunction $row(...args:any[]) {\n  let [elem, children] = handleArgs(args);\n  elem.t = \"row\";\n  elem.children = children;\n  return elem;\n}\n\nfunction $col(...args:any[]) {\n  let [elem, children] = handleArgs(args);\n  elem.t = \"column\";\n  elem.children = children;\n  return elem;\n}\n\nfunction $text(...args:any[]) {\n  let [elem, text] = handleArgs(args);\n  elem.t = \"text\";\n  elem.text = text;\n  return elem;\n}\n\nfunction $button(...args:any[]) {\n  let [elem, click, content] = handleArgs(args);\n  elem.t = \"button\";\n  if(typeof content === \"string\") {\n    elem.text = content;\n  } else {\n    elem.children = [content];\n  }\n  elem.click = click;\n  return elem;\n}\n\nfunction $spacer(...args:any[]) {\n  let [elem] = handleArgs(args);\n  elem.t = \"spacer\";\n  return elem;\n}\n\n//------------------------------------------------------------------------\n// Tracer\n//------------------------------------------------------------------------\n\nexport enum TraceNode {\n  Join,\n  Choose,\n  Union,\n  LinearFlow,\n  BinaryJoin,\n  AntiJoin,\n  AntiJoinPresolvedRight,\n  Aggregate,\n  AggregateOuterLookup,\n  Output,\n  Watch,\n}\n\nexport enum TraceFrameType {\n  Program,\n  Transaction,\n  Input,\n  Block,\n  Node,\n  MaybeOutput,\n  MaybeExternalInput,\n}\n\nlet typeToParentField = {\n  [TraceFrameType.Transaction]: \"transactions\",\n  [TraceFrameType.Input]: \"inputs\",\n  [TraceFrameType.Block]: \"blocks\",\n  [TraceFrameType.Node]: \"nodes\",\n  [TraceFrameType.MaybeOutput]: \"outputs\",\n  [TraceFrameType.MaybeExternalInput]: \"externalInputs\",\n}\n\nexport interface Frame {type:TraceFrameType};\nexport interface ProgramFrame extends Frame {transactions: TransactionFrame[]}\nexport interface TransactionFrame extends Frame {id:number, externalInputs:any[], inputs:any[]}\n\nexport class Tracer {\n  stack:any[] = [{type:TraceFrameType.Program, transactions: []}];\n  _currentInput:Change|undefined;\n  inputsToOutputs:any = {};\n  outputsToInputs:any = {};\n  eToChange:any = {};\n  renderer:Renderer;\n  activeBlock = \"\";\n  tracker = new PerformanceTracker();\n\n  constructor(public context:EvaluationContext, shouldDraw = true) {\n    if(typeof window !== \"undefined\" && shouldDraw) {\n      let renderer = this.renderer = new Renderer();\n      document.body.appendChild(renderer.content);\n    }\n  }\n\n  changeKey(change:Change) {\n    let {e,a,v,n,round,transaction,count} = change;\n    return `${e}|${a}|${v}|${n}|${round}|${transaction}|${count}`;\n  }\n\n  current() {\n    return this.stack[this.stack.length - 1];\n  }\n\n  transaction(id:number) {\n    this.stack.push({type:TraceFrameType.Transaction, id, externalInputs: [], inputs: []})\n    this.tracker.time(\"transaction\");\n  }\n\n  frame(commits:Change[]) {\n    // @TODO\n  }\n\n  indexChange(change:Change) {\n    let found = this.eToChange[change.e];\n    if(!found) found = this.eToChange[change.e] = [];\n    found.push(change);\n  }\n\n  input(input:Change) {\n    this._currentInput = input;\n    this.indexChange(input);\n    this.stack.push({type:TraceFrameType.Input, input, blocks: []})\n  }\n\n  block(name:string) {\n    this.activeBlock = name;\n    this.stack.push({type:TraceFrameType.Block, name, nodes: []})\n    this.tracker.block(name);\n  }\n\n  node(node:Runtime.Node, inputPrefix:Prefix) {\n    this.stack.push({type:TraceFrameType.Node, nodeType:node.traceType, node, inputPrefix:inputPrefix.slice(), nodes: [], prefixes: [], outputs: [], commits: []})\n  }\n\n  capturePrefix(prefix:Prefix) {\n    let parent = this.current();\n    parent.prefixes.push(prefix.slice());\n  }\n\n  _mapOutput(output:Change) {\n    let {_currentInput} = this;\n    if(_currentInput) {\n      let outKey = this.changeKey(output);\n      let inKey = this.changeKey(_currentInput);\n      this.outputsToInputs[outKey] = _currentInput;\n      let inList = this.inputsToOutputs[inKey];\n      if(!inList) inList = this.inputsToOutputs[inKey] = [];\n      inList.push(output);\n    }\n  }\n\n  maybeOutput(change:Change) {\n    // this._mapOutput(change);\n    let cur = this.current();\n    let type = TraceFrameType.MaybeOutput;\n    if(cur.type === TraceFrameType.Transaction) {\n      type = TraceFrameType.MaybeExternalInput;\n    }\n    let counts = (this.context.distinctIndex.getCounts(change) || []).slice();\n    this.stack.push({type, distinct: {pre: counts, post: undefined}, change, outputs: []})\n  }\n\n  postDistinct() {\n    let cur = this.current();\n    let counts = this.context.distinctIndex.getCounts(cur.change)!.slice();\n    cur.distinct.post = counts;\n  }\n\n  output(output:Change) {\n    let safe = output.clone();\n    this._mapOutput(safe);\n    let parent = this.current();\n    parent.outputs.push(safe);\n  }\n\n  commit(commit:Change) {\n    let safe = commit.clone();\n    this._mapOutput(safe);\n    let parent = this.current();\n    parent.commits.push(safe);\n  }\n\n  distinctCheck() {\n    let error = false;\n    let {index} = this.context.distinctIndex;\n    for(let key in index) {\n      let counts = index[key]!;\n      let sum = 0;\n      for(let c of counts) {\n        if(!c) continue;\n        sum += c;\n        if(sum < 0) {\n          console.error(\"Negative postDistinct: \", key, counts.slice())\n          error = true;\n          // throw new Error(\"Negative postDistinct at the end of a transaction\")\n        }\n      }\n    }\n    return error;\n  }\n\n  pop(type:TraceFrameType) {\n    let {stack} = this;\n    let cur = stack.pop();\n    if(cur.type !== type) {\n      if(cur.type !== TraceFrameType.MaybeExternalInput || type !== TraceFrameType.MaybeOutput) {\n        throw new Error(`Popping the wrong type! expected: ${TraceFrameType[type]}, actual: ${TraceFrameType[cur.type]}`)\n      }\n    }\n    let parent = this.current();\n    if(!parent) {\n      throw new Error(\"Removed everything from the stack\");\n    }\n    if(cur.type === TraceFrameType.Transaction) {\n      parent = this.stack[0];\n      parent.transactions[cur.id] = cur;\n    } else {\n      let field = typeToParentField[cur.type];\n      if(!parent[field]) throw new Error(`Trying to write trace field '${field}', but ${TraceFrameType[parent.type]} doesn't have it`);\n      parent[field].push(cur);\n    }\n\n    if(cur.type === TraceFrameType.Block) this.tracker.blockEnd(cur.name);\n    if(cur.type === TraceFrameType.Input) this._currentInput = undefined;\n    if(cur.type === TraceFrameType.Transaction) {\n      this.tracker.timeEnd(\"transaction\");\n      let error = this.distinctCheck();\n      this.draw();\n    }\n  }\n\n  //------------------------------------------------------------------------\n  // UI\n  //------------------------------------------------------------------------\n\n  activeSearch:string = \"\";\n  activeInput:Change|undefined;\n\n  draw() {\n    let {renderer} = this;\n    if(!renderer) return;\n    renderer.render([this.$interface()])\n  }\n\n  $interface = () => {\n    let program = this.stack[0];\n    return $row({c: \"trace\"}, [\n      this.$searcher(program),\n      this.$visualization(program)\n    ])\n  }\n\n  makeSearch = (query:string) => {\n    let [e,a,v] = query.split(\",\").map((v) => v.trim());\n    let conditions = [];\n    if(e && e !== \"?\") {\n      conditions.push(`input.e == ${+e}`);\n    }\n    if(a && a !== \"?\") {\n      conditions.push(`input.a === ${GlobalInterner.intern(a)}`);\n    }\n    if(v && v !== \"?\") {\n      if(this.eToChange[+v]) {\n        conditions.push(`input.v == ${+v}`);\n      } else {\n        conditions.push(`input.v == ${GlobalInterner.intern(isNaN(+v) ? v : +v)}`);\n      }\n    }\n    if(!conditions.length) {\n      conditions.push(\"true\");\n    }\n    return new Function(\"input\", `return ${conditions.join(\" && \")}`) as (input:Change) => boolean;\n  }\n\n  inSearch = (input:Change) => {\n    return true;\n  }\n\n  $searcher = (program:ProgramFrame) => {\n    let inputs = [];\n    outer: for(let transaction of program.transactions) {\n      if(!transaction) continue;\n      for(let input of transaction.inputs) {\n        if(this.inSearch(input.input)) {\n          inputs.push(this.$changeLink(input.input));\n          if(inputs.length === 500) {\n            break outer;\n          }\n        }\n      }\n    }\n    return $col({c: \"searcher\"}, [\n      {t: \"input\", type: \"text\", placeholder:\"search\", keydown: (e:any) => {\n        if(e.keyCode === 13) {\n          this.activeSearch = e.target.value.trim();\n          this.inSearch = this.makeSearch(this.activeSearch);\n          this.draw();\n        }\n      }},\n      $col(inputs)\n    ])\n  }\n\n  getInputFrame(program:ProgramFrame, input:Change) {\n    let trans = program.transactions[input.transaction];\n    for(let frame of trans.inputs) {\n      if(frame.input === input) return frame;\n    }\n  }\n\n  $visualization = (program:ProgramFrame) => {\n    let {$changeLink, activeInput, getInputFrame, $block} = this;\n    if(activeInput) {\n      let frame = getInputFrame(program, activeInput);\n      let key = this.changeKey(activeInput);\n      let from = this.outputsToInputs[key];\n      let to = this.inputsToOutputs[key];\n      let fromInfo;\n      if(from) {\n        fromInfo = $col([\n          $text(\"generated by: \"),\n          $changeLink(from),\n        ]);\n      } else {\n        fromInfo = $col([\n          $text(\"generated by: \"),\n          $text(\"unknown\"),\n        ]);\n      }\n      let toInfo;\n      if(to) {\n        toInfo = $col([\n          $text(\"causes output:\"),\n          $col(to.map($changeLink)),\n        ]);\n      }\n      let counts = this.context.distinctIndex.getCounts(activeInput)!;\n      console.log(frame);\n      return $col({c: \"vis\"}, [\n        $changeLink(activeInput),\n        fromInfo,\n        $col(frame.blocks.map($block)),\n        toInfo,\n        $text(`counts: [${counts.join(\", \")}]`)\n      ])\n    }\n    return $text(\"select an input\");\n    // return $col({c: \"program\"}, program.transactions.map(this.$transaction));\n  }\n\n  $block = (block:any) => {\n    return $col({c: \"block\"}, [\n      $text({c: \"name\"}, block.name),\n      $col(block.nodes.map(this.$node)),\n    ]);\n  }\n\n  $node = (node:any) => {\n    let {$prefix, $node, $changeLink} = this;\n    let subs = node.nodes.map($node);\n\n    let out;\n    if(node.prefixes.length) {\n      out = $row([\n        $text(\"out: \"),\n        $col(node.prefixes.map($prefix)),\n      ]);\n    }\n    if(node.outputs.length) {\n      let outs = [];\n      for(let output of node.outputs) {\n        outs.push(\n          $col([\n            $row([\n              $text(\"maybe out: \"),\n              $changeLink(output.change),\n            ]),\n            $text(`pre: ${output.distinct.pre}`),\n            $text(`post: ${output.distinct.post}`),\n            $row([\n              $text(\"distinct out: \"),\n              $col(output.outputs.map($changeLink)),\n            ]),\n          ])\n        );\n      }\n      out = $col(outs);\n    }\n\n    return $col({c: \"node\"}, [\n      $text(TraceNode[node.nodeType]),\n      $row([\n        $text(\"in: \"),\n        $prefix(node.inputPrefix),\n      ]),\n      $row(subs),\n      out,\n    ]);\n  }\n\n  $prefix = (prefix:Prefix) => {\n    let items = [];\n    let hasValue = false;\n    for(let ix = 0; ix < prefix.length - 2; ix++) {\n      let value:any = prefix[ix];\n      if(value === undefined) {\n        value = \"?\";\n      } else if(!isID(GlobalInterner.reverse(value))) {\n        hasValue = true;\n        value = GlobalInterner.reverse(value);\n      } else {\n        hasValue = true;\n      }\n      items.push(value);\n    }\n    if(!hasValue) {\n      return $text(\"(empty)\");\n    }\n    let round = prefix[prefix.length - 2];\n    let count = prefix[prefix.length - 1];\n    return $text(`(${items.join(\", \")}) [${round}, ${count}]`);\n  }\n\n  setLink = (e:any, elem:any) => {\n    this.activeInput = elem.input;\n    this.draw();\n  }\n\n  $changeLink = (change:Change) => {\n    let {e,a,v}:any = change;\n    a = GlobalInterner.reverse(a);\n    v = GlobalInterner.reverse(v);\n    if(typeof v === \"string\" && (v.indexOf(\"|\") > -1 || (v[8] === \"-\" && v.length === 36))) {\n      v = change.v;\n    }\n    return $button({c: \"change-link\", input:change}, this.setLink, $row([\n      $text(`${e}, ${a}, ${v}`),\n      $spacer(),\n      $text(` [${change.transaction}, ${change.round}, ${change.count}]`),\n    ]));\n  }\n}\n\n\nexport class NoopTracer extends Tracer {\n\n  activeBlock = \"\";\n\n  constructor(public context:EvaluationContext) {\n    super(context, false);\n  }\n\n  transaction(id:number) { }\n  frame(commits:Change[]) { }\n  input(input:Change) { }\n  block(name:string) { this.activeBlock = name; }\n  node(node:Runtime.Node, inputPrefix:Prefix) { }\n  capturePrefix(prefix:Prefix) { }\n  maybeOutput(change:Change) { }\n  postDistinct() { }\n  output(output:Change) { }\n  commit(commit:Change) { }\n  distinctCheck() { return false; }\n  pop(type:TraceFrameType) { }\n}\n"
  },
  {
    "path": "src/system-polyfills.js",
    "content": "/*\n * SystemJS Promise Polyfill\n */\n!function(t){!function(e){\"object\"==typeof exports?module.exports=e():\"function\"==typeof t&&t.amd?t(e):\"undefined\"!=typeof window?window.Promise=e():\"undefined\"!=typeof global?global.Promise=e():\"undefined\"!=typeof self&&(self.Promise=e())}(function(){var t;return function t(e,n,o){function r(u,c){if(!n[u]){if(!e[u]){var f=\"function\"==typeof require&&require;if(!c&&f)return f(u,!0);if(i)return i(u,!0);throw new Error(\"Cannot find module '\"+u+\"'\")}var s=n[u]={exports:{}};e[u][0].call(s.exports,function(t){var n=e[u][1][t];return r(n?n:t)},s,s.exports,t,e,n,o)}return n[u].exports}for(var i=\"function\"==typeof require&&require,u=0;u<o.length;u++)r(o[u]);return r}({1:[function(t,e,n){var o=t(\"../lib/decorators/unhandledRejection\"),r=o(t(\"../lib/Promise\"));e.exports=\"undefined\"!=typeof global?global.Promise=r:\"undefined\"!=typeof self?self.Promise=r:r},{\"../lib/Promise\":2,\"../lib/decorators/unhandledRejection\":4}],2:[function(e,n,o){!function(t){\"use strict\";t(function(t){var e=t(\"./makePromise\"),n=t(\"./Scheduler\"),o=t(\"./env\").asap;return e({scheduler:new n(o)})})}(\"function\"==typeof t&&t.amd?t:function(t){n.exports=t(e)})},{\"./Scheduler\":3,\"./env\":5,\"./makePromise\":7}],3:[function(e,n,o){!function(t){\"use strict\";t(function(){function t(t){this._async=t,this._running=!1,this._queue=this,this._queueLen=0,this._afterQueue={},this._afterQueueLen=0;var e=this;this.drain=function(){e._drain()}}return t.prototype.enqueue=function(t){this._queue[this._queueLen++]=t,this.run()},t.prototype.afterQueue=function(t){this._afterQueue[this._afterQueueLen++]=t,this.run()},t.prototype.run=function(){this._running||(this._running=!0,this._async(this.drain))},t.prototype._drain=function(){for(var t=0;t<this._queueLen;++t)this._queue[t].run(),this._queue[t]=void 0;for(this._queueLen=0,this._running=!1,t=0;t<this._afterQueueLen;++t)this._afterQueue[t].run(),this._afterQueue[t]=void 0;this._afterQueueLen=0},t})}(\"function\"==typeof t&&t.amd?t:function(t){n.exports=t()})},{}],4:[function(e,n,o){!function(t){\"use strict\";t(function(t){function e(t){throw t}function n(){}var o=t(\"../env\").setTimer,r=t(\"../format\");return function(t){function i(t){t.handled||(l.push(t),a(\"Potentially unhandled rejection [\"+t.id+\"] \"+r.formatError(t.value)))}function u(t){var e=l.indexOf(t);e>=0&&(l.splice(e,1),h(\"Handled previous rejection [\"+t.id+\"] \"+r.formatObject(t.value)))}function c(t,e){p.push(t,e),null===d&&(d=o(f,0))}function f(){for(d=null;p.length>0;)p.shift()(p.shift())}var s,a=n,h=n;\"undefined\"!=typeof console&&(s=console,a=\"undefined\"!=typeof s.error?function(t){s.error(t)}:function(t){s.log(t)},h=\"undefined\"!=typeof s.info?function(t){s.info(t)}:function(t){s.log(t)}),t.onPotentiallyUnhandledRejection=function(t){c(i,t)},t.onPotentiallyUnhandledRejectionHandled=function(t){c(u,t)},t.onFatalRejection=function(t){c(e,t.value)};var p=[],l=[],d=null;return t}})}(\"function\"==typeof t&&t.amd?t:function(t){n.exports=t(e)})},{\"../env\":5,\"../format\":6}],5:[function(e,n,o){!function(t){\"use strict\";t(function(t){function e(){return\"undefined\"!=typeof process&&\"[object process]\"===Object.prototype.toString.call(process)}function n(){return\"function\"==typeof MutationObserver&&MutationObserver||\"function\"==typeof WebKitMutationObserver&&WebKitMutationObserver}function o(t){function e(){var t=n;n=void 0,t()}var n,o=document.createTextNode(\"\"),r=new t(e);r.observe(o,{characterData:!0});var i=0;return function(t){n=t,o.data=i^=1}}var r,i=\"undefined\"!=typeof setTimeout&&setTimeout,u=function(t,e){return setTimeout(t,e)},c=function(t){return clearTimeout(t)},f=function(t){return i(t,0)};if(e())f=function(t){return process.nextTick(t)};else if(r=n())f=o(r);else if(!i){var s=t,a=s(\"vertx\");u=function(t,e){return a.setTimer(e,t)},c=a.cancelTimer,f=a.runOnLoop||a.runOnContext}return{setTimer:u,clearTimer:c,asap:f}})}(\"function\"==typeof t&&t.amd?t:function(t){n.exports=t(e)})},{}],6:[function(e,n,o){!function(t){\"use strict\";t(function(){function t(t){var n=\"object\"==typeof t&&null!==t&&(t.stack||t.message)?t.stack||t.message:e(t);return t instanceof Error?n:n+\" (WARNING: non-Error used)\"}function e(t){var e=String(t);return\"[object Object]\"===e&&\"undefined\"!=typeof JSON&&(e=n(t,e)),e}function n(t,e){try{return JSON.stringify(t)}catch(t){return e}}return{formatError:t,formatObject:e,tryStringify:n}})}(\"function\"==typeof t&&t.amd?t:function(t){n.exports=t()})},{}],7:[function(e,n,o){!function(t){\"use strict\";t(function(){return function(t){function e(t,e){this._handler=t===_?e:n(t)}function n(t){function e(t){r.resolve(t)}function n(t){r.reject(t)}function o(t){r.notify(t)}var r=new b;try{t(e,n,o)}catch(t){n(t)}return r}function o(t){return k(t)?t:new e(_,new x(v(t)))}function r(t){return new e(_,new x(new P(t)))}function i(){return $}function u(){return new e(_,new b)}function c(t,e){var n=new b(t.receiver,t.join().context);return new e(_,n)}function f(t){return a(B,null,t)}function s(t,e){return a(M,t,e)}function a(t,n,o){function r(e,r,u){u.resolved||h(o,i,e,t(n,r,e),u)}function i(t,e,n){a[t]=e,0===--s&&n.become(new q(a))}for(var u,c=\"function\"==typeof n?r:i,f=new b,s=o.length>>>0,a=new Array(s),p=0;p<o.length&&!f.resolved;++p)u=o[p],void 0!==u||p in o?h(o,c,p,u,f):--s;return 0===s&&f.become(new q(a)),new e(_,f)}function h(t,e,n,o,r){if(U(o)){var i=m(o),u=i.state();0===u?i.fold(e,n,void 0,r):u>0?e(n,i.value,r):(r.become(i),p(t,n+1,i))}else e(n,o,r)}function p(t,e,n){for(var o=e;o<t.length;++o)l(v(t[o]),n)}function l(t,e){if(t!==e){var n=t.state();0===n?t.visit(t,void 0,t._unreport):n<0&&t._unreport()}}function d(t){return\"object\"!=typeof t||null===t?r(new TypeError(\"non-iterable passed to race()\")):0===t.length?i():1===t.length?o(t[0]):y(t)}function y(t){var n,o,r,i=new b;for(n=0;n<t.length;++n)if(o=t[n],void 0!==o||n in t){if(r=v(o),0!==r.state()){i.become(r),p(t,n+1,r);break}r.visit(i,i.resolve,i.reject)}return new e(_,i)}function v(t){return k(t)?t._handler.join():U(t)?j(t):new q(t)}function m(t){return k(t)?t._handler.join():j(t)}function j(t){try{var e=t.then;return\"function\"==typeof e?new g(e,t):new q(t)}catch(t){return new P(t)}}function _(){}function w(){}function b(t,n){e.createContext(this,n),this.consumers=void 0,this.receiver=t,this.handler=void 0,this.resolved=!1}function x(t){this.handler=t}function g(t,e){b.call(this),G.enqueue(new E(t,e,this))}function q(t){e.createContext(this),this.value=t}function P(t){e.createContext(this),this.id=++Y,this.value=t,this.handled=!1,this.reported=!1,this._report()}function R(t,e){this.rejection=t,this.context=e}function C(t){this.rejection=t}function O(){return new P(new TypeError(\"Promise cycle\"))}function T(t,e){this.continuation=t,this.handler=e}function Q(t,e){this.handler=e,this.value=t}function E(t,e,n){this._then=t,this.thenable=e,this.resolver=n}function L(t,e,n,o,r){try{t.call(e,n,o,r)}catch(t){o(t)}}function S(t,e,n,o){this.f=t,this.z=e,this.c=n,this.to=o,this.resolver=X,this.receiver=this}function k(t){return t instanceof e}function U(t){return(\"object\"==typeof t||\"function\"==typeof t)&&null!==t}function H(t,n,o,r){return\"function\"!=typeof t?r.become(n):(e.enterContext(n),F(t,n.value,o,r),void e.exitContext())}function N(t,n,o,r,i){return\"function\"!=typeof t?i.become(o):(e.enterContext(o),W(t,n,o.value,r,i),void e.exitContext())}function J(t,n,o,r,i){return\"function\"!=typeof t?i.notify(n):(e.enterContext(o),z(t,n,r,i),void e.exitContext())}function M(t,e,n){try{return t(e,n)}catch(t){return r(t)}}function F(t,e,n,o){try{o.become(v(t.call(n,e)))}catch(t){o.become(new P(t))}}function W(t,e,n,o,r){try{t.call(o,e,n,r)}catch(t){r.become(new P(t))}}function z(t,e,n,o){try{o.notify(t.call(n,e))}catch(t){o.notify(t)}}function A(t,e){e.prototype=V(t.prototype),e.prototype.constructor=e}function B(t,e){return e}function K(){}function D(){return\"undefined\"!=typeof process&&null!==process&&\"function\"==typeof process.emit?function(t,e){return\"unhandledRejection\"===t?process.emit(t,e.value,e):process.emit(t,e)}:\"undefined\"!=typeof self&&\"function\"==typeof CustomEvent?function(t,e,n){var o=!1;try{var r=new n(\"unhandledRejection\");o=r instanceof n}catch(t){}return o?function(t,o){var r=new n(t,{detail:{reason:o.value,key:o},bubbles:!1,cancelable:!0});return!e.dispatchEvent(r)}:t}(K,self,CustomEvent):K}var G=t.scheduler,I=D(),V=Object.create||function(t){function e(){}return e.prototype=t,new e};e.resolve=o,e.reject=r,e.never=i,e._defer=u,e._handler=v,e.prototype.then=function(t,e,n){var o=this._handler,r=o.join().state();if(\"function\"!=typeof t&&r>0||\"function\"!=typeof e&&r<0)return new this.constructor(_,o);var i=this._beget(),u=i._handler;return o.chain(u,o.receiver,t,e,n),i},e.prototype.catch=function(t){return this.then(void 0,t)},e.prototype._beget=function(){return c(this._handler,this.constructor)},e.all=f,e.race=d,e._traverse=s,e._visitRemaining=p,_.prototype.when=_.prototype.become=_.prototype.notify=_.prototype.fail=_.prototype._unreport=_.prototype._report=K,_.prototype._state=0,_.prototype.state=function(){return this._state},_.prototype.join=function(){for(var t=this;void 0!==t.handler;)t=t.handler;return t},_.prototype.chain=function(t,e,n,o,r){this.when({resolver:t,receiver:e,fulfilled:n,rejected:o,progress:r})},_.prototype.visit=function(t,e,n,o){this.chain(X,t,e,n,o)},_.prototype.fold=function(t,e,n,o){this.when(new S(t,e,n,o))},A(_,w),w.prototype.become=function(t){t.fail()};var X=new w;A(_,b),b.prototype._state=0,b.prototype.resolve=function(t){this.become(v(t))},b.prototype.reject=function(t){this.resolved||this.become(new P(t))},b.prototype.join=function(){if(!this.resolved)return this;for(var t=this;void 0!==t.handler;)if(t=t.handler,t===this)return this.handler=O();return t},b.prototype.run=function(){var t=this.consumers,e=this.handler;this.handler=this.handler.join(),this.consumers=void 0;for(var n=0;n<t.length;++n)e.when(t[n])},b.prototype.become=function(t){this.resolved||(this.resolved=!0,this.handler=t,void 0!==this.consumers&&G.enqueue(this),void 0!==this.context&&t._report(this.context))},b.prototype.when=function(t){this.resolved?G.enqueue(new T(t,this.handler)):void 0===this.consumers?this.consumers=[t]:this.consumers.push(t)},b.prototype.notify=function(t){this.resolved||G.enqueue(new Q(t,this))},b.prototype.fail=function(t){var e=\"undefined\"==typeof t?this.context:t;this.resolved&&this.handler.join().fail(e)},b.prototype._report=function(t){this.resolved&&this.handler.join()._report(t)},b.prototype._unreport=function(){this.resolved&&this.handler.join()._unreport()},A(_,x),x.prototype.when=function(t){G.enqueue(new T(t,this))},x.prototype._report=function(t){this.join()._report(t)},x.prototype._unreport=function(){this.join()._unreport()},A(b,g),A(_,q),q.prototype._state=1,q.prototype.fold=function(t,e,n,o){N(t,e,this,n,o)},q.prototype.when=function(t){H(t.fulfilled,this,t.receiver,t.resolver)};var Y=0;A(_,P),P.prototype._state=-1,P.prototype.fold=function(t,e,n,o){o.become(this)},P.prototype.when=function(t){\"function\"==typeof t.rejected&&this._unreport(),H(t.rejected,this,t.receiver,t.resolver)},P.prototype._report=function(t){G.afterQueue(new R(this,t))},P.prototype._unreport=function(){this.handled||(this.handled=!0,G.afterQueue(new C(this)))},P.prototype.fail=function(t){this.reported=!0,I(\"unhandledRejection\",this),e.onFatalRejection(this,void 0===t?this.context:t)},R.prototype.run=function(){this.rejection.handled||this.rejection.reported||(this.rejection.reported=!0,I(\"unhandledRejection\",this.rejection)||e.onPotentiallyUnhandledRejection(this.rejection,this.context))},C.prototype.run=function(){this.rejection.reported&&(I(\"rejectionHandled\",this.rejection)||e.onPotentiallyUnhandledRejectionHandled(this.rejection))},e.createContext=e.enterContext=e.exitContext=e.onPotentiallyUnhandledRejection=e.onPotentiallyUnhandledRejectionHandled=e.onFatalRejection=K;var Z=new _,$=new e(_,Z);return T.prototype.run=function(){this.handler.join().when(this.continuation)},Q.prototype.run=function(){var t=this.handler.consumers;if(void 0!==t)for(var e,n=0;n<t.length;++n)e=t[n],J(e.progress,this.value,this.handler,e.receiver,e.resolver)},E.prototype.run=function(){function t(t){o.resolve(t)}function e(t){o.reject(t)}function n(t){o.notify(t)}var o=this.resolver;L(this._then,this.thenable,t,e,n)},S.prototype.fulfilled=function(t){this.f.call(this.c,this.z,t,this.to)},S.prototype.rejected=function(t){this.to.reject(t)},S.prototype.progress=function(t){this.to.notify(t)},e}})}(\"function\"==typeof t&&t.amd?t:function(t){n.exports=t()})},{}]},{},[1])(1)}),\"undefined\"!=typeof systemJSBootstrap&&systemJSBootstrap()}();\n//# sourceMappingURL=system-polyfills.js.map\n"
  },
  {
    "path": "src/system.js",
    "content": "/*\n * SystemJS v0.19.37\n */\n!function(){function e(){!function(e){function t(e,r){if(\"string\"!=typeof e)throw new TypeError(\"URL must be a string\");var n=String(e).replace(/^\\s+|\\s+$/g,\"\").match(/^([^:\\/?#]+:)?(?:\\/\\/(?:([^:@\\/?#]*)(?::([^:@\\/?#]*))?@)?(([^:\\/?#]*)(?::(\\d*))?))?([^?#]*)(\\?[^#]*)?(#[\\s\\S]*)?/);if(!n)throw new RangeError(\"Invalid URL format\");var a=n[1]||\"\",o=n[2]||\"\",i=n[3]||\"\",s=n[4]||\"\",l=n[5]||\"\",u=n[6]||\"\",d=n[7]||\"\",c=n[8]||\"\",f=n[9]||\"\";if(void 0!==r){var m=r instanceof t?r:new t(r),p=!a&&!s&&!o;!p||d||c||(c=m.search),p&&\"/\"!==d[0]&&(d=d?(!m.host&&!m.username||m.pathname?\"\":\"/\")+m.pathname.slice(0,m.pathname.lastIndexOf(\"/\")+1)+d:m.pathname);var h=[];d.replace(/^(\\.\\.?(\\/|$))+/,\"\").replace(/\\/(\\.(\\/|$))+/g,\"/\").replace(/\\/\\.\\.$/,\"/../\").replace(/\\/?[^\\/]*/g,function(e){\"/..\"===e?h.pop():h.push(e)}),d=h.join(\"\").replace(/^\\//,\"/\"===d[0]?\"/\":\"\"),p&&(u=m.port,l=m.hostname,s=m.host,i=m.password,o=m.username),a||(a=m.protocol)}d=d.replace(/\\\\/g,\"/\"),this.origin=s?a+(\"\"!==a||\"\"!==s?\"//\":\"\")+s:\"\",this.href=a+(a&&s||\"file:\"==a?\"//\":\"\")+(\"\"!==o?o+(\"\"!==i?\":\"+i:\"\")+\"@\":\"\")+s+d+c+f,this.protocol=a,this.username=o,this.password=i,this.host=s,this.hostname=l,this.port=u,this.pathname=d,this.search=c,this.hash=f}e.URLPolyfill=t}(\"undefined\"!=typeof self?self:global),function(e){function t(e,t){if(!e.originalErr)for(var r=((e.message||e)+(e.stack?\"\\n\"+e.stack:\"\")).toString().split(\"\\n\"),n=[],a=0;a<r.length;a++)\"undefined\"!=typeof $__curScript&&r[a].indexOf($__curScript.src)!=-1||n.push(r[a]);var o=\"(SystemJS) \"+(n?n.join(\"\\n\\t\"):e.message.substr(11))+\"\\n\\t\"+t;F||(o=o.replace(q?/file:\\/\\/\\//g:/file:\\/\\//g,\"\"));var i=$?new Error(o,e.fileName,e.lineNumber):new Error(o);return F?i.stack=null:i.stack=o,i.originalErr=e.originalErr||e,i}function r(){}function n(t){this._loader={loaderObj:this,loads:[],modules:{},importPromises:{},moduleRecords:{}},D(this,\"global\",{get:function(){return e}})}function a(){n.call(this),this.paths={},this._loader.paths={},V.call(this)}function o(){}function i(e,t){a.prototype[e]=t(a.prototype[e]||function(){})}function s(e){V=e(V||function(){})}function l(e){return e.match(Y)}function u(e){return\".\"==e[0]&&(!e[1]||\"/\"==e[1]||\".\"==e[1])||\"/\"==e[0]}function d(e){return!u(e)&&!l(e)}function c(e,t){if(\".\"==e[0]){if(\"/\"==e[1]&&\".\"!=e[2])return(t&&t.substr(0,t.lastIndexOf(\"/\")+1)||J)+e.substr(2)}else if(\"/\"!=e[0]&&e.indexOf(\":\")==-1)return(t&&t.substr(0,t.lastIndexOf(\"/\")+1)||J)+e;return new H(e,t&&t.replace(/#/g,\"%05\")||K).href.replace(/%05/g,\"#\")}function f(e,t){var r,n=\"\",a=0,o=e.paths,i=e._loader.paths;for(var s in o)if(!o.hasOwnProperty||o.hasOwnProperty(s)){var l=o[s];if(l!==i[s]&&(l=o[s]=i[s]=c(o[s],u(o[s])?J:e.baseURL)),s.indexOf(\"*\")===-1){if(t==s)return o[s];if(t.substr(0,s.length-1)==s.substr(0,s.length-1)&&(t.length<s.length||t[s.length-1]==s[s.length-1])&&(\"/\"==o[s][o[s].length-1]||\"\"==o[s]))return o[s].substr(0,o[s].length-1)+(t.length>s.length?(o[s]&&\"/\"||\"\")+t.substr(s.length):\"\")}else{var d=s.split(\"*\");if(d.length>2)throw new TypeError(\"Only one wildcard in a path is permitted\");var f=d[0].length;f>=a&&t.substr(0,d[0].length)==d[0]&&t.substr(t.length-d[1].length)==d[1]&&(a=f,n=s,r=t.substr(d[0].length,t.length-d[1].length-d[0].length))}}var m=o[n];return\"string\"==typeof r&&(m=m.replace(\"*\",r)),m}function m(e){for(var t=[],r=[],n=0,a=e.length;n<a;n++){var o=U.call(t,e[n]);o===-1?(t.push(e[n]),r.push([n])):r[o].push(n)}return{names:t,indices:r}}function p(t){var r={};if((\"object\"==typeof t||\"function\"==typeof t)&&t!==e)if(Q)for(var n in t)\"default\"!==n&&h(r,t,n);else g(r,t);return r.default=t,D(r,\"__useDefault\",{value:!0}),r}function h(e,t,r){try{var n;(n=Object.getOwnPropertyDescriptor(t,r))&&D(e,r,n)}catch(n){return e[r]=t[r],!1}}function g(e,t,r){var n=t&&t.hasOwnProperty;for(var a in t)n&&!t.hasOwnProperty(a)||r&&a in e||(e[a]=t[a]);return e}function v(e,t,r){var n=t&&t.hasOwnProperty;for(var a in t)if(!n||t.hasOwnProperty(a)){var o=t[a];a in e?o instanceof Array&&e[a]instanceof Array?e[a]=[].concat(r?o:e[a]).concat(r?e[a]:o):\"object\"==typeof o&&null!==o&&\"object\"==typeof e[a]?e[a]=g(g({},e[a]),o,r):r||(e[a]=o):e[a]=o}}function b(e,t,r,n,a){for(var o in t)if(U.call([\"main\",\"format\",\"defaultExtension\",\"basePath\"],o)!=-1)e[o]=t[o];else if(\"map\"==o)g(e.map=e.map||{},t.map);else if(\"meta\"==o)g(e.meta=e.meta||{},t.meta);else if(\"depCache\"==o)for(var i in t.depCache){var s;s=\"./\"==i.substr(0,2)?r+\"/\"+i.substr(2):P.call(n,i),n.depCache[s]=(n.depCache[s]||[]).concat(t.depCache[i])}else!a||U.call([\"browserConfig\",\"nodeConfig\",\"devConfig\",\"productionConfig\"],o)!=-1||t.hasOwnProperty&&!t.hasOwnProperty(o)||w.call(n,'\"'+o+'\" is not a valid package configuration option in package '+r)}function y(e,t,r,n){var a;if(e.packages[t]){var o=e.packages[t];a=e.packages[t]={},b(a,n?r:o,t,e,n),b(a,n?o:r,t,e,!n)}else a=e.packages[t]=r;return\"object\"==typeof a.main&&(a.map=a.map||{},a.map[\"./@main\"]=a.main,a.main.default=a.main.default||\"./\",a.main=\"@main\"),a}function w(e){this.warnings&&\"undefined\"!=typeof console&&console.warn}function x(e,t){for(var r=e.split(\".\");r.length;)t=t[r.shift()];return t}function S(e,t){var r,n=0;for(var a in e)if(t.substr(0,a.length)==a&&(t.length==a.length||\"/\"==t[a.length])){var o=a.split(\"/\").length;if(o<=n)continue;r=a,n=o}return r}function E(e){this._loader.baseURL!==this.baseURL&&(\"/\"!=this.baseURL[this.baseURL.length-1]&&(this.baseURL+=\"/\"),this._loader.baseURL=this.baseURL=new H(this.baseURL,K).href)}function _(e,t){this.set(\"@system-env\",te=this.newModule({browser:F,node:!!this._nodeRequire,production:!t&&e,dev:t||!e,build:t,default:!0}))}function j(e,t){if(!d(e))throw new Error(\"Node module \"+e+\" can't be loaded as it is not a package require.\");if(!re){var r=this._nodeRequire(\"module\"),n=t.substr(q?8:7);re=new r(n),re.paths=r._nodeModulePaths(n)}return re.require(e)}function P(e,t){if(u(e))return c(e,t);if(l(e))return e;var r=S(this.map,e);if(r){if(e=this.map[r]+e.substr(r.length),u(e))return c(e);if(l(e))return e}if(this.has(e))return e;if(\"@node/\"==e.substr(0,6)){if(!this._nodeRequire)throw new TypeError(\"Error loading \"+e+\". Can only load node core modules in Node.\");return this.set(e,this.newModule(p(j.call(this,e.substr(6),this.baseURL)))),e}return E.call(this),f(this,e)||this.baseURL+e}function k(e,t,r){te.browser&&t.browserConfig&&r(t.browserConfig),te.node&&t.nodeConfig&&r(t.nodeConfig),te.dev&&t.devConfig&&r(t.devConfig),te.build&&t.buildConfig&&r(t.buildConfig),te.production&&t.productionConfig&&r(t.productionConfig)}function O(e){var t=e.match(oe);return t&&\"System.register\"==e.substr(t[0].length,15)}function M(){return{name:null,deps:null,originalIndices:null,declare:null,execute:null,executingRequire:!1,declarative:!1,normalizedDeps:null,groupIndex:null,evaluated:!1,module:null,esModule:null,esmExports:!1}}function R(t){if(\"string\"==typeof t)return x(t,e);if(!(t instanceof Array))throw new Error(\"Global exports must be a string or array.\");for(var r={},n=!0,a=0;a<t.length;a++){var o=x(t[a],e);n&&(r.default=o,n=!1),r[t[a].split(\".\").pop()]=o}return r}function z(e){var t,r,n,n=\"~\"==e[0],a=e.lastIndexOf(\"|\");return a!=-1?(t=e.substr(a+1),r=e.substr(n,a-n),n&&w.call(this,'Condition negation form \"'+e+'\" is deprecated for \"'+r+\"|~\"+t+'\"'),\"~\"==t[0]&&(n=!0,t=t.substr(1))):(t=\"default\",r=e.substr(n),se.indexOf(r)!=-1&&(t=r,r=null)),{module:r||\"@system-env\",prop:t,negate:n}}function I(e){return e.module+\"|\"+(e.negate?\"~\":\"\")+e.prop}function T(e,t,r){var n=this;return this.normalize(e.module,t).then(function(t){return n.load(t).then(function(a){var o=x(e.prop,n.get(t));if(r&&\"boolean\"!=typeof o)throw new TypeError(\"Condition \"+I(e)+\" did not resolve to a boolean.\");return e.negate?!o:o})})}function C(e,t){var r=e.match(le);if(!r)return Promise.resolve(e);var n=z.call(this,r[0].substr(2,r[0].length-3));return this.builder?this.normalize(n.module,t).then(function(t){return n.module=t,e.replace(le,\"#{\"+I(n)+\"}\")}):T.call(this,n,t,!1).then(function(r){if(\"string\"!=typeof r)throw new TypeError(\"The condition value for \"+e+\" doesn't resolve to a string.\");if(r.indexOf(\"/\")!=-1)throw new TypeError(\"Unabled to interpolate conditional \"+e+(t?\" in \"+t:\"\")+\"\\n\\tThe condition value \"+r+' cannot contain a \"/\" separator.');return e.replace(le,r)})}function L(e,t){var r=e.lastIndexOf(\"#?\");if(r==-1)return Promise.resolve(e);var n=z.call(this,e.substr(r+2));return this.builder?this.normalize(n.module,t).then(function(t){return n.module=t,e.substr(0,r)+\"#?\"+I(n)}):T.call(this,n,t,!0).then(function(t){return t?e.substr(0,r):\"@empty\"})}var A=\"undefined\"==typeof window&&\"undefined\"!=typeof self&&\"undefined\"!=typeof importScripts,F=\"undefined\"!=typeof window&&\"undefined\"!=typeof document,q=\"undefined\"!=typeof process&&\"undefined\"!=typeof process.platform&&!!process.platform.match(/^win/);e.console||(e.console={assert:function(){}});var D,U=Array.prototype.indexOf||function(e){for(var t=0,r=this.length;t<r;t++)if(this[t]===e)return t;return-1};!function(){try{Object.defineProperty({},\"a\",{})&&(D=Object.defineProperty)}catch(e){D=function(e,t,r){try{e[t]=r.value||r.get.call(e)}catch(e){}}}}();var J,$=\"_\"==new Error(0,\"_\").fileName;if(\"undefined\"!=typeof document&&document.getElementsByTagName){if(J=document.baseURI,!J){var N=document.getElementsByTagName(\"base\");J=N[0]&&N[0].href||window.location.href}}else\"undefined\"!=typeof location&&(J=e.location.href);if(J)J=J.split(\"#\")[0].split(\"?\")[0],J=J.substr(0,J.lastIndexOf(\"/\")+1);else{if(\"undefined\"==typeof process||!process.cwd)throw new TypeError(\"No environment baseURI\");J=\"file://\"+(q?\"/\":\"\")+process.cwd()+\"/\",q&&(J=J.replace(/\\\\/g,\"/\"))}try{var B=\"test:\"==new e.URL(\"test:///\").protocol}catch(e){}var H=B?e.URL:e.URLPolyfill;D(r.prototype,\"toString\",{value:function(){return\"Module\"}}),function(){function e(e){return{status:\"loading\",name:e||\"<Anonymous\"+ ++y+\">\",linkSets:[],dependencies:[],metadata:{}}}function a(e,t,r){return new Promise(u({step:r.address?\"fetch\":\"locate\",loader:e,moduleName:t,moduleMetadata:r&&r.metadata||{},moduleSource:r.source,moduleAddress:r.address}))}function o(t,r,n,a){return new Promise(function(e,o){e(t.loaderObj.normalize(r,n,a))}).then(function(r){var n;if(t.modules[r])return n=e(r),n.status=\"linked\",n.module=t.modules[r],n;for(var a=0,o=t.loads.length;a<o;a++)if(n=t.loads[a],n.name==r)return n;return n=e(r),t.loads.push(n),i(t,n),n})}function i(e,t){s(e,t,Promise.resolve().then(function(){return e.loaderObj.locate({name:t.name,metadata:t.metadata})}))}function s(e,t,r){l(e,t,r.then(function(r){if(\"loading\"==t.status)return t.address=r,e.loaderObj.fetch({name:t.name,metadata:t.metadata,address:r})}))}function l(e,t,r){r.then(function(r){if(\"loading\"==t.status)return t.address=t.address||t.name,Promise.resolve(e.loaderObj.translate({name:t.name,metadata:t.metadata,address:t.address,source:r})).then(function(r){return t.source=r,e.loaderObj.instantiate({name:t.name,metadata:t.metadata,address:t.address,source:r})}).then(function(e){if(void 0===e)throw new TypeError(\"Declarative modules unsupported in the polyfill.\");if(\"object\"!=typeof e)throw new TypeError(\"Invalid instantiate return value\");t.depsList=e.deps||[],t.execute=e.execute}).then(function(){t.dependencies=[];for(var r=t.depsList,n=[],a=0,i=r.length;a<i;a++)(function(r,a){n.push(o(e,r,t.name,t.address).then(function(e){if(t.dependencies[a]={key:r,value:e.name},\"linked\"!=e.status)for(var n=t.linkSets.concat([]),o=0,i=n.length;o<i;o++)c(n[o],e)}))})(r[a],a);return Promise.all(n)}).then(function(){t.status=\"loaded\";for(var e=t.linkSets.concat([]),r=0,n=e.length;r<n;r++)m(e[r],t)})}).catch(function(e){t.status=\"failed\",t.exception=e;for(var r=t.linkSets.concat([]),n=0,a=r.length;n<a;n++)p(r[n],t,e)})}function u(t){return function(r,n){var a=t.loader,o=t.moduleName,u=t.step;if(a.modules[o])throw new TypeError('\"'+o+'\" already exists in the module table');for(var c,f=0,m=a.loads.length;f<m;f++)if(a.loads[f].name==o&&(c=a.loads[f],\"translate\"!=u||c.source||(c.address=t.moduleAddress,l(a,c,Promise.resolve(t.moduleSource))),c.linkSets.length&&c.linkSets[0].loads[0].name==c.name))return c.linkSets[0].done.then(function(){r(c)});var p=c||e(o);p.metadata=t.moduleMetadata;var h=d(a,p);a.loads.push(p),r(h.done),\"locate\"==u?i(a,p):\"fetch\"==u?s(a,p,Promise.resolve(t.moduleAddress)):(p.address=t.moduleAddress,l(a,p,Promise.resolve(t.moduleSource)))}}function d(e,t){var r={loader:e,loads:[],startingLoad:t,loadingCount:0};return r.done=new Promise(function(e,t){r.resolve=e,r.reject=t}),c(r,t),r}function c(e,t){if(\"failed\"!=t.status){for(var r=0,n=e.loads.length;r<n;r++)if(e.loads[r]==t)return;e.loads.push(t),t.linkSets.push(e),\"loaded\"!=t.status&&e.loadingCount++;for(var a=e.loader,r=0,n=t.dependencies.length;r<n;r++)if(t.dependencies[r]){var o=t.dependencies[r].value;if(!a.modules[o])for(var i=0,s=a.loads.length;i<s;i++)if(a.loads[i].name==o){c(e,a.loads[i]);break}}}}function f(e){var t=!1;try{b(e,function(r,n){p(e,r,n),t=!0})}catch(r){p(e,null,r),t=!0}return t}function m(e,t){if(e.loadingCount--,!(e.loadingCount>0)){var r=e.startingLoad;if(e.loader.loaderObj.execute===!1){for(var n=[].concat(e.loads),a=0,o=n.length;a<o;a++){var t=n[a];t.module={name:t.name,module:w({}),evaluated:!0},t.status=\"linked\",h(e.loader,t)}return e.resolve(r)}var i=f(e);i||e.resolve(r)}}function p(e,r,n){var a=e.loader;e:if(r)if(e.loads[0].name==r.name)n=t(n,\"Error loading \"+r.name);else{for(var o=0;o<e.loads.length;o++)for(var i=e.loads[o],s=0;s<i.dependencies.length;s++){var l=i.dependencies[s];if(l.value==r.name){n=t(n,\"Error loading \"+r.name+' as \"'+l.key+'\" from '+i.name);break e}}n=t(n,\"Error loading \"+r.name+\" from \"+e.loads[0].name)}else n=t(n,\"Error linking \"+e.loads[0].name);for(var u=e.loads.concat([]),o=0,d=u.length;o<d;o++){var r=u[o];a.loaderObj.failed=a.loaderObj.failed||[],U.call(a.loaderObj.failed,r)==-1&&a.loaderObj.failed.push(r);var c=U.call(r.linkSets,e);if(r.linkSets.splice(c,1),0==r.linkSets.length){var f=U.call(e.loader.loads,r);f!=-1&&e.loader.loads.splice(f,1)}}e.reject(n)}function h(e,t){if(e.loaderObj.trace){e.loaderObj.loads||(e.loaderObj.loads={});var r={};t.dependencies.forEach(function(e){r[e.key]=e.value}),e.loaderObj.loads[t.name]={name:t.name,deps:t.dependencies.map(function(e){return e.key}),depMap:r,address:t.address,metadata:t.metadata,source:t.source}}t.name&&(e.modules[t.name]=t.module);var n=U.call(e.loads,t);n!=-1&&e.loads.splice(n,1);for(var a=0,o=t.linkSets.length;a<o;a++)n=U.call(t.linkSets[a].loads,t),n!=-1&&t.linkSets[a].loads.splice(n,1);t.linkSets.splice(0,t.linkSets.length)}function g(e,t,n){try{var a=t.execute()}catch(e){return void n(t,e)}return a&&a instanceof r?a:void n(t,new TypeError(\"Execution must define a Module instance\"))}function v(e,t,r){var n=e._loader.importPromises;return n[t]=r.then(function(e){return n[t]=void 0,e},function(e){throw n[t]=void 0,e})}function b(e,t){var r=e.loader;if(e.loads.length)for(var n=e.loads.concat([]),a=0;a<n.length;a++){var o=n[a],i=g(e,o,t);if(!i)return;o.module={name:o.name,module:i},o.status=\"linked\",h(r,o)}}var y=0;n.prototype={constructor:n,define:function(e,t,r){if(this._loader.importPromises[e])throw new TypeError(\"Module is already loading.\");return v(this,e,new Promise(u({step:\"translate\",loader:this._loader,moduleName:e,moduleMetadata:r&&r.metadata||{},moduleSource:t,moduleAddress:r&&r.address})))},delete:function(e){var t=this._loader;return delete t.importPromises[e],delete t.moduleRecords[e],!!t.modules[e]&&delete t.modules[e]},get:function(e){if(this._loader.modules[e])return this._loader.modules[e].module},has:function(e){return!!this._loader.modules[e]},import:function(e,t,r){\"object\"==typeof t&&(t=t.name);var n=this;return Promise.resolve(n.normalize(e,t)).then(function(e){var t=n._loader;return t.modules[e]?t.modules[e].module:t.importPromises[e]||v(n,e,a(t,e,{}).then(function(r){return delete t.importPromises[e],r.module.module}))})},load:function(e){var t=this._loader;return t.modules[e]?Promise.resolve():t.importPromises[e]||v(this,e,new Promise(u({step:\"locate\",loader:t,moduleName:e,moduleMetadata:{},moduleSource:void 0,moduleAddress:void 0})).then(function(){delete t.importPromises[e]}))},module:function(t,r){var n=e();n.address=r&&r.address;var a=d(this._loader,n),o=Promise.resolve(t),i=this._loader,s=a.done.then(function(){return n.module.module});return l(i,n,o),s},newModule:function(e){if(\"object\"!=typeof e)throw new TypeError(\"Expected object\");var t=new r,n=[];if(Object.getOwnPropertyNames&&null!=e)n=Object.getOwnPropertyNames(e);else for(var a in e)n.push(a);for(var o=0;o<n.length;o++)(function(r){D(t,r,{configurable:!1,enumerable:!0,get:function(){return e[r]},set:function(){throw new Error(\"Module exports cannot be changed externally.\")}})})(n[o]);return Object.freeze&&Object.freeze(t),t},set:function(e,t){if(!(t instanceof r))throw new TypeError(\"Loader.set(\"+e+\", module) must be a module\");this._loader.modules[e]={module:t}},normalize:function(e,t,r){},locate:function(e){return e.name},fetch:function(e){},translate:function(e){return e.source},instantiate:function(e){}};var w=n.prototype.newModule}();var X,G;if(\"undefined\"!=typeof XMLHttpRequest)G=function(e,t,r,n){function a(){r(i.responseText)}function o(){n(new Error(\"XHR error\"+(i.status?\" (\"+i.status+(i.statusText?\" \"+i.statusText:\"\")+\")\":\"\")+\" loading \"+e))}var i=new XMLHttpRequest,s=!0,l=!1;if(!(\"withCredentials\"in i)){var u=/^(\\w+:)?\\/\\/([^\\/]+)/.exec(e);u&&(s=u[2]===window.location.host,u[1]&&(s&=u[1]===window.location.protocol))}s||\"undefined\"==typeof XDomainRequest||(i=new XDomainRequest,i.onload=a,i.onerror=o,i.ontimeout=o,i.onprogress=function(){},i.timeout=0,l=!0),i.onreadystatechange=function(){4===i.readyState&&(0==i.status?i.responseText?a():(i.addEventListener(\"error\",o),i.addEventListener(\"load\",a)):200===i.status?a():o())},i.open(\"GET\",e,!0),i.setRequestHeader&&(i.setRequestHeader(\"Accept\",\"application/x-es-module, */*\"),t&&(\"string\"==typeof t&&i.setRequestHeader(\"Authorization\",t),i.withCredentials=!0)),l?setTimeout(function(){i.send()},0):i.send(null)};else if(\"undefined\"!=typeof require&&\"undefined\"!=typeof process){var Z;G=function(e,t,r,n){if(\"file:///\"!=e.substr(0,8))throw new Error('Unable to fetch \"'+e+'\". Only file URLs of the form file:/// allowed running in Node.');return Z=Z||require(\"fs\"),e=q?e.replace(/\\//g,\"\\\\\").substr(8):e.substr(7),Z.readFile(e,function(e,t){if(e)return n(e);var a=t+\"\";\"\\ufeff\"===a[0]&&(a=a.substr(1)),r(a)})}}else{if(\"undefined\"==typeof self||\"undefined\"==typeof self.fetch)throw new TypeError(\"No environment fetch API available.\");G=function(e,t,r,n){var a={headers:{Accept:\"application/x-es-module, */*\"}};t&&(\"string\"==typeof t&&(a.headers.Authorization=t),a.credentials=\"include\"),fetch(e,a).then(function(e){if(e.ok)return e.text();throw new Error(\"Fetch error: \"+e.status+\" \"+e.statusText)}).then(r,n)}}var W=function(){function t(t){var n=this;return Promise.resolve(e[\"typescript\"==n.transpiler?\"ts\":n.transpiler]||(n.pluginLoader||n).import(n.transpiler)).then(function(e){e.__useDefault&&(e=e.default);var a;return a=e.Compiler?r:e.createLanguageService?i:o,\"(function(__moduleName){\"+a.call(n,t,e)+'\\n})(\"'+t.name+'\");\\n//# sourceURL='+t.address+\"!transpiled\"})}function r(e,t){var r=this.traceurOptions||{};r.modules=\"instantiate\",r.script=!1,void 0===r.sourceMaps&&(r.sourceMaps=\"inline\"),r.filename=e.address,r.inputSourceMap=e.metadata.sourceMap,r.moduleName=!1;var n=new t.Compiler(r);return a(e.source,n,r.filename)}function a(e,t,r){try{return t.compile(e,r)}catch(e){if(e.length)throw e[0];throw e}}function o(e,t){var r=this.babelOptions||{};return r.modules=\"system\",void 0===r.sourceMap&&(r.sourceMap=\"inline\"),r.inputSourceMap=e.metadata.sourceMap,r.filename=e.address,r.code=!0,r.ast=!1,t.transform(e.source,r).code}function i(e,t){var r=this.typescriptOptions||{};return r.target=r.target||t.ScriptTarget.ES5,void 0===r.sourceMap&&(r.sourceMap=!0),r.sourceMap&&r.inlineSourceMap!==!1&&(r.inlineSourceMap=!0),r.module=t.ModuleKind.System,t.transpile(e.source,r,e.address)}return n.prototype.transpiler=\"traceur\",t}();o.prototype=n.prototype,a.prototype=new o,a.prototype.constructor=a;var V,Y=/^[^\\/]+:\\/\\//,K=new H(J),Q=!0;try{Object.getOwnPropertyDescriptor({a:0},\"a\")}catch(e){Q=!1}var ee;!function(){function r(e){return l?d+new Buffer(e).toString(\"base64\"):\"undefined\"!=typeof btoa?d+btoa(unescape(encodeURIComponent(e))):\"\"}function n(e,t){var n=e.source.lastIndexOf(\"\\n\");\"global\"==e.metadata.format&&(t=!1);var a=e.metadata.sourceMap;if(a){if(\"object\"!=typeof a)throw new TypeError(\"load.metadata.sourceMap must be set to an object.\");a=JSON.stringify(a)}return(t?\"(function(System, SystemJS) {\":\"\")+e.source+(t?\"\\n})(System, System);\":\"\")+(\"\\n//# sourceURL=\"!=e.source.substr(n,15)?\"\\n//# sourceURL=\"+e.address+(a?\"!transpiled\":\"\"):\"\")+(a&&r(a)||\"\")}function a(t,r){u=r,0==p++&&(c=e.System),e.System=e.SystemJS=t}function o(){0==--p&&(e.System=e.SystemJS=c),u=void 0}function s(e){v||(v=document.head||document.body||document.documentElement);var r=document.createElement(\"script\");r.text=n(e,!1);var i,s=window.onerror;if(window.onerror=function(r){i=t(r,\"Evaluating \"+e.address),s&&s.apply(this,arguments)},a(this,e),e.metadata.integrity&&r.setAttribute(\"integrity\",e.metadata.integrity),e.metadata.nonce&&r.setAttribute(\"nonce\",e.metadata.nonce),v.appendChild(r),v.removeChild(r),o(),window.onerror=s,i)throw i}var l=\"undefined\"!=typeof Buffer;try{l&&\"YQ==\"!=new Buffer(\"a\").toString(\"base64\")&&(l=!1)}catch(e){l=!1}var u,d=\"\\n//# sourceMappingURL=data:application/json;base64,\";i(\"pushRegister_\",function(){return function(e){return!!u&&(this.reduceRegister_(u,e),!0)}});var c,f,m,p=0;ee=function(e){if(e.source){if((e.metadata.integrity||e.metadata.nonce)&&h)return s.call(this,e);try{a(this,e),u=e,!m&&this._nodeRequire&&(m=this._nodeRequire(\"vm\"),f=m.runInThisContext(\"typeof System !== 'undefined' && System\")===this),f?m.runInThisContext(n(e,!0),{filename:e.address+(e.metadata.sourceMap?\"!transpiled\":\"\")}):(0,eval)(n(e,!0)),o()}catch(r){throw o(),t(r,\"Evaluating \"+e.address)}}};var h=!1;if(F&&\"undefined\"!=typeof document&&document.getElementsByTagName){var g=document.getElementsByTagName(\"script\");$__curScript=g[g.length-1],window.chrome&&window.chrome.extension||navigator.userAgent.match(/^Node\\.js/)||(h=!0)}var v}();var te;s(function(e){return function(){e.call(this),this.baseURL=J,this.map={},\"undefined\"!=typeof $__curScript&&(this.scriptSrc=$__curScript.src),this.warnings=!1,this.defaultJSExtensions=!1,this.pluginFirst=!1,this.loaderErrorStack=!1,this.set(\"@empty\",this.newModule({})),_.call(this,!1,!1)}}),\"undefined\"==typeof require||\"undefined\"==typeof process||process.browser||(a.prototype._nodeRequire=require);var re;i(\"normalize\",function(e){return function(e,t,r){var n=P.call(this,e,t);return!this.defaultJSExtensions||r||\".js\"==n.substr(n.length-3,3)||d(n)||(n+=\".js\"),n}});var ne=\"undefined\"!=typeof XMLHttpRequest;i(\"locate\",function(e){return function(t){return Promise.resolve(e.call(this,t)).then(function(e){return ne?e.replace(/#/g,\"%23\"):e})}}),i(\"fetch\",function(){return function(e){return new Promise(function(t,r){G(e.address,e.metadata.authorization,t,r)})}}),i(\"import\",function(e){return function(t,r,n){return r&&r.name&&w.call(this,\"SystemJS.import(name, { name: parentName }) is deprecated for SystemJS.import(name, parentName), while importing \"+t+\" from \"+r.name),e.call(this,t,r,n).then(function(e){return e.__useDefault?e.default:e})}}),i(\"translate\",function(e){return function(t){return\"detect\"==t.metadata.format&&(t.metadata.format=void 0),e.apply(this,arguments)}}),i(\"instantiate\",function(e){return function(e){if(\"json\"==e.metadata.format&&!this.builder){var t=e.metadata.entry=M();t.deps=[],t.execute=function(){try{return JSON.parse(e.source)}catch(t){throw new Error(\"Invalid JSON file \"+e.name)}}}}}),a.prototype.getConfig=function(e){var t={},r=this;for(var n in r)r.hasOwnProperty&&!r.hasOwnProperty(n)||n in a.prototype&&\"transpiler\"!=n||U.call([\"_loader\",\"amdDefine\",\"amdRequire\",\"defined\",\"failed\",\"version\",\"loads\"],n)==-1&&(t[n]=r[n]);return t.production=te.production,t};var ae;a.prototype.config=function(e,t){function r(e){for(var t in e)if(e.hasOwnProperty(t))return!0}var n=this;if(\"loaderErrorStack\"in e&&(ae=$__curScript,e.loaderErrorStack?$__curScript=void 0:$__curScript=ae),\"warnings\"in e&&(n.warnings=e.warnings),e.transpilerRuntime===!1&&(n._loader.loadedTranspilerRuntime=!0),(\"production\"in e||\"build\"in e)&&_.call(n,!!e.production,!!(e.build||te&&te.build)),!t){var a;if(k(n,e,function(e){a=a||e.baseURL}),a=a||e.baseURL){if(r(n.packages)||r(n.meta)||r(n.depCache)||r(n.bundles)||r(n.packageConfigPaths))throw new TypeError(\"Incorrect configuration order. The baseURL must be configured with the first SystemJS.config call.\");this.baseURL=a,E.call(this)}if(e.paths&&g(n.paths,e.paths),k(n,e,function(e){e.paths&&g(n.paths,e.paths)}),this.warnings)for(var o in n.paths)o.indexOf(\"*\")!=-1&&w.call(n,'Paths configuration \"'+o+'\" -> \"'+n.paths[o]+'\" uses wildcards which are being deprecated for simpler trailing \"/\" folder paths.')}if(e.defaultJSExtensions&&(n.defaultJSExtensions=e.defaultJSExtensions,w.call(n,\"The defaultJSExtensions configuration option is deprecated, use packages configuration instead.\")),e.pluginFirst&&(n.pluginFirst=e.pluginFirst),e.map){var i=\"\";for(var o in e.map){var s=e.map[o];if(\"string\"!=typeof s){i+=(i.length?\", \":\"\")+'\"'+o+'\"';var l=n.defaultJSExtensions&&\".js\"!=o.substr(o.length-3,3),u=n.decanonicalize(o);l&&\".js\"==u.substr(u.length-3,3)&&(u=u.substr(0,u.length-3));var c=\"\";for(var f in n.packages)u.substr(0,f.length)==f&&(!u[f.length]||\"/\"==u[f.length])&&c.split(\"/\").length<f.split(\"/\").length&&(c=f);c&&n.packages[c].main&&(u=u.substr(0,u.length-n.packages[c].main.length-1));var f=n.packages[u]=n.packages[u]||{};f.map=s}else n.map[o]=s}i&&w.call(n,\"The map configuration for \"+i+' uses object submaps, which is deprecated in global map.\\nUpdate this to use package contextual map with configs like SystemJS.config({ packages: { \"'+o+'\": { map: {...} } } }).')}if(e.packageConfigPaths){for(var m=[],p=0;p<e.packageConfigPaths.length;p++){var h=e.packageConfigPaths[p],v=Math.max(h.lastIndexOf(\"*\")+1,h.lastIndexOf(\"/\")),b=P.call(n,h.substr(0,v));m[p]=b+h.substr(v)}n.packageConfigPaths=m}if(e.bundles)for(var o in e.bundles){for(var x=[],p=0;p<e.bundles[o].length;p++){var l=n.defaultJSExtensions&&\".js\"!=e.bundles[o][p].substr(e.bundles[o][p].length-3,3),S=n.decanonicalize(e.bundles[o][p]);l&&\".js\"==S.substr(S.length-3,3)&&(S=S.substr(0,S.length-3)),x.push(S)}n.bundles[o]=x}if(e.packages)for(var o in e.packages){if(o.match(/^([^\\/]+:)?\\/\\/$/))throw new TypeError('\"'+o+'\" is not a valid package name.');var u=P.call(n,o);\"/\"==u[u.length-1]&&(u=u.substr(0,u.length-1)),y(n,u,e.packages[o],!1)}for(var j in e){var s=e[j];if(U.call([\"baseURL\",\"map\",\"packages\",\"bundles\",\"paths\",\"warnings\",\"packageConfigPaths\",\"loaderErrorStack\",\"browserConfig\",\"nodeConfig\",\"devConfig\",\"buildConfig\",\"productionConfig\"],j)==-1)if(\"object\"!=typeof s||s instanceof Array)n[j]=s;else{n[j]=n[j]||{};for(var o in s)if(\"meta\"==j&&\"*\"==o[0])g(n[j][o]=n[j][o]||{},s[o]);else if(\"meta\"==j){var O=P.call(n,o);n.defaultJSExtensions&&\".js\"!=O.substr(O.length-3,3)&&!d(O)&&(O+=\".js\"),g(n[j][O]=n[j][O]||{},s[o])}else if(\"depCache\"==j){var l=n.defaultJSExtensions&&\".js\"!=o.substr(o.length-3,3),u=n.decanonicalize(o);l&&\".js\"==u.substr(u.length-3,3)&&(u=u.substr(0,u.length-3)),n[j][u]=[].concat(s[o])}else n[j][o]=s[o]}}k(n,e,function(e){n.config(e,!0)})},function(){function e(e,t){var r,n,a=0;for(var o in e.packages)t.substr(0,o.length)!==o||t.length!==o.length&&\"/\"!==t[o.length]||(n=o.split(\"/\").length,n>a&&(r=o,a=n));return r}function t(e,t,r,n,a){if(!n||\"/\"==n[n.length-1]||a||t.defaultExtension===!1)return n;var o=!1;if(t.meta&&p(t.meta,n,function(e,t,r){if(0==r||e.lastIndexOf(\"*\")!=e.length-1)return o=!0}),!o&&e.meta&&p(e.meta,r+\"/\"+n,function(e,t,r){if(0==r||e.lastIndexOf(\"*\")!=e.length-1)return o=!0}),o)return n;var i=\".\"+(t.defaultExtension||\"js\");return n.substr(n.length-i.length)!=i?n+i:n}function r(e,r,n,a,i){if(!a){if(!r.main)return n+(e.defaultJSExtensions?\".js\":\"\");a=\"./\"==r.main.substr(0,2)?r.main.substr(2):r.main}if(r.map){var s=\"./\"+a,l=S(r.map,s);if(l||(s=\"./\"+t(e,r,n,a,i),s!=\"./\"+a&&(l=S(r.map,s))),l){var u=o(e,r,n,l,s,i);if(u)return u}}return n+\"/\"+t(e,r,n,a,i)}function n(e,t,r,n){if(\".\"==e)throw new Error(\"Package \"+r+' has a map entry for \".\" which is not permitted.');return!(t.substr(0,e.length)==e&&n.length>e.length)}function o(e,r,a,o,i,s){\"/\"==i[i.length-1]&&(i=i.substr(0,i.length-1));var l=r.map[o];if(\"object\"==typeof l)throw new Error(\"Synchronous conditional normalization not supported sync normalizing \"+o+\" in \"+a);if(n(o,l,a,i)&&\"string\"==typeof l){if(\".\"==l)l=a;else if(\"./\"==l.substr(0,2))return a+\"/\"+t(e,r,a,l.substr(2)+i.substr(o.length),s);return e.normalizeSync(l+i.substr(o.length),a+\"/\")}}function l(e,r,n,a,o){if(!a){if(!r.main)return Promise.resolve(n+(e.defaultJSExtensions?\".js\":\"\"));a=\"./\"==r.main.substr(0,2)?r.main.substr(2):r.main}var i,s;return r.map&&(i=\"./\"+a,s=S(r.map,i),s||(i=\"./\"+t(e,r,n,a,o),i!=\"./\"+a&&(s=S(r.map,i)))),(s?d(e,r,n,s,i,o):Promise.resolve()).then(function(i){return i?Promise.resolve(i):Promise.resolve(n+\"/\"+t(e,r,n,a,o))})}function u(e,r,n,a,o,i,s){if(\".\"==o)o=n;else if(\"./\"==o.substr(0,2))return Promise.resolve(n+\"/\"+t(e,r,n,o.substr(2)+i.substr(a.length),s)).then(function(t){return C.call(e,t,n+\"/\")});return e.normalize(o+i.substr(a.length),n+\"/\")}function d(e,t,r,a,o,i){\"/\"==o[o.length-1]&&(o=o.substr(0,o.length-1));var s=t.map[a];if(\"string\"==typeof s)return n(a,s,r,o)?u(e,t,r,a,s,o,i):Promise.resolve();if(e.builder)return Promise.resolve(r+\"/#:\"+o);var l=[],d=[];for(var c in s){var f=z(c);d.push({condition:f,map:s[c]}),l.push(e.import(f.module,r))}return Promise.all(l).then(function(e){for(var t=0;t<d.length;t++){var r=d[t].condition,n=x(r.prop,e[t]);if(!r.negate&&n||r.negate&&!n)return d[t].map}}).then(function(s){if(s){if(!n(a,s,r,o))return;return u(e,t,r,a,s,o,i)}})}function c(e){var t=e.lastIndexOf(\"*\"),r=Math.max(t+1,e.lastIndexOf(\"/\"));return{length:r,regEx:new RegExp(\"^(\"+e.substr(0,r).replace(/[.+?^${}()|[\\]\\\\]/g,\"\\\\$&\").replace(/\\*/g,\"[^\\\\/]+\")+\")(\\\\/|$)\"),wildcard:t!=-1}}function f(e,t){for(var r,n,a=!1,o=0;o<e.packageConfigPaths.length;o++){var i=e.packageConfigPaths[o],s=h[i]||(h[i]=c(i));if(!(t.length<s.length)){var l=t.match(s.regEx);!l||r&&(a&&s.wildcard||!(r.length<l[1].length))||(r=l[1],a=!s.wildcard,n=r+i.substr(s.length))}}if(r)return{packageName:r,configPath:n}}function m(e,t,r){var n=e.pluginLoader||e;return(n.meta[r]=n.meta[r]||{}).format=\"json\",n.meta[r].loader=null,n.load(r).then(function(){var a=n.get(r).default;return a.systemjs&&(a=a.systemjs),a.modules&&(a.meta=a.modules,w.call(e,\"Package config file \"+r+' is configured with \"modules\", which is deprecated as it has been renamed to \"meta\".')),y(e,t,a,!0)})}function p(e,t,r){var n;for(var a in e){var o=\"./\"==a.substr(0,2)?\"./\":\"\";if(o&&(a=a.substr(2)),n=a.indexOf(\"*\"),n!==-1&&a.substr(0,n)==t.substr(0,n)&&a.substr(n+1)==t.substr(t.length-a.length+n+1)&&r(a,e[o+a],a.split(\"/\").length))return}var i=e[t]&&e.hasOwnProperty&&e.hasOwnProperty(t)?e[t]:e[\"./\"+t];i&&r(i,i,0)}s(function(e){return function(){e.call(this),this.packages={},this.packageConfigPaths=[]}}),a.prototype.normalizeSync=a.prototype.decanonicalize=a.prototype.normalize,i(\"decanonicalize\",function(t){return function(r,n){if(this.builder)return t.call(this,r,n,!0);var a=t.call(this,r,n,!1);if(!this.defaultJSExtensions)return a;var o=e(this,a),i=this.packages[o],s=i&&i.defaultExtension;return void 0==s&&i&&i.meta&&p(i.meta,a.substr(o),function(e,t,r){if(0==r||e.lastIndexOf(\"*\")!=e.length-1)return s=!1,!0}),(s===!1||s&&\".js\"!=s)&&\".js\"!=r.substr(r.length-3,3)&&\".js\"==a.substr(a.length-3,3)&&(a=a.substr(0,a.length-3)),a}}),i(\"normalizeSync\",function(t){return function(n,a,i){var s=this;if(i=i===!0,a)var l=e(s,a)||s.defaultJSExtensions&&\".js\"==a.substr(a.length-3,3)&&e(s,a.substr(0,a.length-3));\nvar u=l&&s.packages[l];if(u&&\".\"!=n[0]){var d=u.map,c=d&&S(d,n);if(c&&\"string\"==typeof d[c]){var m=o(s,u,l,c,n,i);if(m)return m}}var p=s.defaultJSExtensions&&\".js\"!=n.substr(n.length-3,3),h=t.call(s,n,a,!1);p&&\".js\"!=h.substr(h.length-3,3)&&(p=!1),p&&(h=h.substr(0,h.length-3));var g=f(s,h),v=g&&g.packageName||e(s,h);if(!v)return h+(p?\".js\":\"\");var b=h.substr(v.length+1);return r(s,s.packages[v]||{},v,b,i)}}),i(\"normalize\",function(t){return function(r,n,a){var o=this;return a=a===!0,Promise.resolve().then(function(){if(n)var t=e(o,n)||o.defaultJSExtensions&&\".js\"==n.substr(n.length-3,3)&&e(o,n.substr(0,n.length-3));var i=t&&o.packages[t];if(i&&\"./\"!=r.substr(0,2)){var s=i.map,l=s&&S(s,r);if(l)return d(o,i,t,l,r,a)}return Promise.resolve()}).then(function(i){if(i)return i;var s=o.defaultJSExtensions&&\".js\"!=r.substr(r.length-3,3),u=t.call(o,r,n,!1);s&&\".js\"!=u.substr(u.length-3,3)&&(s=!1),s&&(u=u.substr(0,u.length-3));var d=f(o,u),c=d&&d.packageName||e(o,u);if(!c)return Promise.resolve(u+(s?\".js\":\"\"));var p=o.packages[c],h=p&&(p.configured||!d);return(h?Promise.resolve(p):m(o,c,d.configPath)).then(function(e){var t=u.substr(c.length+1);return l(o,e,c,t,a)})})}});var h={};i(\"locate\",function(t){return function(r){var n=this;return Promise.resolve(t.call(this,r)).then(function(t){var a=e(n,r.name);if(a){var o=n.packages[a],i=r.name.substr(a.length+1),s={};if(o.meta){var l=0;p(o.meta,i,function(e,t,r){r>l&&(l=r),v(s,t,r&&l>r)}),v(r.metadata,s)}o.format&&!r.metadata.loader&&(r.metadata.format=r.metadata.format||o.format)}return t})}})}(),function(){function t(){if(s&&\"interactive\"===s.script.readyState)return s.load;for(var e=0;e<d.length;e++)if(\"interactive\"==d[e].script.readyState)return s=d[e],s.load}function r(e,t){return new Promise(function(e,r){t.metadata.integrity&&r(new Error(\"Subresource integrity checking is not supported in web workers.\")),l=t;try{importScripts(t.address)}catch(e){l=null,r(e)}l=null,t.metadata.entry||r(new Error(t.address+\" did not call System.register or AMD define. If loading a global, ensure the meta format is set to global.\")),e(\"\")})}if(\"undefined\"!=typeof document)var n=document.getElementsByTagName(\"head\")[0];var a,o,s,l=null,u=n&&function(){var e=document.createElement(\"script\"),t=\"undefined\"!=typeof opera&&\"[object Opera]\"===opera.toString();return e.attachEvent&&!(e.attachEvent.toString&&e.attachEvent.toString().indexOf(\"[native code\")<0)&&!t}(),d=[],c=0,f=[];i(\"pushRegister_\",function(e){return function(r){return!e.call(this,r)&&(l?this.reduceRegister_(l,r):u?this.reduceRegister_(t(),r):c?f.push(r):this.reduceRegister_(null,r),!0)}}),i(\"fetch\",function(t){return function(i){var l=this;return\"json\"!=i.metadata.format&&i.metadata.scriptLoad&&(F||A)?A?r(l,i):new Promise(function(t,r){function m(e){if(!g.readyState||\"loaded\"==g.readyState||\"complete\"==g.readyState){if(c--,i.metadata.entry||f.length){if(!u){for(var n=0;n<f.length;n++)l.reduceRegister_(i,f[n]);f=[]}}else l.reduceRegister_(i);h(),i.metadata.entry||i.metadata.bundle||r(new Error(i.name+\" did not call System.register or AMD define. If loading a global module configure the global name via the meta exports property for script injection support.\")),t(\"\")}}function p(e){h(),r(new Error(\"Unable to load script \"+i.address))}function h(){if(e.System=a,e.require=o,g.detachEvent){g.detachEvent(\"onreadystatechange\",m);for(var t=0;t<d.length;t++)d[t].script==g&&(s&&s.script==g&&(s=null),d.splice(t,1))}else g.removeEventListener(\"load\",m,!1),g.removeEventListener(\"error\",p,!1);n.removeChild(g)}var g=document.createElement(\"script\");g.async=!0,i.metadata.crossOrigin&&(g.crossOrigin=i.metadata.crossOrigin),i.metadata.integrity&&g.setAttribute(\"integrity\",i.metadata.integrity),u?(g.attachEvent(\"onreadystatechange\",m),d.push({script:g,load:i})):(g.addEventListener(\"load\",m,!1),g.addEventListener(\"error\",p,!1)),c++,a=e.System,o=e.require,g.src=i.address,n.appendChild(g)}):t.call(this,i)}})}();var oe=/^(\\s*\\/\\*[^\\*]*(\\*(?!\\/)[^\\*]*)*\\*\\/|\\s*\\/\\/[^\\n]*|\\s*\"[^\"]+\"\\s*;?|\\s*'[^']+'\\s*;?)*\\s*/;!function(){function t(e,r,n){if(n[e.groupIndex]=n[e.groupIndex]||[],U.call(n[e.groupIndex],e)==-1){n[e.groupIndex].push(e);for(var a=0,o=e.normalizedDeps.length;a<o;a++){var i=e.normalizedDeps[a],s=r.defined[i];if(s&&!s.evaluated){var l=e.groupIndex+(s.declarative!=e.declarative);if(null===s.groupIndex||s.groupIndex<l){if(null!==s.groupIndex&&(n[s.groupIndex].splice(U.call(n[s.groupIndex],s),1),0==n[s.groupIndex].length))throw new Error(\"Mixed dependency cycle detected\");s.groupIndex=l}t(s,r,n)}}}}function n(e,r,n){if(!r.module){r.groupIndex=0;var a=[];t(r,n,a);for(var o=!!r.declarative==a.length%2,i=a.length-1;i>=0;i--){for(var s=a[i],l=0;l<s.length;l++){var d=s[l];o?u(d,n):c(d,n)}o=!o}}}function o(){}function l(e,t){return t[e]||(t[e]={name:e,dependencies:[],exports:new o,importers:[]})}function u(t,r){if(!t.module){var n=r._loader.moduleRecords,a=t.module=l(t.name,n),o=t.module.exports,i=t.declare.call(e,function(e,t){if(a.locked=!0,\"object\"==typeof e)for(var r in e)o[r]=e[r];else o[e]=t;for(var n=0,i=a.importers.length;n<i;n++){var s=a.importers[n];if(!s.locked){var l=U.call(s.dependencies,a),u=s.setters[l];u&&u(o)}}return a.locked=!1,t},{id:t.name});if(\"function\"==typeof i&&(i={setters:[],execute:i}),i=i||{setters:[],execute:function(){}},a.setters=i.setters,a.execute=i.execute,!a.setters||!a.execute)throw new TypeError(\"Invalid System.register form for \"+t.name);for(var s=0,d=t.normalizedDeps.length;s<d;s++){var c,f=t.normalizedDeps[s],m=r.defined[f],p=n[f];p?c=p.exports:m&&!m.declarative?c=m.esModule:m?(u(m,r),p=m.module,c=p.exports):c=r.get(f),p&&p.importers?(p.importers.push(a),a.dependencies.push(p)):a.dependencies.push(null);for(var h=t.originalIndices[s],g=0,v=h.length;g<v;++g){var b=h[g];a.setters[b]&&a.setters[b](c)}}}}function d(e,t){var r,n=t.defined[e];if(n)n.declarative?f(e,n,[],t):n.evaluated||c(n,t),r=n.module.exports;else if(r=t.get(e),!r)throw new Error(\"Unable to load dependency \"+e+\".\");return(!n||n.declarative)&&r&&r.__useDefault?r.default:r}function c(t,n){if(!t.module){var a={},o=t.module={exports:a,id:t.name};if(!t.executingRequire)for(var i=0,s=t.normalizedDeps.length;i<s;i++){var l=t.normalizedDeps[i],u=n.defined[l];u&&c(u,n)}t.evaluated=!0;var f=t.execute.call(e,function(e){for(var r=0,a=t.deps.length;r<a;r++)if(t.deps[r]==e)return d(t.normalizedDeps[r],n);var o=n.normalizeSync(e,t.name);if(U.call(t.normalizedDeps,o)!=-1)return d(o,n);throw new Error(\"Module \"+e+\" not declared as a dependency of \"+t.name)},a,o);void 0!==f&&(o.exports=f),a=o.exports,a&&(a.__esModule||a instanceof r)?t.esModule=n.newModule(a):t.esmExports&&a!==e?t.esModule=n.newModule(p(a)):t.esModule=n.newModule({default:a})}}function f(t,r,n,a){if(r&&!r.evaluated&&r.declarative){n.push(t);for(var o=0,i=r.normalizedDeps.length;o<i;o++){var s=r.normalizedDeps[o];U.call(n,s)==-1&&(a.defined[s]?f(s,a.defined[s],n,a):a.get(s))}r.evaluated||(r.evaluated=!0,r.module.execute.call(e))}}a.prototype.register=function(e,t,r){if(\"string\"!=typeof e&&(r=t,t=e,e=null),\"boolean\"==typeof r)return this.registerDynamic.apply(this,arguments);var n=M();n.name=e&&(this.decanonicalize||this.normalize).call(this,e),n.declarative=!0,n.deps=t,n.declare=r,this.pushRegister_({amd:!1,entry:n})},a.prototype.registerDynamic=function(e,t,r,n){\"string\"!=typeof e&&(n=r,r=t,t=e,e=null);var a=M();a.name=e&&(this.decanonicalize||this.normalize).call(this,e),a.deps=t,a.execute=n,a.executingRequire=r,this.pushRegister_({amd:!1,entry:a})},i(\"reduceRegister_\",function(){return function(e,t){if(t){var r=t.entry,n=e&&e.metadata;if(r.name&&(r.name in this.defined||(this.defined[r.name]=r),n&&(n.bundle=!0)),!r.name||e&&!n.entry&&r.name==e.name){if(!n)throw new TypeError(\"Invalid System.register call. Anonymous System.register calls can only be made by modules loaded by SystemJS.import and not via script tags.\");if(n.entry)throw\"register\"==n.format?new Error(\"Multiple anonymous System.register calls in module \"+e.name+\". If loading a bundle, ensure all the System.register calls are named.\"):new Error(\"Module \"+e.name+\" interpreted as \"+n.format+\" module format, but called System.register.\");n.format||(n.format=\"register\"),n.entry=r}}}}),s(function(e){return function(){e.call(this),this.defined={},this._loader.moduleRecords={}}}),D(o,\"toString\",{value:function(){return\"Module\"}}),i(\"delete\",function(e){return function(t){return delete this._loader.moduleRecords[t],delete this.defined[t],e.call(this,t)}}),i(\"fetch\",function(e){return function(t){return this.defined[t.name]?(t.metadata.format=\"defined\",\"\"):(t.metadata.deps=t.metadata.deps||[],e.call(this,t))}}),i(\"translate\",function(e){return function(t){return t.metadata.deps=t.metadata.deps||[],Promise.resolve(e.apply(this,arguments)).then(function(e){return(\"register\"==t.metadata.format||!t.metadata.format&&O(t.source))&&(t.metadata.format=\"register\"),e})}}),i(\"load\",function(e){return function(t){var r=this,a=r.defined[t];return!a||a.deps.length?e.apply(this,arguments):(a.originalIndices=a.normalizedDeps=[],n(t,a,r),f(t,a,[],r),a.esModule||(a.esModule=r.newModule(a.module.exports)),r.trace||(r.defined[t]=void 0),r.set(t,a.esModule),Promise.resolve())}}),i(\"instantiate\",function(e){return function(t){\"detect\"==t.metadata.format&&(t.metadata.format=void 0),e.call(this,t);var r,a=this;if(a.defined[t.name])r=a.defined[t.name],r.declarative||(r.deps=r.deps.concat(t.metadata.deps)),r.deps=r.deps.concat(t.metadata.deps);else if(t.metadata.entry)r=t.metadata.entry,r.deps=r.deps.concat(t.metadata.deps);else if(!(a.builder&&t.metadata.bundle||\"register\"!=t.metadata.format&&\"esm\"!=t.metadata.format&&\"es6\"!=t.metadata.format)){if(\"undefined\"!=typeof ee&&ee.call(a,t),!t.metadata.entry&&!t.metadata.bundle)throw new Error(t.name+\" detected as \"+t.metadata.format+\" but didn't execute.\");r=t.metadata.entry,r&&t.metadata.deps&&(r.deps=r.deps.concat(t.metadata.deps))}r||(r=M(),r.deps=t.metadata.deps,r.execute=function(){}),a.defined[t.name]=r;var o=m(r.deps);r.deps=o.names,r.originalIndices=o.indices,r.name=t.name,r.esmExports=t.metadata.esmExports!==!1;for(var i=[],s=0,l=r.deps.length;s<l;s++)i.push(Promise.resolve(a.normalize(r.deps[s],t.name)));return Promise.all(i).then(function(e){return r.normalizedDeps=e,{deps:r.deps,execute:function(){return n(t.name,r,a),f(t.name,r,[],a),r.esModule||(r.esModule=a.newModule(r.module.exports)),a.trace||(a.defined[t.name]=void 0),r.esModule}}})}})}(),function(){var r=/(^\\s*|[}\\);\\n]\\s*)(import\\s*(['\"]|(\\*\\s+as\\s+)?[^\"'\\(\\)\\n;]+\\s*from\\s*['\"]|\\{)|export\\s+\\*\\s+from\\s+[\"']|export\\s*(\\{|default|function|class|var|const|let|async\\s+function))/,n=/\\$traceurRuntime\\s*\\./,a=/babelHelpers\\s*\\./;i(\"translate\",function(o){return function(i){var s=this,l=arguments;return o.apply(s,l).then(function(o){if(\"esm\"==i.metadata.format||\"es6\"==i.metadata.format||!i.metadata.format&&o.match(r)){if(\"es6\"==i.metadata.format&&w.call(s,\"Module \"+i.name+' has metadata setting its format to \"es6\", which is deprecated.\\nThis should be updated to \"esm\".'),i.metadata.format=\"esm\",i.metadata.deps){for(var u=\"\",d=0;d<i.metadata.deps.length;d++)u+='import \"'+i.metadata.deps[d]+'\"; ';i.source=u+o}if(s.transpiler===!1){if(s.builder)return o;throw new TypeError(\"Unable to dynamically transpile ES module as SystemJS.transpiler set to false.\")}return s._loader.loadedTranspiler=s._loader.loadedTranspiler||!1,s.pluginLoader&&(s.pluginLoader._loader.loadedTranspiler=s._loader.loadedTranspiler||!1),(s._loader.transpilerPromise||(s._loader.transpilerPromise=Promise.resolve(e[\"typescript\"==s.transpiler?\"ts\":s.transpiler]||(s.pluginLoader||s).import(s.transpiler)))).then(function(e){return s._loader.loadedTranspilerRuntime=!0,e.translate?e==i.metadata.loaderModule?i.source:(\"string\"==typeof i.metadata.sourceMap&&(i.metadata.sourceMap=JSON.parse(i.metadata.sourceMap)),Promise.resolve(e.translate.apply(s,l)).then(function(e){var t=i.metadata.sourceMap;if(t&&\"object\"==typeof t){var r=i.address.split(\"!\")[0];t.file&&t.file!=i.address||(t.file=r+\"!transpiled\"),(!t.sources||t.sources.length<=1&&(!t.sources[0]||t.sources[0]==i.address))&&(t.sources=[r])}return\"esm\"==i.metadata.format&&!s.builder&&O(e)&&(i.metadata.format=\"register\"),e})):(s.builder&&(i.metadata.originalSource=i.source),W.call(s,i).then(function(e){return i.metadata.sourceMap=void 0,e}))},function(e){throw t(e,\"Unable to load transpiler to transpile \"+i.name)})}if(s.transpiler===!1)return o;if(s._loader.loadedTranspiler!==!1||\"traceur\"!=s.transpiler&&\"typescript\"!=s.transpiler&&\"babel\"!=s.transpiler||i.name!=s.normalizeSync(s.transpiler)||(o.length>100&&!i.metadata.format&&(i.metadata.format=\"global\",\"traceur\"===s.transpiler&&(i.metadata.exports=\"traceur\"),\"typescript\"===s.transpiler&&(i.metadata.exports=\"ts\")),s._loader.loadedTranspiler=!0),s._loader.loadedTranspilerRuntime===!1&&(i.name!=s.normalizeSync(\"traceur-runtime\")&&i.name!=s.normalizeSync(\"babel/external-helpers*\")||(o.length>100&&(i.metadata.format=i.metadata.format||\"global\"),s._loader.loadedTranspilerRuntime=!0)),(\"register\"==i.metadata.format||i.metadata.bundle)&&s._loader.loadedTranspilerRuntime!==!0){if(\"traceur\"==s.transpiler&&!e.$traceurRuntime&&i.source.match(n))return s._loader.loadedTranspilerRuntime=s._loader.loadedTranspilerRuntime||!1,s.import(\"traceur-runtime\").then(function(){return o});if(\"babel\"==s.transpiler&&!e.babelHelpers&&i.source.match(a))return s._loader.loadedTranspilerRuntime=s._loader.loadedTranspilerRuntime||!1,s.import(\"babel/external-helpers\").then(function(){return o})}return o})}})}();var ie=\"undefined\"!=typeof self?\"self\":\"global\";i(\"fetch\",function(e){return function(t){return t.metadata.exports&&!t.metadata.format&&(t.metadata.format=\"global\"),e.call(this,t)}}),i(\"instantiate\",function(e){return function(t){var r=this;if(t.metadata.format||(t.metadata.format=\"global\"),\"global\"==t.metadata.format&&!t.metadata.entry){var n=M();t.metadata.entry=n,n.deps=[];for(var a in t.metadata.globals){var o=t.metadata.globals[a];o&&n.deps.push(o)}n.execute=function(e,n,a){var o;if(t.metadata.globals){o={};for(var i in t.metadata.globals)t.metadata.globals[i]&&(o[i]=e(t.metadata.globals[i]))}var s=t.metadata.exports;s&&(t.source+=\"\\n\"+ie+'[\"'+s+'\"] = '+s+\";\");var l=r.get(\"@@global-helpers\").prepareGlobal(a.id,s,o,!!t.metadata.encapsulateGlobal);return ee.call(r,t),l()}}return e.call(this,t)}}),i(\"reduceRegister_\",function(e){return function(t,r){if(r||!t.metadata.exports&&(!A||\"global\"!=t.metadata.format))return e.call(this,t,r);t.metadata.format=\"global\";var n=t.metadata.entry=M();n.deps=t.metadata.deps;var a=R(t.metadata.exports);n.execute=function(){return a}}}),s(function(t){return function(){function r(t){if(Object.keys)Object.keys(e).forEach(t);else for(var r in e)i.call(e,r)&&t(r)}function n(t){r(function(r){if(U.call(s,r)==-1){try{var n=e[r]}catch(e){s.push(r)}t(r,n)}})}var a=this;t.call(a);var o,i=Object.prototype.hasOwnProperty,s=[\"_g\",\"sessionStorage\",\"localStorage\",\"clipboardData\",\"frames\",\"frameElement\",\"external\",\"mozAnimationStartTime\",\"webkitStorageInfo\",\"webkitIndexedDB\",\"mozInnerScreenY\",\"mozInnerScreenX\"];a.set(\"@@global-helpers\",a.newModule({prepareGlobal:function(t,r,a,i){var s=e.define;e.define=void 0;var l;if(a){l={};for(var u in a)l[u]=e[u],e[u]=a[u]}return r||(o={},n(function(e,t){o[e]=t})),function(){var t,a=r?R(r):{},u=!!r;if(r&&!i||n(function(n,s){o[n]!==s&&\"undefined\"!=typeof s&&(i&&(e[n]=void 0),r||(a[n]=s,\"undefined\"!=typeof t?u||t===s||(u=!0):t=s))}),a=u?a:t,l)for(var d in l)e[d]=l[d];return e.define=s,a}}}))}}),function(){function t(e){function t(e,t){for(var r=0;r<e.length;r++)if(e[r][0]<t.index&&e[r][1]>t.index)return!0;return!1}n.lastIndex=a.lastIndex=o.lastIndex=0;var r,i=[],s=[],l=[];if(e.length/e.split(\"\\n\").length<200){for(;r=o.exec(e);)s.push([r.index,r.index+r[0].length]);for(;r=a.exec(e);)t(s,r)||l.push([r.index+r[1].length,r.index+r[0].length-1])}for(;r=n.exec(e);)if(!t(s,r)&&!t(l,r)){var u=r[1].substr(1,r[1].length-2);if(u.match(/\"|'/))continue;\"/\"==u[u.length-1]&&(u=u.substr(0,u.length-1)),i.push(u)}return i}var r=/(?:^\\uFEFF?|[^$_a-zA-Z\\xA0-\\uFFFF.])(exports\\s*(\\[['\"]|\\.)|module(\\.exports|\\['exports'\\]|\\[\"exports\"\\])\\s*(\\[['\"]|[=,\\.]))/,n=/(?:^\\uFEFF?|[^$_a-zA-Z\\xA0-\\uFFFF.\"'])require\\s*\\(\\s*(\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*')\\s*\\)/g,a=/(^|[^\\\\])(\\/\\*([\\s\\S]*?)\\*\\/|([^:]|^)\\/\\/(.*)$)/gm,o=/(\"[^\"\\\\\\n\\r]*(\\\\.[^\"\\\\\\n\\r]*)*\"|'[^'\\\\\\n\\r]*(\\\\.[^'\\\\\\n\\r]*)*')/g,s=/^\\#\\!.*/;i(\"instantiate\",function(a){return function(o){var i=this;if(o.metadata.format||(r.lastIndex=0,n.lastIndex=0,(n.exec(o.source)||r.exec(o.source))&&(o.metadata.format=\"cjs\")),\"cjs\"==o.metadata.format){var l=o.metadata.deps,u=o.metadata.cjsRequireDetection===!1?[]:t(o.source);for(var d in o.metadata.globals)o.metadata.globals[d]&&u.push(o.metadata.globals[d]);var c=M();o.metadata.entry=c,c.deps=u,c.executingRequire=!0,c.execute=function(t,r,n){function a(e){return\"/\"==e[e.length-1]&&(e=e.substr(0,e.length-1)),t.apply(this,arguments)}if(a.resolve=function(e){return i.get(\"@@cjs-helpers\").requireResolve(e,n.id)},n.paths=[],n.require=t,!o.metadata.cjsDeferDepsExecute)for(var u=0;u<l.length;u++)a(l[u]);var d=i.get(\"@@cjs-helpers\").getPathVars(n.id),c={exports:r,args:[a,r,n,d.filename,d.dirname,e,e]},f=\"(function(require, exports, module, __filename, __dirname, global, GLOBAL\";if(o.metadata.globals)for(var m in o.metadata.globals)c.args.push(a(o.metadata.globals[m])),f+=\", \"+m;var p=e.define;e.define=void 0,e.__cjsWrapper=c,o.source=f+\") {\"+o.source.replace(s,\"\")+\"\\n}).apply(__cjsWrapper.exports, __cjsWrapper.args);\",ee.call(i,o),e.__cjsWrapper=void 0,e.define=p}}return a.call(i,o)}})}(),s(function(e){return function(){function t(e){return\"file:///\"==e.substr(0,8)?e.substr(7+!!q):n&&e.substr(0,n.length)==n?e.substr(n.length):e}var r=this;if(e.call(r),\"undefined\"!=typeof window&&\"undefined\"!=typeof document&&window.location)var n=location.protocol+\"//\"+location.hostname+(location.port?\":\"+location.port:\"\");r.set(\"@@cjs-helpers\",r.newModule({requireResolve:function(e,n){return t(r.normalizeSync(e,n))},getPathVars:function(e){var r,n=e.lastIndexOf(\"!\");r=n!=-1?e.substr(0,n):e;var a=r.split(\"/\");return a.pop(),a=a.join(\"/\"),{filename:t(r),dirname:t(a)}}}))}}),i(\"fetch\",function(t){return function(r){return r.metadata.scriptLoad&&F&&(e.define=this.amdDefine),t.call(this,r)}}),s(function(t){return function(){function r(e,t){e=e.replace(s,\"\");var r=e.match(d),n=(r[1].split(\",\")[t]||\"require\").replace(c,\"\"),a=f[n]||(f[n]=new RegExp(l+n+u,\"g\"));a.lastIndex=0;for(var o,i=[];o=a.exec(e);)i.push(o[2]||o[3]);return i}function n(e,t,r,a){if(\"object\"==typeof e&&!(e instanceof Array))return n.apply(null,Array.prototype.splice.call(arguments,1,arguments.length-1));if(\"string\"==typeof e&&\"function\"==typeof t&&(e=[e]),!(e instanceof Array)){if(\"string\"==typeof e){var i=o.defaultJSExtensions&&\".js\"!=e.substr(e.length-3,3),s=o.decanonicalize(e,a);i&&\".js\"==s.substr(s.length-3,3)&&(s=s.substr(0,s.length-3));var l=o.get(s);if(!l)throw new Error('Module not already loaded loading \"'+e+'\" as '+s+(a?' from \"'+a+'\".':\".\"));return l.__useDefault?l.default:l}throw new TypeError(\"Invalid require\")}for(var u=[],d=0;d<e.length;d++)u.push(o.import(e[d],a));Promise.all(u).then(function(e){t&&t.apply(null,e)},r)}function a(t,a,i){function s(t,r,s){function c(e,r,a){return\"string\"==typeof e&&\"function\"!=typeof r?t(e):n.call(o,e,r,a,s.id)}for(var f=[],m=0;m<a.length;m++)f.push(t(a[m]));s.uri=s.id,s.config=function(){},d!=-1&&f.splice(d,0,s),u!=-1&&f.splice(u,0,r),l!=-1&&(c.toUrl=function(e){var t=o.defaultJSExtensions&&\".js\"!=e.substr(e.length-3,3),r=o.decanonicalize(e,s.id);return t&&\".js\"==r.substr(r.length-3,3)&&(r=r.substr(0,r.length-3)),r},f.splice(l,0,c));var p=e.require;e.require=n;var h=i.apply(u==-1?e:r,f);if(e.require=p,\"undefined\"==typeof h&&s&&(h=s.exports),\"undefined\"!=typeof h)return h}\"string\"!=typeof t&&(i=a,a=t,t=null),a instanceof Array||(i=a,a=[\"require\",\"exports\",\"module\"].splice(0,i.length)),\"function\"!=typeof i&&(i=function(e){return function(){return e}}(i)),void 0===a[a.length-1]&&a.pop();var l,u,d;(l=U.call(a,\"require\"))!=-1&&(a.splice(l,1),t||(a=a.concat(r(i.toString(),l)))),(u=U.call(a,\"exports\"))!=-1&&a.splice(u,1),(d=U.call(a,\"module\"))!=-1&&a.splice(d,1);var c=M();c.name=t&&(o.decanonicalize||o.normalize).call(o,t),c.deps=a,c.execute=s,o.pushRegister_({amd:!0,entry:c})}var o=this;t.call(this);var s=/(\\/\\*([\\s\\S]*?)\\*\\/|([^:]|^)\\/\\/(.*)$)/gm,l=\"(?:^|[^$_a-zA-Z\\\\xA0-\\\\uFFFF.])\",u=\"\\\\s*\\\\(\\\\s*(\\\"([^\\\"]+)\\\"|'([^']+)')\\\\s*\\\\)\",d=/\\(([^\\)]*)\\)/,c=/^\\s+|\\s+$/g,f={};a.amd={},i(\"reduceRegister_\",function(e){return function(t,r){if(!r||!r.amd)return e.call(this,t,r);var n=t&&t.metadata,a=r.entry;if(n)if(n.format&&\"detect\"!=n.format){if(!a.name&&\"amd\"!=n.format)throw new Error(\"AMD define called while executing \"+n.format+\" module \"+t.name)}else n.format=\"amd\";if(a.name)n&&(n.entry||n.bundle?n.entry&&n.entry.name&&n.entry.name!=t.name&&(n.entry=void 0):n.entry=a,n.bundle=!0),a.name in this.defined||(this.defined[a.name]=a);else{if(!n)throw new TypeError(\"Unexpected anonymous AMD define.\");if(n.entry&&!n.entry.name)throw new Error(\"Multiple anonymous defines in module \"+t.name);n.entry=a}}}),o.amdDefine=a,o.amdRequire=n}}),function(){var t=/(?:^\\uFEFF?|[^$_a-zA-Z\\xA0-\\uFFFF.])define\\s*\\(\\s*(\"[^\"]+\"\\s*,\\s*|'[^']+'\\s*,\\s*)?\\s*(\\[(\\s*((\"[^\"]+\"|'[^']+')\\s*,|\\/\\/.*\\r?\\n|\\/\\*(.|\\s)*?\\*\\/))*(\\s*(\"[^\"]+\"|'[^']+')\\s*,?)?(\\s*(\\/\\/.*\\r?\\n|\\/\\*(.|\\s)*?\\*\\/))*\\s*\\]|function\\s*|{|[_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*\\))/;i(\"instantiate\",function(r){return function(n){var a=this;if(\"amd\"==n.metadata.format||!n.metadata.format&&n.source.match(t))if(n.metadata.format=\"amd\",a.builder||a.execute===!1)n.metadata.execute=function(){return n.metadata.builderExecute.apply(this,arguments)};else{var o=e.define;e.define=this.amdDefine;try{ee.call(a,n)}finally{e.define=o}if(!n.metadata.entry&&!n.metadata.bundle)throw new TypeError(\"AMD module \"+n.name+\" did not define\")}return r.call(a,n)}})}(),function(){function e(e,t){if(t){var r;if(e.pluginFirst){if((r=t.lastIndexOf(\"!\"))!=-1)return t.substr(r+1)}else if((r=t.indexOf(\"!\"))!=-1)return t.substr(0,r);return t}}function t(e,t){var r,n,a=t.lastIndexOf(\"!\");if(a!=-1)return e.pluginFirst?(r=t.substr(a+1),n=t.substr(0,a)):(r=t.substr(0,a),n=t.substr(a+1)||r.substr(r.lastIndexOf(\".\")+1)),{argument:r,plugin:n}}function r(e,t,r,n){return n&&\".js\"==t.substr(t.length-3,3)&&(t=t.substr(0,t.length-3)),e.pluginFirst?r+\"!\"+t:t+\"!\"+r}function n(e,t){return e.defaultJSExtensions&&\".js\"!=t.substr(t.length-3,3)}function a(a){return function(o,i,s){var l=this,u=t(l,o);if(i=e(this,i),!u)return a.call(this,o,i,s);var d=l.normalizeSync(u.argument,i,!0),c=l.normalizeSync(u.plugin,i,!0);return r(l,d,c,n(l,u.argument))}}i(\"decanonicalize\",a),i(\"normalizeSync\",a),i(\"normalize\",function(a){return function(o,i,s){var l=this;i=e(this,i);var u=t(l,o);return u?Promise.all([l.normalize(u.argument,i,!0),l.normalize(u.plugin,i,!1)]).then(function(e){return r(l,e[0],e[1],n(l,u.argument))}):a.call(l,o,i,s)}}),i(\"locate\",function(e){return function(t){var r,n=this,a=t.name;return n.pluginFirst?(r=a.indexOf(\"!\"))!=-1&&(t.metadata.loader=a.substr(0,r),t.name=a.substr(r+1)):(r=a.lastIndexOf(\"!\"))!=-1&&(t.metadata.loader=a.substr(r+1),t.name=a.substr(0,r)),e.call(n,t).then(function(e){return r==-1&&t.metadata.loader?(n.pluginLoader||n).normalize(t.metadata.loader,t.name).then(function(r){return t.metadata.loader=r,e}):e}).then(function(e){var r=t.metadata.loader;if(!r)return e;if(t.name==r)throw new Error(\"Plugin \"+r+\" cannot load itself, make sure it is excluded from any wildcard meta configuration via a custom loader: false rule.\");if(n.defined&&n.defined[a])return e;var o=n.pluginLoader||n;return o.import(r).then(function(r){return t.metadata.loaderModule=r,t.address=e,r.locate?r.locate.call(n,t):e})})}}),i(\"fetch\",function(e){return function(t){var r=this;return t.metadata.loaderModule&&t.metadata.loaderModule.fetch&&\"defined\"!=t.metadata.format?(t.metadata.scriptLoad=!1,t.metadata.loaderModule.fetch.call(r,t,function(t){return e.call(r,t)})):e.call(r,t)}}),i(\"translate\",function(e){return function(t){var r=this,n=arguments;return t.metadata.loaderModule&&t.metadata.loaderModule.translate&&\"defined\"!=t.metadata.format?Promise.resolve(t.metadata.loaderModule.translate.apply(r,n)).then(function(a){var o=t.metadata.sourceMap;if(o){if(\"object\"!=typeof o)throw new Error(\"load.metadata.sourceMap must be set to an object.\");var i=t.address.split(\"!\")[0];o.file&&o.file!=t.address||(o.file=i+\"!transpiled\"),(!o.sources||o.sources.length<=1&&(!o.sources[0]||o.sources[0]==t.address))&&(o.sources=[i])}return\"string\"==typeof a?t.source=a:w.call(this,\"Plugin \"+t.metadata.loader+\" should return the source in translate, instead of setting load.source directly. This support will be deprecated.\"),e.apply(r,n)}):e.apply(r,n)}}),i(\"instantiate\",function(e){return function(t){var r=this,n=!1;return t.metadata.loaderModule&&t.metadata.loaderModule.instantiate&&!r.builder&&\"defined\"!=t.metadata.format?Promise.resolve(t.metadata.loaderModule.instantiate.call(r,t,function(t){if(n)throw new Error(\"Instantiate must only be called once.\");return n=!0,e.call(r,t)})).then(function(a){return n?a:(t.metadata.entry=M(),t.metadata.entry.execute=function(){return a},t.metadata.entry.deps=t.metadata.deps,t.metadata.format=\"defined\",e.call(r,t))}):e.call(r,t)}})}();var se=[\"browser\",\"node\",\"dev\",\"build\",\"production\",\"default\"],le=/#\\{[^\\}]+\\}/;i(\"normalize\",function(e){return function(t,r,n){var a=this;return L.call(a,t,r).then(function(t){return e.call(a,t,r,n)}).then(function(e){return C.call(a,e,r)})}}),function(){i(\"fetch\",function(e){return function(t){var r=t.metadata.alias,n=t.metadata.deps||[];if(r){t.metadata.format=\"defined\";var a=M();return this.defined[t.name]=a,a.declarative=!0,a.deps=n.concat([r]),a.declare=function(e){return{setters:[function(t){for(var r in t)e(r,t[r]);t.__useDefault&&(a.module.exports.__useDefault=!0)}],execute:function(){}}},\"\"}return e.call(this,t)}})}(),function(){function e(e,t,r){for(var n,a=t.split(\".\");a.length>1;)n=a.shift(),e=e[n]=e[n]||{};n=a.shift(),n in e||(e[n]=r)}s(function(e){return function(){this.meta={},e.call(this)}}),i(\"locate\",function(e){return function(t){var r,n=this.meta,a=t.name,o=0;for(var i in n)if(r=i.indexOf(\"*\"),r!==-1&&i.substr(0,r)===a.substr(0,r)&&i.substr(r+1)===a.substr(a.length-i.length+r+1)){var s=i.split(\"/\").length;s>o&&(o=s),v(t.metadata,n[i],o!=s)}return n[a]&&v(t.metadata,n[a]),e.call(this,t)}});var t=/^(\\s*\\/\\*[^\\*]*(\\*(?!\\/)[^\\*]*)*\\*\\/|\\s*\\/\\/[^\\n]*|\\s*\"[^\"]+\"\\s*;?|\\s*'[^']+'\\s*;?)+/,r=/\\/\\*[^\\*]*(\\*(?!\\/)[^\\*]*)*\\*\\/|\\/\\/[^\\n]*|\"[^\"]+\"\\s*;?|'[^']+'\\s*;?/g;i(\"translate\",function(n){return function(a){if(\"defined\"==a.metadata.format)return a.metadata.deps=a.metadata.deps||[],Promise.resolve(a.source);var o=a.source.match(t);if(o)for(var i=o[0].match(r),s=0;s<i.length;s++){var l=i[s],u=l.length,d=l.substr(0,1);if(\";\"==l.substr(u-1,1)&&u--,'\"'==d||\"'\"==d){var c=l.substr(1,l.length-3),f=c.substr(0,c.indexOf(\" \"));if(f){var m=c.substr(f.length+1,c.length-f.length-1);\"[]\"==f.substr(f.length-2,2)?(f=f.substr(0,f.length-2),a.metadata[f]=a.metadata[f]||[],a.metadata[f].push(m)):a.metadata[f]instanceof Array?(w.call(this,\"Module \"+a.name+' contains deprecated \"deps '+m+'\" meta syntax.\\nThis should be updated to \"deps[] '+m+'\" for pushing to array meta.'),a.metadata[f].push(m)):e(a.metadata,f,m)}else a.metadata[c]=!0}}return n.apply(this,arguments)}})}(),function(){s(function(e){return function(){e.call(this),this.bundles={},this._loader.loadedBundles={}}}),i(\"locate\",function(e){return function(t){var r=this,n=!1;if(!(t.name in r.defined))for(var a in r.bundles){for(var o=0;o<r.bundles[a].length;o++){var i=r.bundles[a][o];if(i==t.name){n=!0;break}if(i.indexOf(\"*\")!=-1){var s=i.split(\"*\");if(2!=s.length){r.bundles[a].splice(o--,1);continue}if(t.name.substring(0,s[0].length)==s[0]&&t.name.substr(t.name.length-s[1].length,s[1].length)==s[1]&&t.name.substr(s[0].length,t.name.length-s[1].length-s[0].length).indexOf(\"/\")==-1){n=!0;break}}}if(n)return r.import(a).then(function(){return e.call(r,t)})}return e.call(r,t)}})}(),function(){s(function(e){return function(){e.call(this),this.depCache={}}}),i(\"locate\",function(e){return function(t){var r=this,n=r.depCache[t.name];if(n)for(var a=0;a<n.length;a++)r.import(n[a],t.name);return e.call(r,t)}})}(),X=new a,e.SystemJS=X,X.version=\"0.19.37 Standard\",\"object\"==typeof module&&module.exports&&\"object\"==typeof exports&&(module.exports=X),e.System=X}(\"undefined\"!=typeof self?self:global)}var t=\"undefined\"==typeof Promise;if(\"undefined\"!=typeof document){var r=document.getElementsByTagName(\"script\");if($__curScript=r[r.length-1],($__curScript.defer||$__curScript.async)&&($__curScript=document.currentScript),t){var n=$__curScript.src,a=n.substr(0,n.lastIndexOf(\"/\")+1);window.systemJSBootstrap=e,document.write('<script type=\"text/javascript\" src=\"'+a+'system-polyfills.js\"></script>')}else e()}else if(\"undefined\"!=typeof importScripts){var a=\"\";try{throw new Error(\"_\")}catch(e){e.stack.replace(/(?:at|@).*(http.+):[\\d]+:[\\d]+/,function(e,t){$__curScript={src:t},a=t.replace(/\\/[^\\/]*$/,\"/\")})}t&&importScripts(a+\"system-polyfills.js\"),e()}else $__curScript=\"undefined\"!=typeof __filename?{src:__filename}:null,e()}();\n//# sourceMappingURL=system.js.map\n"
  },
  {
    "path": "src/systemJSConfig.js",
    "content": "if(typeof _watchers === \"undefined\") {\n  console.warn(\"Please run `npm run build` in order to bundle the watchers for the browser.\")\n}\n\nSystemJS.config({\n  baseURL: \"node_modules/\",\n  map: {\n    fs: \"@empty\",\n    path: \"@empty\",\n    glob: \"@empty\",\n    mkdirp: \"@empty\"\n  },\n  meta: {\"/build/src/bootstrap.js\": {deps: (typeof _watchers === \"undefined\") ? [] : _watchers}},\n  packages: {\n    \"/build\": {defaultExtension: \"js\"},\n    \"node-uuid\": {main: \"index.js\"},\n    \"falafel\": {main: \"index.js\"},\n    \"acorn\": {main: \"dist/acorn.js\"},\n    \"isarray\": {main: \"index.js\"},\n    \"object-keys\": {main: \"index.js\"},\n    \"foreach\": {main: \"index.js\"},\n    \"commonmark\": {main: \"dist/commonmark.js\"},\n    \"chevrotain\": {main: \"lib/chevrotain.js\"},\n    \"setimmediate\": {main: \"setImmediate.js\"},\n    \"javascript-natural-sort\": {main: \"naturalSort.js\"}\n  }\n});\n"
  },
  {
    "path": "src/types.d.ts",
    "content": "declare module \"falafel\";\ndeclare module \"setimmediate\";\n\ndeclare module \"javascript-natural-sort\" {\n  export = naturalSort;\n  function naturalSort(a:any, b:any):number;\n}\n\n\n// Via <http://stackoverflow.com/a/34110229>\n\n// Class\ninterface Path2D {\n    addPath?(path: Path2D, transform?: SVGMatrix):void;\n    closePath(): void;\n    moveTo(x: number, y: number): void;\n    lineTo(x: number, y: number): void;\n    bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void;\n    quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void;\n    arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void;\n    arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void;\n    ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void;\n    rect(x: number, y: number, width: number, height: number): void;\n}\n\n// Constructor\ninterface Path2DConstructor {\n    new (): Path2D;\n    new (d: string): Path2D;\n    new (path: Path2D, fillRule?: string): Path2D;\n    prototype: Path2D;\n}\n//declare var Path2D: Path2DConstructor;\n\n// Extend Window\ninterface Window { Path2D: Path2DConstructor; }\n\n// Extend CanvasRenderingContext2D\ninterface CanvasRenderingContext2D {\n    fill(path: Path2D): void;\n    stroke(path: Path2D): void;\n    clip(path: Path2D, fillRule?: string): void;\n}\n"
  },
  {
    "path": "src/watchers/canvas.ts",
    "content": "import {Watcher, RawMap, RawValue, RawEAV, RawEAVC, maybeIntern} from \"./watcher\";\nimport {HTMLWatcher} from \"./html\";\nimport {v4 as uuid} from \"uuid\";\n\nfunction asValue(value:RawValue|undefined) {\n  if(typeof value == \"string\") {\n    if(value == \"true\") return true;\n    if(value == \"false\") return false;\n  }\n  return value;\n}\n\nfunction ixComparator(idMap:{[key:string]:{ix:number}}) {\n  return (a:string, b:string) => {\n    return idMap[a].ix - idMap[b].ix;\n  }\n}\n\nlet operationFields:{[type:string]: string[]} = {\n  moveTo: [\"x\", \"y\"],\n  lineTo: [\"x\", \"y\"],\n  bezierQuadraticCurveTo: [\"cp1x\", \"cp1y\", \"cp2x\", \"cp2y\", \"x\", \"y\"],\n  quadraticCurveTo: [\"cpx\", \"cpy\", \"x\", \"y\"],\n  arc: [\"x\", \"y\", \"radius\", \"startAngle\", \"endAngle\", \"anticlockwise\"],\n  arcTo: [\"x1\", \"y1\", \"x2\", \"y2\", \"radius\"],\n  ellipse: [\"x\", \"y\", \"radiusX\", \"radiusY\", \"rotation\", \"startAngle\", \"endAngle\", \"anticlockwise\"],\n  rect: [\"x\", \"y\", \"width\", \"height\"],\n  closePath: []\n};\n\nlet defaultOperationFieldValue:{[field:string]: any} = {\n  rotation: 0,\n  startAngle: 0,\n  endAngle: 2 * Math.PI,\n  anticlockwise: false\n};\n\nfunction isOperationType(val:RawValue): val is OperationType {\n  return !!operationFields[val];\n}\n\nconst EMPTY = {};\n\nexport interface Canvas extends HTMLCanvasElement { __element?: RawValue }\nexport type OperationType = keyof Path2D;\nexport interface Operation {type: OperationType, args:any, paths:RawValue[]};\n// {fillStyle: \"#000000\", strokeStyle: \"#000000\", lineWidth: 1, lineCap: \"butt\", lineJoin: \"miter\"}\nexport interface PathStyle {[key:string]: RawValue|undefined, fillStyle?:string, strokeStyle?:string, lineWidth?:number, lineCap?:string, lineJoin?: string };\n\nexport class CanvasWatcher extends Watcher {\n  html:HTMLWatcher;\n  canvases:RawMap<RawValue[]|undefined> = {};\n  paths:RawMap<RawValue[]|undefined> = {};\n  operations:RawMap<Operation|undefined> = {};\n  canvasPaths:RawMap<RawValue[]|undefined> = {};\n  pathToCanvases:RawMap<RawValue[]|undefined> = {};\n  pathStyles:RawMap<PathStyle|undefined> = {};\n  pathCache:RawMap<Path2D|undefined> = {};\n  dirty:RawMap<boolean|undefined> = {};\n\n  // addCanvas(canvasId:RawValue, instanceId:RawValue) {\n  //   if(this.canvases[id]) throw new Error(`Recreating canvas instance ${maybeIntern(id)}`);\n  //   let elements = this.html.elementToInstances[id];\n  //   // if(!elements || !elements.length) throw new Error(`No matching canvas instance found for ${id}.`);\n  //   if(!elements || !elements.length) return; // @FIXME: Really seems like this is an error case...\n  //   if(elements.length > 1) throw new Error(`Multiple canvas instances found for ${id}.`);\n  //   return this.canvases[id] = this.html.getInstance(elements[0]) as HTMLCanvasElement;\n  // }\n  // clearCanvas(id:RawValue) {\n  //   if(!this.canvases[id]) throw new Error(`Missing canvas instance ${maybeIntern(id)}`);\n  //   this.canvases[id] = undefined;\n  // }\n  // getCanvas(id:RawValue) {\n  //   let canvas = this.canvases[id];\n  //   if(!canvas) throw new Error(`Missing canvas instance ${maybeIntern(id)}`);\n  //   return canvas;\n  // }\n\n  addCanvasInstance(canvasId:RawValue, instanceId:RawValue) {\n    let instances = this.canvases[canvasId] = this.canvases[canvasId] || [];\n    instances.push(instanceId);\n  }\n  clearCanvasInstance(canvasId:RawValue, instanceId:RawValue) {\n    let instances = this.canvases[canvasId];\n    if(!instances) return; // @FIXME: Seems like an error though\n    let ix = instances.indexOf(instanceId);\n    if(ix !== -1) {\n      instances.splice(ix, 1);\n      if(!instances.length) this.canvases[canvasId] = undefined;\n    }\n  }\n  getCanvasInstances(canvasId:RawValue) {\n    let instances = this.canvases[canvasId];\n    if(!instances) throw new Error(`Missing canvas instance(s) for ${maybeIntern(canvasId)}`);\n    return instances;\n  }\n  getCanvasPaths(canvasId:RawValue) {\n    return this.canvasPaths[canvasId];\n  }\n\n  addPath(id:RawValue) {\n    if(this.paths[id]) throw new Error(`Recreating path instance ${maybeIntern(id)}`);\n    this.pathStyles[id] = {};\n    return this.paths[id] = [];\n  }\n  clearPath(id:RawValue) {\n    if(!this.paths[id]) throw new Error(`Missing path instance ${maybeIntern(id)}`);\n    this.pathStyles[id] = undefined;\n    this.paths[id] = undefined;\n  }\n  getPath(id:RawValue) {\n    let path = this.paths[id];\n    if(!path) throw new Error(`Missing path instance ${maybeIntern(id)}`);\n    return path;\n  }\n\n  addOperation(id:RawValue, type:RawValue) {\n    if(this.operations[id]) throw new Error(`Recreating operation instance ${maybeIntern(id)}`);\n    if(!isOperationType(type)) throw new Error(`Invalid operation type ${type}`);\n    return this.operations[id] = {type, args: {}, paths: []};\n  }\n  clearOperation(id:RawValue) {\n    if(!this.operations[id]) throw new Error(`Missing operation instance ${maybeIntern(id)}`);\n    this.operations[id] = undefined;\n  }\n  getOperation(id:RawValue) {\n    let operation = this.operations[id];\n    if(!operation) throw new Error(`Missing operation instance ${maybeIntern(id)}`);\n    return operation;\n  }\n\n  getOperationArgs(operation:Operation) {\n    let {type, args} = operation;\n    let fields:string[] = operationFields[type as string];\n\n    let input = [];\n    let restOptional = false;\n    for(let field of fields) {\n      let value = asValue(args[field]) || defaultOperationFieldValue[field];\n      if(value === undefined) return;\n      input.push(value);\n    }\n    return input;\n  }\n\n  updateCache(dirtyPaths:RawValue[]) {\n    for(let id of dirtyPaths) {\n      if(!this.dirty[id]) continue;\n      let path = this.paths[id];\n      if(!path) continue;\n      let path2d = this.pathCache[id] = new window.Path2D();\n      for(let opId of path) {\n        let operation = this.getOperation(opId);\n        let input = this.getOperationArgs(operation);\n        if(!input) {\n          console.warn(`Skipping incomplete or invalid operation ${maybeIntern(opId)}`, operation.type, operation.args);\n          continue;\n        }\n        if(!path2d[operation.type]) {\n          console.warn(`Skipping unavailable operation type ${operation.type}. Check your browser's Path2D compatibility.`);\n          continue;\n        }\n        (path2d[operation.type] as (...args:any[]) => void)(...input);\n      }\n    }\n  }\n\n  rerender(dirtyPaths:RawValue[]) {\n    let dirtyCanvases:RawMap<boolean|undefined> = {};\n    for(let id of dirtyPaths) {\n      let canvasIds = this.pathToCanvases[id];\n      if(!canvasIds) continue;\n      for(let canvasId of canvasIds) {\n        dirtyCanvases[canvasId] = true;\n      }\n    }\n\n    for(let canvasId of Object.keys(dirtyCanvases)) {\n      let pathIds = this.canvasPaths[canvasId];\n      for(let instanceId of this.getCanvasInstances(canvasId)) {\n        let canvas = this.html.getInstance(instanceId) as Canvas;\n        let ctx = canvas.getContext(\"2d\")!;\n        ctx.clearRect(0, 0, canvas.width, canvas.height);\n        if(!pathIds) continue;\n\n        for(let id of pathIds) {\n          let cached = this.pathCache[id];\n          if(!cached) continue // This thing isn't a path (yet?)\n\n          let style = this.pathStyles[id] || EMPTY as PathStyle;\n          let {fillStyle = \"#000000\", strokeStyle = \"#000000\", lineWidth = 1, lineCap = \"butt\", lineJoin = \"miter\"} = style;\n          ctx.fillStyle = fillStyle;\n          ctx.strokeStyle = strokeStyle;\n          ctx.lineWidth = lineWidth;\n          ctx.lineCap = lineCap;\n          ctx.lineJoin = lineJoin;\n          if(style.strokeStyle) ctx.stroke(cached);\n          if(style.fillStyle || !style.strokeStyle) ctx.fill(cached);\n\n        }\n      }\n    }\n  }\n\n  changed = () => {\n    let dirtyPaths = Object.keys(this.dirty);\n    this.updateCache(dirtyPaths);\n    this.rerender(dirtyPaths);\n    this.dirty = {};\n  }\n\n  setup() {\n    this.html = this.program.attach(\"html\") as HTMLWatcher;\n\n    this.program\n      .bind(\"Canvas roots are html elements.\", ({find}) => {\n        let canvas = find(\"canvas/root\");\n        return [canvas.add({tag: \"html/element\", tagname: \"canvas\"})]\n      })\n      .bind(\"If an ellipse operation specifies a radius, copy it into radiusX and radiusY.\", ({find}) => {\n        let path = find(\"canvas/path\");\n        let operation = path.children;\n        operation.type == \"ellipse\";\n        let {radius} = operation;\n        return [\n          operation.add({radiusX: radius, radiusY: radius})\n        ];\n      })\n\n      // .watch(\"Export canvas roots.\", ({find}) => {\n      //   let canvas = find(\"canvas/root\");\n      //   return [canvas.add(\"tag\", \"canvas/root\")]\n      // })\n      // .asDiffs((diffs) => {\n      //   for(let [e] of diffs.adds) this.addCanvas(e);\n      //   for(let [e] of diffs.removes) this.clearCanvas(e);\n      //   setImmediate(this.changed);\n      // })\n\n      .watch(\"Export canvas instances.\", ({find}) => {\n        let canvas = find(\"canvas/root\");\n        let instance = find(\"html/instance\", {element: canvas});\n        return [canvas.add(\"instance\", instance)]\n      })\n      .asDiffs((diffs) => {\n        for(let [canvas, _, instance] of diffs.adds) this.addCanvasInstance(canvas, instance);\n        for(let [canvas, _, instance] of diffs.removes) this.clearCanvasInstance(canvas, instance);\n        setImmediate(this.changed);\n      })\n\n      .watch(\"Export canvas paths.\", ({find}) => {\n        let path = find(\"canvas/path\");\n        return [path.add(\"tag\", \"canvas/path\")]\n      })\n      .asDiffs((diffs) => {\n        for(let [e] of diffs.adds) {\n          this.addPath(e);\n          this.dirty[e] = true;\n        }\n        for(let [e] of diffs.removes) {\n          this.clearPath(e);\n          this.dirty[e] = true;\n        }\n        setImmediate(this.changed);\n      })\n\n      .watch(\"Export canvas operations.\", ({find}) => {\n        let path = find(\"canvas/path\");\n        let operation = path.children;\n        return [operation.add(\"type\", operation.type)]\n      })\n      .asDiffs((diffs) => {\n        for(let [e, _, type] of diffs.adds) this.addOperation(e, type);\n        for(let [e] of diffs.removes) this.clearOperation(e);\n        setImmediate(this.changed);\n      })\n\n\n      .watch(\"Export paths of canvas.\", ({find, choose, gather, record}) => {\n        let canvas = find(\"canvas/root\");\n        let child = canvas.children;\n        // @FIXME: non-deterministic sort bug :(\n        //let ix = gather(child.sort).per(canvas).sort();\n        let ix = choose(() => child.sort, () => child[\"eve-auto-index\"], () => 1);\n\n        return [record({canvas, child, ix})]\n      })\n      .asObjects<{canvas:RawValue, child:RawValue, ix:number}>((diffs) => {\n        let removeIds = Object.keys(diffs.removes);\n        removeIds.sort(ixComparator(diffs.removes)).reverse();\n        for(let removeId of removeIds) {\n          let {canvas:canvasId, child:childId, ix} = diffs.removes[removeId];\n          let instances = this.canvases[canvasId];\n\n          let paths = this.canvasPaths[canvasId];\n          if(paths) paths.splice(ix - 1, 1);\n          let canvases = this.pathToCanvases[childId] = this.pathToCanvases[childId];\n          if(canvases) {\n            let ix = canvases.indexOf(canvasId);\n            if(ix !== -1) canvases.splice(ix, 1);\n          }\n\n          // @FIXME: need a proper way to indicate dirtyness when an unchanged path is added a canvas.\n          // This hack just marks the path dirty, which will rerender any other canvases containing it o_o\n          this.dirty[childId] = true;\n        }\n        let addIds = Object.keys(diffs.adds);\n        addIds.sort(ixComparator(diffs.adds));\n        for(let addId of addIds) {\n          let {canvas:canvasId, child:childId, ix} = diffs.adds[addId];\n          let paths = this.canvasPaths[canvasId] = this.canvasPaths[canvasId] || [];\n          paths.splice(ix - 1, 0, childId)\n          let canvases = this.pathToCanvases[childId] = this.pathToCanvases[childId] || [];\n          canvases.push(canvasId);\n\n          // @FIXME: need a proper way to indicate dirtyness when an unchanged path is added a canvas.\n          // This hack just marks the path dirty, which will rerender any other canvases containing it o_o\n          this.dirty[childId] = true;\n        }\n        setImmediate(this.changed);\n      })\n\n      .watch(\"Export operations of paths.\", ({find, choose, gather, record}) => {\n        let path = find(\"canvas/path\");\n        let child = path.children;\n        // @FIXME: non-deterministic sort bug :(\n        //let ix = gather(child.sort).per(path).sort();\n        let ix = choose(() => child.sort, () => child[\"eve-auto-index\"], () => 1);\n        return [record({path, child, ix})]\n      })\n      .asObjects<{path:RawValue, child:RawValue, ix:number}>((diffs) => {\n        let removeIds = Object.keys(diffs.removes);\n        removeIds.sort(ixComparator(diffs.removes)).reverse();\n        for(let removeId of removeIds) {\n          let {path:pathId, child:childId, ix} = diffs.removes[removeId];\n          let path = this.paths[pathId];\n          if(path) path.splice(ix - 1, 1);\n          let operation = this.operations[childId];\n          if(operation) {\n            let ix = operation.paths.indexOf(pathId);\n            if(ix !== -1) operation.paths.splice(ix, 1);\n          }\n\n          this.dirty[pathId] = true;\n        }\n\n        let addIds = Object.keys(diffs.adds);\n        addIds.sort(ixComparator(diffs.adds));\n        for(let addId of addIds) {\n          let {path:pathId, child:childId, ix} = diffs.adds[addId];\n          let path = this.getPath(pathId);\n          path.splice(ix - 1, 0, childId)\n          let operation = this.getOperation(childId);\n          operation.paths.push(pathId);\n\n          this.dirty[pathId] = true;\n        }\n        setImmediate(this.changed);\n      })\n\n      .watch(\"Export attributes of operations.\", ({find, lookup, record}) => {\n        let path = find(\"canvas/path\");\n        let child = path.children;\n        let {attribute, value} = lookup(child);\n        return [child.add(attribute, value)];\n      })\n      .asDiffs((diffs) => {\n        for(let [opId, attribute, value] of diffs.removes) {\n          let operation = this.operations[opId];\n          if(!operation) continue;\n          operation.args[attribute] = undefined;\n          for(let pathId of operation.paths) this.dirty[pathId] = true;\n        }\n        for(let [opId, attribute, value] of diffs.adds) {\n          let operation = this.operations[opId];\n          if(!operation) throw new Error(`Missing operation ${maybeIntern(opId)} for AV ${attribute}: $[value}`);\n          if(operation.args[attribute]) throw new Error(`Attempting to overwrite existing attribute ${attribute} of ${opId}: ${operation.args[attribute]} => ${value}`);\n          operation.args[attribute] = value;\n          for(let pathId of operation.paths) this.dirty[pathId] = true;\n        }\n        setImmediate(this.changed);\n      })\n\n      .watch(\"Export path styles.\", ({find, lookup, record}) => {\n        let path = find(\"canvas/path\");\n        let {attribute, value} = lookup(path);\n        attribute != \"children\";\n        attribute != \"tag\";\n        attribute != \"sort\";\n        attribute != \"eve-auto-index\";\n        return [path.add(attribute, value)];\n      })\n      .asDiffs((diffs) => {\n        for(let [pathId, attribute, value] of diffs.removes) {\n          let pathStyle = this.pathStyles[pathId];\n          if(!pathStyle) continue;\n          pathStyle[attribute] = undefined;\n          this.dirty[pathId] = true;\n        }\n        for(let [pathId, attribute, value] of diffs.adds) {\n          let pathStyle = this.pathStyles[pathId];\n          if(!pathStyle) throw new Error(`Missing path style for ${pathId}.`);\n          // if(pathStyle[attribute]) throw new Error(`Attempting to overwrite existing attribute ${attribute} of ${pathId}: ${pathStyle[attribute]} => ${value}`);\n          pathStyle[attribute] = value;\n          this.dirty[pathId] = true;\n        }\n        setImmediate(this.changed);\n      });\n  }\n}\n\nWatcher.register(\"canvas\", CanvasWatcher);\n\n/*\n * [#canvas/root width height children:\n *  [#canvas/rect x y width height fill? stroke?]]\n */\n"
  },
  {
    "path": "src/watchers/compiler.ts",
    "content": "//--------------------------------------------------------------------\n// The Eve compiler as a watcher\n//--------------------------------------------------------------------\n\nimport {Watcher, RawValue, DiffConsumer} from \"./watcher\";\nimport {ID, Block, FunctionConstraint, printBlock} from \"../runtime/runtime\";\nimport {Program, LinearFlow, ReferenceContext, Reference, Record, Insert, Remove, Value, Fn, Not, Choose, Union, Aggregate, WatchFlow, LinearFlowFunction, CommitFlow} from \"../runtime/dsl2\";\nimport {SumAggregate} from \"../runtime/stdlib\";\nimport * as Runtime from \"../runtime/runtime\";\nimport \"setimmediate\";\n\nexport interface CompilationContext {\n  variables: {[id:string]: {[id:string]: Reference}},\n}\n\nexport class CompilerWatcher extends Watcher {\n\n  blocksToCompile:{[blockID:string]: boolean} = {};\n  blocksToRemove:{[blockID:string]: boolean} = {};\n  blocks:{[blockID:string]: Block} = {};\n  items:{[id:string]: any} = {};\n  watcherFunctions:{[name:string]: DiffConsumer} = {};\n  programToInjectInto = this.program;\n\n  //------------------------------------------------------------------\n  // Compile queue\n  //------------------------------------------------------------------\n\n  queued = false;\n  queue(blockID:string, isAdd = true) {\n    if(isAdd) this.blocksToCompile[blockID] = true;\n    this.blocksToRemove[blockID] = true;\n\n    if(!this.queued) {\n      this.queued = true;\n      setImmediate(this.runQueue)\n    }\n  }\n  runQueue = () => {\n    let adds = [];\n    let removes = [];\n    for(let ID in this.blocksToRemove) {\n      if(!this.blocks[ID]) continue;\n      removes.push(this.blocks[ID]);\n      delete this.blocks[ID];\n    }\n    for(let ID in this.blocksToCompile) {\n      if(!this.items[ID]) continue;\n      let neue = this.compileBlock(ID);\n      if(neue) {\n        adds.push(neue);\n        this.blocks[ID] = neue;\n      }\n    }\n    this.programToInjectInto.blockChangeTransaction(adds, removes);\n    this.queued = false;\n    this.blocksToCompile = {};\n    this.blocksToRemove = {};\n  }\n\n  //------------------------------------------------------------------\n  // Program to inject into\n  //------------------------------------------------------------------\n\n  injectInto(prog:Program) {\n    this.programToInjectInto = prog;\n  }\n\n  //------------------------------------------------------------------\n  // Watch functions\n  //------------------------------------------------------------------\n\n  registerWatcherFunction(name:string, consumer:DiffConsumer) {\n    this.watcherFunctions[name] = consumer;\n  }\n\n  //------------------------------------------------------------------\n  // Compiler\n  //------------------------------------------------------------------\n\n  inContext(flow:LinearFlow, func: () => void) {\n    ReferenceContext.push(flow.context);\n    func();\n    ReferenceContext.pop();\n  }\n\n  compileValue = (compile:CompilationContext, context:ReferenceContext, value:RawValue|undefined):Value|undefined => {\n    if(value === undefined) return undefined;\n    let {items} = this;\n    if(items[value] && items[value].type === \"variable\") {\n      let found;\n      let cur:ReferenceContext|undefined = context;\n      while(!found && cur) {\n        found = compile.variables[cur.ID] && compile.variables[cur.ID][value];\n        cur = cur.parent;\n      }\n      if(!found) {\n        if(!compile.variables[context.ID]) {\n          compile.variables[context.ID] = {};\n        }\n        found = compile.variables[context.ID][value] = new Reference(context);\n      }\n      return found;\n    }\n    return value;\n  }\n\n  compileFlow(compile:CompilationContext, flow:LinearFlow, constraints: any[]) {\n    let {inContext, items, compileValue} = this;\n    let {context} = flow;\n    let subBlocks:any[] = [];\n    for(let constraintID of constraints) {\n      let constraint = items[constraintID];\n      if(!constraint) continue;\n\n      if(constraint.type === \"record\") {\n        inContext(flow, () => {\n          let attrs:any = {};\n          for(let [attribute, value] of constraint.attributes) {\n            let safeValue = compileValue(compile, context, value);\n            let found = attrs[attribute];\n            if(!found) {\n              found = attrs[attribute] = [];\n            }\n            found.push(safeValue);\n          }\n          let recordVar = compileValue(compile, context, constraint.record) as Reference;\n          let record = new Record(flow.context, [], attrs, recordVar);\n          recordVar.__owner = record;\n        })\n      }\n      if(constraint.type === \"output\") {\n        inContext(flow, () => {\n          let attrs:any = {};\n          for(let [attribute, value] of constraint.attributes) {\n            let safeValue = compileValue(compile, context, value);\n            let found = attrs[attribute];\n            if(!found) {\n              found = attrs[attribute] = [];\n            }\n            found.push(safeValue);\n          }\n          let outputType = Insert;\n          let outputOp = \"add\";\n          if(constraint.outputType === \"remove\") {\n            outputType = Remove;\n            outputOp = \"remove\";\n          }\n          let outputVar = compileValue(compile, context, constraint.record) as Reference;\n          let output;\n          if(constraint.attributes.length > 0) {\n            output = new outputType(flow.context, [], attrs);\n            context.equality(output.reference(), outputVar);\n          } else {\n            output = new outputType(flow.context, [], attrs, outputVar);\n          }\n          let record = output.reference() as any;\n          for(let [attribute, value] of constraint.nonIdentityAttribute) {\n            record[outputOp](compileValue(compile, context, attribute), compileValue(compile, context, value));\n          }\n        })\n      }\n      if(constraint.type === \"removeRecord\") {\n        inContext(flow, () => {\n          let outputVar = compileValue(compile, context, constraint.record) as Reference;\n          let output;\n          if(!outputVar.__owner) {\n            throw new Error(\"Trying to fully remove a record that doesn't exist\");\n          }\n          outputVar.remove();\n        })\n      }\n      if(constraint.type === \"lookup\") {\n        inContext(flow, () => {\n          let lookup = flow.lookup(compileValue(compile, context, constraint.record) as Value);\n          context.equality(lookup.attribute, compileValue(compile, context, constraint.attribute) as Value);\n          context.equality(lookup.value, compileValue(compile, context, constraint.value) as Value);\n        })\n      }\n      if(constraint.type === \"expression\") {\n        inContext(flow, () => {\n          let args = constraint.args.map((v:RawValue) => compileValue(compile, context, v));\n          let returns = constraint.returns.map((v:RawValue) => compileValue(compile, context, v))[0];\n          let fn = new Fn(flow.context, constraint.op, args, returns);\n        })\n      }\n      if(constraint.type === \"aggregate\") {\n        inContext(flow, () => {\n          let projection:Reference[] = [];\n          let group:Reference[] = [];\n          let args:Value[] = [];\n          for(let arg in constraint.namedArgs) {\n            let values = constraint.namedArgs[arg].map((v:RawValue) => compileValue(compile, context, v));\n            if(arg === \"for\" || arg === \"given\") {\n              projection = values;\n            } else if(arg === \"per\") {\n              group = values;\n            } else if(arg === \"direction\" || arg === \"value\") {\n              args = args.concat(values);\n            }\n          }\n          let aggOp:any = SumAggregate;\n          if(constraint.op === \"gather/sort\") {\n            aggOp = Runtime.SortNode;\n          } else if(constraint.op === \"gather/count\" && args.length === 0) {\n            args.push(1);\n          }\n          let returns = constraint.returns.map((v:RawValue) => compileValue(compile, context, v))[0];\n          let agg = new Aggregate(flow.context, aggOp, projection, group, args, returns);\n        })\n      }\n      if(constraint.type === \"equality\") {\n        inContext(flow, () => {\n          context.equality(compileValue(compile, context, constraint.left) as Value, compileValue(compile, context, constraint.right) as Value);\n        })\n      }\n      if(constraint.type === \"not\" || constraint.type === \"choose\" || constraint.type === \"union\") {\n        subBlocks.push(constraint);\n      }\n    }\n\n    for(let constraint of subBlocks) {\n      if(constraint.type === \"not\") {\n        inContext(flow, () => {\n          let not = new Not((a:any) => [], flow);\n          this.compileFlow(compile, not, constraint.constraints);\n        });\n      }\n      if(constraint.type === \"choose\" || constraint.type == \"union\") {\n        let builder = constraint.type == \"choose\" ? Choose : Union;\n        let branchFuncs:LinearFlowFunction[] = [];\n        for(let branchId of constraint.branches) {\n          let branch = items[branchId];\n          branchFuncs.push((self) => {\n            return branch.outputs.map((v:RawValue) => {\n              return compileValue(compile, self.context, v) as Value;\n            })\n          });\n        }\n        inContext(flow, () => {\n          let outputs = constraint.outputs.map((v:RawValue) => {\n            return compileValue(compile, context, v) as Value;\n          })\n          let choose = new builder(flow.context, branchFuncs, flow, outputs);\n          let branchIx = 0;\n          for(let branchId of constraint.branches) {\n            let branch = items[branchId];\n            let compiled = choose.branches[branchIx];\n            inContext(compiled, () => {\n              this.compileFlow(compile, compiled, branch.constraints);\n              choose.setBranchInputs(branchIx, compiled.context.getInputReferences());\n            })\n            branchIx++;\n          }\n        });\n      }\n\n    }\n\n  }\n\n  compileBlock(blockID:string) {\n    let {inContext, items, compileValue} = this;\n    let item = items[blockID];\n    let {name, constraints, type} = item;\n    let compile:CompilationContext = {variables: {}};\n    let flow:LinearFlow;\n    if(type === \"commit\") {\n      flow = new CommitFlow((a) => []);\n    } else if(type === \"watch\") {\n      flow = new WatchFlow((a) => []);\n    } else if(type === \"bind\") {\n      flow = new LinearFlow((a) => []);\n    } else {\n      return;\n    }\n\n    this.compileFlow(compile, flow, constraints);\n\n    let block = (this.programToInjectInto as any)[`_${type}`](name, flow);\n    if(type === \"watch\" && item.watcher) {\n      let func = this.watcherFunctions[item.watcher];\n      if(!func) {\n        console.error(\"No such watcher function registered: \" + item.watcher);\n      } else {\n        this.programToInjectInto.asDiffs(func);\n      }\n    }\n    // if(typeof process === \"undefined\" && console.groupCollapsed) {\n    //   console.groupCollapsed(\"Compiled: \" + block.name);\n    //   console.log(block, flow);\n    //   console.log(printBlock(block));\n    //   console.groupEnd();\n    // }\n    return block;\n  }\n\n  //------------------------------------------------------------------\n  // Compile item extraction via watch blocks\n  //------------------------------------------------------------------\n\n  setup() {\n    let {program:me} = this;\n\n    me.bind(\"show errors\", ({find, record}) => {\n      let err = find(\"eve/compiler/error\");\n      return [\n        record(\"ui/column\", {err, class: \"eve-compiler-error\"}).add(\"children\", [\n          record(\"ui/row\", {err, class: \"eve-compiler-error-message-container\"}).add(\"children\", [\n            record(\"ui/column\", {err, class: \"eve-compiler-line-info\", sort:0}).add(\"children\", [\n              record(\"ui/text\", {err, text: `Line`, sort: 0}),\n              record(\"ui/text\", {err, class: \"eve-compiler-error-message-line\", text:err.start.line, sort: 1}),\n            ]),\n            record(\"ui/column\", {err, class: \"eve-compiler-error-content\", sort:1}).add(\"children\", [\n              record(\"ui/text\", {err, class: \"eve-compiler-error-message\", text: err.message, sort: 0}),\n              record(\"ui/text\", {err, class: \"eve-compiler-error-sample\", text: err.sample, sort:1})\n            ]),\n          ]),\n        ])\n      ]\n    });\n\n    me.watch(\"get blocks\", ({find, record}) => {\n      let block = find(\"eve/compiler/rule\");\n      let {constraint, name, type} = block;\n      return [\n        record({block, constraint, name, type})\n      ]\n    })\n\n    me.asObjects<{block:string, name:string, constraint:string, type:string}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, name, constraint, type} = adds[key];\n        let found = items[block];\n        if(!found) {\n          found = items[block] = {type, name, constraints: []};\n        }\n        found.name = name;\n        if(found.constraints.indexOf(constraint) === -1) {\n          found.constraints.push(constraint);\n        }\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, name, constraint} = removes[key];\n        let found = items[block];\n        if(!found) {\n          continue;\n        }\n        let ix = found.constraints.indexOf(constraint)\n        if(ix > -1) {\n          found.constraints.splice(ix, 1);\n        }\n        if(found.constraints.length === 0) {\n          delete items[block];\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get nots\", ({find, record}) => {\n      let not = find(\"eve/compiler/not\");\n      let block = find(\"eve/compiler/block\", {constraint: not});\n      return [\n        record({block, not, constraint:not.constraint})\n      ]\n    })\n\n    me.asObjects<{block:string, not:string, constraint:string}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, not, constraint} = adds[key];\n        let found = items[not];\n        if(!found) {\n          found = items[not] = {type: \"not\", constraints: []};\n        }\n        if(found.constraints.indexOf(constraint) === -1) {\n          found.constraints.push(constraint);\n        }\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, not, constraint} = removes[key];\n        let found = items[not];\n        if(!found) {\n          continue;\n        }\n        let ix = found.constraints.indexOf(constraint)\n        if(ix > -1) {\n          found.constraints.splice(ix, 1);\n        }\n        if(found.constraints.length === 0) {\n          delete items[not];\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get choose branches\", ({find, record, choose}) => {\n      let item = find(\"eve/compiler/branch-set\");\n      let [itemType] = choose(() => {\n        item.tag == \"eve/compiler/choose\";\n        return [\"choose\"];\n      }, () => {\n        item.tag == \"eve/compiler/union\";\n        return [\"union\"];\n      })\n      let block = find(\"eve/compiler/rule\", {constraint: item});\n      return [\n        record({block, item, itemType, branch:item.branch})\n      ]\n    })\n\n    me.asObjects<{block:string, item:string, itemType:string, branch:string}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, item, branch, itemType} = adds[key];\n        let found = items[item];\n        if(!found) {\n          found = items[item] = {type: itemType, branches: [], outputs: []};\n        }\n        if(!items[branch]) {\n          items[branch] = {type: \"branch\", constraints: [], outputs: []};\n        }\n        if(found.branches.indexOf(branch) === -1) {\n          found.branches.push(branch);\n        }\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, item, itemType, branch} = removes[key];\n        let found = items[item];\n        if(!found) {\n          continue;\n        }\n        let ix = found.branches.indexOf(branch)\n        if(ix > -1) {\n          found.branches.splice(ix, 1);\n        }\n        if(found.branches.length === 0) {\n          delete items[item];\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get choose outputs\", ({find, record}) => {\n      let choose = find(\"eve/compiler/branch-set\");\n      let block = find(\"eve/compiler/block\", {constraint: choose});\n      let {value, index} = choose.output;\n      return [\n        record({block, choose, value, index})\n      ]\n    })\n\n    me.asObjects<{block:string, choose:string, value:RawValue, index:number}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, choose, value, index} = adds[key];\n        let found = items[choose];\n        if(!found) {\n          console.error(\"adding output for a branch that doesn't exist\")\n          return;\n        }\n        found.outputs[index - 1] = value;\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, choose, value, index} = removes[key];\n        let found = items[choose];\n        if(!found) {\n          continue;\n        }\n        let cur = found.outputs[index];\n        if(cur === value) {\n          found.outputs[index - 1] = undefined;\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get choose branch constraints\", ({find, record}) => {\n      let choose = find(\"eve/compiler/branch-set\");\n      let block = find(\"eve/compiler/block\", {constraint: choose});\n      let {branch} = choose\n      return [\n        record({block, branch, constraint:branch.constraint})\n      ]\n    })\n\n    me.asObjects<{block:string, constraint:string, branch:string}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, constraint, branch} = adds[key];\n        let found = items[branch];\n        if(!found) {\n          console.error(\"adding constraint for a branch that doesn't exist\")\n          return;\n        }\n        if(found.constraints.indexOf(constraint) === -1) {\n          found.constraints.push(constraint);\n        }\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, constraint, branch} = removes[key];\n        let found = items[branch];\n        if(!found) {\n          continue;\n        }\n        let ix = found.constraints.indexOf(constraint)\n        if(ix > -1) {\n          found.constraints.splice(ix, 1);\n        }\n        if(found.constraints.length === 0) {\n          delete items[branch];\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get choose branch outputs\", ({find, record}) => {\n      let choose = find(\"eve/compiler/branch-set\");\n      let block = find(\"eve/compiler/block\", {constraint: choose});\n      let branch = choose.branch\n      let {value, index} = branch.output\n      return [\n        record({block, branch, value, index})\n      ]\n    })\n\n    me.asObjects<{block:string, branch:string, value:RawValue, index:number}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, branch, value, index} = adds[key];\n        let found = items[branch];\n        if(!found) {\n          console.error(\"adding output for a branch that doesn't exist\")\n          return;\n        }\n        found.outputs[index - 1] = value;\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, branch, value, index} = removes[key];\n        let found = items[branch];\n        if(!found) {\n          continue;\n        }\n        let cur = found.outputs[index];\n        if(cur === value) {\n          found.outputs[index - 1] = undefined;\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get watcher property\", ({find, record}) => {\n      let block = find(\"eve/compiler/rule\");\n      let {constraint, name, type, watcher} = block;\n      return [\n        record({block, watcher})\n      ]\n    })\n\n    me.asObjects<{block:string, watcher:string}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, watcher} = adds[key];\n        let found = items[block];\n        found.watcher = watcher;\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, watcher} = removes[key];\n        let found = items[block];\n        if(!found) {\n          continue;\n        }\n        found.watcher = undefined;\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get variables\", ({find, record}) => {\n      let variable = find(\"eve/compiler/var\");\n      return [\n        record({variable})\n      ]\n    })\n\n    me.asObjects<{variable:string}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {variable} = adds[key];\n        items[variable] = {type: \"variable\"};\n      }\n    })\n\n    me.watch(\"get equalities\", ({find, record}) => {\n      let eq = find(\"eve/compiler/equality\");\n      return [\n        record({eq, left:eq.left, right:eq.right})\n      ]\n    })\n\n    me.asObjects<{eq:string, left:RawValue, right:RawValue}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {eq, left, right} = adds[key];\n        items[eq] = {type: \"equality\", left, right};\n      }\n      for(let key in removes) {\n        let {eq} = removes[key];\n        items[eq] = undefined;\n      }\n    })\n\n    me.watch(\"get lookups\", ({find, record}) => {\n      let lookup = find(\"eve/compiler/lookup\");\n      let block = find(\"eve/compiler/block\", {constraint: lookup});\n      let {record:rec, attribute, value} = lookup;\n      return [\n        record({block, id:lookup, record:rec, attribute, value})\n      ]\n    })\n\n    me.asObjects<{block:string, id:string, record:string, attribute:string, value:RawValue}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, id, record, attribute, value} = adds[key];\n        items[id] = {type: \"lookup\", record: record, attribute, value};\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, id, record, attribute, value} = removes[key];\n        delete items[id];\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get expressions\", ({find, record, choose}) => {\n      let expr = find(\"eve/compiler/expression\");\n      let [type] = choose(() => {\n        expr.tag == \"eve/compiler/aggregate\";\n        return [\"aggregate\"];\n      }, () => {\n        return [\"expression\"];\n      })\n      let block = find(\"eve/compiler/block\", {constraint: expr});\n      return [\n        record({block, id:expr, op:expr.op, type})\n      ]\n    })\n\n    me.asObjects<{block:string, id:string, op:string, type:string}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, id, op, type} = adds[key];\n        let found = items[id];\n        if(!found) {\n          found = items[id] = {type, op, args: [], returns: [], namedArgs: {}};\n        }\n        found.op = op;\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, id, op} = removes[key];\n        let found = items[id];\n        if(!found) { continue; }\n        delete items[id];\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get expression args\", ({find, record}) => {\n      let expr = find(\"eve/compiler/expression\");\n      let block = find(\"eve/compiler/block\", {constraint: expr});\n      let {arg} = expr;\n      return [\n        record({block, id:expr, index:arg.index, value:arg.value})\n      ]\n    })\n\n    me.asObjects<{block:string, id:string, index:number, value:RawValue}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, id, index, value} = adds[key];\n        let found = items[id];\n        if(!found) { throw new Error(\"args for a non existent expression\"); }\n        found.args[index - 1] = value;\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, id, index, value} = removes[key];\n        let found = items[id];\n        if(!found) { continue; }\n        if(found.args[index - 1] == value) {\n          found.args[index - 1] = undefined;\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get expression named args\", ({find, record, not}) => {\n      let expr = find(\"eve/compiler/expression\");\n      not(() => {\n        expr.tag == \"eve/compiler/aggregate\";\n      })\n      let block = find(\"eve/compiler/block\", {constraint: expr});\n      let {arg} = expr;\n      return [\n        record({block, id:expr, name:arg.name, value:arg.value, index:arg.index})\n      ]\n    })\n\n    me.asObjects<{block:string, id:string, name:string, value:RawValue, index:number}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, id, name, value, index} = adds[key];\n        let found = items[id];\n        if(!found) { throw new Error(\"args for a non existent expression\"); }\n        let {argNames, returnNames} = FunctionConstraint.fetchInfo(found.op)\n        let argIx = argNames.indexOf(name);\n        let retIx = returnNames.indexOf(name);\n        if(argIx > -1) {\n          found.args[argIx] = value;\n        } else if(retIx > -1) {\n          found.returns[retIx] = value;\n        } else {\n          console.error(`Unknown arg for expression: ${found.op}[${name}]`);\n        }\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, id, name, value} = removes[key];\n        let found = items[id];\n        if(!found) { continue; }\n        let {argNames, returnNames} = FunctionConstraint.fetchInfo(found.op)\n        let argIx = argNames.indexOf(name);\n        let retIx = returnNames.indexOf(name);\n        if(argIx > -1) {\n          found.args[argIx] = undefined;\n        } else if(retIx > -1) {\n          found.returns[retIx] = undefined;\n        } else {\n          console.error(`Unknown arg for expression: ${found.op}[${name}]`);\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get aggregate named args\", ({find, record, not}) => {\n      let expr = find(\"eve/compiler/aggregate\");\n      let block = find(\"eve/compiler/block\", {constraint: expr});\n      let {arg} = expr;\n      return [\n        record({block, id:expr, name:arg.name, value:arg.value, index:arg.index})\n      ]\n    })\n\n    me.asObjects<{block:string, id:string, name:string, value:RawValue, index:number}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, id, name, value, index} = adds[key];\n        let found = items[id];\n        if(!found) { throw new Error(\"args for a non existent expression\"); }\n        let args = found.namedArgs[name] || [];\n        args[index - 1] = value;\n        found.namedArgs[name] = args;\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, id, name, value} = removes[key];\n        let found = items[id];\n        if(!found) { continue; }\n        let args = found.namedArgs[name];\n        if(args) {\n          let ix = args.indexOf(value);\n          args.splice(ix, 1);\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get expression returns\", ({find, record}) => {\n      let expr = find(\"eve/compiler/expression\");\n      let block = find(\"eve/compiler/block\", {constraint: expr});\n      let {return:ret} = expr;\n      return [\n        record({block, id:expr, index:ret.index, value:ret.value})\n      ]\n    })\n\n    me.asObjects<{block:string, id:string, index:number, value:RawValue}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, id, index, value} = adds[key];\n        let found = items[id];\n        if(!found) { throw new Error(\"returns for a non existent expression\"); }\n        found.returns[index - 1] = value;\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, id, index, value} = removes[key];\n        let found = items[id];\n        if(!found) { continue; }\n        if(found.returns[index - 1] == value) {\n          found.returns[index - 1] = undefined;\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get records\", ({find, record}) => {\n      let compilerRecord = find(\"eve/compiler/record\");\n      let block = find(\"eve/compiler/block\", {constraint: compilerRecord});\n      let {record:id, attribute} = compilerRecord;\n      return [\n        record({block, id:compilerRecord, record:id, attribute:attribute.attribute, value:attribute.value})\n      ]\n    })\n\n    me.asObjects<{block:string, id:string, record:string, attribute:string, value:RawValue}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, id, record, attribute, value} = adds[key];\n        let found = items[id];\n        if(!found) {\n          found = items[id] = {type: \"record\", attributes: [], record: record};\n        }\n        found.attributes.push([attribute, value]);\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, id, record, attribute, value} = removes[key];\n        let found = items[id];\n        if(!found) { continue; }\n\n        found.attributes = found.attributes.filter(([a, v]:RawValue[]) => a !== attribute || v !== value);\n        if(found.attributes.length === 0) {\n          delete items[id];\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get outputs\", ({find, record, choose}) => {\n      let compilerRecord = find(\"eve/compiler/output\");\n      let block = find(\"eve/compiler/block\", {constraint: compilerRecord});\n      let {record:id, attribute} = compilerRecord;\n      let [attributeType] = choose(() => {\n        attribute.tag == \"eve/compiler/attribute/non-identity\";\n        return \"non-identity\";\n      }, () => {\n        return \"identity\";\n      });\n      let [outputType] = choose(() => {\n        compilerRecord.tag == \"eve/compiler/remove\";\n        return \"remove\";\n      }, () => {\n        return \"add\";\n      });\n      return [\n        record({block, id:compilerRecord, record:id, attribute:attribute.attribute, value:attribute.value, attributeType, outputType})\n      ]\n    })\n\n    me.asObjects<{block:string, id:string, record:string, attribute:string, value:RawValue, attributeType:string, outputType:string}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, id, record, attribute, value, attributeType, outputType} = adds[key];\n        let found = items[id];\n        if(!found) {\n          found = items[id] = {type: \"output\", attributes: [], nonIdentityAttribute:[], record: record, outputType};\n        }\n        if(attributeType === \"identity\") {\n          found.attributes.push([attribute, value]);\n        } else {\n          found.nonIdentityAttribute.push([attribute, value]);\n        }\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, id, record, attribute, value} = removes[key];\n        let found = items[id];\n        if(!found) { continue; }\n\n        found.attributes = found.attributes.filter(([a, v]:RawValue[]) => a !== attribute || v !== value);\n        found.nonIdentityAttribute = found.nonIdentityAttribute.filter(([a, v]:RawValue[]) => a !== attribute || v !== value);\n        if(found.attributes.length === 0) {\n          delete items[id];\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get valueless outputs\", ({find, record, choose, not}) => {\n      let compilerRecord = find(\"eve/compiler/output\");\n      let block = find(\"eve/compiler/block\", {constraint: compilerRecord});\n      let {attribute} = compilerRecord;\n      not(() => attribute.value)\n      return [\n        record({block, id:compilerRecord, record:compilerRecord.record, attribute:attribute.attribute})\n      ]\n    })\n\n    me.asObjects<{block:string, id:string, record:string, attribute:string}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, id, record, attribute} = adds[key];\n        let found = items[id];\n        if(!found) {\n          found = items[id] = {type: \"output\", attributes: [], nonIdentityAttribute:[], record: record, outputType:\"remove\"};\n        }\n        found.nonIdentityAttribute.push([attribute, undefined]);\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, id, attribute} = removes[key];\n        let found = items[id];\n        if(!found) { continue; }\n\n        found.nonIdentityAttribute = found.nonIdentityAttribute.filter(([a, v]:RawValue[]) => a !== attribute || v !== undefined);\n        if(found.nonIdentityAttribute.length === 0) {\n          delete items[id];\n        }\n        this.queue(block);\n      }\n    })\n\n    me.watch(\"get full remove\", ({find, record, choose, not}) => {\n      let compilerRecord = find(\"eve/compiler/remove\");\n      let block = find(\"eve/compiler/block\", {constraint: compilerRecord});\n      not(() => compilerRecord.attribute);\n      return [\n        record({block, id:compilerRecord, record:compilerRecord.record})\n      ]\n    })\n\n    me.asObjects<{block:string, id:string, record:string}>(({adds, removes}) => {\n      let {items} = this;\n      for(let key in adds) {\n        let {block, id, record} = adds[key];\n        let found = items[id];\n        if(!found) {\n          found = items[id] = {type: \"removeRecord\", record: record};\n        }\n        this.queue(block);\n      }\n      for(let key in removes) {\n        let {block, id} = removes[key];\n        let found = items[id];\n        if(!found) { continue; }\n        delete items[id];\n        this.queue(block);\n      }\n    })\n\n\n\n  }\n}\n\nWatcher.register(\"compiler\", CompilerWatcher);\n"
  },
  {
    "path": "src/watchers/console.ts",
    "content": "import * as fs from \"fs\";\nimport {Watcher} from \"./watcher\";\nimport {ID} from \"../runtime/runtime\";\n\nexport class ConsoleWatcher extends Watcher {\n\n  setup() {\n    if(console) {\n      this.program\n      .watch(\"Print to console log.\", ({find, record}) => {\n        let log = find(\"console/log\");\n        return [log.add(\"text\", log.text)]\n      })\n      .asDiffs(({adds}) => {\n        for(let [log, _, text] of adds) {\n          console.log(text);\n        }\n      })\n      .watch(\"Print to console error.\", ({find, record}) => {\n        let log = find(\"console/error\");\n        return [log.add(\"text\", log.text)]\n      })\n      .asDiffs(({adds}) => {\n        for(let [log, _, text] of adds) {\n          console.error(text);\n        }\n      })\n      .watch(\"Print to console warn.\", ({find, record}) => {\n        let log = find(\"console/warn\");\n        return [log.add(\"text\", log.text)]\n      })\n      .asDiffs(({adds}) => {\n        for(let [log, _, text] of adds) {\n          console.warn(text);\n        }\n      })\n    }\n  }\n}\n\nWatcher.register(\"console\", ConsoleWatcher);\n"
  },
  {
    "path": "src/watchers/dom.ts",
    "content": "import {Watcher, RawValue, RawEAV, RawEAVC, _isId, asJS} from \"./watcher\";\nimport {v4 as uuid} from \"uuid\";\n\nimport naturalSort = require(\"javascript-natural-sort\");\n\nexport interface Map<V>{[key:string]: V}\n\nexport interface Style extends Map<RawValue|undefined> {__size: number}\nexport interface ElemInstance extends Element {__element?: RawValue, __styles?: RawValue[], __sort?: RawValue, style?: any, listeners?: {[event:string]: boolean}}\n\nexport abstract class DOMWatcher<Instance extends ElemInstance> extends Watcher {\n  styles:Map<Style|undefined> = Object.create(null);\n  roots:Map<Instance|undefined> = Object.create(null);\n  instances:Map<Instance|undefined> = Object.create(null);\n  elementToInstances:Map<RawValue[]|undefined> = Object.create(null);\n  styleToInstances:Map<RawValue[]|undefined> = Object.create(null);\n\n  abstract tagPrefix:string;\n  abstract createInstance(id:RawValue, element:RawValue, tagname:RawValue):Instance;\n  abstract getInstance(id:RawValue):Instance|undefined;\n  abstract createRoot(id:RawValue):Instance|undefined;\n  abstract addAttribute(instance:Instance, attribute:RawValue, value:RawValue|boolean):void;\n  abstract removeAttribute(instance:Instance, attribute:RawValue, value:RawValue|boolean):void;\n\n  protected _dummy:HTMLElement;\n\n  protected _sendEvent(eavs:(RawEAV|RawEAVC)[]) {\n    this.program.inputEAVs(eavs);\n  }\n\n  getStyle(id:RawValue) {\n    return this.styles[id] = this.styles[id] || {__size: 0};\n  }\n\n  isInstance(elem?:any): elem is Instance {\n    if(!elem || !(elem instanceof Element)) return false;\n    let instance = elem as Instance;\n    return instance && !!instance[\"__element\"];\n  }\n\n  addInstance(id:RawValue, element:RawValue, tagname:RawValue):Instance|undefined {\n    let instance = this.instances[id] = this.createInstance(id, element, tagname);\n    if(!this.elementToInstances[element]) this.elementToInstances[element] = [];\n    this.elementToInstances[element]!.push(id);\n    return instance;\n  }\n\n  clearInstance(id:RawValue) {\n    let instance = this.instances[id];\n    if(instance && instance.parentElement) {\n      instance.parentElement.removeChild(instance);\n    }\n    this.instances[id] = undefined;\n\n    let instances = instance && this.elementToInstances[instance.__element!];\n    if(instances) instances.splice(instances.indexOf(id), 1);\n  }\n\n  getRoot(id:RawValue, tagname:RawValue = \"div\"):Instance|undefined {\n    return this.roots[id] = this.roots[id];\n  }\n\n  clearRoot(id:RawValue) {\n    this.clearInstance(id);\n    this.roots[id] = undefined;\n  }\n\n  insertChild(parent:Element|null, child:Instance, at = child.__sort) {\n    child.__sort = at\n    if(at !== undefined) child.setAttribute(\"sort\", \"\"+at);\n    if(!parent) return;\n\n    let current;\n    for(let curIx = 0; curIx < parent.childNodes.length; curIx++) {\n      let cur = parent.childNodes[curIx] as Instance;\n      if(cur === child) continue;\n      if(cur.__sort !== undefined && at !== undefined && naturalSort(cur.__sort, at) > 0) {\n        current = cur;\n        break;\n      }\n    }\n\n    if(current) {\n      parent.insertBefore(child, current);\n    } else {\n      parent.appendChild(child);\n    }\n  }\n\n  insertSortedChild(parent:Element|null, child:Instance, sort?:RawValue) {\n    child.__sort = sort;\n    if(sort !== undefined) child.setAttribute(\"sort\", \"\"+sort);\n    else child.removeAttribute(\"sort\");\n    this.insertChild(parent, child);\n  }\n\n  insertAutoSortedChild(parent:Element|null, child:Instance, autoSort?:RawValue) {\n    if(autoSort !== undefined) {\n      child.setAttribute(\"auto-sort\", \"\"+autoSort);\n      if(!child.hasAttribute(\"sort\")) {\n        child.__sort = autoSort;\n        this.insertChild(parent, child);\n      }\n    }\n    else child.removeAttribute(\"auto-sort\");\n  }\n\n  // @NOTE: This requires styles to have disjoint attribute sets or it'll do bad things.\n  // @NOTE: Styles may only have a single value for each attribute due to our inability\n  //        to express an ordering of non-record values.\n  setStyleAttribute(styleId:RawValue, attribute:RawValue, value:RawValue, count:-1|1) {\n    let style = this.getStyle(styleId);\n    if(count === -1) {\n      //if(!style[attribute]) throw new Error(`Cannot remove non-existent attribute '${attribute}'`);\n      //if(style[attribute] !== value) throw new Error(`Cannot remove mismatched AV ${attribute}: ${value} (current: ${style[attribute]})`);\n      style[attribute] = undefined;\n    } else {\n      if(style[attribute]) throw new Error(`Cannot add already present attribute '${attribute}'`);\n      style[attribute] = value;\n    }\n    style.__size += count;\n\n    // Update all existing instances with this style.\n    let instances = this.styleToInstances[styleId];\n    if(instances) {\n      for(let instanceId of instances) {\n        let instance = this.getInstance(instanceId);\n        if(!instance) {\n          // We may have removed one instance of multiple subscribed to this style.\n          continue;\n        }\n        instance.style[attribute as any] = style[attribute] as any;\n      }\n    }\n  }\n\n  addStyleInstance(styleId:RawValue, instanceId:RawValue) {\n    let instance = this.getInstance(instanceId);\n    if(!instance) throw new Error(`Orphaned instance '${instanceId}'`);\n    let style = this.getStyle(styleId);\n\n    // Instead of a style record, we may be dealing with a style string.\n    if(style.__size === 0) {\n      this._dummy.setAttribute(\"style\", styleId as string);\n      let props = this._dummy.style;\n      // Yep, we're inline CSS.\n      if(props.length) {\n        this.styles[styleId] = style;\n\n        for(let propIx = 0; propIx < props.length; propIx++) {\n          let prop = props[propIx];\n          let value = props.getPropertyValue(prop);\n          style[prop] = value;\n          style.__size += 1;\n        }\n      }\n    }\n\n    for(let prop in style) {\n      if(prop === \"__size\") continue;\n      instance.style[prop as any] = style[prop] as string;\n    }\n    if(this.styleToInstances[styleId]) this.styleToInstances[styleId]!.push(instanceId);\n    else this.styleToInstances[styleId] = [instanceId];\n\n    if(!instance.__styles) instance.__styles = [];\n    if(instance.__styles.indexOf(styleId) === -1) instance.__styles.push(styleId);\n  }\n\n  removeStyleInstance(styleId:RawValue, instanceId:RawValue) {\n    let instance = this.getInstance(instanceId);\n    if(!instance) return;\n    instance.removeAttribute(\"style\");\n    let ix = instance.__styles!.indexOf(styleId);\n    instance.__styles!.splice(ix, 1);\n\n    for(let otherStyleId of instance.__styles!) {\n      let style = this.getStyle(otherStyleId);\n      for(let prop in style) {\n        if(prop === \"__size\") continue;\n        instance.style[prop as any] = style[prop] as string;\n      }\n    }\n  }\n\n  setup() {\n    if(typeof document === \"undefined\") return;\n    this._dummy = document.createElement(\"div\");\n\n    this.program\n      .constants({tagPrefix: this.tagPrefix})\n      .commit(\"Remove click events!\", ({find}) => {\n        let click = find(\"{{tagPrefix}}/event/click\");\n        return [click.remove()];\n      })\n\n      .bind(\"Create instances for each root.\", ({find, record, lib}) => {\n        let elem = find(\"{{tagPrefix}}/root\");\n        return [\n          record(\"{{tagPrefix}}/instance\", {element: elem, tagname: elem.tagname})\n        ];\n      })\n\n      .bind(\"Create an instance for each child of a rooted parent.\", ({find, record, lib}) => {\n        let elem = find(\"{{tagPrefix}}/element\");\n        let parentElem = find(\"{{tagPrefix}}/element\", {children: elem});\n        let parent = find(\"{{tagPrefix}}/instance\", {element: parentElem});\n\n        return [\n          record(\"{{tagPrefix}}/instance\", {element: elem, tagname: elem.tagname, parent})\n        ];\n      })\n      .watch(\"Export all instances.\", ({find, record}) => {\n        let instance = find(\"{{tagPrefix}}/instance\");\n        return [\n          record({tagname: instance.tagname, element: instance.element, instance})\n        ];\n      })\n\n      .asObjects<{tagname:string, element:string, instance:string}>((diff) => {\n        for(let e of Object.keys(diff.removes)) {\n          let {instance:instanceId} = diff.removes[e];\n          this.clearInstance(instanceId);\n\n        }\n\n        for(let e of Object.keys(diff.adds)) {\n          let {instance:instanceId, tagname, element} = diff.adds[e];\n          this.addInstance(instanceId, element, tagname);\n        }\n      })\n\n      .watch(\"Export roots.\", ({find, record}) => {\n        let root = find(\"{{tagPrefix}}/root\");\n        let instance = find(\"{{tagPrefix}}/instance\", {element: root});\n        return [\n          record({instance})\n        ];\n      })\n      .asDiffs((diff) => {\n        for(let [e, a, rootId] of diff.removes) {\n          this.clearRoot(rootId);\n        }\n        for(let [e, a, rootId] of diff.adds) {\n          this.roots[rootId] = this.createRoot(rootId);\n        }\n      })\n\n      .watch(\"Export instance parents.\", ({find, record}) => {\n        let instance = find(\"{{tagPrefix}}/instance\");\n        return [\n          record({instance, parent: instance.parent})\n        ];\n      })\n      .asObjects<{instance:string, parent:string}>((diff) => {\n        for(let e of Object.keys(diff.removes)) {\n          let {instance:instanceId, parent:parentId} = diff.removes[e];\n          let instance = this.getInstance(instanceId);\n          let parent = this.getInstance(parentId);\n          if(!instance || !parent) continue;\n\n          if(instance && instance.parentElement) {\n            instance.parentElement.removeChild(instance);\n          }\n\n        }\n        for(let e of Object.keys(diff.adds)) {\n          let {instance:instanceId, parent:parentId} = diff.adds[e];\n          let instance = this.getInstance(instanceId);\n          if(!instance) throw new Error(`Orphaned instance '${instanceId}'`);\n          let parent = this.getInstance(parentId);\n          if(!parent) throw new Error(`Missing parent instance '${parentId}', ${instanceId}`);\n          this.insertChild(parent, instance);\n        }\n      })\n\n      .watch(\"Export element styles.\", ({find, record, lib, lookup}) => {\n        let elem = find(\"{{tagPrefix}}/element\");\n        let style = elem.style;\n        let {attribute, value} = lookup(style);\n        return [\n          style.add(attribute, value)\n        ];\n      })\n      .asDiffs((diff) => {\n        let maybeGC = [];\n        for(let [styleId, a, v] of diff.removes) {\n          maybeGC.push(styleId);\n          this.setStyleAttribute(styleId, a, v, -1);\n        }\n\n\n        for(let [styleId, a, v] of diff.adds) {\n          this.setStyleAttribute(styleId, a, v, 1);\n        }\n\n        for(let styleId of maybeGC) {\n          let style = this.getStyle(styleId);\n          if(style.__size === 0) {\n            this.styles[styleId] = undefined;\n          }\n        }\n      })\n\n      .watch(\"Export element attributes.\", ({find, record, lookup}) => {\n        let instance = find(\"{{tagPrefix}}/instance\");\n        let elem = instance.element;\n        let {attribute, value} = lookup(elem);\n        attribute != \"class\";\n        return [\n          instance.add(attribute, value)\n        ];\n      })\n      .asDiffs((diff) => {\n        for(let [e, a, v] of diff.removes) {\n          let instance = this.getInstance(e);\n          if(!instance) continue;\n\n          else if(a === \"tagname\") continue;\n          else if(a === \"children\") continue;\n          else if(a === \"tag\") continue;\n          else if(a === \"sort\") continue; // I guess..?\n          else if(a === \"eve-auto-index\") continue; // I guess..?\n          else if(a === \"text\") instance.textContent = null;\n          else if(a === \"style\") this.removeStyleInstance(v, e);\n          else this.removeAttribute(instance, a, asJS(v)!);\n        }\n\n        for(let [e, a, v] of diff.adds) {\n          let instance = this.getInstance(e);\n          if(!instance) throw new Error(`Orphaned instance '${e}'`);\n\n          else if((a === \"tagname\")) continue;\n          else if(a === \"children\") continue;\n          else if(a === \"tag\") continue;\n          else if(a === \"sort\") this.insertSortedChild(instance.parentElement, instance, v);\n          else if(a === \"eve-auto-index\") this.insertAutoSortedChild(instance.parentElement, instance, v);\n          else if(a === \"text\") instance.textContent = \"\"+v;\n          else if(a === \"style\") this.addStyleInstance(v, e);\n          else this.addAttribute(instance, a, asJS(v)!);\n        }\n      })\n\n      .watch(\"Export static classes.\", ({find, not, lookup}) => {\n        let instance = find(\"{{tagPrefix}}/instance\");\n        let elem = instance.element;\n        let klass = elem.class;\n        not(() => lookup(klass));\n\n        return [instance.add(\"class\", klass)];\n      })\n      .asDiffs((diff) => {\n        for(let [e, a, v] of diff.removes) {\n          let instance = this.getInstance(e);\n          if(!instance) continue;\n\n          for(let klass of (\"\"+v).split(\" \")) {\n            if(!klass) continue;\n            instance.classList.remove(klass);\n          }\n        }\n\n        for(let [e, a, v] of diff.adds) {\n          let instance = this.getInstance(e);\n          if(!instance) throw new Error(`Orphaned instance '${e}'`);\n\n          for(let klass of (\"\"+v).split(\" \")) {\n            if(!klass) continue;\n            instance.classList.add(klass);\n          }\n        }\n      })\n\n      .bind(\"Elements with a dynamic class record apply classes for each true attribute.\", ({find, lookup, record}) => {\n        let element = find(\"{{tagPrefix}}/element\");\n        let {attribute, value} = lookup(element.class);\n        value == \"true\";\n        return [\n          element.add(\"class\", attribute)\n        ];\n      });\n  }\n}\n"
  },
  {
    "path": "src/watchers/editor.ts",
    "content": "//--------------------------------------------------------------------\n// Editor\n//--------------------------------------------------------------------\n\nimport {Watcher, Program, RawMap, RawValue, RawEAV, forwardDiffs, appendAsEAVs, createId} from \"../watchers/watcher\";\nimport {CompilerWatcher} from \"../watchers/compiler\";\n\nclass EditorWatcher extends Watcher {\n  editor: Program;\n  setup() {\n    this.editor = this.createEditor();\n    let {editor, program} = this;\n\n    editor\n      .bind(\"Draw the root editor view.\", ({find, record}) => {\n        let editor = find(\"editor/root\");\n\n        return [\n          record(\"editor/view\", \"ui/row\", {editor}).add(\"children\", [\n            record(\"editor/nav\", \"ui/column\", {editor, sort: 0}),\n            record(\"editor/main\", \"ui/column\", {editor, sort: 1}).add(\"children\", [\n              record(\"ui/row\", {editor, sort: 0, class: \"editor-block-header\"}).add(\"children\", [\n                record(\"editor/block/description\", \"ui/column\", {editor}),\n                record(\"editor/block/storyboard\", \"ui/row\", {editor})\n              ]),\n              record(\"ui/row\", \"editor/block/content\", {editor, sort: 1})\n            ])\n          ])\n        ];\n      })\n      .bind(\"Attach the current frame type to the editor content window.\", ({find}) => {\n        let editor = find(\"editor/root\");\n        let {active_frame} = editor;\n        let content = find(\"editor/block/content\", {editor});\n        return [content.add(\"type\", active_frame.type)];\n      })\n\n\n      .bind(\"A block's next node sort is it's max node sort + 1 (or 1).\", ({find, choose, gather}) => {\n        let block = find(\"block\");\n        // @NOTE: We can only reliably use an aggregate in a choose if the choose inputs are *only* used in the aggregate grouping.\n        let [sort] = choose(() => {\n          let {node} = block;\n          1 == gather(node.sort, node).per(block).sort(\"down\");\n          return node.sort + 1;\n        }, () => 1);\n        return [block.add(\"next_node_sort\", sort)];\n      })\n\n      .bind(\"A node is another node's parent if it has an AV who's V is the other node's entity\", ({find}) => {\n        let parent = find(\"node\");\n        let node = find(\"node\");\n        node != parent;\n        let {attribute} = parent;\n        attribute.value == node.entity;\n        return [node.add({parent, parent_field: attribute.attribute})];\n      })\n\n      .bind(\"Mark nodes without parents as root nodes.\", ({find, not}) => {\n        let node = find(\"node\");\n        not(() => node.parent);\n        return [node.add(\"tag\", \"root-node\")];\n      })\n\n      .commit(\"A node with no entity creates one.\", ({find, not, record}) => {\n        let node = find(\"node\");\n        not(() => node.entity);\n        return [node.add(\"entity\", record(\"entity\", {node, sort: node.sort}))];\n      })\n\n      .commit(\"If a node's attribute is a record and it's not already a subnode, fix that.\", ({find, not, record}) => {\n        let editor = find(\"editor/root\");\n        let {active_block} = editor;\n        let {node} = active_block;\n        let {attribute} = node;\n        find(\"editor/existing-node-attribute\", {node, text: attribute.attribute, is_record: \"true\"});\n        not(() => attribute.value == find(\"node\").entity);\n        let sort = active_block.next_node_sort;\n\n        let subnode;\n        return [\n          active_block.add(\"node\", [\n            subnode = record(\"node\", \"derived-subnode\", {sort, entity: record(\"entity\", {sort, _node: node, _attr: attribute.attribute})})\n          ]),\n          attribute.remove(\"value\").add(\"value\", subnode.entity)\n        ];\n      })\n\n      .commit(\"A node is only derived once.\", ({find, record}) => {\n        let node = find(\"node\", \"derived-subnode\");\n        return [node.remove(\"tag\", \"derived-subnode\")];\n      })\n\n      .commit(\"Deriving a subnode closes it's parent and opens it.\", ({find, record}) => {\n        let node = find(\"node\", \"derived-subnode\");\n        let tree_node = find(\"editor/node-tree/node\", {node});\n        let parent_tree_node = find(\"editor/node-tree/node\", {node: node.parent});\n        parent_tree_node.open;\n        return [\n          tree_node.add(\"open\", \"true\"),\n          parent_tree_node.remove(\"open\")\n        ];\n      })\n\n      .commit(\"A node with an empty value has no value at all.\", ({find}) => {\n        let node = find(\"node\");\n        let {attribute} = node;\n        attribute.value == \"\";\n        return [attribute.remove(\"value\", \"\")];\n      })\n\n      .bind(\"A node's name is it's parent_field if it has one, or it's tag attribute.\", ({find, choose}) => {\n        let node = find(\"node\");\n        let [name] = choose(\n          () => node.parent_field,\n          () => {\n            let {attribute} = node;\n            attribute.attribute == \"tag\";\n            return attribute.value;\n          },\n          () => \"???\"\n        );\n        return [node.add(\"name\", name)]\n      })\n      .bind(\"A node's label is the uppercased first character of it's name.\", ({find, lib:{string}}) => {\n        let node = find(\"node\");\n        let {name} = node;\n        let label = string.uppercase(string.get(name, 1));\n        return [node.add(\"label\", label)];\n      })\n      .bind(\"A node's color is derived from it's sort.\", ({find, lib:{math}}) => {\n        let node = find(\"node\");\n        let {color} = find(\"node-color\", {sort: math.mod(node.sort - 1, 5) + 1})\n        return [node.add(\"color\", color)];\n      })\n\n    // this.navigation();\n    this.header();\n\n    this.nodeTree();\n    this.queryEditor();\n\n    this.moleculeGenerator();\n    this.moleculeLayout();\n\n    this.infobox();\n\n    this.completionGenerator();\n\n    this.fixtures();\n    this.initEditor();\n  }\n\n  initEditor() {\n    const EDITOR_ID = createId();\n    const STYLE_ID = createId();\n\n    const TAG_MARINA_ID = createId();\n    const TAG_MARINARA_ID = createId();\n    const BLOCK_PPL_W_BOATS_ID = createId();\n    const BLOCK_BOAT_TYPES_ID = createId();\n    const FRAME_PPL_W_BOATS_QUERY_ID = createId();\n\n    let fixture:RawEAV[] = [\n      [EDITOR_ID, \"tag\", \"editor/root\"],\n      [STYLE_ID, \"tag\", \"html/element\"],\n      [STYLE_ID, \"tagname\", \"link\"],\n      [STYLE_ID, \"rel\", \"stylesheet\"],\n      [STYLE_ID, \"href\", \"/assets/css/editor.css\"],\n      [\"|init\", \"tag\", \"editor/init\"]\n    ];\n\n    // @NOTE: To get successive layers, multiply offsets by magnitude = ceil(mod(ix - 2, 6) + 2) and connect the dots\n    // @NOTE: Take special care about the 0,0, in ix: 1.\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 1, sort: 1, x: 0, y: 0});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 1, sort: 2, x: 1, y: 0});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 1, sort: 3, x: 1, y: 1});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 1, sort: 4, x: 0, y: 1});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 1, sort: 5, x: -1, y: 0});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 1, sort: 6, x: 0, y: -1});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 1, sort: 7, x: 1, y: -1});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 8, sort: 8, x: 2, y: -1});\n\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 0, sort: 1, x: 0, y: 0});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 0, sort: 2, x: 1, y: 0});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 0, sort: 3, x: 0, y: 1});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 0, sort: 4, x: -1, y: 1});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 0, sort: 5, x: -1, y: 0});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 0, sort: 6, x: -1, y: -1});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 0, sort: 7, x: 0, y: -1});\n    appendAsEAVs(fixture, {tag: \"spiral\", row: 0, sort: 8, x: 1, y: -1});\n\n    appendAsEAVs(fixture, {tag: \"node-color\", sort: 1, color: \"#9926ea\"});\n    appendAsEAVs(fixture, {tag: \"node-color\", sort: 2, color: \"#6c86ff\"});\n    appendAsEAVs(fixture, {tag: \"node-color\", sort: 3, color: \"red\"});\n    appendAsEAVs(fixture, {tag: \"node-color\", sort: 4, color: \"orange\"});\n    appendAsEAVs(fixture, {tag: \"node-color\", sort: 5, color: \"green\"});\n    appendAsEAVs(fixture, {tag: \"node-color\", sort: 6, color: \"indigo\"});\n\n    this.editor.inputEAVs(fixture);\n  }\n\n  fixtures() {\n    this.editor\n      .commit(\"When the init tag is added, preload the system with sample data.\", ({find, record}) => {\n        let init = find(\"editor/init\");\n        let editor = find(\"editor/root\");\n\n        let block1, frame1, person_node, boat_node, dock_node;\n        return [\n          editor.add(\"block\", [\n            block1 = record(\"block\", {sort: 1}).add({\n              nav_tag: record(\"nav/tag\", {name: \"Marina\"}),\n              name: \"People with boats\",\n              description: \"Add a description...\",\n              storyboard: [\n                frame1 = record(\"frame\", {type: \"query\", sort: 1}),\n                record(\"frame\", {type: \"output\", sort: 2}),\n              ],\n\n              node: [\n                // dock_node = record(\"node\", {sort: 3, entity: record(\"entity\", {z: 1})}).add(\"attribute\", [\n                //   record({attribute: \"state\", z: 11})\n                // ]),\n                // boat_node = record(\"node\", {sort: 2, entity: record(\"entity\", {z: 2})}).add(\"attribute\", [\n                //   record({attribute: \"type\", value: \"yacht\", z:21}),\n                //   record({attribute: \"name\", z:22}),\n                //   record({attribute: \"dock\", value: dock_node.entity, z:23})\n                // ]),\n                // person_node = record(\"node\", {sort: 1, entity: record(\"entity\", {z: 3})}).add(\"attribute\", [\n                //   record({attribute: \"tag\", value: \"person\", z:31}),\n                //   // record({attribute: \"tag\"}),\n                //   record({attribute: \"age\", z:32}),\n                //   record({attribute: \"boat\", value: boat_node.entity, z:33})\n                // ]),\n              ]\n            })\n          ]),\n          record(\"node\", {sort: 0}), // Magic node. Do not remove. (Workaround sort bug)\n          editor.add({active_block: block1, active_frame: frame1}),\n          init.remove()\n        ];\n      })\n\n      .commit(\"DEBUG: Add a spiral range to iterate over when expanding the spiral.\", ({find, record}) => {\n        find(\"editor/root\");\n        return [\n          record(\"spiral-range\", {ix: 9}),\n          record(\"spiral-range\", {ix: 10}),\n          record(\"spiral-range\", {ix: 11}),\n          record(\"spiral-range\", {ix: 12}),\n          record(\"spiral-range\", {ix: 13}),\n          record(\"spiral-range\", {ix: 14}),\n          record(\"spiral-range\", {ix: 15}),\n          record(\"spiral-range\", {ix: 16}),\n\n          record(\"range\", {ix: 1}),\n          record(\"range\", {ix: 2}),\n          record(\"range\", {ix: 3}),\n          record(\"range\", {ix: 4}),\n          record(\"range\", {ix: 5}),\n          record(\"range\", {ix: 6}),\n          record(\"range\", {ix: 7}),\n          record(\"range\", {ix: 8}),\n          record(\"range\", {ix: 8}),\n          record(\"range\", {ix: 9}),\n          record(\"range\", {ix: 10}),\n          record(\"range\", {ix: 11}),\n          record(\"range\", {ix: 12}),\n          record(\"range\", {ix: 13}),\n          record(\"range\", {ix: 14}),\n          record(\"range\", {ix: 15}),\n          record(\"range\", {ix: 16}),\n        ];\n      })\n  }\n\n  createEditor() {\n    let editor = new Program(\"Editor\");\n    editor.attach(\"compiler\");\n\n    editor.attach(\"ui\");\n    editor.attach(\"shape\");\n\n    let compiler = editor.attach(\"compiler\") as CompilerWatcher;\n    compiler.injectInto(this.program);\n    compiler.registerWatcherFunction(\"send-to-editor\", forwardDiffs(editor, \"send-to-editor\"));\n\n    return editor;\n  }\n\n  //--------------------------------------------------------------------\n  // Navigation\n  //--------------------------------------------------------------------\n\n  navigation() {\n    this.editor\n      .bind(\"Populate the nav bar with the program's block tags.\", ({find, record}) => {\n        let nav = find(\"editor/nav\");\n        let tag = nav.editor.block.nav_tag;\n        return [\n          nav.add(\"children\", [\n            record(\"editor/nav/tag\", \"ui/column\", {editor: nav.editor, sort: tag.name, nav_tag: tag}).add(\"children\", [\n              record(\"ui/text\", {sort: 0, text: tag.name})\n            ])\n          ])\n        ];\n      })\n\n      .bind(\"Populate nav tags with the blocks that have them.\", ({find, choose, record}) => {\n        let tag = find(\"editor/nav/tag\");\n        let block = tag.editor.block;\n        block.nav_tag == tag.nav_tag;\n\n        let [name] = choose(() => block.name, () => \"Untitled Block\");\n\n        return [\n          tag.add(\"children\", [\n            record(\"editor/nav/block\", \"ui/text\", {editor: tag.editor, nav_tag: tag.nav_tag, block, text: name, sort: name})\n          ])\n        ];\n      });\n  }\n\n  //--------------------------------------------------------------------\n  // Header\n  //--------------------------------------------------------------------\n\n  header() {\n    this.editor\n      .bind(\"Populate the block description for the active block.\", ({find, choose, record}) => {\n        let description = find(\"editor/block/description\");\n        let active_block = description.editor.active_block;\n\n        let [name] = choose(() => active_block.name, () => \"Untitled Block\");\n        let [text] = choose(() => active_block.description, () => \"\");\n\n        return [\n          description.add(\"children\", [\n            record(\"ui/text\", {sort: 0, text: name, class: \"editor-block-title\"}),\n            record(\"ui/text\", {sort: 1, text})\n          ])\n        ];\n      })\n\n      .bind(\"Populate the block storyboard for the active block.\", ({find, record}) => {\n        let storyboard = find(\"editor/block/storyboard\");\n        let {editor} = storyboard;\n        let {active_block} = editor;\n        let frame = active_block.storyboard;\n        return [\n          storyboard.add(\"children\", [\n            record(\"editor/block/frame\", \"ui/column\", {editor, sort: frame.sort, frame}).add(\"children\", [\n              record(\"ui/text\", {text: frame.type})\n            ])\n          ])\n        ];\n      })\n\n      .bind(\"Mark the active frame.\", ({find}) => {\n        let editor = find(\"editor/root\");\n        let {active_frame:frame} = editor;\n        let frame_elem = find(\"editor/block/frame\", {frame});\n        return [frame_elem.add(\"class\", \"active\")];\n      })\n\n      .commit(\"Clicking a frame activates it.\", ({find}) => {\n        let frame_elem = find(\"editor/block/frame\");\n        find(\"html/event/click\", {element: frame_elem});\n        let {frame, editor} = frame_elem;\n        return [editor.remove(\"active_frame\").add(\"active_frame\", frame)];\n      })\n\n      .bind(\"Add new frame button to the storyboard.\", ({find, record}) => {\n        let storyboard = find(\"editor/block/storyboard\");\n        let {editor} = storyboard;\n        let {active_block} = editor;\n        return [\n          storyboard.add(\"children\", [\n            record(\"editor/new-frame\", \"editor/block/frame\", \"ui/column\", {editor, sort: Infinity})\n          ])\n        ];\n      })\n\n      .commit(\"Clicking the new frame button opens it.\", ({find}) => {\n        let new_frame = find(\"editor/new-frame\");\n        find(\"html/event/click\", \"html/direct-target\", {element: new_frame});\n        return [\n          new_frame.add(\"open\", \"true\")\n        ];\n      })\n\n      .bind(\"When the new frame is open, display a list of editor types to choose from.\", ({find, record}) => {\n        let new_frame = find(\"editor/new-frame\", {open: \"true\"});\n        let {editor} = new_frame;\n        return [\n          new_frame.add(\"children\", [\n            record(\"editor/new-frame/type\", \"ui/button\", {editor, text: \"Query\", type: \"query\", class: \"flat\"}),\n            record(\"editor/new-frame/type\", \"ui/button\", {editor, text: \"Output\", type: \"output\", class: \"flat\"}),\n          ])\n        ];\n      })\n\n      .commit(\"Clicking a new frame type adds a frame of that type and closes the new frame button.\", ({find, gather, choose, record}) => {\n        let new_frame_type = find(\"editor/new-frame/type\");\n        find(\"html/event/click\", \"html/direct-target\", {element: new_frame_type});\n        let {type, editor} = new_frame_type;\n        let new_frame = find(\"editor/new-frame\", {editor});\n        let {active_block:block} = editor;\n        let [ix] = choose(() => gather(block.storyboard).per(block).count() + 1, () => 1);\n        return [\n          new_frame.remove(\"open\"),\n          block.add(\"storyboard\", [\n            record(\"frame\", {block, type, sort: ix})\n          ])\n        ];\n      });\n  }\n\n  //--------------------------------------------------------------------\n  // Node Tree\n  //--------------------------------------------------------------------\n\n  nodeTree() {\n    this.editor\n      .bind(\"Decorate the node tree as a column.\", ({find, record}) => {\n        let tree = find(\"editor/node-tree\");\n        let side = 21, lineWidth = 1, strokeStyle = \"#AAA\";\n        return [tree.add({tag: \"ui/column\"}).add(\"children\", [\n          record(\"editor/node-tree/node\", \"editor/node-tree/node/new\", \"ui/row\", {sort: Infinity, tree}).add(\"children\", [\n            record(\"editor/node-tree/node/hex\", \"shape/hexagon\", {\n              sort: 0, tree, side, lineWidth, strokeStyle\n            }).add(\"content\", [\n              record(\"ui/button\", {icon: \"android-add\"})\n            ])\n          ])\n        ])];\n      })\n\n      .bind(\"When the new node is open, it has an input for specifying the tag.\", ({find, record}) => {\n        let new_node = find(\"editor/node-tree/node/new\", {open: \"true\"});\n        return [\n          new_node.add(\"children\", [\n            record(\"editor/node-tree/node/new/tag\", \"ui/autocomplete\", \"html/trigger-focus\", {sort: 2, new_node, placeholder: \"tag...\"})\n          ])\n        ];\n      })\n\n      .bind(\"Each root node is an element in the tree.\", ({find, record}) => {\n        let tree = find(\"editor/node-tree\");\n        let {node} = tree;\n        node.tag == \"root-node\";\n        return [\n          tree.add(\"children\", [\n            record(\"editor/node-tree/node\", {tree, node, sort: node.sort})\n          ])\n        ];\n      })\n\n      .bind(\"A node consists of a hex, and a pattern.\", ({find, record}) => {\n        let tree_node = find(\"editor/node-tree/node\");\n        let {tree, node} = tree_node;\n        let {color, label} = node;\n        let side = 21, lineWidth = 1, strokeStyle = \"#AAA\";\n        return [\n          tree_node.add({tag: \"ui/row\"}).add(\"children\", [\n            record(\"editor/node-tree/node/hex\", \"shape/hexagon\", {sort: 0, tree_node, side, lineWidth, strokeStyle}).add(\"content\", [\n              record(\"ui/text\", {text: label, style: record({color})})\n            ]),\n            record(\"editor/node-tree/node/pattern\", {sort: 1, tree_node})\n          ])\n        ];\n      })\n\n      .bind(\"A node pattern is a column of fields on the node.\", ({find, record}) => {\n        let node_pattern = find(\"editor/node-tree/node/pattern\");\n        let {tree_node} = node_pattern;\n        let {name} = tree_node.node;\n\n        return [\n          node_pattern.add({tag: \"ui/column\"}).add(\"children\", [\n            record(\"ui/row\", {sort: 0, node_pattern}).add(\"children\", [\n              record(\"editor/node-tree/node/pattern/name\", \"ui/text\", {tree_node, text: name})\n            ])\n          ])\n        ];\n      })\n\n      .bind(\"If a node has attributes, display them in it's pattern.\", ({find, not, choose, record}) => {\n        let node_pattern = find(\"editor/node-tree/node/pattern\");\n        let {tree_node} = node_pattern;\n        let {node} = tree_node;\n        let {attribute} = node;\n        not(() => {attribute.attribute == \"tag\"; attribute.value == node.name});\n        not(() => attribute.value == find(\"entity\"));\n        let [sort] = choose(() => `z${attribute.sort}`, () => attribute.attribute, () => 999);\n        return [\n          node_pattern.add(\"children\", [\n            record(\"editor/node-tree/fields\", \"ui/column\", {sort: 1, tree_node, attribute}).add(\"children\", [\n              record(\"editor/node-tree/node/pattern/field\", \"ui/row\", {sort, tree_node, attribute})\n            ])\n          ])\n        ];\n      })\n      .bind(\"A node displays attributes as text\", ({find, record}) => {\n        let pattern_field = find(\"editor/node-tree/node/pattern/field\");\n        let {tree_node, attribute} = pattern_field;\n        let field = attribute.attribute;\n        return [\n          pattern_field.add(\"children\", [\n            record(\"ui/text\", {sort: 1, text: field})\n          ])\n        ];\n      })\n\n      .bind(\"If a node's attribute has a value, display them in it's field.\", ({find, not, record}) => {\n        let field = find(\"editor/node-tree/node/pattern/field\");\n        let {tree_node, attribute} = field;\n        let {node} = tree_node;\n        not(() => field.open);\n        not(() => {attribute.attribute == \"tag\"; attribute.value == node.name});\n        return [\n          field.add(\"children\", [\n            record(\"editor/node-tree/node/pattern/value\", \"ui/text\", {sort: 2, tree_node, text: attribute.value})\n          ])\n        ];\n      })\n\n      .bind(\"An open field has a value cell even if it's attribute lacks one.\", ({find, choose, record}) => {\n        let field = find(\"editor/node-tree/node/pattern/field\", {open: \"true\"});\n        let {tree_node, attribute} = field;\n        let [value] = choose(() => attribute.value, () => \"\");\n        return [\n          field.add(\"children\", [\n            record(\"editor/node-tree/node/pattern/value\", \"ui/input\", \"html/trigger-focus\", \"html/autosize-input\", {sort: 2, tree_node, attribute, initial: value})\n          ])\n        ];\n      })\n\n      .bind(\"An open node displays controls beneath itself.\", ({find, record}) => {\n        let tree_node = find(\"editor/node-tree/node\", {open: \"true\"});\n        let hex = find(\"editor/node-tree/node/hex\", {tree_node});\n        return [\n          hex.add(\"children\", [\n            record(\"editor/node-tree/node/controls\", \"ui/row\", {tree_node}).add(\"children\", [\n              //record(\"editor/node-tree/node/add-field\", \"ui/button\", {sort: 1, tree_node, icon: \"android-add\"}),\n              record(\"editor/node-tree/node/delete\", \"ui/button\", {sort: 2, tree_node, icon: \"android-close\"})\n            ])\n          ])\n        ];\n      })\n\n      .bind(\"An open node displays delete buttons on its fields.\", ({find, choose, record}) => {\n        let field = find(\"editor/node-tree/node/pattern/field\");\n        let {tree_node, attribute} = field;\n        tree_node.open;\n        return [\n          field.add(\"children\", [\n            record(\"editor/node-tree/node/field/delete\", \"ui/button\", {sort: 0, tree_node, attribute, icon: \"android-close\"})\n          ])\n        ];\n      })\n\n      .bind(\"An open node displays a plus field button in its pattern.\", ({find, record}) => {\n        let tree_node = find(\"editor/node-tree/node\", {open: \"true\"});\n        let node_pattern = find(\"editor/node-tree/node/pattern\", {tree_node});\n        return [\n          node_pattern.add(\"children\", [\n            record(\"ui/column\", {sort: 2, node_pattern, class: \"editor-node-tree-new-field\"}).add(\"children\", [\n              record(\"editor/node-tree/node/pattern/new-field\", \"ui/row\", {tree_node}).add(\"children\", [\n                record(\"editor/node-tree/node/field/new\", \"ui/button\", {sort: 1, tree_node, icon: \"android-add\"}),\n                record(\"editor/node-tree/node/field/new/attribute\", \"ui/autocomplete\", {sort: 2, tree_node, placeholder: \"field...\"}),\n              ])\n            ])\n          ])\n        ];\n      })\n\n      .bind(\"Non root nodes are children of their parent's pattern.\", ({find, record}) => {\n        let node = find(\"node\");\n        let {parent} = node;\n        let tree_node = find(\"editor/node-tree/node\", {node: parent});\n        let node_pattern = find(\"editor/node-tree/node/pattern\", {tree_node});\n        let {tree} = tree_node;\n        tree.node == node;\n\n        return [\n          node_pattern.add(\"children\", [\n            record(\"ui/column\", {sort: 3, node_pattern: node_pattern, class: \"editor-node-tree-subnodes\"}).add(\"children\", [\n              record(\"editor/node-tree/node\", {tree, node, sort: node.sort})\n            ])\n          ])\n        ];\n      })\n\n      .bind(\"Fill tag completions.\", ({find, record}) => {\n        let new_tag = find(\"editor/node-tree/node/new/tag\");\n        let completion = find(\"editor/existing-tag\");\n        return [new_tag.add(\"completion\", completion)];\n      })\n\n      .bind(\"Fill attribute completions.\", ({find, record}) => {\n        let new_attribute = find(\"editor/node-tree/node/field/new/attribute\");\n        let {tree_node} = new_attribute;\n        let completion = find(\"editor/existing-node-attribute\", {node: tree_node.node});\n        return [new_attribute.add(\"completion\", completion)];\n      })\n\n      .bind(\"An open tree node require completions.\", ({find}) => {\n        let tree_node = find(\"editor/node-tree/node\", {open: \"true\"});\n        let {node} = tree_node;\n        return [node.add(\"completing\", \"true\")];\n      })\n\n\n    //--------------------------------------------------------------------\n    // Node Tree Interaction\n    //--------------------------------------------------------------------\n    this.editor\n      .commit(\"Clicking a node's hex opens it.\", ({find, not}) => {\n        let hex = find(\"editor/node-tree/node/hex\");\n        find(\"html/event/click\", {element: hex});\n        let {tree_node} = hex;\n        not(() => tree_node.open);\n        return [tree_node.add(\"open\", \"true\")];\n      })\n      .commit(\"Clicking a node's name opens it.\", ({find, not}) => {\n        let name_elem = find(\"editor/node-tree/node/pattern/name\");\n        find(\"html/event/click\", {element: name_elem});\n        let {tree_node} = name_elem;\n        not(() => tree_node.open);\n        return [tree_node.add(\"open\", \"true\")];\n      })\n      .commit(\"Clicking an open node's hex closes it.\", ({find}) => {\n        let hex = find(\"editor/node-tree/node/hex\");\n        find(\"html/event/click\", {element: hex});\n        let {tree_node} = hex;\n        tree_node.open;\n        return [tree_node.remove(\"open\")];\n      })\n      .commit(\"Clicking outside an open tree node closes it.\", ({find, not}) => {\n        let tree_node = find(\"editor/node-tree/node\", {open: \"true\"});\n        find(\"html/event/click\");\n        not(() =>  find(\"html/event/click\", {element: tree_node}));\n        return [tree_node.remove(\"open\")];\n      })\n      .commit(\"Clicking inside a child node of an open tree node closes it.\", ({find}) => {\n        let tree_node = find(\"editor/node-tree/node\", {open: \"true\"});\n        let child_node = find(\"editor/node-tree/node\");\n        child_node.node.parent == tree_node.node;\n        find(\"html/event/click\", {element: child_node});\n        return [tree_node.remove(\"open\")];\n      })\n\n      .bind(\"Clicking the delete node button removes its node from the block.\", ({find, record}) => {\n        let delete_node = find(\"editor/node-tree/node/delete\");\n        find(\"html/event/click\", {element: delete_node})\n        let {tree_node} = delete_node;\n        let {node} = tree_node;\n        return [record(\"editor/event/delete-node\", {node})];\n      })\n      .bind(\"Deleting a node deletes its children.\", ({find, choose, gather, record}) => {\n        let {node:parent} = find(\"editor/event/delete-node\");\n        let node = find(\"node\", {parent});\n        return [record(\"editor/event/delete-node\", {node})];\n      })\n      .commit(\"Deleting a node nukes it and removes it from it's blocks.\", ({find, choose, gather, record}) => {\n        let {node} = find(\"editor/event/delete-node\");\n        let {block} = find(\"editor/root\");\n        return [\n          block.remove(\"node\", node),\n          node.remove()\n        ];\n      })\n      .commit(\"Deleting a node removes it from its parent.\", ({find, choose, gather, record}) => {\n        let {node:child} = find(\"editor/event/delete-node\");\n        let node = find(\"node\");\n        let {attribute} = node;\n        attribute.value == child.entity;\n        return [\n          node.remove(\"attribute\", attribute),\n          //attribute.remove()\n        ];\n      })\n\n      .commit(\"Clicking the new node button opens it.\", ({find, not, record}) => {\n        let new_node = find(\"editor/node-tree/node/new\");\n        not(() => new_node.open);\n        find(\"html/event/click\", {element: new_node})\n        return [new_node.add(\"open\", \"true\")];\n      })\n      .commit(\"Clicking outside an open new node closes it.\", ({find, not, record}) => {\n        let new_node = find(\"editor/node-tree/node/new\", {open: \"true\"});\n        find(\"html/event/click\");\n        not(() =>  find(\"html/event/click\", {element: new_node}));\n        return [new_node.remove(\"open\")];\n      })\n      .bind(\"Clicking the new node save button saves it.\", ({find, not, record}) => {\n        let save_new = find(\"editor/node-tree/node/new/save\");\n        find(\"html/event/click\", {element: save_new});\n        let {new_node} = save_new;\n        return [record(\"editor/event/save-node\", {new_node})];\n      })\n      .bind(\"selecting a tag in the new node autocomplete saves it.\", ({find, not, record}) => {\n        let tag_autocomplete = find(\"editor/node-tree/node/new/tag\");\n        let {new_node, selected} = tag_autocomplete;\n        return [record(\"editor/event/save-node\", {new_node})];\n      })\n      .commit(\"Saving a new node commits, resets, and closes it.\", ({find, not, record}) => {\n        let {new_node} = find(\"editor/event/save-node\");\n        let tag_autocomplete = find(\"editor/node-tree/node/new/tag\");\n        let {value} = tag_autocomplete;\n        let tag_input = find(\"ui/autocomplete/input\", {autocomplete: tag_autocomplete});\n        value != \"\";\n        let {tree} = new_node;\n        let {active_block} = tree.editor;\n        let sort = active_block.next_node_sort;\n\n        return [\n          active_block.add(\"node\", [\n            record(\"node\", {sort, attribute: record({sort, attribute: \"tag\", value})})\n          ]),\n          new_node.remove(\"open\"),\n          tag_input.remove(\"value\")\n        ];\n      })\n\n      .commit(\"Clicking a field opens it.\", ({find, not}) => {\n        let field = find(\"editor/node-tree/node/pattern/field\");\n        find(\"html/event/click\", {element: field});\n        not(() => field.open);\n        return [field.add(\"open\", \"true\")];\n      })\n      .commit(\"Clicking outside an open field closes it.\", ({find, not}) => {\n        let field = find(\"editor/node-tree/node/pattern/field\", {open: \"true\"});\n        find(\"html/event/click\");\n        not(() =>  find(\"html/event/click\", {element: field}));\n        return [field.remove(\"open\")];\n      })\n\n      .commit(\"Clicking the new field save button focuses it's autocomplete.\", ({find}) => {\n        let add_field = find(\"editor/node-tree/node/field/new\");\n        let {tree_node} = add_field;\n        let event = find(\"html/event/click\", {element: add_field})\n        let field_autocomplete = find(\"editor/node-tree/node/field/new/attribute\", {tree_node});\n        return [field_autocomplete.add(\"tag\", \"html/trigger-focus\")];\n      })\n\n      .bind(\"Clicking the new field save button saves it.\", ({find, record}) => {\n        let add_field = find(\"editor/node-tree/node/field/new\");\n        let event = find(\"html/event/click\", {element: add_field})\n        let {tree_node} = add_field;\n        let field_autocomplete = find(\"editor/node-tree/node/field/new/attribute\", {tree_node});\n        field_autocomplete.value != \"\";\n        return [\n          record(\"editor/event/save-field\", {node: tree_node.node, attribute: field_autocomplete.value}),\n          record(\"ui/event/clear\", {autocomplete: field_autocomplete})\n        ];\n      })\n      .bind(\"Selecting a completion in the new field autocomplete saves it.\", ({find, not, record}) => {\n        let field_autocomplete = find(\"editor/node-tree/node/field/new/attribute\");\n        let {tree_node, selected} = field_autocomplete;\n        return [\n          record(\"editor/event/save-field\", {node: tree_node.node, attribute: selected.text}),\n          record(\"ui/event/clear\", {autocomplete: field_autocomplete})\n        ];\n      })\n\n      .commit(\"Saving a new field adds a new attribute to the node.\", ({find, choose, gather, record}) => {\n        let {node, attribute} = find(\"editor/event/save-field\");\n        attribute != \"\";\n\n        // @FIXME: busted as frig...\n        // let [count] = choose(() => {\n        //   let {attribute} = node;\n        //   1 == gather(attribute.sort).per(node).sort(\"down\");\n        //   return attribute.sort + 1;\n        // }, () => 1);\n        return [node.add(\"attribute\", record(\"node/attribute\", {sort: \"@FIXME\", attribute}))];\n      })\n      .commit(\"Clicking the delete field button removes its attribute from the node.\", ({find, choose, gather, record}) => {\n        let delete_field = find(\"editor/node-tree/node/field/delete\");\n        find(\"html/event/click\", {element: delete_field})\n        let {tree_node, attribute} = delete_field;\n        let {node} = tree_node;\n        return [\n          node.remove(\"attribute\", attribute),\n          attribute.remove()\n        ];\n      })\n\n      .bind(\"Blurring a field's value input saves it.\", ({find, record}) => {\n        let field_value = find(\"editor/node-tree/node/pattern/value\");\n        let {value} = find(\"html/event/blur\", {element: field_value});\n        let {tree_node, attribute} = field_value;\n        return [record(\"editor/event/save-value\", {tree_node, attribute, value})];\n      })\n      .commit(\"Saving a field value commits it to the attribute.\", ({find, record}) => {\n        let {attribute, value} = find(\"editor/event/save-value\");\n        return [attribute.remove(\"value\").add(\"value\", value)];\n      })\n  }\n\n  //--------------------------------------------------------------------\n  // Query Editor\n  //--------------------------------------------------------------------\n\n  queryEditor() {\n    this.editor\n      .bind(\"Display a node tree for the active block.\", ({find, record}) => {\n        let content = find(\"editor/block/content\", {type: \"query\"});\n        let {editor} = content;\n        return [\n          content.add(\"children\", [\n            record(\"editor/node-tree\", \"editor/query-tree\", {sort: 1, editor}),\n            record(\"editor/molecule-list\", \"editor/query-molecules\", {sort: 2, editor})\n          ])\n        ]\n      })\n      .bind(\"Fill the tree with the active block's nodes.\", ({find, record}) => {\n        let node_tree = find(\"editor/query-tree\");\n        let {editor} = node_tree;\n        return [node_tree.add(\"node\", editor.active_block.node)];\n      })\n      .bind(\"Fill the list with the active block's molecules.\", ({find, record}) => {\n        let molecule_list = find(\"editor/query-molecules\");\n        let {editor} = molecule_list;\n        let molecule = find(\"editor/molecule\", {block: editor.active_block})\n        return [molecule_list.add(\"molecule\", molecule)];\n      })\n  }\n\n  //--------------------------------------------------------------------\n  // Molecule Generator\n  //--------------------------------------------------------------------\n\n  moleculeGenerator() {\n    this.editor\n      .bind(\"Create a molecule generator for the active block if it has any nodes.\", ({find, record}) => {\n        let editor = find(\"editor/root\");\n        let {active_block:block} = editor;\n        block.node.attribute;\n        return [\n          block.add(\"molecule_generator\", [\n            record(\"editor/molecule/generator\", \"eve/compiler/block\", {block, name: \"Generate molecules.\", type: \"watch\", watcher: \"send-to-editor\"})\n          ])\n        ];\n      })\n      .bind(\"Create an atom record and output for each node of the block with attributes.\", ({find, record}) => {\n        let generator = find(\"editor/molecule/generator\");\n        let {block} = generator;\n        let {node} = block;\n        return [\n          node.entity.add(\"tag\", \"eve/compiler/var\"),\n          generator.add(\"constraint\", [\n            record(\"editor/atom/record\", \"eve/compiler/record\", {generator, node, record: node.entity}).add(\"attribute\", [\n              //@FIXME: Bogus scan to hint to the compiler watcher that it shouldn't try to gen an id.\n              record({attribute: \"tag\", value: record(\"editor/bogus-var\", \"eve/compiler/var\", {node})})\n            ]),\n            record(\"editor/atom/output\", \"eve/compiler/output\", {\n              generator, node, record: record(\"editor/atom/output/entity\", \"eve/compiler/var\", {node})\n            }).add(\"attribute\", [\n              record({attribute: \"tag\", value: \"editor/atom\"}),\n              record({attribute: \"record\", value: node.entity}),\n              record({attribute: \"node\", value: node})\n            ]),\n            record(\"editor/record/output\", \"eve/compiler/output\", {generator, node, record: node.entity}).add(\"attribute\", [\n              record({attribute: \"tag\", value: \"editor/record\"})\n            ])\n          ])\n        ];\n      })\n      .bind(\"Attributes with no value are free fields.\", ({find, not, record}) => {\n        let generator = find(\"editor/molecule/generator\");\n        let {block} = generator;\n        let {node} = block;\n        let {attribute} = node;\n        not(() => attribute.value);\n        return [record(\"editor/molecule/free-field\", \"eve/compiler/var\", {node, attribute})];\n      })\n      .bind(\"Attach attributes to atom records and outputs.\", ({find, choose, record}) => {\n        let atom_record = find(\"editor/atom/record\");\n        let {generator, node} = atom_record;\n        let record_output = find(\"editor/record/output\", {node});\n        let {attribute} = node;\n        let [value] = choose(\n          () => attribute.value,\n          () => find(\"editor/molecule/free-field\", {node, attribute})\n        );\n        let [identifying] = choose(\n          () => { attribute.value == find(\"node\").entity; return \"eve/compiler/attribute/non-identity\"; },\n          () => \"eve/compiler/attribute/identity\"\n        );\n\n        return [\n          atom_record.add(\"attribute\", record({attribute: attribute.attribute, value})),\n          record_output.add(\"attribute\", [\n            record({tag: identifying, attribute: attribute.attribute, value}),\n          ])\n        ];\n      })\n      .bind(\"Create a molecule output for each root node.\", ({find, record}) => {\n        let generator = find(\"editor/molecule/generator\");\n        let {block} = generator;\n        let {node} = block;\n        node.tag == \"root-node\";\n        let atom_output_entity = find(\"editor/atom/output/entity\", {node});\n        let molecule_entity;\n        return [\n          molecule_entity = record(\"editor/molecule/entity\", \"eve/compiler/var\", {node}),\n          generator.add(\"constraint\", [\n            record(\"editor/molecule/output\", \"eve/compiler/output\", {generator, node, record: molecule_entity})\n              .add(\"parent\", node)\n              .add(\"attribute\", [\n                record({attribute: \"atom\", value: atom_output_entity}),\n                record({attribute: \"tag\", value: \"editor/molecule\"}),\n                record({attribute: \"block\", value: block}),\n                record(\"editor/mol-av\", {attribute: \"node\", value: node}),\n              ])\n          ])\n        ];\n      })\n\n      .bind(\"Attach subnode atoms to their parent's molecules.\", ({find, record}) => {\n        let molecule = find(\"editor/molecule/output\");\n        let {generator} = molecule;\n        let {block} = generator;\n        let {node} = block;\n        node.parent == molecule.parent;\n        let atom_output_entity = find(\"editor/atom/output/entity\", {node});\n        return [\n          molecule.add({\n            parent: node,\n            attribute: record(\"eve/compiler/attribute/non-identity\", {attribute: \"atom\", value: atom_output_entity})\n          })\n        ];\n      })\n  }\n\n  //--------------------------------------------------------------------\n  // Molecule Layout\n  //--------------------------------------------------------------------\n\n  moleculeLayout() {\n    this.editor\n      .bind(\"Decorate a molecule list.\", ({find}) => {\n        let molecule_list = find(\"editor/molecule-list\");\n        return [molecule_list.add({tag: \"ui/row\"})];\n      })\n\n      .bind(\"Draw some molecules.\", ({find, record}) => {\n        let molecule_list = find(\"editor/molecule-list\");\n        let {molecule} = molecule_list;\n        let {atom} = molecule;\n        let side = 21;\n        let molecule_cell;\n        return [\n          molecule_list.add(\"children\", [\n            molecule_cell = record(\"editor/molecule-list/molecule\", \"html/element\", \"html/listener/hover\", {tagname: \"div\", molecule_list, molecule}),\n            molecule_cell.add(\"children\", [\n              record(\"editor/molecule-list/molecule/grid\", \"shape/hex-grid\", {molecule_cell, molecule, side, gap: 5})\n            ]),\n          ])\n        ];\n      })\n\n      .bind(\"Add cells to molecules.\", ({find, record}) => {\n        let molecule_grid = find(\"editor/molecule-list/molecule/grid\");\n        let {molecule_cell} = molecule_grid;\n        let {molecule} = molecule_cell;\n        let {atom} = molecule;\n        let side = 21;\n        return [\n          molecule_grid.add(\"cell\", [\n            record(\"editor/molecule-list/molecule/cell\", {molecule_cell, molecule, atom, side})\n          ])\n        ];\n      })\n\n      .bind(\"A molecule's size is it's largest atom sort.\", ({find, not, record}) => {\n        let cell = find(\"editor/molecule-list/molecule/cell\");\n        let {molecule_cell} = cell;\n        not(() => {\n          let other_cell = find(\"editor/molecule-list/molecule/cell\", {molecule_cell});\n          other_cell.sort > cell.sort\n        });\n        return [\n          molecule_cell.add(\"size\", cell.sort),\n        ];\n      })\n\n      .bind(\"HACK: workaround double choose bug offset + mag.\", ({find, lib:{math}, choose, record}) => {\n        let molecule_cell = find(\"editor/molecule-list/molecule\");\n        let {size} = molecule_cell;\n        // @FIXME: This isn't quite right due to 0,0 offset\n        let offset = math.mod(size - 1, 6);\n        // @FIXME: this measure doesn't take into account the fact that shell size increases...\n        let mag = math.floor((size - 1) / 7) + 1;\n        return [\n          molecule_cell.add({offset, mag}),\n        ];\n      })\n\n      .bind(\"A molecule's width and height are derived from it's size.\", ({find, choose, record}) => {\n        let molecule_cell = find(\"editor/molecule-list/molecule\");\n        let {offset, mag} = molecule_cell;\n        let cell_width = 39 + 5;\n        let cell_height = 44 + 5;\n        let width = cell_width * 3 * mag;\n        let height = cell_height * 3 * mag;\n        let padLeft = width / 2 - cell_width / 2;\n        let padTop = height / 2 - cell_height / 2;\n        return [\n          molecule_cell.add(\"style\", record({width: `${width}px`, height: `${height}px`, \"padding-left\": `${padLeft}px`, \"padding-top\": `${padTop}px`}))\n        ];\n      })\n\n      .bind(\"Populate atom cells from their atoms.\", ({find, choose, record}) => {\n        let atom_cell = find(\"editor/molecule-list/molecule/cell\");\n        let {molecule_cell, molecule, atom, side, x, y} = atom_cell;\n        let {node} = atom;\n        let lineWidth = 1; //, strokeStyle = \"#AAA\";\n        let [strokeStyle] = choose(\n          () => {atom_cell.open; return \"#4444ff\"},\n          () => {atom_cell.tag == \"html/hovered\"; return \"#4444ff\"},\n          () => {molecule_cell.tag == \"html/hovered\"; return \"#aaaaff\"},\n          () => \"#aaa\"\n        );\n        return [\n          atom_cell.add({tag: [\"shape/hexagon\", \"html/listener/hover\"],  side, lineWidth, strokeStyle, x, y}).add(\"content\", [\n            record(\"ui/text\", {atom_cell, text: node.label, style: record({color: node.color})})\n          ])\n        ];\n      })\n\n      .bind(\"Sort atoms by id.\", ({find, gather, record}) => {\n        let atom = find(\"editor/atom\");\n        let molecule = find(\"editor/molecule\", {atom});\n        let {node} = atom;\n        let ix = gather(atom).per(molecule, node).sort();\n        return [atom.add(\"sort\", ix)];\n      })\n\n      .bind(\"Sort atom cells by id.\", ({find, gather, record}) => {\n        let atom_cell = find(\"editor/molecule-list/molecule/cell\");\n        let {molecule, atom} = atom_cell;\n        let ix = gather(atom_cell.atom.node.sort, atom_cell).per(molecule).sort();\n        return [atom_cell.add(\"sort\", ix)];\n      })\n      .bind(\"Position atom cells in a spiral.\", ({find, choose, record}) => {\n        let atom_cell = find(\"editor/molecule-list/molecule/cell\");\n        let {molecule, atom} = atom_cell;\n        let {x, y} = find(\"spiral\", {row: 0, sort: atom_cell.sort});\n        return [atom_cell.add({x, y})];\n      })\n\n      .bind(\"When a molecule is open, display it's infobox.\", ({find, record}) => {\n        let molecule_cell = find(\"editor/molecule-list/molecule\", {open: \"true\"});\n        let {molecule} = molecule_cell;\n        return [molecule_cell.add(\"children\", [\n          record(\"editor/infobox\", {molecule, molecule_cell})\n        ])];\n      })\n\n    //--------------------------------------------------------------------\n    // Molecule Interactions\n    //--------------------------------------------------------------------\n\n    this.editor\n      .commit(\"Clicking a molecule opens it.\", ({find}) => {\n        let molecule_cell = find(\"editor/molecule-list/molecule\");\n        find(\"html/event/click\", {element: molecule_cell});\n        return [molecule_cell.add(\"open\", \"true\")];\n      })\n      .commit(\"Clicking outside an open molecule closes it.\", ({find, not}) => {\n        let molecule_cell = find(\"editor/molecule-list/molecule\", {open: \"true\"});\n        find(\"html/event/click\");\n        not(() => find(\"html/event/click\", {element: molecule_cell}));\n        return [molecule_cell.remove(\"open\")];\n      })\n\n      .commit(\"Clicking an atom opens it.\", ({find}) => {\n        let atom_cell = find(\"editor/molecule-list/molecule/cell\");\n        find(\"html/event/click\", {element: atom_cell});\n        return [atom_cell.add(\"open\", \"true\")];\n      })\n      .commit(\"Clicking an atom closes any other open atoms.\", ({find}) => {\n        let atom_cell = find(\"editor/molecule-list/molecule/cell\");\n        find(\"html/event/click\", {element: atom_cell});\n        let other = find(\"editor/molecule-list/molecule/cell\", {open: \"true\"});\n        other != atom_cell;\n        return [other.remove(\"open\")];\n      })\n\n      .commit(\"Close any open atoms of closed molecules.\", ({find, not}) => {\n        let atom_cell = find(\"editor/molecule-list/molecule/cell\", {open: \"true\"});\n        not(() => atom_cell.molecule_cell.open);\n        return [atom_cell.remove(\"open\")];\n      })\n\n  }\n\n  //--------------------------------------------------------------------\n  // Infobox\n  //--------------------------------------------------------------------\n\n  infobox() {\n    this.editor\n      .bind(\"A molecule infobox is a column of node infoboxes.\", ({find, record}) => {\n        let infobox = find(\"editor/infobox\");\n        return [infobox.add(\"tag\", \"ui/column\")];\n      })\n\n      .bind(\"A molecule infobox has a node infobox for each unique node.\", ({find, record}) => {\n        let infobox = find(\"editor/infobox\");\n        let {molecule} = infobox;\n        let {atom} = molecule;\n        let {node} = atom;\n        return [infobox.add(\"children\", [\n          record(\"editor/infobox/node\", {sort: node.sort, infobox, node}).add(\"atom\", atom)\n        ])];\n      })\n\n      .bind(\"A node infobox has the node name, a plus field button.\", ({find, record}) => {\n        let node_infobox = find(\"editor/infobox/node\");\n        let {node} = node_infobox;\n        return [\n          node_infobox.add(\"tag\", \"ui/column\").add(\"children\", [\n            record(\"editor/infobox/node/header\", \"ui/row\", {sort: 1, node_infobox}).add(\"children\", [\n              record(\"editor/infobox/node/name\", \"ui/text\", {sort: 1, node_infobox, text: node.name}),\n            ]),\n            record(\"ui/row\", {sort: 3, node_infobox}).add(\"children\", [\n              record(\"editor/infobox/field/new\", \"ui/button\", {sort: 1, node_infobox, icon: \"android-add\"}),\n              record(\"editor/infobox/field/attribute\", \"ui/autocomplete\", {sort: 2, node_infobox}) // , placeholder: \"field...\"\n            ])\n          ])\n        ];\n      })\n\n      .bind(\"A node infobox with multiple atoms shows a paginator in it's name row.\", ({find, gather, record}) => {\n        let node_infobox = find(\"editor/infobox/node\");\n        let infobox_header = find(\"editor/infobox/node/header\", {node_infobox});\n        let {node, count} = node_infobox;\n        count > 1;\n        return [\n          infobox_header.add(\"children\", [\n            record(\"ui/row\", {sort: 2, node_infobox, class: \"editor-paginator\"}).add(\"children\", [\n              // record(\"ui/button\", {sort: 1, node_infobox, icon: \"arrow-left-b\"}),\n              record(\"ui/text\", {sort: 2, text: `(1/${count})`}),\n              // record(\"ui/button\", {sort: 3, node_infobox, icon: \"arrow-right-b\"}),\n            ])\n          ])\n        ];\n      })\n\n      .bind(\"A node infobox's count is the number of atoms it has that match.\", ({find, gather, record}) => {\n        let node_infobox = find(\"editor/infobox/node\");\n        let {node, infobox} = node_infobox;\n        let {molecule} = infobox;\n        let {atom} = molecule;\n        atom.node == node;\n        let count = gather(atom).per(node).count();\n        return [node_infobox.add(\"count\", count)];\n      })\n\n      .bind(\"A molecule infobox atom's fields are derived from its record AVs. Show the greatest if there are multiple and none are open.\", ({find, lookup, gather, not, record}) => {\n        let node_infobox = find(\"editor/infobox/node\");\n        let {node, atom} = node_infobox;\n        let {attribute, value} = lookup(atom.record);\n        // @FIXME: Strongly coupled to molecule list here. Instead, pass in an `active` attribute on the infobox.\n        not(() => find(\"editor/molecule-list/molecule/cell\", {open: \"true\"}).atom.node == node);\n        // @FIXME: This bug isn't not related, it's due to atom sharing.\n        // not(() => {\n        //   let other_atom = find(\"editor/atom\", {node});\n        //   node_infobox.atom == other_atom;\n        //   other_atom.sort > atom.sort;\n        // })\n        // 2 < gather(atom).per(node).sort();\n\n        // @FIXME: This will be sad with reused atoms.\n        atom.sort < 2;\n\n        attribute != \"tag\";\n        not(() => value.tag);\n\n        return [\n          node_infobox.add(\"children\", [\n            record(\"editor/infobox/atom\", \"ui/field-table\", {sort: 2, node_infobox, atom, record: atom.record}).add(\"field\", [\n              record({node_infobox, record: atom.record, attribute}).add(\"value\", value)\n            ])\n          ])\n        ];\n      })\n\n      .bind(\"A molecule infobox atom's fields are derived from its record AVs. Show the open atom if it exists.\", ({find, lookup, not, record}) => {\n        let node_infobox = find(\"editor/infobox/node\");\n        let {node, atom, infobox} = node_infobox;\n        // @FIXME: Strongly coupled to molecule list here.\n        let atom_cell = find(\"editor/molecule-list/molecule/cell\", {molecule_cell: infobox.molecule_cell, open: \"true\"});\n        atom_cell.atom == atom;\n        let {attribute, value} = lookup(atom.record);\n        attribute != \"tag\";\n        not(() => value.tag);\n\n        return [\n          node_infobox.add(\"children\", [\n            record(\"editor/infobox/atom\", \"ui/field-table\", {sort: 2, node_infobox, atom, record: atom.record}).add(\"field\", [\n              record({node_infobox, record: atom.record, attribute}).add(\"value\", value)\n            ])\n          ])\n        ];\n      })\n\n      .bind(\"Fill infobox attribute completions.\", ({find, record}) => {\n        let new_attribute = find(\"editor/infobox/field/attribute\");\n        let {node_infobox} = new_attribute;\n        let completion = find(\"editor/existing-node-attribute\", {node: node_infobox.node});\n        return [new_attribute.add(\"completion\", completion)];\n      })\n\n      .bind(\"An infobox require completions.\", ({find}) => {\n        let node_infobox = find(\"editor/infobox/node\");\n        let {node} = node_infobox;\n        return [node.add(\"completing\", \"true\")];\n      })\n;\n\n    //--------------------------------------------------------------------\n    // Infobox Interactions\n    //--------------------------------------------------------------------\n\n    this.editor\n      .commit(\"Clicking the infobox new field button focuses it's autocomplete.\", ({find}) => {\n        let add_field = find(\"editor/infobox/field/new\");\n        let {node_infobox} = add_field;\n        let event = find(\"html/event/click\", {element: add_field})\n        let field_autocomplete = find(\"editor/infobox/field/attribute\", {node_infobox});\n        return [field_autocomplete.add(\"tag\", \"html/trigger-focus\")];\n      })\n\n      .bind(\"Selecting a completion in the new field autocomplete saves it.\", ({find, not, record}) => {\n        let field_autocomplete = find(\"editor/infobox/field/attribute\");\n        let {node_infobox, selected} = field_autocomplete;\n        return [\n          record(\"editor/event/save-field\", {node: node_infobox.node, attribute: selected.text}),\n          record(\"ui/event/clear\", {autocomplete: field_autocomplete})\n        ];\n      })\n\n  }\n\n  //--------------------------------------------------------------------\n  // Completion Generator\n  //--------------------------------------------------------------------\n\n  completionGenerator() {\n    //--------------------------------------------------------------------\n    // Tag completions\n    //--------------------------------------------------------------------\n\n    this.program\n      .watch(\"Make existing tags in the program available to the editor.\", ({find, record}) => {\n        let rec = find();\n        return [record(\"editor/existing-tag\", {text: rec.tag})];\n      })\n      .asDiffs(forwardDiffs(this.editor, \"Send tags to editor.\", false));\n\n\n    //--------------------------------------------------------------------\n    // Node -> Attribute completions\n    //--------------------------------------------------------------------\n\n    this.editor\n      .bind(\"Create a completion generator for node -> attribute.\", ({find, record}) => {\n        let node = find(\"node\", {completing: \"true\"});\n        return [\n          record(\"editor/node/attribute/completer\", \"eve/compiler/block\", {node, name: \"Node attribute completer.\", type: \"watch\", watcher: \"send-to-editor\"})\n            .add(\"joined_node\", node)\n        ];\n      })\n      .bind(\"Create a record for each node.\", ({find, record}) => {\n        // @NOTE: This is intentionally local. Do we want it to be block-level filtering vs node-level?\n        let completer = find(\"editor/node/attribute/completer\");\n        let {joined_node:node} = completer;\n        return [\n          node.entity.add(\"tag\", \"eve/compiler/var\"),\n          completer.add(\"constraint\", [\n            record(\"editor/node/record\", \"eve/compiler/record\", {completer, node, record: node.entity}).add(\"attribute\", [\n              //@FIXME: Bogus scan to hint to the compiler watcher that it shouldn't try to gen an id.\n              record({attribute: \"tag\", value: record(\"editor/bogus-var\", \"eve/compiler/var\", {node})})\n            ])\n          ])\n        ];\n      })\n      .bind(\"Attributes with no value are free fields.\", ({find, not, record}) => {\n        let completer = find(\"editor/node/attribute/completer\");\n        let {joined_node:node} = completer;\n        let {attribute} = node;\n        not(() => attribute.value);\n        return [record(\"editor/molecule/free-field\", \"eve/compiler/var\", {node, attribute})]; // @FIXME: Rename this tag something more generic.\n      })\n      .bind(\"Attach attributes to node record.\", ({find, choose, record}) => {\n        let node_record = find(\"editor/node/record\");\n        let {completer, node} = node_record;\n        let {attribute} = node;\n        let [value] = choose(\n          () => attribute.value,\n          () => find(\"editor/molecule/free-field\", {node, attribute})\n        );\n        let [identifying] = choose(\n          () => { attribute.value == find(\"node\").entity; return \"eve/compiler/attribute/non-identity\"; },\n          () => \"eve/compiler/attribute/identity\"\n        );\n\n        return [node_record.add(\"attribute\", record({attribute: attribute.attribute, value}))];\n      })\n      .bind(\"Parent nodes are joined nodes.\", ({find, choose, record}) => {\n        let completer = find(\"editor/node/attribute/completer\");\n        let {joined_node:node} = completer;\n        return [completer.add(\"joined_node\", node.parent)];\n      })\n      .bind(\"The completions are the attributes of any records that still match.\", ({find, record}) => {\n        let completer = find(\"editor/node/attribute/completer\");\n        let {node} = completer;\n        let output_var;\n        let attr_match;\n        let val_match;\n        return [\n          output_var = record(\"eve/compiler/var\", \"editor/node/attribute/completer/output\", {node}),\n          attr_match = record(\"eve/compiler/var\", \"editor/node/attribute/completer/attribute\", {node}),\n          val_match = record(\"eve/compiler/var\", \"editor/node/attribute/completer/value\", {node}),\n\n          completer.add(\"constraint\", [\n            record(\"eve/compiler/output\", {node, record: output_var}).add(\"attribute\", [\n              record({attribute: \"tag\", value: \"editor/existing-node-attribute\"}),\n              record({attribute: \"node\", value: node}),\n              record({attribute: \"text\", value: attr_match}),\n              // @FIXME: We can't gen a choose so we have to send it over and figure out is_record editor-side.\n              record(\"eve/compiler/attribute/non-identity\", {attribute: \"value\", value: val_match}),\n            ]),\n            record(\"eve/compiler/lookup\", {record: node.entity, attribute: attr_match, value: val_match}),\n          ])\n        ];\n      })\n\n      .bind(\"Compute is_record based on the values of existing node attributes.\", ({find, lib:{string}}) => {\n        let existing = find(\"editor/existing-node-attribute\");\n        string[\"index-of\"](existing.value, \"|\"); // @FIXME: hacky gen id detection.\n        return [existing.add(\"is_record\", \"true\")];\n      })\n  }\n}\n\nWatcher.register(\"editor\", EditorWatcher);\n"
  },
  {
    "path": "src/watchers/file.ts",
    "content": "import * as fs from \"fs\";\nimport {Watcher, RawEAV} from \"./watcher\";\nimport {ID} from \"../runtime/runtime\";\n\nexport class FileWatcher extends Watcher {\n\nsetup() {\n  let {program:me} = this;\n  me.load(`\nAttach errors to the associated file\n~~~\nsearch\nerror = [#file/error file code message]\n\nbind\nfile.error += error\n~~~     \n  `)\n  if(fs.readFile) {\n    me.watch(\"Read a file.\", ({find, record, choose}) => {\n      let file = find(\"file/read\");\n      let encoding = choose(() => file.encoding, () => \"utf-8\");\n      return [\n        record({file, path: file.path, encoding})\n      ]\n    })\n    .asObjects<{file:ID, path:string, encoding:string}>(({adds, removes}) => {\n      Object.keys(adds).forEach((id) => {\n        let {file, path, encoding} = adds[id];\n          fs.readFile(path, {encoding}, function(err, contents){\n            if (!err) {\n              me.inputEAVs([[file, \"contents\", contents]]);\n            } else {\n              let id = `${file}|error`\n              let changes:RawEAV[] = [];\n              changes.push(\n                [id, \"tag\", \"file/error\"],\n                [id, \"file\", file],\n                [id, \"code\", `${err.code}`],\n                [id, \"message\", `${err.message}`]\n              );\n              me.inputEAVs(changes);\n            }\n          });\n        })\n      })\n    }\n\n    if(fs.writeFile) {\n      me.watch(\"Write a file.\", ({find, record, choose}) => {\n        let file = find(\"file/write\");\n        let encoding = choose(() => file.encoding, () => \"utf-8\");\n        return [\n          record({file, path: file.path, encoding, contents: file.contents})\n        ]\n      })\n      .asObjects<{file:ID, path:string, contents: string, encoding:string}>(({adds, removes}) => {\n        Object.keys(adds).forEach((id) => {\n          let {file, path, contents, encoding} = adds[id];\n          fs.writeFile(path, contents, {encoding: encoding}, function(err){\n            if (!err) {\n              me.inputEAVs([[file, \"tag\", \"file/complete\"]])\n            } else {\n              let id = `${file}|error`\n              let changes:RawEAV[] = [];\n              changes.push(\n                [id, \"tag\", \"file/error\"],\n                [id, \"file\", file],\n                [id, \"code\", `${err.code}`],\n                [id, \"message\", `${err.message}`]\n              );\n              me.inputEAVs(changes);\n            }\n          });\n        })\n      })\n    }\n  }\n}\n\n\nWatcher.register(\"file\", FileWatcher);"
  },
  {
    "path": "src/watchers/html.ts",
    "content": "import {Watcher, RawValue, RawEAV, RawEAVC, maybeIntern, ObjectDiffs, createId, asJS} from \"./watcher\";\nimport {DOMWatcher, ElemInstance} from \"./dom\";\nimport {ID} from \"../runtime/runtime\";\n\nexport interface Instance extends HTMLElement {__element?: RawValue, __styles?: RawValue[], __sort?: RawValue, listeners?: {[event: string]: boolean}}\n\nexport class HTMLWatcher extends DOMWatcher<Instance> {\n  tagPrefix = \"html\";\n\n  _checkedRadios:{[name:string]: RawValue, [name:number]: RawValue} = {};\n\n  addExternalRoot(tag:string, element:HTMLElement) {\n    let elemId = createId();\n    let eavs:RawEAV[] = [\n      [elemId, \"tag\", tag],\n      [elemId, \"tag\", \"html/root/external\"]\n    ];\n\n    this.instances[elemId] = element;\n    this._sendEvent(eavs);\n  }\n\n  createInstance(id:RawValue, element:RawValue, tagname:RawValue):Instance {\n    let elem:Instance\n    if(tagname === \"svg\") elem = document.createElementNS(\"http://www.w3.org/2000/svg\", tagname as string) as any;\n    else elem = document.createElement(tagname as string);\n\n    //elem.setAttribute(\"instance\", \"\"+maybeIntern(id));\n    //elem.setAttribute(\"element\", \"\"+maybeIntern(element));\n    elem.__element = element;\n    elem.__styles = [];\n    return elem;\n  }\n\n  getInstance(id:RawValue):Instance|undefined {\n    return this.instances[id];\n  }\n\n  createRoot(id:RawValue):Instance {\n    let elem = this.instances[id];\n    if(!elem) throw new Error(`Orphaned instance '${id}'`);\n    document.body.appendChild(elem);\n    return elem;\n  }\n\n  addAttribute(instance:Instance, attribute:RawValue, value:RawValue|boolean):void {\n    // @TODO: Error checking to ensure we don't double-set attributes.\n    if(attribute == \"value\") {\n      if(instance.classList.contains(\"html-autosize-input\") && instance instanceof HTMLInputElement) {\n        instance.size = (instance.value || \"\").length || 1;\n      }\n      (instance as HTMLInputElement).value = \"\"+value;\n    } else if(attribute == \"tag\") {\n      if(value === \"html/autosize-input\" && instance instanceof HTMLInputElement) {\n        setImmediate(() => instance.size = (instance.value || \"\").length || 1);\n      } else if(value === \"html/trigger/focus\" && instance instanceof HTMLInputElement) {\n        setImmediate(() => instance.focus());\n      } else if(value === \"html/trigger/blur\" && instance instanceof HTMLInputElement) {\n        setImmediate(() => instance.blur());\n      } else {\n        instance.setAttribute(attribute, \"\"+value);\n      }\n    } else if(value === false) {\n      instance.removeAttribute(attribute as string);\n    } else {\n      instance.setAttribute(attribute as string, \"\"+maybeIntern(value as RawValue));\n    }\n  }\n\n  removeAttribute(instance:Instance, attribute:RawValue, value:RawValue|boolean):void {\n    // @TODO: Error checking to ensure we don't double-remove attributes or remove the wrong value.\n    instance.removeAttribute(attribute as string);\n    if(attribute === \"value\") {\n      let input = instance as HTMLInputElement;\n      if(input.value === value) input.value = \"\";\n    }\n  }\n\n  _updateURL(tagname = \"url-change\") {\n    let eventId = createId();\n    let eavs:(RawEAV|RawEAVC)[] = [\n      [eventId, \"tag\", \"html/event\"],\n      [eventId, \"tag\", `html/event/${tagname}`]\n    ];\n\n    let hash = window.location.hash.slice(1);\n    let ix = 1;\n    for(let segment of hash.split(\"/\")) {\n      let segmentId = createId();\n      eavs.push(\n        [eventId, \"hash-segment\", segmentId],\n        [segmentId, \"index\", ix],\n        [segmentId, \"value\", segment]\n      );\n      ix += 1;\n    }\n\n    this._sendEvent(eavs);\n  }\n\n  //------------------------------------------------------------------\n  // Event handlers\n  //------------------------------------------------------------------\n\n  _mouseEventHandler(tagname:string) {\n    return (event:MouseEvent) => {\n      let {target} = event;\n      if(!this.isInstance(target)) return;\n\n      let eventId = createId();\n      let eavs:(RawEAV|RawEAVC)[] = [\n        [eventId, \"tag\", \"html/event\"],\n        [eventId, \"tag\", `html/event/${tagname}`],\n        [eventId, \"page-x\", event.pageX],\n        [eventId, \"page-y\", event.pageY],\n        [eventId, \"window-x\", event.clientX],\n        [eventId, \"window-y\", event.clientY],\n\n        [eventId, \"target\", target.__element!]\n      ];\n      let button = event.button;\n\n      if(button === 0) eavs.push([eventId, \"button\", \"left\"]);\n      else if(button === 2) eavs.push([eventId, \"button\", \"right\"]);\n      else if(button === 1) eavs.push([eventId, \"button\", \"middle\"]);\n      else if(button) eavs.push([eventId, \"button\", button]);\n\n      let current:Element|null = target;\n      let elemIds = [];\n      let capturesContextMenu = false;\n      while(current && this.isInstance(current)) {\n        eavs.push([eventId, \"element\", current.__element!]);\n        if(button === 2 && current.listeners && current.listeners[\"context-menu\"] === true) {\n          capturesContextMenu = true;\n        }\n        current = current.parentElement;\n      }\n      // @NOTE: You'll get a mousedown but no mouseup for a right click if you don't capture the context menu,\n      //   so we throw out the mousedown entirely in that case. :(\n      if(button === 2 && !capturesContextMenu) return;\n      if(eavs.length) this._sendEvent(eavs);\n    };\n  }\n\n  _captureContextMenuHandler() {\n    return (event:MouseEvent) => {\n      let captureContextMenu = false;\n      let current:Element|null = event.target as Element;\n      while(current && this.isInstance(current)) {\n        if(current.listeners && current.listeners[\"context-menu\"] === true) {\n          captureContextMenu = true;\n        }\n        current = current.parentElement;\n      }\n      if(captureContextMenu && event.button === 2) {\n        event.preventDefault();\n      }\n    };\n  }\n\n  _inputEventHandler(tagname:string) {\n    return (event:Event) => {\n      let target = event.target as (Instance & HTMLInputElement);\n      let elementId = target.__element;\n      if(elementId) {\n        if(target.classList.contains(\"html-autosize-input\")) {\n          target.size = target.value.length || 1;\n        }\n        let eventId = createId();\n        let eavs:RawEAV[] = [\n          [eventId, \"tag\", \"html/event\"],\n          [eventId, \"tag\", `html/event/${tagname}`],\n          [eventId, \"element\", elementId],\n          [eventId, \"value\", target.value]\n        ];\n        if(eavs.length) this._sendEvent(eavs);\n      }\n    }\n  }\n\n  _changeEventHandler(tagname:string) {\n    return (event:Event) => {\n      let target = event.target as (Instance & HTMLInputElement);\n      if(!(target instanceof HTMLInputElement)) return;\n      if(target.type == \"checkbox\" || target.type == \"radio\") {\n        let elementId = target.__element;\n        if(elementId) {\n          let eventId = createId();\n          let eavs:RawEAV[] = [\n            [eventId, \"tag\", \"html/event\"],\n            [eventId, \"tag\", `html/event/${tagname}`],\n            [eventId, \"element\", elementId],\n            [eventId, \"checked\", \"\"+target.checked]\n          ];\n          let name = target.name;\n          if(target.type == \"radio\" && name !== undefined) {\n            let prev = this._checkedRadios[name];\n            if(prev && prev !== target.__element) {\n              // @NOTE: This is two events in one TX, a bit dangerous.\n              let event2Id = createId();\n              eavs.push(\n                [event2Id, \"tag\", \"html/event\"],\n                [event2Id, \"tag\", `html/event/${tagname}`],\n                [event2Id, \"element\", prev],\n                [event2Id, \"checked\", \"false\"]\n              );\n            }\n            this._checkedRadios[name] = elementId;\n          }\n          if(eavs.length) this._sendEvent(eavs);\n        }\n      }\n    }\n  }\n\n  _keyMap:{[key:number]: string|undefined} = { // Overrides to provide sane names for common control codes.\n    9: \"tab\",\n    13: \"enter\",\n    16: \"shift\",\n    17: \"control\",\n    18: \"alt\",\n    27: \"escape\",\n    37: \"left\",\n    38: \"up\",\n    39: \"right\",\n    40: \"down\",\n    91: \"meta\"\n  }\n\n  _keyEventHandler(tagname:string) {\n    return (event:KeyboardEvent) => {\n      if(event.repeat) return;\n      let current:Element|null = event.target as Element;\n\n      let code = event.keyCode;\n      let key = this._keyMap[code];\n\n      let eventId = createId();\n      let eavs:(RawEAV|RawEAVC)[] = [\n        [eventId, \"tag\", \"html/event\"],\n        [eventId, \"tag\", `html/event/${tagname}`],\n        [eventId, \"key-code\", code]\n      ];\n      if(key) eavs.push([eventId, \"key\", key]);\n\n      while(current && this.isInstance(current)) {\n        let elemId = current.__element!;\n        eavs.push([eventId, \"element\", elemId]);\n        current = current.parentElement;\n      };\n      if(eavs.length)this._sendEvent(eavs);\n    };\n  }\n\n  _focusEventHandler(tagname:string) {\n    return (event:FocusEvent) => {\n      let target = event.target as (Instance & HTMLInputElement);\n      let elementId = target.__element;\n      if(elementId) {\n        let eventId = createId();\n        let eavs:RawEAV[] = [\n          [eventId, \"tag\", \"html/event\"],\n          [eventId, \"tag\", `html/event/${tagname}`],\n          [eventId, \"element\", elementId]\n        ];\n        if(target.value !== undefined) eavs.push([eventId, \"value\", target.value]);\n        if(eavs.length) this._sendEvent(eavs);\n      }\n    }\n  }\n\n  _hoverEventHandler(tagname:string) {\n    return (event:MouseEvent) => {\n      let {target} = event;\n      if(!this.isInstance(target)) return;\n\n      let eavs:(RawEAV|RawEAVC)[] = [];\n      let elemId = target.__element!;\n      if(target.listeners && target.listeners[\"hover\"]) {\n        let eventId = createId();\n        eavs.push(\n          [eventId, \"tag\", \"html/event\"],\n          [eventId, \"tag\", `html/event/${tagname}`],\n          [eventId, \"element\", elemId]\n        );\n      }\n      if(eavs.length) this._sendEvent(eavs);\n    };\n  }\n\n  _hashChangeHandler(tagname:string) {\n    return (event:HashChangeEvent) => {\n      this._updateURL(tagname);\n    };\n  }\n\n  //------------------------------------------------------------------\n  // Watcher handlers\n  //------------------------------------------------------------------\n\n  exportListeners({adds, removes}:ObjectDiffs<{listener:string, elemId:ID, instanceId:RawValue}>) {\n    for(let e of Object.keys(adds)) {\n      let {listener, elemId, instanceId} = adds[e];\n      let instance = this.getInstance(instanceId)!;\n      if(!instance.listeners) instance.listeners = {};\n      instance.listeners[listener] = true;\n    }\n    for(let e of Object.keys(removes)) {\n      let {listener, elemId, instanceId} = removes[e];\n      let instance = this.getInstance(instanceId)\n      if(!instance || !instance.listeners) continue;\n      instance.listeners[listener] = false;\n    }\n  }\n\n\n  //------------------------------------------------------------------\n  // Setup\n  //------------------------------------------------------------------\n\n  setup() {\n    if(typeof window === \"undefined\") return;\n    this.tagPrefix = \"html\"; // @FIXME: hacky, due to inheritance chain evaluation order.\n    super.setup();\n\n    this.program\n      .bind(\"All html elements add their tags as classes\", ({find, lib:{string}, record}) => {\n        let element = find(\"html/element\");\n        element.tag != \"html/element\"\n        let klass = string.replace(element.tag, \"/\", \"-\");\n        return [\n          element.add(\"class\", klass)\n        ];\n      });\n\n    window.addEventListener(\"click\", this._mouseEventHandler(\"click\"));\n    window.addEventListener(\"dblclick\", this._mouseEventHandler(\"double-click\"));\n    window.addEventListener(\"mousedown\", this._mouseEventHandler(\"mouse-down\"));\n    window.addEventListener(\"mouseup\", this._mouseEventHandler(\"mouse-up\"));\n    window.addEventListener(\"contextmenu\", this._captureContextMenuHandler());\n\n    window.addEventListener(\"input\", this._inputEventHandler(\"change\"));\n    window.addEventListener(\"change\", this._changeEventHandler(\"change\"));\n    window.addEventListener(\"keydown\", this._keyEventHandler(\"key-down\"));\n    window.addEventListener(\"keyup\", this._keyEventHandler(\"key-up\"));\n    window.addEventListener(\"focus\", this._focusEventHandler(\"focus\"), true);\n    window.addEventListener(\"blur\", this._focusEventHandler(\"blur\"), true);\n\n    document.body.addEventListener(\"mouseenter\", this._hoverEventHandler(\"hover-in\"), true);\n    document.body.addEventListener(\"mouseleave\", this._hoverEventHandler(\"hover-out\"), true);\n\n    window.addEventListener(\"hashchange\", this._hashChangeHandler(\"url-change\"));\n\n    this.program\n      .bind(\"Elements with no parents are roots.\", ({find, record, lib, not}) => {\n        let elem = find(\"html/element\");\n        not(() => find(\"html/element\", {children: elem}));\n        return [\n          elem.add(\"tag\", \"html/root\")\n        ];\n      })\n      .bind(\"Create an instance for each child of an external root.\", ({find, record, lib, not}) => {\n        let elem = find(\"html/element\");\n        let parent = find(\"html/root/external\", {children: elem});\n        return [\n          record(\"html/instance\", {element: elem, tagname: elem.tagname, parent}),\n          parent.add(\"tag\", \"html/element\")\n        ];\n      });\n\n    this.program\n      .commit(\"Remove html events.\", ({find, choose}) => {\n        let event = find(\"html/event\");\n        return [event.remove()];\n      })\n      .bind(\"Inputs with an initial but no value use the initial.\", ({find, choose}) => {\n        let input = find(\"html/element\", {tagname: \"input\"});\n        let [value] = choose(() => input.value, () => input.initial);\n        return [input.add(\"value\", value)]\n      })\n      .commit(\"Apply input value changes.\", ({find}) => {\n        let {element, value} = find(\"html/event/change\");\n        return [element.remove(\"value\").add(\"value\", value)];\n      })\n      .commit(\"Apply input checked changes.\", ({find}) => {\n        let {element, checked} = find(\"html/event/change\");\n        return [element.remove(\"checked\").add(\"checked\", checked)];\n      })\n\n      .commit(\"When an element is entered, mark it hovered.\", ({find, record}) => {\n        let {element} = find(\"html/event/hover-in\");\n        return [element.add(\"tag\", \"html/hovered\")];\n      })\n      .commit(\"When an element is left, clear it's hovered.\", ({find, record}) => {\n        let {element} = find(\"html/event/hover-out\");\n        return [element.remove(\"tag\", \"html/hovered\")];\n      })\n      .watch(\"When an element is hoverable, it subscribes to mouseover/mouseout.\", ({find, record}) => {\n        let elemId = find(\"html/listener/hover\");\n        let instanceId = find(\"html/instance\", {element: elemId});\n        return [record({listener: \"hover\", elemId, instanceId})]\n      })\n      .asObjects<{listener:string, elemId:ID, instanceId:RawValue}>((diffs) => this.exportListeners(diffs))\n\n      .watch(\"When an element listeners for context-menu, it prevents default on right click.\", ({find, record}) => {\n        let elemId = find(\"html/listener/context-menu\");\n        let instanceId = find(\"html/instance\", {element: elemId});\n        return [record({listener: \"context-menu\", elemId, instanceId})]\n      })\n      .asObjects<{listener:string, elemId:ID, instanceId:RawValue}>((diffs) => this.exportListeners(diffs))\n\n      .commit(\"When the url changes, delete its previous segments.\", ({find, record}) => {\n        let change = find(\"html/event/url-change\");\n        let url = find(\"html/url\");\n        return [\n          url.remove(\"hash-segment\"),\n          url[\"hash-segment\"].remove()\n        ];\n      })\n      .commit(\"When the url changes, commit its new state.\", ({find, lookup, record}) => {\n        let change = find(\"html/event/url-change\");\n        let {attribute, value} = lookup(change);\n        attribute != \"tag\";\n        return [\n          record(\"html/url\").add(attribute, value)\n        ];\n      });\n\n    //setTimeout(() => this._updateURL(), 100);\n    this._updateURL()\n  }\n}\n\nWatcher.register(\"html\", HTMLWatcher);\n"
  },
  {
    "path": "src/watchers/index.ts",
    "content": "export {CanvasWatcher} from \"./canvas\";\nexport {CompilerWatcher} from \"./compiler\";\nexport {HTMLWatcher} from \"./html\";\nexport {NotifyWatcher} from \"./notify\";\nexport {FileWatcher} from \"./file\";\nexport {ConsoleWatcher} from \"./console\";\nexport {ShapeWatcher} from \"./shape\";\nexport {SVGWatcher} from \"./svg\";\nexport {SystemWatcher} from \"./system\";\nexport {TagBrowserWatcher} from \"./tag-browser\";\nexport {UIWatcher} from \"./ui\";\n"
  },
  {
    "path": "src/watchers/notify.ts",
    "content": "import {Program, Watcher, RawValue, RawMap, RawEAVC} from \"./watcher\";\nimport {HTMLWatcher} from \"./html\";\n\nexport class Notice {\n  element:HTMLElement = document.createElement(\"notice\");\n  name:string;\n  time:number;\n\n  constructor(public program:Program, public id:RawValue, public type:RawValue) {\n    let html = program.attach(\"html\") as HTMLWatcher;\n    html.addExternalRoot(id as string, this.element);\n  }\n\n  clear() {\n    let parent = this.element.parentElement;\n    if(parent) parent.removeChild(this.element);\n    // @FIXME: html.removeExternalRoot.\n  }\n}\n\n// @FIXME: do tihs with two program isolation instead of manual rendering?\n\nexport class NotifyWatcher extends Watcher {\n  html:HTMLWatcher;\n  notices:RawMap<Notice|undefined> = {};\n  root:HTMLElement;\n  scroller:HTMLElement;\n  wrapper:HTMLElement;\n\n  setup() {\n    this.html = this.program.attach(\"html\") as HTMLWatcher;\n\n    this.wrapper = document.createElement(\"div\");\n    this.wrapper.className = \"notify-wrapper\";\n    document.body.appendChild(this.wrapper);\n    this.scroller = document.createElement(\"div\");\n    this.scroller.className = \"notify-scroller\";\n    this.wrapper.appendChild(this.scroller);\n\n    this.root = document.createElement(\"column\");\n    this.root.className = \"notify-root ui-column\";\n    this.scroller.appendChild(this.root);\n    this.html.addExternalRoot(\"notify/root\", this.root);\n\n    this.program\n      .bind(\"Notices that aren't dismissed are children of the notify root.\", ({find, not}) => {\n        let root = find(\"notify/root\");\n        let wrapper = find(\"notify/notice-wrapper\");\n        not(() => wrapper.notice.tag == \"notify/dismissed\");\n        return [root.add(\"children\", wrapper)];\n      })\n      .bind(\"Decorate notices.\", ({find, choose, record}) => {\n        let notice = find(\"notify/notice\");\n        let type = choose(() => notice.type, () => \"info\");\n        return [\n          record(\"notify/notice-wrapper\", \"ui/row\", {notice, type}).add(\"children\", [\n            notice.add(\"tag\", \"ui/row\"),\n            record(\"ui/spacer\", {sort: 5, notice})\n          ])\n        ];\n      })\n      .bind(\"Notices which are dismissable get a button to do so.\", ({find, record}) => {\n        let notice = find(\"notify/notice\", \"notify/dismissible\");\n        let wrapper = find(\"notify/notice-wrapper\", {notice});\n        return [wrapper.add(\"children\", [\n          record(\"notify/dismiss\", \"ui/button\", {class: \"flat\", notice, sort: 15, icon: \"close-round\"})\n        ])];\n      })\n      .commit(\"Dismissed notices are marked.\", ({find}) => {\n        let notice = find(\"notify/notice\");\n        find(\"html/event/click\", {element: find(\"notify/dismiss\", {notice})});\n        return [notice.remove().add(\"tag\", \"notify/dismissed\")];\n      })\n      .bind(\"If a notice has a timestamp, display it.\", ({find, lib:{date}, record}) => {\n        let notice = find(\"notify/notice\");\n        let wrapper = find(\"notify/notice-wrapper\", {notice});\n        return [\n          wrapper.add(\"children\", [\n            record(\"ui/text\", {sort: 10, notice, text: date.format(notice.timestamp, \"HH:MM:ss\")})\n          ])\n        ];\n      })\n      .commit(\"Retract timestamps for bound notices that have ceased to be.\", ({find}) => {\n        let {notice} = find(\"notify/retract-timestamp\");\n        return [notice.remove(\"timestamp\")];\n      })\n      .watch(\"The notify watcher attaches a timestamp to notices without one.\", ({find, not}) => {\n        let notice = find(\"notify/notice\");\n        not(() => notice.timestamp);\n        return [notice.add(\"tag\", \"notify/notice\")];\n      })\n      .asDiffs(({adds}) => {\n        let timestamp = Date.now();\n        let eavs:RawEAVC[] = [];\n        for(let [notice] of adds) eavs.push([notice, \"timestamp\", timestamp, 1]);\n        if(eavs.length) this.program.inputEAVs(eavs);\n      })\n      .watch(\"The notify watcher also cleans up those timestamps when the notice goes away.\", ({find}) => {\n        let notice = find(\"notify/notice\");\n        return [notice.add(\"timestamp\", notice.timestamp)];\n      })\n      .asDiffs(({removes}) => {\n        if(!removes.length) return;\n        let eavs:RawEAVC[] = [];\n        eavs.push([\"||notify/retract-timestamp\", \"tag\", \"notify/retract-timestamp\", 1]);\n        for(let [notice, _, timestamp] of removes) {\n          eavs.push([notice, \"timestamp\", timestamp, -1]);\n          //eavs.push([\"||notify/retract-timestamp\", \"notice\", notice, 1]);\n        }\n        if(eavs.length) this.program.inputEAVs(eavs);\n      })\n  }\n}\n\nWatcher.register(\"notify\", NotifyWatcher);\n"
  },
  {
    "path": "src/watchers/shape.ts",
    "content": "import {Watcher, RawValue, RawEAV, RawEAVC} from \"./watcher\";\n\nexport class ShapeWatcher extends Watcher {\n  setup() {\n    this.program.attach(\"html\");\n    this.program.attach(\"canvas\");\n    this.hexagon();\n    this.hexGrid();\n\n    this.squarePath();\n    this.hexagonPath();\n  }\n\n  //--------------------------------------------------------------------\n  // #shape/hexagon\n  //--------------------------------------------------------------------\n  hexagonHTML() {\n    this.program\n      .bind(\"Draw a hexagon\", ({find, choose, record, lib: {math}}) => {\n        let hex = find(\"shape/hexagon\");\n        let {side} = hex;\n\n        let tri_height = math.round(side * 0.5); // sin(30deg)\n        let tri_width = math.round(side * 0.86603); // cos(30deg)\n        let width = 2 * tri_width;\n\n        let [background] = choose(\n          () => {hex.tag == \"shape/outline\"; return hex.border},\n          () =>  hex.background\n        );\n\n        let sideBorder = `${tri_width}px`;\n        let activeBorder = `${tri_height}px`;\n\n        return [\n          hex.add({tag: \"html/element\", tagname: \"div\", class: \"shape-hexagon\", style: record({width: `${width}px`}), children: [\n            record(\"shape/hexagon/cap\", \"html/element\", {sort: 1, tagname: \"div\", class: [\"shape-hexagon-cap\", \"first\"], style: record({\n              width: 0, height: 0,\n              \"border\": \"0 solid transparent\",\n              \"border-left-width\": sideBorder, \"border-right-width\": sideBorder,\n              \"border-bottom-width\": activeBorder, \"border-bottom-color\": background\n            })}),\n            record(\"shape/hexagon/body\", \"ui/column\", {hex, sort: 2, style: record({height: `${side}px`, width: `${width}`, background}), class: \"shape-hexagon-body\"}),\n            record(\"shape/hexagon/cap\", \"html/element\", {sort: 3, tagname: \"div\", class: [\"shape-hexagon-cap\", \"last\"], style: record({\n              width: 0, height: 0,\n              \"border\": \"0 solid transparent\",\n              \"border-left-width\": sideBorder, \"border-right-width\": sideBorder,\n              \"border-top-width\": activeBorder, \"border-top-color\": background\n            })}),\n          ]})\n        ];\n      })\n\n      .bind(\"Hexagons with border and thickness are outlined.\", ({find}) => {\n        let hex = find(\"shape/hexagon\");\n        hex.border;\n        hex.thickness;\n        return [\n          hex.add(\"tag\", \"shape/outline\")\n        ];\n      })\n\n      .bind(\"An outlined hexagon contains another hexagon inset by thickness.\", ({find, record}) => {\n        let hex = find(\"shape/hexagon\", \"shape/outline\");\n        let {thickness} = hex;\n        let side = hex.side - thickness;\n        let side_thickness = thickness * 0.86603; // cos(30deg)\n        return [\n          hex.add(\"children\", [\n            record(\"shape/hexagon\", \"shape/hexagon/inner\", {outer: hex, sort: 4, side, background: hex.background, class: \"shape-hexagon-inner\", style: record({\n              position: \"absolute\", top: 0, left: 0, \"margin-top\": `${thickness}px`, \"margin-left\": `${side_thickness}`\n            })})\n          ])\n        ];\n      })\n\n      .bind(\"Populate hexagon with content\", ({find, not}) => {\n        let hex_body = find(\"shape/hexagon/body\");\n        not(() => hex_body.hex.tag == \"shape/outline\")\n        let {content} = hex_body.hex;\n        return [\n          hex_body.add(\"children\", [\n            content\n          ])\n        ];\n      })\n\n      .bind(\"Populate an outlined hexagon's inner with content\", ({find}) => {\n        let hex_inner = find(\"shape/hexagon/inner\");\n        return [\n          hex_inner.add(\"content\", hex_inner.outer.content)\n        ];\n      });\n  }\n\n  //--------------------------------------------------------------------\n  // #shape/hex-grid\n  //--------------------------------------------------------------------\n  hexGrid() {\n    // [#hex-grid cells side gap]\n    this.program.bind(\"Decorate all the hex-grid cells as hexagons.\", ({find, lib:{math}, record}) => {\n      let hex_grid = find(\"shape/hex-grid\");\n\n      let {side, gap} = hex_grid;\n      let {cell} = hex_grid;\n      let {x:x_ix, y:y_ix} = cell;\n\n      let top_gap = gap * 0.86603; // sin(60deg)\n\n      let tri_height = side * 0.5;\n      let tri_width = side * 0.86603;\n\n      let width = math.round(2 * tri_width + gap);\n      let x_offset = math.round(math.mod(math.absolute(y_ix), 2) * width / 2);\n      let height = math.round(side + tri_height + top_gap);\n\n      let x = math.round(width * x_ix + x_offset);\n      let y = math.round(height * y_ix);\n\n      return [\n        hex_grid.add({tag: \"html/element\", tagname: \"div\", class: \"shape-hex-grid\"}),\n        hex_grid.add(\"children\", [\n          cell.add({\n            style: record({position: \"absolute\", left: `${x}px`, top: `${y}px`})\n          })\n        ])\n      ];\n    });\n  }\n\n  hexagon() {\n    this.program\n      .bind(\"Decorate a shape/hexagon as a canvas.\", ({find, choose, lib:{math}, record}) => {\n        let hex = find(\"shape/hexagon\");\n        let {side} = hex;\n        let tri_height = side * 0.5;\n        let tri_width = side * 0.86603;\n        let [pad] = choose(() => hex.lineWidth, () => 0);\n        let dpad = 2 * pad;\n        let width = math.ceiling(2 * tri_width + dpad);\n        let height = math.ceiling(2 * side + dpad);\n\n\n        return [\n          hex.add({tag: \"html/element\", tagname: \"div\", style: record({width: `${width}px`, height: `${height}px`})}).add(\"children\", [\n            record(\"canvas/root\", {sort: 1, hex, width: `${width}px`, height: `${height}px`}).add(\"children\", [\n              record(\"shape/hexagon-path\", {sort: 1, hex, x: pad, y: pad, side})\n            ]),\n            record(\"shape/hexagon/content\", \"html/element\", {sort: 2, hex, tagname: \"div\", style: record({top: `${tri_height}px`, bottom: `${tri_height}px`, left: `${pad / 2}px`, right: `${pad}px`})})\n          ])\n        ];\n      })\n      .bind(\"Copy hexagon content into it's appropriate container.\", ({find, record}) => {\n        let hex = find(\"shape/hexagon\");\n        let container = find(\"shape/hexagon/content\", {hex});\n        return [container.add(\"children\", hex.content)];\n      })\n      .bind(\"Copy style properties onto hexagon path.\", ({find, lookup}) => {\n        let hex = find(\"shape/hexagon\");\n        let path = find(\"shape/hexagon-path\", {hex});\n        let {attribute, value} = lookup(hex);\n        attribute != \"tag\";\n        attribute != \"class\";\n        attribute != \"tagname\";\n        attribute != \"style\";\n        attribute != \"children\";\n        attribute != \"x\";\n        attribute != \"y\";\n        return [path.add(attribute, value)];\n      })\n  }\n\n  squarePath() {\n    this.program.bind(\"Decorate a shape/square-path as a canvas/path\", ({find, record}) => {\n      let square = find(\"shape/square-path\");\n      let {x, y, side} = square;\n      return [\n        square.add({tag: \"canvas/path\"}).add(\"children\", [\n          record({sort: 1, type: \"rect\", x, y, width: side, height: side}),\n        ])\n      ];\n    });\n  }\n\n  hexagonPath() {\n    this.program.bind(\"Decorate shape/hexagon-path as a canvas/path\", ({find, record}) => {\n      let hex = find(\"shape/hexagon-path\");\n      let {x, y, side} = hex;\n\n      let tri_height = side * 0.5;\n      let tri_width = side * 0.86603;\n      let width = 2 * tri_width;\n\n      let xl = x + width;\n      let xm = x + tri_width;\n      let y14 = y + tri_height;\n      let y34 = y + 3 * tri_height;\n      let yb = y + 2 * side;\n      return [\n        hex.add({tag: \"canvas/path\"}).add(\"children\", [\n          record({sort: 1, type: \"moveTo\", x: xm, y}),\n          record({sort: 2, type: \"lineTo\", x: xl, y: y14}),\n          record({sort: 3, type: \"lineTo\", x: xl, y: y34}),\n          record({sort: 4, type: \"lineTo\", x: xm, y: yb}),\n          record({sort: 5, type: \"lineTo\", x, y: y34}),\n          record({sort: 6, type: \"lineTo\", x, y: y14}),\n          record({sort: 7, type: \"closePath\"}),\n        ])\n      ];\n    })\n  }\n}\n\nWatcher.register(\"shape\", ShapeWatcher);\n"
  },
  {
    "path": "src/watchers/svg.ts",
    "content": "import {Watcher, RawValue, RawEAV, RawEAVC} from \"./watcher\";\nimport {DOMWatcher, ElemInstance} from \"./dom\";\nimport {HTMLWatcher} from \"./html\";\n\nexport interface Instance extends SVGElement {__element?: RawValue, __styles?: RawValue[], __sort?: RawValue}\n\n\nexport class SVGWatcher extends DOMWatcher<Instance> {\n  tagPrefix = \"svg\";\n  html:HTMLWatcher;\n\n  createInstance(id:RawValue, element:RawValue, tagname:RawValue):Instance {\n    let elem:Instance = document.createElementNS(\"http://www.w3.org/2000/svg\", tagname as string);\n    elem.__element = element;\n    elem.__styles = [];\n    return elem;\n  }\n\n  getInstance(id:RawValue):Instance|undefined {\n    if(this.instances[id]) return this.instances[id];\n    if(this.html.instances[id]) return this.html.instances[id] as any;\n  }\n\n  createRoot(id:RawValue) {\n    // This is delegated to the HTML watcher for #svg/roots, and it's nonsensical to try to make any other svg element a root.\n    return undefined;\n  }\n\n  addAttribute(instance:Instance, attribute:RawValue, value:RawValue):void {\n    // @TODO: Namespace attributes as appropriate.\n    instance.setAttribute(attribute as string, value as string);\n  }\n\n  removeAttribute(instance:Instance, attribute:RawValue, value:RawValue):void {\n    // @TODO: Namespace attributes as appropriate.\n    instance.removeAttribute(attribute as string);\n  }\n\n  setup() {\n    this.html = this.program.attach(\"html\") as HTMLWatcher;\n    this.tagPrefix = \"svg\";\n    super.setup();\n    this.program\n      .bind(\"Create an instance for each child of an svg/root.\", ({find, record, lib}) => {\n        let elem = find(\"svg/element\");\n        let parentElem = find(\"svg/root\", {children: elem});\n        let parent = find(\"html/instance\", {element: parentElem});\n\n        return [\n          record(\"svg/instance\", {element: elem, tagname: elem.tagname, parent})\n        ];\n      })\n\n      .bind(\"Decorate a svg roots as html.\", ({find}) => {\n        let elem = find(\"svg/root\");\n        return [elem.add({tag: \"html/element\", tagname: \"svg\"})];\n      })\n      .bind(\"Decorate line as svg.\", ({find}) => {\n        let elem = find(\"svg/line\");\n        return [elem.add({tag: \"svg/element\", tagname: \"line\"})];\n      })\n      .bind(\"Decorate circle as svg.\", ({find}) => {\n        let elem = find(\"svg/circle\");\n        return [elem.add({tag: \"svg/element\", tagname: \"circle\"})];\n      })\n      .bind(\"Decorate rect as svg.\", ({find}) => {\n        let elem = find(\"svg/rect\");\n        return [elem.add({tag: \"svg/element\", tagname: \"rect\"})];\n      })\n      .bind(\"Decorate text as svg.\", ({find}) => {\n        let elem = find(\"svg/text\");\n        return [elem.add({tag: \"svg/element\", tagname: \"text\"})];\n      })\n      .bind(\"Decorate image as svg.\", ({find}) => {\n        let elem = find(\"svg/image\");\n        return [elem.add({tag: \"svg/element\", tagname: \"image\"})];\n      });\n  }\n}\n\nWatcher.register(\"svg\", SVGWatcher);\n"
  },
  {
    "path": "src/watchers/system.ts",
    "content": "import {Watcher} from \"./watcher\";\nimport {ID} from \"../runtime/runtime\";\n\nexport class SystemWatcher extends Watcher {\n  timers:{[key:string]: {timer:any, prev:Date|undefined, tick:number}} = {};\n\n  getTime(changes:any[], timer:ID, tick:number, date?:Date) {\n    let multiplicity = -1;\n    if(!date) {\n      multiplicity = 1;\n      date = new Date();\n    }\n    changes.push(\n      [timer, \"year\", date.getFullYear(), multiplicity],\n      [timer, \"month\", date.getMonth() + 1, multiplicity],\n      [timer, \"day\", date.getDate(), multiplicity],\n      [timer, \"weekday\", date.getDay() + 1, multiplicity],\n      [timer, \"hour\", date.getHours(), multiplicity],\n      [timer, \"minute\", date.getMinutes(), multiplicity],\n      [timer, \"second\", date.getSeconds(), multiplicity],\n      [timer, \"millisecond\", date.getMilliseconds(), multiplicity],\n      [timer, \"timestamp\", date.getTime(), multiplicity],\n      [timer, \"tick\", tick, multiplicity],\n    );\n    return date;\n  }\n\n  setup() {\n    let {program:me} = this;\n\n    me.watch(\"setup timers\", ({find, record}) => {\n      let timer = find(\"system/timer\");\n      return [\n        record({timer, resolution: timer.resolution})\n      ]\n    })\n\n    me.asObjects<{timer:ID, resolution:number}>(({adds, removes}) => {\n      Object.keys(adds).forEach((id) => {\n        let {timer, resolution} = adds[id];\n        let prev:Date;\n        let timerHandle = setInterval(() => {\n          let {prev, tick} = this.timers[id];\n          let changes:any[] = [];\n          if(prev) {\n            this.getTime(changes, timer, tick, prev)\n          }\n          this.timers[id].tick = ++tick;\n          this.timers[id].prev = this.getTime(changes, timer, tick);\n          me.inputEAVs(changes);\n        }, resolution);\n        this.timers[id] = {timer:timerHandle, prev:undefined, tick:0};\n      })\n      console.log(\"GOT TIMER!\", adds);\n    })\n  }\n}\n\nWatcher.register(\"system\", SystemWatcher);\n"
  },
  {
    "path": "src/watchers/tag-browser.ts",
    "content": "import {Watcher, Program, RawMap, RawValue, RawEAVC} from \"./watcher\";\n\nimport {UIWatcher} from \"../watchers/ui\";\n\ninterface Attrs extends RawMap<RawValue> {}\n\nfunction t(tag:string) {\n  return `tag-browser/${tag}`;\n}\n\nfunction collapse<T extends any[]>(...args:T[]):T {\n  let all:T = [] as any;\n  for(let sublist of args) {\n    for(let item of sublist) {\n      all.push(item);\n    }\n  }\n  return all;\n}\n\nexport class TagBrowserWatcher extends Watcher {\n  browser:Program = this.createTagBrowser();\n\n  setup() {\n    this.program\n      .watch(\"Export all tags\", ({find, record}) => {\n        let rec = find();\n        return [\n          record(\"child-tag\", {\"child-tag\": rec.tag})\n        ];\n      })\n      .asDiffs((diffs) => {\n        let eavs:RawEAVC[] = [];\n        for(let [e, a, v] of diffs.removes) {\n          eavs.push([e, a, v, -1]);\n        }\n        for(let [e, a, v] of diffs.adds) {\n          eavs.push([e, a, v, 1]);\n        }\n        if(eavs.length) {\n          this.browser.inputEAVs(eavs);\n        }\n      })\n\n      // .watch(\"Export records with active tags\", ({find, lookup, record}) => {\n      //   let {\"active-tag\": activeTag} = find(\"tag-browser/active-tag\");\n      //   let rec = find({tag: activeTag});\n      //   let {attribute, value} = lookup(rec);\n      //   return [\n      //     rec.add(\"tag\", \"child-record\").add(attribute, value)\n      //   ];\n    // })\n\n      .watch(\"Export all records\", ({find, lookup, choose, record}) => {\n        let rec = find();\n        let {attribute, value} = lookup(rec);\n        // let [attrName] = choose(\n        //   () => { attribute == \"tag\"; return \"child-tag\"; },\n        //   () => attribute\n        // );\n\n        return [\n          rec.add(\"tag\", \"child-record\").add(attribute, value)\n        ];\n      })\n      .asDiffs((diffs) => {\n        let eavs:RawEAVC[] = [];\n        for(let [e, a, v] of diffs.removes) {\n          // @NOTE: this breaks tag-browser inspecting tag-browser\n          if(a === \"tag\" && v !== \"child-record\") a = \"child-tag\";\n          eavs.push([e, a, v, -1]);\n        }\n        for(let [e, a, v] of diffs.adds) {\n          // @NOTE: this breaks tag-browser inspecting tag-browser\n          if(a === \"tag\" && v !== \"child-record\") a = \"child-tag\";\n          eavs.push([e, a, v, 1]);\n        }\n        if(eavs.length) {\n          this.browser.inputEAVs(eavs);\n        }\n      })\n  }\n\n  createTagBrowser() {\n    let prog = new Program(\"Tag Browser\");\n    prog.attach(\"ui\");\n    let {$style, $row, $column, $text, $elem} = UIWatcher.helpers;\n\n    //--------------------------------------------------------------------\n    // Custom UI Components\n    //--------------------------------------------------------------------\n\n    // Tag Button\n    prog\n      .bind(\"Tag button component\", ({find, record}) => {\n        let tagButton = find(\"tag-browser/tag\");\n        let tag = tagButton.target;\n        return [\n          tagButton.add({tag: \"ui/button\", class: \"inset\", text: tag, sort: tag})\n        ];\n      })\n      .commit(\"When a tag button is clicked, update the view target.\", ({find, record}) => {\n        let click = find(\"html/event/click\");\n        let {element} = click;\n        element.tag == \"tag-browser/tag\";\n        let view = find(\"tag-browser/view\");\n\n        return [\n          view.remove(\"target\").add(\"target\", element.target)\n        ];\n      })\n      // @FIXME: Work around for a bug that occurs when leaving a record open and then letting it be retracted and reasserted\n      // .commit(\"When a tag button is clicked, clear any open child records\", ({find, record}) => {\n      //   let click = find(\"html/event/click\");\n      //   let {element} = click;\n      //   element.tag == \"tag-browser/tag\";\n      //   //let view = find(\"tag-browser/view\");\n      //   //view.target != element.target;\n\n      //   let openChildren = find(\"tag-browser/record\");\n      //   openChildren.open;\n\n      //   return [\n      //     openChildren.remove(\"open\")\n      //   ];\n      // });\n\n    // Record\n    prog\n      .bind(\"Record component (closed)\", ({find, record}) => {\n        let childRecord = find(\"tag-browser/record\");\n\n        return [\n          childRecord.add({\n            tag: \"ui/column\",\n            style: record({border: \"1px solid gray\", margin: 10, padding: 10}),\n            rec: childRecord.target,\n\n            children: [\n              record(\"ui/row\", \"tag-browser/record-header\", {sort: 0, rec: childRecord.target}).add(\"children\", [\n                record(\"html/element\", {tagname: \"div\", class: \"hexagon\"}),\n                record(\"ui/text\", {rec: childRecord.target, text: childRecord.target.displayName})\n              ])\n            ]\n          })\n        ];\n      })\n\n      .bind(\"Record component (open)\", ({find, lookup, record}) => {\n        let childRecord = find(\"tag-browser/record\", {open: \"true\"});\n        let {attribute, value} = lookup(childRecord.target);\n        attribute != \"tag\";\n        return [\n          childRecord.add({\n            children: [\n              record(\"tag-browser/record-attribute\", {rec: childRecord.target, attr: attribute}).add(\"val\", value)\n            ]\n          })\n        ];\n      })\n\n      .commit(\"Clicking a record toggles it open/closed\", ({find, choose}) => {\n        let recordHeader = find(\"tag-browser/record-header\");\n        let record = find(\"tag-browser/record\", {children: recordHeader});\n        find(\"html/event/click\", {element: recordHeader});\n        let [open] = choose(\n          () => { record.open == \"true\"; return \"false\"; },\n          () => \"true\"\n        )\n\n        return [\n          record.remove(\"open\").add(\"open\", open)\n        ];\n      });\n\n    // Record Attribute\n\n    prog\n      .bind(\"Record attribute component\", ({find, choose, record}) => {\n        let recordAttribute = find(\"tag-browser/record-attribute\");\n        let {target} = find(\"tag-browser/view\")\n\n        let [attrName] = choose(\n          () => { recordAttribute.attr == \"child-tag\"; return \"tag\"; },\n          () => recordAttribute.attr\n        );\n\n        let {rec, val} = recordAttribute;\n        return [\n          recordAttribute.add({tag: \"ui/row\",\n            children: [\n              record(\"ui/text\", {sort: 0, text: `${attrName}:`, rec, style: record({\"margin-right\": 10})}),\n              record(\"ui/column\", {sort: 1, rec, attrName}) // @FIXME: These attrs from the parent shouldn't need to be on the children.\n                .add(\"children\", record(\"tag-browser/record-value\", {rec, attr: attrName, val, \"active-input\":\"foo\"}))\n            ]\n          })\n        ];\n      });\n\n    // Record Value\n    prog\n      .bind(\"Record value component (as tag)\", ({find, record}) => {\n        let recordValue = find(\"tag-browser/record-value\");\n        let {val} = recordValue;\n        let childTag = find(\"child-tag\", {\"child-tag\": val});\n        return [\n          recordValue.add({tag: \"tag-browser/tag\", target: val})\n        ];\n      })\n\n      .bind(\"Record value component (as record)\", ({find, record}) => {\n        let recordValue = find(\"tag-browser/record-value\");\n        let childRecord = find(\"child-record\");\n        childRecord == recordValue.val;\n        return [\n          recordValue.add({tag: \"tag-browser/record\", target: childRecord})\n        ];\n      })\n\n      .bind(\"Record value component (as raw value)\", ({find, not, record}) => {\n        let recordValue = find(\"tag-browser/record-value\");\n        // @FIXME: Not is *still* busted\n        not(() => recordValue.tag == \"tag-browser/tag\");\n        not(() => recordValue.tag == \"tag-browser/record\");\n        let {val} = recordValue;\n        return [\n          recordValue.add({tag: \"ui/text\", sort: val, text: val})\n        ];\n      })\n\n    // Tag Cloud\n    prog.bind(\"List all tags in the tag cloud.\", ({find, not, record}) => {\n       let cloud = find(\"tag-browser/cloud\");\n      let rec = find(\"child-tag\");\n      let tag = rec[\"child-tag\"];\n\n      return [\n        cloud.add(\"children\", record(\"tag-browser/tag\", {target: tag}))\n      ];\n    });\n\n    // Tag View\n    prog\n      .bind(\"Show the targeted tag\", ({find, record}) => {\n        let view = find(\"tag-browser/view\");\n        let target = view.target;\n\n        return [\n          view.add(\"children\", record(\"ui/text\", {text: `Current tag: ${target}`, sort: 0}))\n        ]\n      })\n      .bind(\"Show records with the targeted tag\", ({find, not, record}) => {\n        let view = find(\"tag-browser/view\");\n        let targetedRecord = find(\"child-record\", {\"child-tag\": view.target});\n\n        return [\n          view.add(\"children\", record(\"ui/row\", {\n            sort: 1,\n            style: record({\"flex-wrap\": \"wrap\", \"align-items\": \"flex-start\"}),\n          }).add(\"children\", record(\"tag-browser/record\", {target: targetedRecord, \"active-tag\": view.target})))\n        ];\n      })\n      .watch(\"When the view target changes, mark it as the active tag in the child program\", ({find, record}) => {\n        let view = find(\"tag-browser/view\");\n\n        return [\n          record(\"tag-browser/active-tag\", {\"active-tag\": view.target})\n        ];\n      })\n      .asDiffs((diffs) => {\n        let eavs:RawEAVC[] = [];\n        for(let [e, a, v] of diffs.removes) {\n          eavs.push([e, a, v, -1]);\n        }\n        for(let [e, a, v] of diffs.adds) {\n          eavs.push([e, a, v, 1]);\n        }\n        if(eavs.length) {\n          this.program.inputEAVs(eavs);\n        }\n      });\n\n    // Display Name aliasing\n    prog\n      .bind(\"Alias display names\", ({find, choose}) => {\n        let record = find(\"child-record\");\n        let [name] = choose(\n          //() => record.displayName,\n          () => record.name,\n          () => \"???\"\n        );\n        return [\n          record.add(\"displayName\", name)\n        ];\n      })\n\n    // Create root UI\n    let changes = collapse(\n      $column({tag: t(\"root\")}, [\n        $row({tag: t(\"cloud\"), style: $style({\"flex-wrap\": \"wrap\"})}, []),\n        $column({tag: t(\"view\")}, [])\n      ]),\n\n      $elem(\"html/element\", {\n        tagname: \"style\",\n        text: `\n          .hexagon {\n            width: 22px;\n            height: 22px;\n            margin-right: 5px;\n            vertical-align: middle;\n            border-radius: 100px;\n            border: 2px solid #AAA;\n          }\n        `\n      })\n    );\n    prog.inputEAVs(changes);\n\n    return prog;\n  }\n}\n\nWatcher.register(\"tag browser\", TagBrowserWatcher);\n"
  },
  {
    "path": "src/watchers/ui.ts",
    "content": "import {Watcher, RawMap, RawValue, RawEAV} from \"./watcher\";\nimport {v4 as uuid} from \"uuid\";\n\nexport interface Attrs extends RawMap<RawValue|RawValue[]|RawEAV[]> {}\n\nexport class UIWatcher extends Watcher {\n  protected static _addAttrs(id:string, attrs?: Attrs, eavs:RawEAV[] = []) {\n    if(attrs) {\n      for(let attr in attrs) {\n        if(attrs[attr].constructor !== Array) {\n          eavs.push([id, attr, attrs[attr] as RawValue]);\n\n        } else {\n          let vals = attrs[attr] as RawValue[] | RawEAV[];\n           // We have a nested sub-object (i.e. a set of EAVs).\n          if(vals[0].constructor === Array) {\n            let childEAVs:RawEAV[] = vals as any;\n            let [childId] = childEAVs[0];\n            eavs.push([id, attr, childId]);\n            for(let childEAV of childEAVs) {\n              eavs.push(childEAV);\n            }\n\n          } else {\n            for(let val of vals as RawValue[]) {\n              eavs.push([id, attr, val]);\n            }\n          }\n        }\n      }\n    }\n    return eavs;\n  }\n\n  protected static $elem(tag:string, attrs?: Attrs) {\n    let id = uuid();\n    let eavs:RawEAV[] = [\n      [id, \"tag\", tag],\n    ];\n    UIWatcher._addAttrs(id, attrs, eavs);\n    return eavs;\n  }\n\n  protected static _makeContainer(tag:string) {\n    function $container(children: RawEAV[][]):RawEAV[];\n    function $container(attrs: Attrs, children: RawEAV[][]):RawEAV[];\n    function $container(attrsOrChildren?: Attrs|RawEAV[][], maybeChildren?: RawEAV[][]):RawEAV[] {\n      let attrs:Attrs|undefined;\n      let children:RawEAV[][];\n      if(maybeChildren) {\n        attrs = attrsOrChildren as Attrs|undefined;\n        children = maybeChildren;\n      } else {\n        children = attrsOrChildren as RawEAV[][];\n      }\n\n      let eavs = UIWatcher.$elem(tag, attrs);\n      let [id] = eavs[0];\n      for(let child of children) {\n        let [childId] = child[0];\n        eavs.push([id, \"children\", childId]);\n        for(let childEAV of child) {\n          eavs.push(childEAV);\n        }\n      }\n      return eavs;\n    }\n    return $container;\n  }\n\n  public static helpers = {\n    $style: (attrs?: Attrs) => {\n      return UIWatcher._addAttrs(uuid(), attrs);\n    },\n    $elem: UIWatcher.$elem,\n\n    $text: (text:RawValue, attrs?: Attrs) => {\n      let eavs = UIWatcher.$elem(\"ui/text\", attrs);\n      let [id] = eavs[0];\n      eavs.push([id, \"text\", text]);\n      return eavs;\n    },\n    $button: (attrs?: Attrs) => {\n      return UIWatcher.$elem(\"ui/button\", attrs);\n    },\n    $row: UIWatcher._makeContainer(\"ui/row\"),\n    $column: UIWatcher._makeContainer(\"ui/column\"),\n  }\n\n  public helpers = UIWatcher.helpers;\n\n  setup() {\n    this.program.attach(\"html\");\n\n    this.program\n    // Containers\n      .bind(\"Decorate row elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/row\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"row\")];\n      })\n      .bind(\"Decorate column elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/column\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"column\")];\n      })\n      .bind(\"Decorate spacer elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/spacer\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"spacer\")];\n      })\n      .bind(\"Decorate input elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/input\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"input\")];\n      })\n      .bind(\"Decorate text elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/text\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"text\")];\n      })\n      .bind(\"Decorate a elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/a\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"a\")];\n      })\n      .bind(\"Decorate style elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/style\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"style\")];\n      })\n      .bind(\"Decorate link elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/link\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"link\")];\n      })\n      .bind(\"Decorate div elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/div\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"div\")];\n      })\n      .bind(\"Decorate span elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/span\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"span\")];\n      })\n      .bind(\"Decorate img elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/img\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"img\")];\n      })\n      .bind(\"Decorate h1 elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/h1\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"h1\")];\n      })\n      .bind(\"Decorate h2 elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/h2\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"h2\")];\n      })\n      .bind(\"Decorate h3 elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/h3\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"h3\")];\n      })\n      .bind(\"Decorate ul elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/ul\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"ul\")];\n      })\n      .bind(\"Decorate ol elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/ol\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"ol\")];\n      })\n      .bind(\"Decorate li elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/li\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"li\")];\n      });\n\n\n    // Buttons\n    this.program\n      .bind(\"Decorate button elements as html.\", ({find, record}) => {\n        let elem = find(\"ui/button\");\n        return [elem.add(\"tag\", \"html/element\").add(\"tagname\", \"div\").add(\"class\", \"button\")];\n      })\n      .bind(\"Decorate button elements with icons.\", ({find, record}) => {\n        let elem = find(\"ui/button\");\n        return [elem.add(\"class\", \"iconic\").add(\"class\", `ion-${elem.icon}`)];\n      });\n\n    //--------------------------------------------------------------------\n    // Field Table\n    //--------------------------------------------------------------------\n\n    this.program\n      .bind(\"Decorate field tables as html.\", ({find, record}) => {\n        let elem = find(\"ui/field-table\");\n        return [elem.add({tag: \"html/element\", tagname: \"table\", cellspacing: 0})];\n      })\n      .bind(\"Field tables have a value_row for each AV pair in their fields.\", ({find, choose, record}) => {\n        let table = find(\"ui/field-table\");\n        let {field} = table;\n        let {attribute, value} = field;\n        let [editable] = choose(() => { field.editable == \"value\"; return \"true\"; }, () => \"false\");\n        return [table.add(\"value_row\", [\n          record({field, attribute, value})\n            .add(\"editable\", editable)\n        ])];\n      })\n      .bind(\"If a table is editable: all attach each specific editing mode.\", ({find, choose}) => {\n        let table = find(\"ui/field-table\", \"ui/editable\");\n        return [table.add(\"editable\", [\n          // Modify existing\n          \"value\",\n          \"attribute\",\n          // Create new,\n          \"row\",\n          \"field\"\n        ])];\n      })\n      .bind(\"A table's fields inherit the editing mode of their table if they don't specify their own.\", ({find, choose}) => {\n        let table = find(\"ui/field-table\");\n        let {field} = table;\n        let [editable] = choose(() => field.editable, () => table.editable);\n        return [field.add(\"editable\", editable)];\n      })\n\n      .bind(\"Create a row for each unique field.\", ({find, choose, record}) => {\n        let table = find(\"ui/field-table\");\n        let {field} = table;\n        return [\n          table.add(\"children\", [\n            record(\"ui/field-table/row\", {table, field})\n          ])\n        ];\n      })\n      .commit(\"If a field is row editable, add value_rows for the field when no empty ones exist.\", ({find, not, gather, choose, record}) => {\n        let table = find(\"ui/field-table\");\n        let {field} = table;\n        field.editable == \"row\";\n        not(() => find(\"ui/field-table/cell\", {table, field, column: \"value\", value: \"\"}))\n\n        let [count] = choose(() => {\n          let cell = find(\"ui/field-table/cell\", {field});\n          return gather(cell).per(field).count() + 1;\n        }, () => 1);\n\n        return [\n          table.add(\"value_row\", [\n            record(\"ui/field-table/value-row/new\", {sort: `zz${count}`, field, attribute: field.attribute, value: \"\"})\n              .add(\"editable\", \"true\")\n          ])\n        ];\n      })\n      .commit(\"If a field is row editable, clear any excess empty rows.\", ({find, record}) => {\n        let table = find(\"ui/field-table\");\n        let {field} = table;\n        field.editable == \"row\";\n        let cell = find(\"ui/field-table/cell\", {table, field, column: \"value\", value: \"\"});\n        let other = find(\"ui/field-table/cell\", {table, field, column: \"value\", value: \"\"});\n        other.sort > cell.sort;\n        return [other.value_row.remove()];\n      })\n\n      .commit(\"If a table is field editable, add a field when no empty ones exist.\", ({find, not, gather, choose, record}) => {\n        let table = find(\"ui/field-table\", {editable: \"field\"});\n\n        not(() => find(\"ui/field-table/attribute\", {table, column: \"attribute\", value: \"\"}))\n\n        let [count] = choose(() => {\n          table == find(\"ui/field-table\"); // @FIXME: Hackaround aggregate bug.\n          return gather(table.field).per(table).count() + 1;\n        }, () => 1);\n\n        return [\n          table.add(\"field\", [\n            record(\"ui/field-table/field/new\", {sort: `zz${count}`, attribute: \"\", value: \"\"})\n              .add(\"editable\", table.editable)\n              .add(\"editable\", [\"attribute\", \"value\"])\n          ])\n        ];\n      })\n      .commit(\"If a table is field editable, clear any excess empty fields.\", ({find, lookup, not, choose, record}) => {\n        let table = find(\"ui/field-table\", {editable: \"field\"});\n        let table_alias = find(\"ui/field-table\", {editable: \"field\"});\n        table == table_alias;\n        // Two new fields exist\n        let field = find(\"ui/field-table/field/new\");\n        let other_field = find(\"ui/field-table/field/new\");\n        // In the same table\n        table.field == field;\n        table_alias.field == other_field;\n        // Both are empty\n        not(() => table.change.field == field);\n        not(() => table_alias.change.field == other_field);\n        // And the other is before this one.\n        other_field.sort < field.sort;\n        return [field.remove()];\n      })\n\n      .bind(\"Each field row has an attribute and a value set.\", ({find, choose, record}) => {\n        let field_row = find(\"ui/field-table/row\");\n        let {table, field} = field_row;\n        let [sort] = choose(() => field.sort, () => field.attribute, () => 1);\n        let [editable] = choose(() => { field.editable == \"attribute\"; return \"true\" }, () => \"false\");\n\n        return [\n          field_row.add({tag: \"html/element\", tagname: \"tr\", sort}).add(\"children\", [\n            record(\"html/element\", {sort: 1, tagname: \"td\", table, field}).add(\"children\", [\n              record(\"ui/field-table/attribute\", \"ui/field-table/cell\", {table, field, value_row: field, column: \"attribute\"})\n                .add(\"editable\", editable)\n            ]),\n            record(\"html/element\", {sort: 2, tagname: \"td\", table, field}).add(\"children\", [\n              record(\"ui/field-table/value-set\", \"ui/column\", {table, field})\n            ])\n          ])\n        ];\n      })\n      .bind(\"Create a value for each field value in the value set.\", ({find, choose, record}) => {\n        let value_set = find(\"ui/field-table/value-set\");\n        let {table, field} = value_set;\n        let {value_row} = table;\n        value_row.field == field;\n        let {value, editable} = value_row;\n        let [sort] = choose(() => value_row.sort, () => value);\n        return [\n          value_set.add(\"children\", [\n            record(\"ui/field-table/value\", \"ui/field-table/cell\", {sort, table, field, value_row, column: \"value\", editable}),\n          ])\n        ];\n      })\n\n      .bind(\"The initial value of a cell is pulled off it's value_row or field.\", ({find, choose, not, lookup, record}) => {\n        let cell = find(\"ui/field-table/cell\");\n        let {field, value_row, column} = cell;\n        let {attribute, value:initial} = lookup(value_row);\n        attribute == column;\n        return [cell.add(\"initial\", initial)]\n      })\n\n      .bind(\"Draw field cells as text if they're not editable.\", ({find}) => {\n        let cell = find(\"ui/field-table/cell\", {editable: \"false\"});\n        let {field, column, initial} = cell;\n        return [cell.add({tag: \"ui/text\", text: initial})];\n      })\n\n      .bind(\"Draw field cells as inputs when they're editable.\", ({find}) => {\n        let cell = find(\"ui/field-table/cell\", {editable: \"true\"});\n        let {field, column, initial} = cell;\n        return [cell.add({tag: [\"ui/input\", \"html/autosize-input\"], placeholder: `${column}...`})];\n      })\n\n      .bind(\"When a cell changes value, update the tables changes list.\", ({find, lookup, record}) => {\n        let cell = find(\"ui/field-table/cell\");\n        let {table, field, column, value, value_row, initial} = cell;\n        field.editable == column;\n        value != initial;\n        return [table.add(\"change\", [\n          record(\"ui/field-change\", {field}).add(\"cell\", [\n            record({column, initial, value})\n          ])\n        ])];\n      })\n\n    this.autocomplete();\n  }\n\n  //--------------------------------------------------------------------\n  // Autocomplete\n  //--------------------------------------------------------------------\n\n  autocomplete() {\n    this.program\n      .bind(\"Decorate autocompletes.\", ({find, record}) => {\n        let autocomplete = find(\"ui/autocomplete\");\n        return [\n          autocomplete.add({tag: \"ui/column\"}).add(\"children\", [\n            record(\"ui/autocomplete/input\", \"ui/input\", {sort: 1, autocomplete})\n          ])\n        ];\n      })\n      .bind(\"Copy input placeholder.\", ({find}) => {\n        let input = find(\"ui/autocomplete/input\");\n        return [input.add({placeholder: input.autocomplete.placeholder})];\n      })\n      .bind(\"Copy input initial.\", ({find}) => {\n        let input = find(\"ui/autocomplete/input\");\n        return [input.add({initial: input.autocomplete.initial})];\n      })\n      .bind(\"Copy trigger focus.\", ({find}) => {\n        let autocomplete = find(\"ui/autocomplete\", \"html/trigger/focus\");\n        let input = find(\"ui/autocomplete/input\", {autocomplete});\n        return [input.add({tag: \"html/trigger/focus\"})];\n      })\n      .bind(\"Copy autosize input.\", ({find}) => {\n        let autocomplete = find(\"ui/autocomplete\", \"html/autosize-input\");\n        let input = find(\"ui/autocomplete/input\", {autocomplete});\n        return [input.add({tag: \"html/autosize-input\"})];\n      })\n      .bind(\"An autocompletes value is it's input's.\", ({find, choose}) => {\n        let input = find(\"ui/autocomplete/input\");\n        let [value] = choose(() => input.value, () => \"\");\n        return [input.autocomplete.add(\"value\", value)];\n      })\n      .commit(\"If an autocomplete's value disagrees with it's selected, clear the selected.\", ({find}) => {\n        let autocomplete = find(\"ui/autocomplete\");\n        let {selected, value} = autocomplete;\n        selected.text != value;\n        return [autocomplete.remove(\"selected\")];\n      })\n\n      .bind(\"Completions that match the current input value are matches.\", ({find, lib:{string}}) => {\n        let autocomplete = find(\"ui/autocomplete\");\n        let {value, completion} = autocomplete;\n        let ix = string[\"index-of\"](string.lowercase(completion.text), string.lowercase(value));\n        return [autocomplete.add(\"match\", completion)];\n      })\n\n      .bind(\"Matches are sorted by length.\", ({find, lib:{string}}) => {\n        let autocomplete = find(\"ui/autocomplete\");\n        let {match} = autocomplete;\n        let sort = string[\"codepoint-length\"](match.text);\n        return [match.add(\"sort\", sort)];\n      })\n\n      .bind(\"Show the matches in a popout beneath the input.\", ({find, lookup, record}) => {\n        let autocomplete = find(\"ui/autocomplete\");\n        let {match} = autocomplete;\n        let {attribute, value} = lookup(match);\n        attribute != \"tag\";\n        return [\n          autocomplete.add(\"children\", [\n            record(\"ui/autocomplete/matches\", \"ui/column\", {sort: 2, autocomplete}).add(\"children\", [\n              record(\"ui/autocomplete/match\", \"ui/text\", {autocomplete, match, sort: match.sort}).add(attribute, value)\n            ])\n          ])\n        ];\n      });\n\n    //--------------------------------------------------------------------\n    // Autocomplete Interaction\n    //--------------------------------------------------------------------\n\n    this.program\n      .commit(\"Clicking a match updates the selected and value of the autocomplete.\", ({find, record}) => {\n        let ui_match = find(\"ui/autocomplete/match\");\n        find(\"html/event/mouse-down\", {element: ui_match});\n        let {autocomplete, match} = ui_match;\n        return [\n          record(\"ui/event/select\", {autocomplete, selected: match}),\n        ];\n      })\n      .commit(\"Focusing an autocomplete input opens the autocomplete.\", ({find, record}) => {\n        let input = find(\"ui/autocomplete/input\");\n        find(\"html/event/focus\", {element: input});\n        return [record(\"ui/event/open\", {autocomplete: input.autocomplete})];\n      })\n      .commit(\"Blurring an autocomplete input closes the autocomplete.\", ({find, record}) => {\n        let input = find(\"ui/autocomplete/input\");\n        find(\"html/event/blur\", {element: input});\n        return [record(\"ui/event/close\", {autocomplete: input.autocomplete})];\n      })\n\n      .commit(\"If the value matches perfectly on blur, select that match.\", ({find, lib:{string}, record}) => {\n        let input = find(\"ui/autocomplete/input\");\n        let {value} = find(\"html/event/blur\", {element: input});\n        let {autocomplete} = input;\n        let {match} = autocomplete;\n        string.lowercase(match.text) == string.lowercase(value);\n        return [record(\"ui/event/select\", {autocomplete, selected: match})];\n      })\n\n      .commit(\"Pressing escape in an open autocomplete closes it.\", ({find, not, record}) => {\n        let autocomplete = find(\"ui/autocomplete\", {open: \"true\"});\n        find(\"html/event/key-down\", {key: \"escape\", element: autocomplete});\n        return [record(\"ui/event/close\", {autocomplete})];\n      })\n\n      .commit(\"Pressing enter in an open autocomplete submits it.\", ({find, record}) => {\n        let autocomplete = find(\"ui/autocomplete\", {open: \"true\"});\n        find(\"html/event/key-down\", {key: \"enter\", element: autocomplete});\n        return [\n          record(\"ui/event/submit\", {autocomplete}),\n          record(\"ui/event/close\", {autocomplete})\n        ];\n      })\n      .commit(\"Pressing tab in an open autocomplete selects the top match.\", ({find, gather, record}) => {\n        let autocomplete = find(\"ui/autocomplete\", {open: \"true\"});\n        find(\"html/event/key-down\", {key: \"tab\", element: autocomplete});\n        let {match} = autocomplete;\n        1 == gather(match.sort).per(autocomplete).sort();\n        return [\n          record(\"ui/event/select\", {autocomplete, selected: match}),\n          record(\"ui/event/close\", {autocomplete})\n        ];\n      })\n\n    //--------------------------------------------------------------------\n    // Autocomplete Events\n    //--------------------------------------------------------------------\n\n    this.program\n      .commit(\"Clear the specified autocomplete.\", ({find}) => {\n        let event = find(\"ui/event/clear\");\n        let {autocomplete} = event;\n        let input = find(\"ui/autocomplete/input\", {autocomplete});\n        return [\n          input.remove(\"value\"),\n          event.remove()\n        ];\n      })\n      .commit(\"When an autocomplete is opened, store it's previous value.\", ({find, choose, record}) => {\n        let event = find(\"ui/event/open\");\n        let {autocomplete} = event;\n        let input = find(\"ui/autocomplete/input\", {autocomplete});\n        let [value] = choose(() => autocomplete.previous, () => autocomplete.value, () => \"\");\n        return [\n          autocomplete.remove(\"open\").add({open: \"true\", previous: value}),\n          input.remove(\"tag\", \"html/trigger/blur\"),\n          event.remove()\n        ];\n      })\n      .commit(\"When an autocomplete is closed, erase it's previous value.\", ({find, choose, record}) => {\n        let event = find(\"ui/event/close\");\n        let {autocomplete} = event;\n        let input = find(\"ui/autocomplete/input\", {autocomplete});\n        return [\n          autocomplete.remove(\"open\").remove(\"previous\"),\n          input.add(\"tag\", \"html/trigger/blur\"),\n          autocomplete.remove(\"tag\", \"html/trigger/focus\"),\n          event.remove()\n        ];\n      })\n      .commit(\"When an autocomplete is closed, and it's value is changed, emit a change event.\", ({find, choose, record}) => {\n        let event = find(\"ui/event/close\");\n        let {autocomplete} = event;\n        autocomplete.value != autocomplete.previous;\n        return [\n          record(\"ui/event/change\", {autocomplete, value: autocomplete.value})\n        ];\n      })\n      .commit(\"Selecting a completion updates the autocomplete.\", ({find, record}) => {\n        let event = find(\"ui/event/select\");\n        let {autocomplete, selected} = event;\n        let input = find(\"ui/autocomplete/input\", {autocomplete});\n        return [\n          input.remove(\"value\").add(\"value\", selected.text),\n          autocomplete.remove(\"selected\").add(\"selected\", selected),\n          event.remove()\n        ];\n      })\n      .commit(\"When a selection is made and it's different from the previous value, emit a change event.\", ({find, record}) => {\n        let event = find(\"ui/event/select\");\n        let {autocomplete, selected} = event;\n        selected.text != autocomplete.previous;\n        return [\n          record(\"ui/event/change\", {autocomplete, value: selected.text})\n        ];\n      })\n      .commit(\"Clear the autocomplete change event.\", ({find, record}) => {\n        let event = find(\"ui/event/change\");\n        let {autocomplete} = event;\n        return [event.remove()];\n      })\n\n  }\n\n}\n\nWatcher.register(\"ui\", UIWatcher);\n"
  },
  {
    "path": "src/watchers/watcher.ts",
    "content": "import * as path from \"path\";\nexport {RawValue, RawEAV, RawEAVC} from \"../runtime/runtime\";\nimport {ID, GlobalInterner, RawValue, RawEAV, RawEAVC, Change, createArray, ExportHandler} from \"../runtime/runtime\";\nexport {Program} from \"../runtime/dsl2\";\nimport {Program, LinearFlowFunction} from \"../runtime/dsl2\";\nimport {v4 as uuid} from \"uuid\";\n\n//------------------------------------------------------------------------------\n// Watcher\n//------------------------------------------------------------------------------\n\nexport class Watcher {\n  protected static _registry:{[id:string]: typeof Watcher} = {};\n\n  static register(id:string, watcher:typeof Watcher) {\n    if(this._registry[id]) {\n      if(this._registry[id] === watcher) return;\n      throw new Error(`Attempting to overwrite existing watcher with id '${id}'`);\n    }\n    this._registry[id] = watcher;\n  }\n\n  static unregister(id:string) {\n    delete this._registry[id];\n  }\n\n  static get(id:string) {\n    let watcher = this._registry[id];\n    if(watcher) return watcher;\n  }\n\n  get program() { return this._program; }\n\n  constructor(protected _program:Program) {\n    this.setup();\n  }\n\n  setup() {}\n}\n\n//------------------------------------------------------------------------------\n// Exporter\n//------------------------------------------------------------------------------\n\nexport interface Map<V> {[key:number]: V};\nexport interface RawMap<V> {[key:string]: V, [key:number]: V};\nexport interface RawRecord extends RawMap<RawValue> {}\n\nexport interface Diffs<V> {adds: V, removes: V};\nexport interface EAVDiffs extends Diffs<RawEAV[]> {}\nexport interface ObjectDiffs<T extends RawRecord> extends Diffs<RawMap<T>> {}\n\nexport type DiffConsumer = (diffs:EAVDiffs) => void;\nexport type ObjectConsumer<T extends RawRecord> = (diffs:ObjectDiffs<T>) => void;\n\nexport class Exporter {\n  protected _diffTriggers:Map<DiffConsumer[]> = {};\n  protected _objectTriggers:Map<ObjectConsumer<{}>[]> = {};\n  protected _blocks:ID[] = [];\n\n  triggerOnDiffs(blockId:ID, handler:DiffConsumer):void {\n    if(!this._diffTriggers[blockId]) this._diffTriggers[blockId] = createArray();\n    if(this._diffTriggers[blockId].indexOf(handler) === -1) {\n      this._diffTriggers[blockId].push(handler);\n    }\n    if(this._blocks.indexOf(blockId) === -1) {\n      this._blocks.push(blockId);\n    }\n  }\n\n  triggerOnObjects<Pattern extends RawRecord>(blockId:ID, handler:ObjectConsumer<Pattern>):void {\n    if(!this._objectTriggers[blockId]) this._objectTriggers[blockId] = createArray();\n    if(this._objectTriggers[blockId].indexOf(handler) === -1) {\n      this._objectTriggers[blockId].push(handler);\n    }\n    if(this._blocks.indexOf(blockId) === -1) {\n      this._blocks.push(blockId);\n    }\n  }\n\n  accumulateChangesAs<T extends RawRecord>(changes:Change[]) {\n    let adds:RawMap<T> = {};\n    let removes:RawMap<T> = {};\n\n    for(let change of changes) {\n      let {e, a, v, count} = change.reverse();\n      if(count === 1) {\n        let record = adds[e] = adds[e] || Object.create(null);\n        if(record[a]) throw new Error(\"@FIXME: accumulateChanges supports only a single value per attribute.\");\n        record[a] = v;\n      } else {\n        let record = removes[e] = removes[e] || Object.create(null);\n        if(record[a]) throw new Error(\"@FIXME: accumulateChanges supports only a single value per attribute.\");\n        record[a] = v;\n      }\n    }\n\n    return {adds, removes};\n  }\n\n  handle:ExportHandler = (blockChanges) => {\n    for(let blockId of this._blocks) {\n      let changes = blockChanges[blockId];\n      if(changes && changes.length) {\n        let diffTriggers = this._diffTriggers[blockId];\n        if(diffTriggers) {\n          let output:EAVDiffs = {adds: [], removes: []};\n          for(let change of changes) {\n            let eav = change.toRawEAV();\n            if(change.count > 0) {\n              output.adds.push(eav);\n            } else {\n              output.removes.push(eav);\n            }\n          }\n\n          for(let trigger of diffTriggers) {\n            trigger(output);\n          }\n        }\n\n        let objectTriggers = this._objectTriggers[blockId];\n        if(objectTriggers) {\n          let output:ObjectDiffs<{}> = this.accumulateChangesAs<{}>(changes);\n          for(let trigger of objectTriggers) {\n            trigger(output);\n          }\n        }\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Convenience Diff Handlers\n//------------------------------------------------------------------------------\n\nexport function _isId(value?:RawValue):boolean {\n  return (\"\"+value).indexOf(\"|\") != -1;\n}\n\nexport function maybeIntern(value?:RawValue):ID|RawValue|undefined {\n  if(value === undefined) return value;\n  let interned = _isId(value) ? GlobalInterner.get(value) : undefined\n  return interned !== undefined ? interned : value;\n}\n\nexport function asJS(value?:RawValue):number|string|boolean|undefined {\n  if(typeof value === \"number\") return value;\n  if(value === \"true\") return true;\n  if(value === \"false\") return false;\n  return value;\n}\n\nexport function forwardDiffs(destination:Program, name:string = \"Unnamed\", debug = false) {\n  return (diffs:EAVDiffs) => {\n    let eavs:RawEAVC[] = [];\n    for(let [e, a, v] of diffs.removes) {\n      eavs.push([e, a, v, -1]);\n    }\n    for(let [e, a, v] of diffs.adds) {\n      eavs.push([e, a, v, 1]);\n    }\n    if(eavs.length) {\n      if(debug) {\n        console.log(\"FWD\", name, \"=>\", destination.name);\n        console.log(eavs.map((c) => `[${c.map(maybeIntern).join(\", \")}]`).join(\"\\n\"));\n      }\n      destination.inputEAVs(eavs);\n    }\n  };\n}\n\n//--------------------------------------------------------------------\n// Watcher / Program Utils\n//--------------------------------------------------------------------\n\nexport function createId() {\n  return \"|\" + uuid();\n}\n\nexport function isRawValue(x:any): x is RawValue {\n  return x !== undefined && (typeof x === \"string\" || typeof x === \"number\");\n}\n\nexport function isRawValueArray(x:any): x is RawValue[] {\n  if(x && x.constructor === Array) {\n    for(let value of x) {\n      if(!isRawValue(value)) return false;\n    }\n    return true;\n  }\n  return false;\n}\n\nexport function isRawEAVArray(x:any): x is RawEAV[] {\n  if(x && x.constructor === Array) {\n    for(let value of x) {\n      if(!isRawValueArray(value)) return false;\n      if(value.length !== 3) return false;\n    }\n    return true;\n  }\n  return false;\n}\n\nexport function isRawEAVArraySet(x:any): x is RawEAV[][] {\n  return x && x.constructor === Array && isRawEAVArray(x[0]);\n}\n\nexport function isRecord(x:any): x is Attrs {\n  return x && typeof x === \"object\" && x.constructor !== Array;\n}\n\nexport function isRecordSet(x:any): x is Attrs[] {\n  return x && x.constructor === Array && isRecord(x[0]);\n}\n\nexport interface Attrs extends RawMap<RawValue|RawValue[]|RawEAV[]|RawEAV[][]|Attrs|Attrs[]> {}\nexport function appendAsEAVs(eavs:any[], record: Attrs, id = createId()) {\n  for(let attr in record) {\n    let value = record[attr];\n    if(isRawValue(value)) {\n      eavs.push([id, attr, value]);\n\n    } else if(isRawValueArray(value)) {\n      // We have a set of scalars\n      for(let val of value) eavs.push([id, attr, val]);\n\n    } else if(isRawEAVArray(value)) {\n      // We have a single nested sub-object (i.e. a set of EAVs).\n      let childEAVs = value;\n      let [childId] = childEAVs[0];\n      eavs.push([id, attr, childId]);\n      for(let childEAV of childEAVs) eavs.push(childEAV);\n\n    } else if(isRawEAVArraySet(value)) {\n      // We have a set of nested sub-objects.\n      for(let childEAVs of value) {\n        let [childId] = childEAVs[0];\n        eavs.push([id, attr, childId]);\n        for(let childEAV of childEAVs) eavs.push(childEAV);\n      }\n    } else if(isRecord(value)) {\n      let ix = eavs.length;\n      appendAsEAVs(eavs, value);\n      let [childId] = eavs[ix];\n      eavs.push([id, attr, childId]);\n    } else if(isRecordSet(value)) {\n      for(let record of value) {\n        let ix = eavs.length;\n        appendAsEAVs(eavs, record);\n        let [childId] = eavs[ix];\n        eavs.push([id, attr, childId]);\n      }\n    }\n  }\n\n  return eavs;\n}\n"
  },
  {
    "path": "syntax_diagrams.html",
    "content": "<!DOCTYPE html>\n<meta charset=\"utf-8\">\n<style>\n    body {\n        background-color: hsl(30, 20%, 95%)\n    }\n</style>\n\n<!--References when using npm and chevrotain is installed node_modules/chevrotain.-->\n<link rel='stylesheet' href='node_modules/chevrotain/diagrams/diagrams.css'>\n<script src='node_modules/chevrotain/diagrams/vendor/railroad-diagrams.js'></script>\n<script src='node_modules/chevrotain/diagrams/src/diagrams_builder.js'></script>\n<script src='node_modules/chevrotain/diagrams/src/diagrams_behavior.js'></script>\n<script src='node_modules/chevrotain/diagrams/src/main.js'></script>\n\n<body>\n<div id=\"diagrams\" align=\"center\"></div>\n\n<script src='build/syntax_diagrams/serialized_syntax.js'></script>\n\n<script>\n    var diagramsDiv = document.getElementById(\"diagrams\");\n    main.drawDiagramsFromSerializedGrammar(serializedGrammar, diagramsDiv)\n</script>\n</body>"
  },
  {
    "path": "test/aggregate.ts",
    "content": "import {Program} from \"../src/runtime/dsl2\";\nimport {verify} from \"./util\";\nimport * as test from \"tape\";\n\ntest(\"Aggregate: Count in choose\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.bind(\"test count in choose\", ({find, choose, gather, record}) => {\n    let person = find(\"person\");\n    let [count] = choose(\n      () => gather(person.pet).count(),\n      () => 0\n    );\n    return [\n      record(\"result\", {person, count})\n    ];\n  });\n\n  verify(assert, prog, [\n    [\"A\", \"tag\", \"person\"],\n  ], [\n    [1, \"tag\", \"result\", 1],\n    [1, \"person\", \"A\", 1],\n    [1, \"count\", 0, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"A\", \"pet\", \"B\"],\n  ], [\n    [1, \"tag\", \"result\", 1, -1],\n    [1, \"person\", \"A\", 1, -1],\n    [1, \"count\", 0, 1, -1],\n\n    [2, \"tag\", \"result\", 1, 1],\n    [2, \"person\", \"A\", 1, 1],\n    [2, \"count\", 1, 1, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"A\", \"pet\", \"C\"],\n  ], [\n    [2, \"tag\", \"result\", 1, -1],\n    [2, \"person\", \"A\", 1, -1],\n    [2, \"count\", 1, 1, -1],\n\n    [3, \"tag\", \"result\", 1],\n    [3, \"person\", \"A\", 1],\n    [3, \"count\", 2, 1],\n  ]);\n\n  assert.end();\n});\n\n\ntest(\"Aggregate: direction-less sort\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.bind(\"test direction-less sort\", ({find, choose, gather, record}) => {\n    let person = find(\"person\");\n    let pos = gather(person.name).sort();\n    return [\n      person.add(\"pos\", pos)\n    ];\n  });\n\n  verify(assert, prog, [\n    [\"A\", \"tag\", \"person\"],\n    [\"A\", \"name\", \"Jane\"],\n  ], [\n    [\"A\", \"pos\", 1, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"B\", \"tag\", \"person\"],\n    [\"B\", \"name\", \"Chris\"],\n  ], [\n    [\"B\", \"pos\", 1, 1],\n    [\"A\", \"pos\", 1, 1, -1],\n    [\"A\", \"pos\", 2, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"C\", \"tag\", \"person\"],\n    [\"C\", \"name\", \"Zaria\"],\n  ], [\n    [\"C\", \"pos\", 3, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"B\", \"tag\", \"person\", 0, -1],\n    [\"B\", \"name\", \"Chris\", 0, -1],\n  ], [\n    [\"B\", \"pos\", 1, 1, -1],\n    [\"A\", \"pos\", 1, 1, 1],\n    [\"A\", \"pos\", 2, 1, -1],\n    [\"C\", \"pos\", 2, 1, 1],\n    [\"C\", \"pos\", 3, 1, -1],\n  ]);\n\n  assert.end();\n});\n\ntest(\"Aggregate: down sort\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.bind(\"test down sort\", ({find, choose, gather, record}) => {\n    let person = find(\"person\");\n    let pos = gather(person.name).sort(\"down\");\n    return [\n      person.add(\"pos\", pos)\n    ];\n  });\n\n  verify(assert, prog, [\n    [\"A\", \"tag\", \"person\"],\n    [\"A\", \"name\", \"Jane\"],\n  ], [\n    [\"A\", \"pos\", 1, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"B\", \"tag\", \"person\"],\n    [\"B\", \"name\", \"Chris\"],\n  ], [\n    [\"B\", \"pos\", 2, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"C\", \"tag\", \"person\"],\n    [\"C\", \"name\", \"Zaria\"],\n  ], [\n    [\"C\", \"pos\", 1, 1],\n    [\"A\", \"pos\", 1, 1, -1],\n    [\"A\", \"pos\", 2, 1],\n    [\"B\", \"pos\", 2, 1, -1],\n    [\"B\", \"pos\", 3, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"C\", \"tag\", \"person\", 0, -1],\n    [\"C\", \"name\", \"Zaria\", 0, -1],\n  ], [\n    [\"C\", \"pos\", 1, 1, -1],\n    [\"A\", \"pos\", 1, 1, 1],\n    [\"A\", \"pos\", 2, 1, -1],\n    [\"B\", \"pos\", 2, 1, 1],\n    [\"B\", \"pos\", 3, 1, -1],\n  ]);\n\n  assert.end();\n});\n\n\ntest(\"Aggregate: multi-direction sort\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.bind(\"test multi-direction sort\", ({find, choose, gather, record}) => {\n    let person = find(\"person\");\n    let pos = gather(person.name, person.age).sort(\"down\", \"up\");\n    return [\n      person.add(\"pos\", pos)\n    ];\n  });\n\n  verify(assert, prog, [\n    [\"A\", \"tag\", \"person\"],\n    [\"A\", \"name\", \"Jane\"],\n    [\"A\", \"age\", 27],\n  ], [\n    [\"A\", \"pos\", 1, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"B\", \"tag\", \"person\"],\n    [\"B\", \"name\", \"Chris\"],\n    [\"B\", \"age\", 25],\n  ], [\n    [\"B\", \"pos\", 2, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"C\", \"tag\", \"person\"],\n    [\"C\", \"name\", \"Jane\"],\n    [\"C\", \"age\", 19],\n  ], [\n    [\"C\", \"pos\", 1, 1],\n    [\"A\", \"pos\", 1, 1, -1],\n    [\"A\", \"pos\", 2, 1],\n    [\"B\", \"pos\", 2, 1, -1],\n    [\"B\", \"pos\", 3, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"C\", \"tag\", \"person\", 0, -1],\n  ], [\n    [\"C\", \"pos\", 1, 1, -1],\n    [\"A\", \"pos\", 1, 1, 1],\n    [\"A\", \"pos\", 2, 1, -1],\n    [\"B\", \"pos\", 2, 1, 1],\n    [\"B\", \"pos\", 3, 1, -1],\n  ]);\n\n  assert.end();\n});\n\ntest(\"Aggregate: group sort\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.bind(\"test group sort\", ({find, choose, gather, record}) => {\n    let person = find(\"person\");\n    let pos = gather(person.name).per(person.age).sort(\"down\");\n    return [\n      person.add(\"pos\", pos)\n    ];\n  });\n\n  verify(assert, prog, [\n    [\"A\", \"tag\", \"person\"],\n    [\"A\", \"name\", \"Jane\"],\n    [\"A\", \"age\", 27],\n  ], [\n    [\"A\", \"pos\", 1, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"B\", \"tag\", \"person\"],\n    [\"B\", \"name\", \"Chris\"],\n    [\"B\", \"age\", 27],\n  ], [\n    [\"B\", \"pos\", 2, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"C\", \"tag\", \"person\"],\n    [\"C\", \"name\", \"Zaria\"],\n    [\"C\", \"age\", 25],\n  ], [\n    [\"C\", \"pos\", 1, 1],\n  ]);\n\n\n  verify(assert, prog, [\n    [\"D\", \"tag\", \"person\"],\n    [\"D\", \"name\", \"Dana\"],\n    [\"D\", \"age\", 27],\n  ], [\n    [\"D\", \"pos\", 2, 1],\n    [\"B\", \"pos\", 2, 1, -1],\n    [\"B\", \"pos\", 3, 1],\n  ]);\n\n  verify(assert, prog, [\n    [\"C\", \"tag\", \"person\", 0, -1],\n  ], [\n    [\"C\", \"pos\", 1, 1, -1],\n  ]);\n\n  assert.end();\n});\n\n\ntest(\"Aggregate: committed sort with post filtering\", (assert) => {\n  let prog = new Program(\"test\")\n  .commit(\"Clear events when they come in.\", ({find}) => {\n    let event = find(\"event/create-widget\");\n    return [event.remove()];\n  })\n  .commit(\"Create a new widget of the given model.\", ({find, choose, gather, record}) => {\n    let {model} = find(\"event/create-widget\");\n\n    // The serial number of our next widget is the highest serial we've issued for this model so far + 1.\n    let {widget:other} = model;\n    1 == gather(other.serial).per(model).sort(\"down\");\n    let serial = other.serial + 1;\n    return [model.add(\"widget\", record(\"widget\", {serial}))];\n  });\n\n\n  verify(assert, prog, [\n    [1, \"tag\", \"model\"],\n    [1, \"widget\", 2],\n    [1, \"widget\", 3],\n    [2, \"tag\", \"widget\"],\n    [2, \"serial\", 3],\n    [3, \"tag\", \"widget\"],\n    [3, \"serial\", 5],\n\n    [4, \"tag\", \"event/create-widget\"],\n    [4, \"model\", 1],\n  ], [\n    [4, \"tag\", \"event/create-widget\", 0, -1],\n    [4, \"model\", 1, 0, -1],\n\n    [1, \"widget\", \"tag|widget|serial|6\", 0],\n    [\"tag|widget|serial|6\", \"tag\", \"widget\", 0],\n    [\"tag|widget|serial|6\", \"serial\", 6, 0],\n  ]);\n\n  assert.end();\n});\n\ntest(\"Aggregate: committed sort in choose\", (assert) => {\n  let prog = new Program(\"test\")\n  .commit(\"Clear events when they come in.\", ({find}) => {\n    let event = find(\"event/create-widget\");\n    return [event.remove()];\n  })\n  .commit(\"Create a new widget of the given model.\", ({find, choose, gather, record}) => {\n    let {model} = find(\"event/create-widget\");\n\n    // The serial number of our next widget is the highest serial we've issued for this model so far + 1.\n    let [serial] = choose(() => {\n      let {widget:other} = model;\n      1 == gather(other.serial).per(model).sort(\"down\"); // @NOTE: This breaks differently due to equality bug.\n      return other.serial + 1;\n    }, () => 1);\n    return [model.add(\"widget\", record(\"widget\", {serial}))];\n  });\n\n\n  verify(assert, prog, [\n    [1, \"tag\", \"model\"],\n    [1, \"widget\", 2],\n    [1, \"widget\", 3],\n    [2, \"tag\", \"widget\"],\n    [2, \"serial\", 3],\n    [3, \"tag\", \"widget\"],\n    [3, \"serial\", 5],\n\n    [4, \"tag\", \"event/create-widget\"],\n    [4, \"model\", 1],\n  ], [\n    [4, \"tag\", \"event/create-widget\", 0, -1],\n    [4, \"model\", 1, 0, -1],\n\n    [1, \"widget\", \"tag|widget|serial|6\", 0],\n    [\"tag|widget|serial|6\", \"tag\", \"widget\", 0],\n    [\"tag|widget|serial|6\", \"serial\", 6, 0],\n  ]);\n\n  assert.end();\n});\n\ntest(\"Aggregate: committed sort in choose with post filtering greater than\", (assert) => {\n  let prog = new Program(\"test\")\n  .commit(\"Clear events when they come in.\", ({find}) => {\n    let event = find(\"event/create-widget\");\n    return [event.remove()];\n  })\n  .commit(\"Create a new widget of the given model.\", ({find, choose, gather, record}) => {\n    let {model} = find(\"event/create-widget\");\n\n    // The serial number of our next widget is the highest serial we've issued for this model so far + 1.\n    let [serial] = choose(() => {\n      let {widget:other} = model;\n      2 > gather(other.serial).per(model).sort(\"down\");\n      return other.serial + 1;\n    }, () => 1);\n    return [model.add(\"widget\", record(\"widget\", {serial}))];\n  });\n\n\n  verify(assert, prog, [\n    [1, \"tag\", \"model\"],\n    [1, \"widget\", 2],\n    [1, \"widget\", 3],\n    [2, \"tag\", \"widget\"],\n    [2, \"serial\", 3],\n    [3, \"tag\", \"widget\"],\n    [3, \"serial\", 5],\n\n    [4, \"tag\", \"event/create-widget\"],\n    [4, \"model\", 1],\n  ], [\n    [4, \"tag\", \"event/create-widget\", 0, -1],\n    [4, \"model\", 1, 0, -1],\n\n    [1, \"widget\", \"tag|widget|serial|6\", 0],\n    [\"tag|widget|serial|6\", \"tag\", \"widget\", 0],\n    [\"tag|widget|serial|6\", \"serial\", 6, 0],\n  ]);\n\n  assert.end();\n});\n\n\ntest(\"Aggregate: committed sort with multiple groups\", (assert) => {\n  let prog = new Program(\"test\")\n  .commit(\"Clear events when they come in.\", ({find}) => {\n    let event = find(\"event/create-widget\");\n    return [event.remove()];\n  })\n  .commit(\"Create a new widget of the given model.\", ({find, choose, gather, record}) => {\n    let {model} = find(\"event/create-widget\");\n\n    // The serial number of our next widget is the highest serial we've issued for this model so far + 1.\n    let [serial] = choose(() => {\n      let {widget:other} = model;\n      2 > gather(other.serial).per(model).sort(\"down\");\n      return other.serial + 1;\n    }, () => 1);\n    return [model.add(\"widget\", record(\"widget\", {serial}))];\n  });\n\n\n  verify(assert, prog, [\n    [1, \"tag\", \"model\"],\n    [1, \"widget\", 2],\n    [1, \"widget\", 3],\n    [2, \"tag\", \"widget\"],\n    [2, \"serial\", 3],\n    [3, \"tag\", \"widget\"],\n    [3, \"serial\", 5],\n\n    [5, \"tag\", \"model\"],\n    [5, \"widget\", 6],\n    [6, \"tag\", \"widget\"],\n    [6, \"serial\", 28],\n    [5, \"widget\", 7],\n    [7, \"tag\", \"widget\"],\n    [7, \"serial\", 30],\n\n    [4, \"tag\", \"event/create-widget\"],\n    [4, \"model\", 1],\n  ], [\n    [4, \"tag\", \"event/create-widget\", 0, -1],\n    [4, \"model\", 1, 0, -1],\n\n    [1, \"widget\", \"tag|widget|serial|6\", 0],\n    [\"tag|widget|serial|6\", \"tag\", \"widget\", 0],\n    [\"tag|widget|serial|6\", \"serial\", 6, 0],\n  ]);\n\n  assert.end();\n});\n\ntest(\"Sort: incremental updates\", (assert) => {\n  let prog = new Program(\"test\");\n  prog.bind(\"the block's next is the highest node sort + 1.\", ({find, gather, record}) => {\n    let block = find(\"block\");\n    let {node} = block;\n    2 > gather(node.sort).per(block).sort(\"down\");\n    let sort = node.sort + 1;\n    return [block.add(\"next\", sort)];\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"block\"],\n    [1, \"node\", 2],\n    [2, \"sort\", 1],\n  ], [\n    [1, \"next\", 2, 1]\n  ]);\n  verify(assert, prog, [\n    [1, \"node\", 3],\n    [3, \"sort\", 2],\n  ], [\n    [1, \"next\", 3, 1],\n    [1, \"next\", 2, 1, -1],\n  ]);\n  verify(assert, prog, [\n    [1, \"node\", 4],\n    [4, \"sort\", 5],\n  ], [\n    [1, \"next\", 6, 1],\n    [1, \"next\", 3, 1, -1],\n  ]);\n\n  assert.end();\n});\n\n\ntest(\"Aggregate: inside choose without outer in key\", (assert) => {\n  let prog = new Program(\"test\");\n  prog.bind(\"count the names of people\", ({find, gather, record, choose}) => {\n    let person = find(\"person\");\n    let [sort] = choose(() => {\n      return gather(person.name).count();\n    }, () => \"yo yo yo\");\n    return [person.add(\"next\", sort)];\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [1, \"name\", \"chris\"],\n    [1, \"name\", \"christopher\"],\n    [2, \"name\", \"joe\"],\n  ], [\n    [1, \"next\", 2, 1],\n  ]);\n\n  assert.end();\n});\n\ntest(\"Aggregate: no outer in key variations\", (assert) => {\n  let prog = new Program(\"test\");\n  prog.bind(\"count the names of people\", ({find, gather, record, choose}) => {\n    let person = find(\"person\");\n    let [sort] = choose(() => {\n      return gather(person.name).count();\n    }, () => \"yo yo yo\");\n    return [person.add(\"next\", sort)];\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [1, \"name\", \"chris\"],\n    [1, \"name\", \"christopher\"],\n    [2, \"name\", \"joe\"],\n  ], [\n    [1, \"next\", 2, 1],\n  ]);\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\", 0, -1],\n  ], [\n    [1, \"next\", 2, 1, -1],\n  ]);\n\n  verify(assert, prog, [\n    [1, \"name\", \"chris\", 0, -1],\n    [1, \"tag\", \"person\"],\n  ], [\n    [1, \"next\", 1, 1],\n  ]);\n\n  verify(assert, prog, [\n    [1, \"name\", \"chris\"],\n    [1, \"tag\", \"person\", 0, -1],\n  ], [\n    [1, \"next\", 1, 1, -1],\n  ]);\n\n  assert.end();\n});\n\ntest(\"Aggregate: Test limit\", (assert) => {\n  let prog = new Program(\"test\");\n  prog.bind(\"Find up to two people\", ({find, gather, record, choose}) => {\n    let person = find(\"person\");\n    gather(person).sort() <= 2;\n    return [record({person})];\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [2, \"tag\", \"person\"],\n    [3, \"tag\", \"person\"],\n  ], [\n    [\"A\", \"person\", 1, 1],\n    [\"B\", \"person\", 2, 1],\n  ]);\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\", 0, -1],\n  ], [\n    [\"A\", \"person\", 1, 1, -1],\n    [\"C\", \"person\", 3, 1],\n  ]);\n\n  verify(assert, prog, [\n    [2, \"tag\", \"person\", 0, -1],\n  ], [\n    [\"B\", \"person\", 2, 1, -1],\n  ]);\n\n  assert.end();\n});\n\n// @NOTE: The not following the choose required for this example is currently marked dangerous\n// test(\"Aggregate: stratified after choose\", (assert) => {\n//   let prog = new Program(\"test\");\n//   prog.bind(\"Count the next of kin\", ({find, gather, choose, not, record}) => {\n//     let person = find(\"person\");\n//     let [kin] = choose(() => person.family, () => person.friend, () => person.acquaintance);\n//     not(() => kin.nemesis == person);\n//     let count = gather(kin).per(person).count();\n//     return [person.add(\"kin_count\", count)];\n//   });\n\n//   verify(assert, prog, [\n//     [1, \"tag\", \"person\"],\n//     [1, \"name\", \"chris\"],\n//     [1, \"friend\", \"joe\"],\n//     [1, \"friend\", \"fred\"],\n//     [1, \"friend\", \"steve\"],\n//     [\"steve\", \"nemesis\", 1],\n//   ], [\n//     [1, \"kin_count\", 2, 1],\n//   ]);\n\n//   verify(assert, prog, [\n//     [1, \"tag\", \"person\", 0, -1],\n//   ], [\n//     [1, \"kin_count\", 2, 1, -1],\n//   ]);\n\n//   assert.end();\n// });\n"
  },
  {
    "path": "test/all.ts",
    "content": "import \"./foundation\";\nimport \"./distinct\";\nimport \"./antijoin\";\nimport \"./choose\";\nimport \"./union\";\nimport \"./aggregate\";\nimport \"./stdlib/math\";\n// import \"./performance\";\n"
  },
  {
    "path": "test/antijoin.ts",
    "content": "import {Program} from \"../src/runtime/dsl2\";\nimport {verify, createVerifier} from \"./util\";\nimport * as test from \"tape\";\n\nfunction createProgram() {\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, not}) => {\n    let left = find(\"left\");\n    not(() => {\n      find(\"right\", {left})\n    })\n    return [\n      record(\"success\")\n    ]\n  });\n  return prog;\n}\n\ntest(\"Antijoin: simple left\", (assert) => {\n  let prog = createProgram();\n\n  verify(assert, prog, [\n    [1, \"tag\", \"left\"]\n  ], [\n    [2, \"tag\", \"success\", 1]\n  ])\n\n  assert.end();\n});\n\ntest(\"Antijoin: simple right\", (assert) => {\n  let prog = createProgram();\n\n  verify(assert, prog, [\n    [1, \"tag\", \"right\"]\n  ], [\n    // nothing\n  ])\n\n  verify(assert, prog, [\n    [1, \"left\", 2]\n  ], [\n    //nothing\n  ])\n\n  verify(assert, prog, [\n    [2, \"tag\", \"left\"]\n  ], [\n    //nothing\n  ])\n\n  assert.end();\n});\n\ntest(\"Antijoin: simple left then right\", (assert) => {\n  let prog = createProgram();\n\n  verify(assert, prog, [\n    [2, \"tag\", \"left\"]\n  ], [\n    [3, \"tag\", \"success\", 1]\n  ])\n\n  verify(assert, prog, [\n    [1, \"tag\", \"right\"],\n    [1, \"left\", 2],\n  ], [\n    [3, \"tag\", \"success\", 1, -1]\n  ])\n\n  assert.end();\n});\n\n\ntest(\"Antijoin: simple left then right same transaction\", (assert) => {\n  let prog = createProgram();\n\n  verify(assert, prog, [\n    [2, \"tag\", \"left\"],\n    [1, \"tag\", \"right\", 1],\n    [1, \"left\", 2, 1],\n  ], [\n    [3, \"tag\", \"success\", 1],\n    [3, \"tag\", \"success\", 2, -1]\n  ])\n\n  assert.end();\n});\n\ntest(\"Antijoin: simple right then left same transaction\", (assert) => {\n  let prog = createProgram();\n\n  verify(assert, prog, [\n    [1, \"tag\", \"right\"],\n    [1, \"left\", 2],\n    [2, \"tag\", \"left\", 1],\n  ], [\n    // nothing\n  ])\n\n  assert.end();\n});\n\ntest(\"Antijoin: right -> left -> -right\", (assert) => {\n  let prog = createProgram();\n\n  verify(assert, prog, [\n    [1, \"tag\", \"right\"],\n    [1, \"left\", 2],\n\n    [2, \"tag\", \"left\", 1],\n\n    [1, \"tag\", \"right\", 2, -1],\n    [1, \"left\", 2, 2, -1],\n  ], [\n    [3, \"tag\", \"success\", 3],\n  ])\n\n  assert.end();\n});\n\ntest(\"Antijoin: right -> right -> left -> -right\", (assert) => {\n  let prog = createProgram();\n\n  verify(assert, prog, [\n    [1, \"tag\", \"right\"],\n    [1, \"left\", 2],\n\n    [4, \"tag\", \"right\", 5],\n    [4, \"left\", 2, 5],\n  ], [\n    // nothing\n  ])\n\n  verify(assert, prog, [\n    [2, \"tag\", \"left\", 2],\n    [1, \"tag\", \"right\", 3, -1],\n    [1, \"left\", 2, 3, -1],\n  ], [\n    [3, \"tag\", \"success\", 4],\n    [3, \"tag\", \"success\", 6, -1],\n  ])\n\n  assert.end();\n});\n\ntest(\"Antijoin: right -> -right -> right -> left\", (assert) => {\n  let prog = createProgram();\n\n  verify(assert, prog, [\n    [1, \"tag\", \"right\"],\n    [1, \"tag\", \"right\", 3, -1],\n    [4, \"tag\", \"right\", 5],\n\n    [1, \"left\", 2],\n    [1, \"left\", 2, 3, -1],\n    [4, \"left\", 2, 5],\n  ], [\n    // nothing\n  ])\n\n  verify(assert, prog, [\n    [2, \"tag\", \"left\"],\n  ], [\n    [3, \"tag\", \"success\", 4],\n    [3, \"tag\", \"success\", 6, -1],\n  ])\n\n  assert.end();\n});\n\ntest(\"Antijoin: left -> right -> -right\", (assert) => {\n  let prog = createProgram();\n\n  verify(assert, prog, [\n    [2, \"tag\", \"left\"],\n    [4, \"tag\", \"right\", 5],\n    [4, \"left\", 2, 5],\n  ], [\n    // nothing\n    [3, \"tag\", \"success\", 1],\n    [3, \"tag\", \"success\", 6, -1],\n  ])\n\n  verify(assert, prog, [\n    [4, \"tag\", \"right\", 5, -1],\n    [4, \"left\", 2, 5, -1],\n  ], [\n    [3, \"tag\", \"success\", 6, 1],\n  ])\n\n  assert.end();\n});\n\ntest(\"Antijoin: right -> right -> left\", (assert) => {\n  let prog = createProgram();\n\n  verify(assert, prog, [\n    [1, \"tag\", \"right\", 4],\n    [1, \"left\", 2, 4],\n    [4, \"tag\", \"right\", 5],\n    [4, \"left\", 2, 5],\n  ], [\n    // nothing\n  ])\n\n  verify(assert, prog, [\n    [2, \"tag\", \"left\"],\n  ], [\n    [3, \"tag\", \"success\", 1],\n    [3, \"tag\", \"success\", 5, -1],\n  ])\n\n  assert.end();\n});\n\nlet programs = {\n  \"simple\": () => {\n    let prog = new Program(\"simple\");\n    prog.bind(\"simple block\", ({find, not, record}) => {\n      let input = find(\"input\");\n      not(() => input.arg0)\n      return [\n        record(\"result\")\n      ];\n    });\n    return prog;\n  },\n  \"dynamic\": () => {\n    let prog = new Program(\"simple\");\n    prog.bind(\"simple block\", ({find, not, record}) => {\n      let input = find(\"input\");\n      not(() => input.arg0)\n      return [\n        record(\"result\", {output: input})\n      ];\n    });\n    return prog;\n  },\n};\n\nlet verifyIO = createVerifier(programs);\n\n// -----------------------------------------------------\n// simple\n// -----------------------------------------------------\n\ntest(\"AntiJoin: simple +A; -A; +A\", (assert) => {\n  verifyIO(assert, \"simple\", \"+A; -A; +A\", [\n    [[2, \"tag\", \"result\", 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1]],\n    [[2, \"tag\", \"result\", 1, +1]]\n  ]);\n});\n\ntest(\"AntiJoin: simple +A; +B; -A; -B\", (assert) => {\n  verifyIO(assert, \"simple\", \"+A; +B; -A; -B\", [\n    [[2, \"tag\", \"result\", 1, +1]],\n    [],\n    [],\n    [[2, \"tag\", \"result\", 1, -1]],\n  ]);\n});\n\ntest(\"AntiJoin: simple +A, -A, +A\", (assert) => {\n  verifyIO(assert, \"simple\", \"+A, -A, +A\", [\n    [[2, \"tag\", \"result\", 1, +1],\n     [2, \"tag\", \"result\", 2, -1],\n     [2, \"tag\", \"result\", 3, +1]]\n  ]);\n});\n\ntest(\"AntiJoin: simple +A, +B, -A, -B\", (assert) => {\n  verifyIO(assert, \"simple\", \"+A, +B, -A, -B\", [\n    [[2, \"tag\", \"result\", 1, +1],\n     [2, \"tag\", \"result\", 4, -1]],\n  ]);\n});\n\ntest(\"AntiJoin: simple +A; +A:1; -A:1; -A\", (assert) => {\n  verifyIO(assert, \"simple\", \"+A; +A:1; -A:1; -A\", [\n    [[2, \"tag\", \"result\", 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1]],\n    [[2, \"tag\", \"result\", 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1]]\n  ]);\n});\n\ntest(\"AntiJoin: simple +A; +A:1; +A:2; -A:1; -A:2\", (assert) => {\n  verifyIO(assert, \"simple\", \"+A; +A:1; +A:2; -A:1; -A:2\", [\n    [[2, \"tag\", \"result\", 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1]],\n    [],\n    [],\n    [[2, \"tag\", \"result\", 1, +1]]\n  ]);\n});\n\ntest(\"AntiJoin: simple +A; +B:1; -A; +C\", (assert) => {\n  verifyIO(assert, \"simple\", \"+A; +B:1; -A; +C\", [\n    [[2, \"tag\", \"result\", 1, +1]],\n    [],\n    [[2, \"tag\", \"result\", 1, -1]],\n    [[2, \"tag\", \"result\", 1, +1]]\n  ]);\n});\n\ntest(\"AntiJoin: simple +A; +B:1; +C; +A:1\", (assert) => {\n  verifyIO(assert, \"simple\", \"+A; +B:1; +C; +A:1\", [\n    [[2, \"tag\", \"result\", 1, +1]],\n    [],\n    [],\n    []\n  ]);\n});\n\n// -----------------------------------------------------\n// dynamic\n// -----------------------------------------------------\n\ntest(\"AntiJoin: dynamic +A; -A; +A\", (assert) => {\n  verifyIO(assert, \"dynamic\", \"+A; -A; +A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"output\", \"A\", 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"output\", \"A\", 1, -1]],\n    [[2, \"tag\", \"result\", 1, +1], [2, \"output\", \"A\", 1, +1]]\n  ]);\n});\n\ntest(\"AntiJoin: dynamic +A; +B; -A; -B\", (assert) => {\n  verifyIO(assert, \"dynamic\", \"+A; +B; -A; -B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"output\", \"A\", 1, +1]],\n    [[2, \"tag\", \"result\", 1, +1], [2, \"output\", \"B\", 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"output\", \"A\", 1, -1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"output\", \"B\", 1, -1]],\n  ]);\n});\n"
  },
  {
    "path": "test/choose.ts",
    "content": "import {Program} from \"../src/runtime/dsl2\";\nimport {verify, createVerifier} from \"./util\";\nimport * as test from \"tape\";\n\ntest(\"Choose: basic\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib, choose}) => {\n    let person = find(\"person\");\n    let [info] = choose(() => {\n      person.dog;\n      return \"cool\";\n    }, () => {\n      return \"not cool\";\n    });\n    return [\n      record(\"dog-less\", {info})\n    ]\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n  ], [\n    [2, \"tag\", \"dog-less\", 1],\n    [2, \"info\", \"not cool\", 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"dog\", \"spot\"],\n  ], [\n    [2, \"tag\", \"dog-less\", 1, -1],\n    [2, \"info\", \"not cool\", 1, -1],\n    [3, \"tag\", \"dog-less\", 1],\n    [3, \"info\", \"cool\", 1],\n  ])\n\n  assert.end();\n});\n\ntest(\"Choose: 3 branches\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib, choose}) => {\n    let person = find(\"person\");\n    let [info] = choose(() => {\n      person.dog;\n      return \"cool\";\n    }, () => {\n      person.foo;\n      return \"zomg\";\n    }, () => {\n      return \"not cool\";\n    });\n    return [\n      record(\"dog-less\", {info})\n    ]\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n  ], [\n    [2, \"tag\", \"dog-less\", 1],\n    [2, \"info\", \"not cool\", 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"dog\", \"spot\"],\n  ], [\n    [2, \"tag\", \"dog-less\", 1, -1],\n    [2, \"info\", \"not cool\", 1, -1],\n    [3, \"tag\", \"dog-less\", 1],\n    [3, \"info\", \"cool\", 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"dog\", \"spot\", 0, -1],\n    [1, \"foo\", \"woop\"],\n  ], [\n    [3, \"tag\", \"dog-less\", 1, -1],\n    [3, \"info\", \"cool\", 1, -1],\n    [4, \"tag\", \"dog-less\", 1],\n    [4, \"info\", \"zomg\", 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"foo\", \"woop\", 0, -1],\n  ], [\n    [3, \"tag\", \"dog-less\", 1, -1],\n    [3, \"info\", \"zomg\", 1, -1],\n    [4, \"tag\", \"dog-less\", 1],\n    [4, \"info\", \"not cool\", 1],\n  ])\n\n  assert.end();\n});\n\ntest(\"Choose: 4 branches\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib, choose}) => {\n    let person = find(\"person\");\n    let {boat} = person;\n    let [info] = choose(() => {\n      person.dog;\n      return \"cool\";\n    }, () => {\n      person.foo;\n      return \"zomg\";\n    }, () => {\n      boat.foo;\n      return \"woah\";\n    }, () => {\n      return \"not cool\";\n    });\n    return [\n      record(\"dog-less\", {info})\n    ]\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [1, \"boat\", 9],\n  ], [\n    [2, \"tag\", \"dog-less\", 1],\n    [2, \"info\", \"not cool\", 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"dog\", \"spot\"],\n  ], [\n    [2, \"tag\", \"dog-less\", 1, -1],\n    [2, \"info\", \"not cool\", 1, -1],\n    [3, \"tag\", \"dog-less\", 1],\n    [3, \"info\", \"cool\", 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"dog\", \"spot\", 0, -1],\n    [1, \"foo\", \"woop\"],\n  ], [\n    [3, \"tag\", \"dog-less\", 1, -1],\n    [3, \"info\", \"cool\", 1, -1],\n    [4, \"tag\", \"dog-less\", 1],\n    [4, \"info\", \"zomg\", 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"foo\", \"woop\", 0, -1],\n  ], [\n    [3, \"tag\", \"dog-less\", 1, -1],\n    [3, \"info\", \"zomg\", 1, -1],\n    [4, \"tag\", \"dog-less\", 1],\n    [4, \"info\", \"not cool\", 1],\n  ])\n\n  verify(assert, prog, [\n    [9, \"foo\", \"meep moop\"],\n  ], [\n    [3, \"tag\", \"dog-less\", 1, -1],\n    [3, \"info\", \"not cool\", 1, -1],\n    [4, \"tag\", \"dog-less\", 1],\n    [4, \"info\", \"woah\", 1],\n  ])\n\n  verify(assert, prog, [\n    [9, \"foo\", \"meep moop\", 0, -1],\n  ], [\n    [3, \"tag\", \"dog-less\", 1, -1],\n    [3, \"info\", \"woah\", 1, -1],\n    [4, \"tag\", \"dog-less\", 1],\n    [4, \"info\", \"not cool\", 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"dog\", \"spot\"],\n  ], [\n    [3, \"tag\", \"dog-less\", 1, -1],\n    [3, \"info\", \"not cool\", 1, -1],\n    [4, \"tag\", \"dog-less\", 1],\n    [4, \"info\", \"cool\", 1],\n  ])\n\n  verify(assert, prog, [\n    [5, \"tag\", \"person\"],\n    [5, \"boat\", 10],\n  ], [\n    [4, \"tag\", \"dog-less\", 1],\n    [4, \"info\", \"not cool\", 1],\n  ])\n\n  verify(assert, prog, [\n    [5, \"tag\", \"person\", 0, -1],\n    [1, \"tag\", \"person\", 0, -1],\n  ], [\n    [4, \"tag\", \"dog-less\", 1, -1],\n    [4, \"info\", \"not cool\", 1, -1],\n    [6, \"tag\", \"dog-less\", 1, -1],\n    [6, \"info\", \"cool\", 1, -1],\n  ])\n\n  verify(assert, prog, [\n    [5, \"tag\", \"person\"],\n    [1, \"tag\", \"person\"],\n  ], [\n    [4, \"tag\", \"dog-less\", 1],\n    [4, \"info\", \"not cool\", 1],\n    [6, \"tag\", \"dog-less\", 1],\n    [6, \"info\", \"cool\", 1],\n  ])\n\n\n  assert.end();\n});\n\n// @TODO: Give this a better name when we figure out the specific issue.\ntest(\"Choose: Busted partial identity\", (assert) => {\n  let prog = new Program(\"test\");\n  prog.bind(\"Split up our cat attributes\", ({find, lookup, record}) => {\n    let cat = find(\"cat\");\n    let {attribute, value} = lookup(cat);\n    return [\n      // @NOTE: Issue has to do with add, can't repro if value is part of the identity.\n      record(\"cat-attribute\", {cat, attribute}).add(\"value\", value)\n    ];\n  })\n\n  prog.bind(\"Create value records for each cat attribute.\", ({find, lookup, choose, record}) => {\n    let catAttribute = find(\"cat-attribute\");\n    // Tags about cats are cool.\n    // @FIXME: In some (but not all) cases where the first branch matches both branches emit.\n    //         This may be multiplicity/retraction related.\n    let [attrName] = choose(\n      () => { catAttribute.attribute == \"tag\"; return \"cool tags\"; },\n      () => catAttribute.attribute\n    );\n\n    let {cat, value} = catAttribute;\n    return [\n      record(\"cat-value\", {cat, attr: attrName, val: value})\n    ];\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"pet\"],\n    [1, \"tag\", \"cat\"],\n    [1, \"name\", \"Felicia\"],\n  ], [\n    [2, \"tag\", \"cat-attribute\", 1],\n    [2, \"cat\", 1, 1],\n    [2, \"attribute\", \"tag\", 1],\n    [2, \"value\", \"pet\", 1],\n    [2, \"value\", \"cat\", 1],\n\n    [3, \"tag\", \"cat-attribute\", 1],\n    [3, \"cat\", 1, 1],\n    [3, \"attribute\", \"name\", 1],\n    [3, \"value\", \"Felicia\", 1],\n\n    [4, \"tag\", \"cat-value\", 2],\n    [4, \"cat\", 1, 2],\n    [4, \"attr\", \"cool tags\", 2],\n    [4, \"val\", \"pet\", 2],\n\n    [5, \"tag\", \"cat-value\", 2],\n    [5, \"cat\", 1, 2],\n    [5, \"attr\", \"cool tags\", 2],\n    [5, \"val\", \"cat\", 2],\n\n    [6, \"tag\", \"cat-value\", 2],\n    [6, \"cat\", 1, 2],\n    [6, \"attr\", \"name\", 2],\n    [6, \"val\", \"Felicia\", 2],\n  ]);\n\n  assert.end();\n});\n\ntest(\"Choose: multiple return\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib, choose}) => {\n    let person = find(\"person\");\n    let [displayName, coolness] = choose(() => {\n      return [person.nickName, \"cool\"];\n    }, () => {\n      return [person.name, \"not cool\"];\n    });\n    return [\n      person.add(\"displayName\", displayName),\n      person.add(\"coolness\", coolness),\n    ]\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [1, \"name\", \"joseph\"],\n  ], [\n    [1, \"displayName\", \"joseph\", 1],\n    [1, \"coolness\", \"not cool\", 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"nickName\", \"joey\"],\n  ], [\n    [1, \"displayName\", \"joseph\", 1, -1],\n    [1, \"coolness\", \"not cool\", 1, -1],\n    [1, \"displayName\", \"joey\", 1],\n    [1, \"coolness\", \"cool\", 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"nickName\", \"joey\", 0, -1],\n  ], [\n    [1, \"displayName\", \"joseph\", 1],\n    [1, \"coolness\", \"not cool\", 1],\n    [1, \"displayName\", \"joey\", 1, -1],\n    [1, \"coolness\", \"cool\", 1, -1],\n  ])\n\n  assert.end();\n});\n\ntest(\"Choose: moves only\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, choose}) => {\n    let person = find(\"person\");\n    let {name} = person;\n    let [displayName] = choose(\n      () => { name == \"christopher\"; return \"chris\"; },\n        () => name\n    );\n    return [\n      person.add({displayName})\n    ]\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [1, \"name\", \"christopher\"],\n    [2, \"tag\", \"person\"],\n    [2, \"name\", \"jane\"],\n  ], [\n    [1, \"displayName\", \"chris\", 1],\n    [2, \"displayName\", \"jane\", 1],\n  ])\n\n  assert.end();\n});\n\ntest(\"Choose: post-filtering outer\", (assert) => {\n  let prog = new Program(\"test\");\n  prog.bind(\"froofy\", ({find, choose, record}) => {\n    let person = find(\"person\");\n    let [display] = choose(() => person.display);\n    display.name == \"Ferdinand\";\n    return [record(\"result\", {name: display.name})];\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [1, \"display\", 2],\n    [2, \"name\", \"Jess\"],\n    [3, \"tag\", \"cat\"],\n    [3, \"display\", 4],\n    [4, \"name\", \"Ferdinand\"],\n  ], []);\n  assert.end();\n});\n\ntest(\"Choose: expression-only dynamic branch\", (assert) => {\n  let prog = new Program(\"test\");\n  prog.bind(\"Choose non-static expression only.\", ({find, choose, record}) => {\n    let guy = find(\"guy\");\n    let {radness} = guy;\n    let [radometer] = choose(() => radness * 3); // This does not.\n    // let radometer = radness * 3; // This works\n    return [guy.add(\"radometer\", radometer)];\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"guy\"],\n    [1, \"radness\", 1],\n  ], [\n    [1, \"radometer\", 3, 1]\n  ]);\n  assert.end();\n});\n\ntest(\"Choose: filter and expression-only dynamic branches\", (assert) => {\n  let prog = new Program(\"test\");\n  prog.bind(\"Choose non-static expression only.\", ({find, choose, record}) => {\n    let guy = find(\"guy\");\n    let {radness} = guy;\n    // We need to adjust the scale since radness is roughly logarithmic.\n    let [radometer] = choose(\n      () => { radness < 2; return radness; },\n      () => { radness < 4; return radness * 2; },\n      () => radness * 3\n    );\n    // let radometer = radness * 3;\n    return [guy.add(\"radometer\", radometer)];\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"guy\"],\n    [1, \"radness\", 0],\n    [2, \"tag\", \"guy\"],\n    [2, \"radness\", 1],\n    [3, \"tag\", \"guy\"],\n    [3, \"radness\", 2],\n    [4, \"tag\", \"guy\"],\n    [4, \"radness\", 4],\n  ], [\n    [1, \"radometer\", 0, 1],\n    [2, \"radometer\", 1, 1],\n    [3, \"radometer\", 4, 1],\n    [4, \"radometer\", 12, 1]\n  ]);\n\n  verify(assert, prog, [\n    [1, \"radness\", 0, 0, -1],\n    [1, \"radness\", 8],\n  ], [\n    [1, \"radometer\", 24, 1],\n    [1, \"radometer\", 0, 1, -1],\n  ]);\n  assert.end();\n});\n\n\n\nlet programs = {\n  \"1 static\": () => {\n    let prog = new Program(\"1 static branch\");\n    prog.bind(\"simple block\", ({find, choose, record}) => {\n      let foo = find(\"input\");\n      let [branch] = choose(() => 1);\n      return [\n        record(\"result\", {branch})\n      ];\n    });\n    return prog;\n  },\n\n  \"1 dynamic\": () => {\n    let prog = new Program(\"1 dynamic branch\");\n    prog.bind(\"simple block\", ({find, choose, record}) => {\n      let foo = find(\"input\");\n      let [output] = choose(() => foo.arg0);\n      return [\n        record(\"result\", {output})\n      ];\n    });\n    return prog;\n  },\n\n  \"1 dynamic 1 static\": () => {\n    let prog = new Program(\"1 dynamic branch\");\n    prog.bind(\"simple block\", ({find, choose, record}) => {\n      let foo = find(\"input\");\n      let [output] = choose(\n        () => {foo.arg0 == 1; return \"one\"},\n        () => \"else\"\n      );\n      return [\n        record(\"result\", {output})\n      ];\n    });\n    return prog;\n  },\n\n  \"2 dynamic\": () => {\n    let prog = new Program(\"1 dynamic branch\");\n    prog.bind(\"simple block\", ({find, choose, record}) => {\n      let foo = find(\"input\");\n      let [output] = choose(\n        () => {foo.arg0 == 1; return \"one\"},\n        () => foo.arg0\n      );\n      return [\n        record(\"result\", {output})\n      ];\n    });\n    return prog;\n  },\n};\n\nlet verifyBranches = createVerifier(programs);\n\n// -----------------------------------------------------\n// 1 Static branch\n// -----------------------------------------------------\n\ntest(\"Choose: 1 static branch +A; -A; +A\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A; -A; +A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"branch\", 1, 1, -1]],\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]]\n  ]);\n});\n\ntest(\"Choose: 1 static branch +A; -A; +B\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A; -A; +B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"branch\", 1, 1, -1]],\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]]\n  ]);\n});\n\n// @NOTE: Broken due to verify being too simple.\ntest(\"Choose: 1 static branch +A; +A; -A\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A; +A; -A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [],\n    []\n  ]);\n});\n\n// @NOTE: Broken due to verify being too simple.\ntest(\"Choose: 1 static branch +A; +A; -A; -A\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A; +A; -A; -A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [],\n    [],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"branch\", 1, 1, -1]],\n  ]);\n});\n\ntest(\"Choose: 1 static branch +A +B; -A; +A\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A +B; -A; +A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [],\n    []\n  ]);\n});\n\ntest(\"Choose: 1 static branch +A +B; -A -B\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A +B; -A -B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"branch\", 1, 1, -1]]\n  ]);\n});\n\ntest(\"Choose: 1 static branch +A; -A +B\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A; -A +B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    []\n  ]);\n});\n\ntest(\"Choose: 1 static branch +A, -A\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A, -A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1],\n     [2, \"tag\", \"result\", 2, -1], [2, \"branch\", 1, 2, -1]]\n  ]);\n});\n\ntest(\"Choose: 1 static branch +A, -A +B\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A, -A +B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]]\n  ]);\n});\n\ntest(\"Choose: 1 static branch +A, +B\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A, +B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]]\n  ]);\n});\n\ntest(\"Choose: 1 static branch +A, +B; -A\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A, +B; -A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"branch\", 1, 1, -1],\n     [2, \"tag\", \"result\", 2, +1], [2, \"branch\", 1, 2, +1]]\n  ]);\n});\n\ntest(\"Choose: 1 static branch +A; -A, +B\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A; -A, +B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"branch\", 1, 1, -1],\n     [2, \"tag\", \"result\", 2, +1], [2, \"branch\", 1, 2, +1]]\n  ]);\n});\n\n// -----------------------------------------------------\n// 1 dynamic branch\n// -----------------------------------------------------\n\ntest(\"Choose: 1 dynamic branch +A:1; -A:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic\", \"+A:1; -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", 1, 1, -1]],\n  ]);\n});\n\ntest(\"Choose: 1 dynamic branch +A:1; -A:1; +A:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic\", \"+A:1; -A:1; +A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", 1, 1, -1]],\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n  ]);\n});\n\ntest(\"Choose: 1 dynamic branch +A:1; -A:1; +A:2\", (assert) => {\n  verifyBranches(assert, \"1 dynamic\", \"+A:1; -A:1; +A:2\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", 1, 1, -1]],\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 2, 1, +1]],\n  ]);\n});\n\ntest(\"Choose: 1 dynamic branch +A:1; +A:2; -A:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic\", \"+A:1; +A:2; -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 2, 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", 1, 1, -1]],\n  ]);\n});\n\ntest(\"Choose: 1 dynamic branch +A:1; +B:1; -A:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic\", \"+A:1; +B:1; -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n    [],\n    [],\n  ]);\n});\n\ntest(\"Choose: 1 dynamic branch +A:1; +B:1; -A:1, -B:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic\", \"+A:1; +B:1; -A:1, -B:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n    [],\n    [[1, \"tag\", \"result\", 2, -1], [1, \"output\", 1, 2, -1]],\n  ]);\n});\n\n// -----------------------------------------------------\n// 1 dynamic 1 static\n// -----------------------------------------------------\n\ntest(\"Choose: 1 dynamic 1 static branch +A:1; -A:1; +A:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic 1 static\", \"+A:1; -A:1; +A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", \"one\", 1, -1]],\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]]\n  ]);\n});\n\ntest(\"Choose: 1 dynamic 1 static branch +A:2; -A:2; +A:2\", (assert) => {\n  verifyBranches(assert, \"1 dynamic 1 static\", \"+A:2; -A:2; +A:2\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"else\", 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", \"else\", 1, -1]],\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"else\", 1, +1]]\n  ]);\n});\n\ntest(\"Choose: 1 dynamic 1 static branch +A:1; -A:1; +A:2\", (assert) => {\n  verifyBranches(assert, \"1 dynamic 1 static\", \"+A:1; -A:1; +A:2\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", \"one\", 1, -1]],\n    [[2, \"tag\", \"result\", 1, +1], [2, \"output\", \"else\", 1, +1]]\n  ]);\n});\n\ntest(\"Choose: 1 dynamic 1 static branch +A:1; +A:2\", (assert) => {\n  verifyBranches(assert, \"1 dynamic 1 static\", \"+A:1; +A:2\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]],\n    []\n  ]);\n});\n\ntest(\"Choose: 1 dynamic 1 static branch +A:1; +B:2\", (assert) => {\n  verifyBranches(assert, \"1 dynamic 1 static\", \"+A:1; +B:2\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]],\n    [[2, \"tag\", \"result\", 1, +1], [2, \"output\", \"else\", 1, +1]]\n  ]);\n});\n\ntest(\"Choose: 1 dynamic 1 static branch +A:1; +B:1; -A:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic 1 static\", \"+A:1; +B:1; -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]],\n    [],\n    []\n  ]);\n});\n\ntest(\"Choose: 1 dynamic 1 static branch +A:1, -A:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic 1 static\", \"+A:1, -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1],\n     [1, \"tag\", \"result\", 2, -1], [1, \"output\", \"one\", 2, -1]]\n  ]);\n});\n\n\ntest(\"Choose: 1 dynamic 1 static branch +A:1, -A:1, +A:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic 1 static\", \"+A:1, -A:1, +A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1],\n     [1, \"tag\", \"result\", 2, -1], [1, \"output\", \"one\", 2, -1],\n     [1, \"tag\", \"result\", 3, +1], [1, \"output\", \"one\", 3, +1]]\n  ]);\n});\n\ntest(\"Choose: 1 dynamic 1 static branch +A:1; +B:1; -B:1; +B:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic 1 static\", \"+A:1; +B:1; -B:1; +B:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]],\n    [],\n    [],\n    []\n  ]);\n});\n\n// -----------------------------------------------------\n// 2 dynamics\n// -----------------------------------------------------\n\ntest(\"Choose: 2 dynamic branch +A:1; -A:1; +A:1\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1; -A:1; +A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", \"one\", 1, -1]],\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]]\n  ]);\n});\n\ntest(\"Choose: 2 dynamic branch +A:2; -A:2; +A:2\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:2; -A:2; +A:2\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 2, 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", 2, 1, -1]],\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 2, 1, +1]]\n  ]);\n});\n\ntest(\"Choose: 2 dynamic branch +A:1; -A:1; +A:2\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1; -A:1; +A:2\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", \"one\", 1, -1]],\n    [[2, \"tag\", \"result\", 1, +1], [2, \"output\", 2, 1, +1]]\n  ]);\n});\n\ntest(\"Choose: 2 dynamic branch +A:1; +A:2\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1; +A:2\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]],\n    []\n  ]);\n});\n\ntest(\"Choose: 2 dynamic branch +A:1; +B:2\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1; +B:2\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]],\n    [[2, \"tag\", \"result\", 1, +1], [2, \"output\", 2, 1, +1]]\n  ]);\n});\n\ntest(\"Choose: 2 dynamic branch +A:1; +B:1; -A:1\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1; +B:1; -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]],\n    [],\n    []\n  ]);\n});\n\ntest(\"Choose: 2 dynamic branch +A:1, -A:1\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1, -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1],\n     [1, \"tag\", \"result\", 2, -1], [1, \"output\", \"one\", 2, -1]]\n  ]);\n});\n\n\ntest(\"Choose: 2 dynamic branch +A:1, -A:1, +A:1\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1, -A:1, +A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1],\n     [1, \"tag\", \"result\", 2, -1], [1, \"output\", \"one\", 2, -1],\n     [1, \"tag\", \"result\", 3, +1], [1, \"output\", \"one\", 3, +1]]\n  ]);\n});\n\ntest(\"Choose: 2 dynamic branch +A:1; +B:1; -B:1; +B:1\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1; +B:1; -B:1; +B:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1]],\n    [],\n    [],\n    []\n  ]);\n});\n"
  },
  {
    "path": "test/distinct.ts",
    "content": "import {DistinctIndex} from \"../src/runtime/indexes\";\nimport {Change, Iterator} from \"../src/runtime/runtime\";\nimport {verify} from \"./util\";\nimport * as test from \"tape\";\n\nfunction roundCountsToChanges(rcs:number[][]) {\n  let changes = [];\n  for(let [round, count] of rcs) {\n    changes.push(new Change(1,2,3,4,1,round,count));\n  }\n  return changes;\n}\n\nfunction distinctTest(assert:any, roundCounts: number[][], expected: any) {\n  let index = new DistinctIndex();\n\n  let changes = roundCountsToChanges(roundCounts);\n\n  let final:any = {};\n  for(let change of changes) {\n    let neueChanges = new Iterator<Change>();\n    index.distinct(change, neueChanges);\n    let neue;\n    while((neue = neueChanges.next())) {\n      final[neue.round] = (final[neue.round] || 0) + neue.count;\n    }\n  }\n\n  let badKeys:any = {};\n\n  for(let key in expected) {\n    let finalValue = final[key];\n    let expectedValue = expected[key];\n    if(finalValue || expectedValue) {\n      let valid = finalValue == expectedValue;\n      assert.true(valid, `round ${key} :: expected ${expected[key]}, actual ${final[key]}`);\n      if(!valid) {\n        badKeys[key] = true;\n      }\n    }\n  }\n  for(let key in final) {\n    if(badKeys[key]) continue;\n    let finalValue = final[key];\n    let expectedValue = expected[key];\n    if(finalValue || expectedValue) {\n      let valid = finalValue == expectedValue;\n      assert.true(valid, `round ${key} :: expected ${expected[key]}, actual ${final[key]}`);\n      if(!valid) {\n        badKeys[key] = true;\n      }\n    }\n  }\n}\n\ninterface DistinctTest {\n  only:(name:string, roundCounts:number[][], expected:{[round:number]: number}) => void\n  (name:string, roundCounts:number[][], expected:{[round:number]: number}): void\n}\n\nlet distinct:DistinctTest = ((name:string, roundCounts:number[][], expected:{[round:number]: number}) => {\n  test(`Distinct: ${name}`, (assert) => {\n    distinctTest(assert, roundCounts, expected);\n    assert.end();\n  });\n}) as any;\n\n\n\ndistinct.only = function distinctOnly(name:string, roundCounts:number[][], expected:any) {\n  test.only(`Distinct: ${name}`, (assert) => {\n    distinctTest(assert, roundCounts, expected);\n    assert.end();\n  });\n}\n\ndistinct(\"basic\", [\n  [1,1],\n  [2,-1],\n\n  [1, 1],\n  [3, -1],\n], {\n  1: 1,\n  3: -1\n})\n\n//------------------------------------------------------------\n// Chris's section\n//------------------------------------------------------------\n\ndistinct(\"basic 2\", [\n  [1, 1],\n  [2, -1],\n\n  [3,1],\n  [4,-1],\n], {\n  1: 1,\n  2: -1,\n  3: 1,\n  4: -1,\n})\n\ndistinct(\"basic 2 in reverse order\", [\n  [3,1],\n  [4,-1],\n\n  [1, 1],\n  [2, -1],\n], {\n  1: 1,\n  2: -1,\n  3: 1,\n  4: -1,\n})\n\ndistinct(\"basic 2 undone\", [\n  [1, 1],\n  [2, -1],\n\n  [3,1],\n  [4,-1],\n\n  [1, -1],\n  [2, 1],\n], {\n  3: 1,\n  4: -1,\n})\n\ndistinct(\"multiple counts\", [\n  [1, 1],\n  [1, 1],\n  [1, 1],\n  [2, -1],\n  [2, -1],\n  [2, -1],\n\n  [3,1],\n  [3,1],\n  [3,1],\n  [4,-1],\n  [4,-1],\n  [4,-1],\n], {\n  1: 1,\n  2: -1,\n  3: 1,\n  4: -1,\n})\n\ndistinct(\"multiple counts reversed\", [\n  [3,1],\n  [3,1],\n  [3,1],\n  [4,-1],\n  [4,-1],\n  [4,-1],\n\n  [1, 1],\n  [1, 1],\n  [1, 1],\n  [2, -1],\n  [2, -1],\n  [2, -1],\n], {\n  1: 1,\n  2: -1,\n  3: 1,\n  4: -1,\n})\n\ndistinct(\"multiple counts interleaved\", [\n  [3,1],\n  [4,-1],\n  [3,1],\n  [4,-1],\n  [3,1],\n  [4,-1],\n\n  [1, 1],\n  [2, -1],\n  [1, 1],\n  [2, -1],\n  [1, 1],\n  [2, -1],\n], {\n  1: 1,\n  2: -1,\n  3: 1,\n  4: -1,\n})\n\ndistinct(\"multiple counts negatives first\", [\n  [2, -1],\n  [2, -1],\n  [2, -1],\n  [1, 1],\n  [1, 1],\n  [1, 1],\n\n  [4,-1],\n  [4,-1],\n  [4,-1],\n  [3,1],\n  [3,1],\n  [3,1],\n], {\n  1: 1,\n  2: -1,\n  3: 1,\n  4: -1,\n})\n\ndistinct(\"multiple counts undone\", [\n  [1, 1],\n  [1, 1],\n  [1, 1],\n  [2, -1],\n  [2, -1],\n  [2, -1],\n\n  [3,1],\n  [3,1],\n  [3,1],\n  [4,-1],\n  [4,-1],\n  [4,-1],\n\n  [1, -1],\n  [1, -1],\n  [1, -1],\n  [2, 1],\n  [2, 1],\n  [2, 1],\n], {\n  3: 1,\n  4: -1,\n})\n\ndistinct(\"multiple counts undone interleaved\", [\n  [1, 1],\n  [1, 1],\n  [1, 1],\n  [2, -1],\n  [2, -1],\n  [2, -1],\n\n  [1, -1],\n  [1, -1],\n  [1, -1],\n\n  [3,1],\n  [3,1],\n  [3,1],\n  [4,-1],\n  [4,-1],\n  [4,-1],\n\n  [2, 1],\n  [2, 1],\n  [2, 1],\n], {\n  3: 1,\n  4: -1,\n})\n\ndistinct(\"multiple disparate counts\", [\n  [1, 1],\n  [1, 1],\n  [1, 1],\n  [2, -1],\n  [2, -1],\n  [2, -1],\n\n  [3,1],\n  [4,-1],\n], {\n  1: 1,\n  2: -1,\n  3: 1,\n  4: -1,\n})\n\ndistinct(\"multiple disparate counts with extra removes\", [\n  [1, 1],\n  [1, 1],\n  [1, 1],\n\n  [2, -1],\n  [2, -1],\n  [2, -1],\n\n  [1, -1],\n  [1, -1],\n  [1, -1],\n\n  [2, 1],\n  [2, 1],\n  [2, 1],\n\n  [3,1],\n  [4,-1],\n], {\n  3: 1,\n  4: -1,\n})\n\n//------------------------------------------------------------\n// Josh's section\n//------------------------------------------------------------\n\ndistinct(\"simple round promotion\", [\n  [8, 1],\n  [9, -1],\n\n  [5, 1],\n  [6, -1],\n  [8, -1],\n  [9, 1]\n], {\n  5: 1,\n  6: -1,\n  8: 0,\n  9: 0\n});\n\ndistinct(\"full promotion\", [\n  [9, 1],\n  [9, 1],\n  [10, -1],\n  [10, -1],\n\n  [9, 1],\n  [9, 1],\n  [10, -1],\n  [10, -1],\n\n  [9, -1],\n  [10, 1],\n  [9, -1],\n  [10, 1],\n\n  [9, -1],\n  [10, 1],\n  [9, -1],\n  [10, 1]\n], {\n  9: 0,\n  10: 0\n})\n\ndistinct(\"positive full promotion\", [\n  [7, 1],\n  [8, -1],\n  [8, 1],\n  [7, 1],\n  [8, -1],\n  [4, 1],\n  [8, -1],\n  [7, 1],\n  [8, -1],\n  [8, 1],\n  [5, -1],\n  [7, -3],\n  [8, 1],\n  [8, 3],\n  [5, 1],\n  [8, 1],\n  [8, -2],\n  [8, -1],\n], {\n  4: 1,\n})\n"
  },
  {
    "path": "test/foundation.ts",
    "content": "import {Program} from \"../src/runtime/dsl2\";\nimport {verify} from \"./util\";\nimport * as test from \"tape\";\n\ntest(\"find a record and generate a record as a result\", (assert) => {\n\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib}) => {\n    find({foo: \"bar\"});\n    return [\n      record({zomg: \"baz\"})\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  verify(assert, prog, [\n    [1, \"foo\", \"bar\"]\n  ], [\n    [2, \"zomg\", \"baz\", 1]\n  ])\n\n  assert.end();\n});\n\n\ntest(\"> filters numbers\", (assert) => {\n\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib}) => {\n    let a = find();\n    let b = find();\n    a.age > b.age;\n    return [\n      record({age1: a.age, age2: b.age})\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [1, \"age\", 7],\n    [2, \"tag\", \"person\"],\n    [2, \"age\", 41],\n    [3, \"tag\", \"person\"],\n    [3, \"age\", 3],\n  ], [\n    [4, \"age1\", 41, 1],\n    [4, \"age2\", 7, 1],\n    [5, \"age1\", 41, 1],\n    [5, \"age2\", 3, 1],\n    [6, \"age1\", 7, 1],\n    [6, \"age2\", 3, 1],\n  ])\n\n  assert.end();\n});\n\n\ntest(\"simple addition\", (assert) => {\n\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib}) => {\n    let a = find(\"person\");\n    let b = find(\"person\");\n    a.age > b.age;\n    let result = a.age + b.age;\n    return [\n      record({age1: a.age, age2: b.age, result})\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [1, \"age\", 7],\n    [2, \"tag\", \"person\"],\n    [2, \"age\", 41],\n    [3, \"tag\", \"person\"],\n    [3, \"age\", 3],\n  ], [\n    [4, \"age1\", 41, 1],\n    [4, \"age2\", 7, 1],\n    [4, \"result\", 48, 1],\n    [5, \"age1\", 41, 1],\n    [5, \"age2\", 3, 1],\n    [5, \"result\", 44, 1],\n    [6, \"age1\", 7, 1],\n    [6, \"age2\", 3, 1],\n    [6, \"result\", 10, 1],\n  ])\n\n  assert.end();\n});\n\ntest(\"simple division\", (assert) => {\n\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib}) => {\n    let a = find(\"person\");\n    let b = find(\"person\");\n    a.age > b.age;\n    let result = a.age / b.age;\n    return [\n      record({age1: a.age, age2: b.age, result})\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [1, \"age\", 7],\n    [2, \"tag\", \"person\"],\n    [2, \"age\", 35],\n  ], [\n    [4, \"age1\", 35, 1],\n    [4, \"age2\", 7, 1],\n    [4, \"result\", 5, 1],\n  ])\n\n  assert.end();\n});\n\ntest(\"static equality filters expressions\", (assert) => {\n  let prog = new Program(\"Automatic Teacher's Assistant\");\n  prog.bind(\"Auto TA addition\", ({find, record, lib}) => {\n    let addition = find(\"addition\");\n    1 == addition.a + addition.b;\n    return [record(\"success\", {addition})];\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"addition\"],\n    [1, \"a\", 7],\n    [1, \"b\", 13],\n\n    [2, \"tag\", \"addition\"],\n    [2, \"a\", 3],\n    [2, \"b\", -2],\n  ], [\n    [\"A\", \"tag\", \"success\", 1],\n    [\"A\", \"addition\", 2, 1]\n  ])\n\n  assert.end();\n});\n\ntest(\"dynamic equality filters expressions\", (assert) => {\n  let prog = new Program(\"Automatic Teacher's Assistant\");\n  prog.bind(\"Auto TA addition\", ({find, record, lib}) => {\n    let addition = find(\"addition\");\n    addition.c == addition.a + addition.b;\n    return [record(\"success\", {addition})];\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"addition\"],\n    [1, \"a\", 7],\n    [1, \"b\", 13],\n    [1, \"c\", 1],\n\n    [2, \"tag\", \"addition\"],\n    [2, \"a\", 3],\n    [2, \"b\", -2],\n    [2, \"c\", 1],\n  ], [\n    [\"Z\", \"tag\", \"success\", 1],\n    [\"Z\", \"addition\", 2, 1]\n  ])\n\n  assert.end();\n});\n\n\n\ntest(\"simple recursion\", (assert) => {\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib}) => {\n    let {number} = find();\n    9 > number;\n    let result = number + 1;\n    return [\n      record({number: result})\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  verify(assert, prog, [\n    [1, \"number\", 1],\n  ], [\n    [2, \"number\", 2, 1],\n    [3, \"number\", 3, 2],\n    [4, \"number\", 4, 3],\n    [5, \"number\", 5, 4],\n    [6, \"number\", 6, 5],\n    [7, \"number\", 7, 6],\n    [8, \"number\", 8, 7],\n    [9, \"number\", 9, 8],\n  ]);\n\n  assert.end();\n});\n\ntest(\"test addition operator\", (assert) => {\n\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib}) => {\n    let joof = find({foo: \"bar\"});\n    return [\n     joof.add(\"name\", \"JOOF\")\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  verify(assert, prog, [\n    [1, \"foo\", \"bar\"]\n  ], [\n    [1, \"name\", \"JOOF\", 1]\n  ])\n\n  assert.end();\n});\n\ntest(\"transitive closure\", (assert) => {\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"Every edge is the beginning of a path.\", ({find, record, lib}) => {\n    let from = find();\n    return [\n      from.add(\"path\", from.edge)\n    ];\n  });\n\n  prog.bind(\"Jump from node to node building the path.\", ({find, record, lib}) => {\n    let from = find();\n    let intermediate = find();\n    from.edge == intermediate;\n    let to = intermediate.path;\n\n    intermediate.path;\n    return [\n      from.add(\"path\", to)\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  verify(assert, prog, [\n    [1, \"edge\", 2],\n    [2, \"edge\", 1],\n  ], [\n    [1, \"path\", 2, 1],\n    [2, \"path\", 1, 1],\n    [1, \"path\", 1, 2],\n    [2, \"path\", 2, 2],\n  ])\n\n  verify(assert, prog, [\n    [1, \"edge\", 2, 0, -1],\n  ], [\n    [1, \"path\", 2, 1, -1],\n    [1, \"path\", 1, 2, -1],\n    [2, \"path\", 2, 2, -1],\n    //[2, \"path\", 1, 3, -1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"edge\", 2, 0, 1],\n  ], [\n    [1, \"path\", 2, 1, 1],\n    [1, \"path\", 1, 2, 1],\n    [2, \"path\", 2, 2, 1],\n    //[2, \"path\", 1, 3, 1],\n  ])\n\n  // verify(assert, prog, [\n  //   [1, \"edge\", 2],\n  //   [2, \"edge\", 3],\n  //   [3, \"edge\", 4],\n  //   [4, \"edge\", 1],\n  // ], [\n  //   [1, \"path\", 2, 1],\n  //   [2, \"path\", 3, 1],\n  //   [3, \"path\", 4, 1],\n  //   [4, \"path\", 1, 1],\n\n  //   [1, \"path\", 3, 2],\n  //   [2, \"path\", 4, 2],\n  //   [3, \"path\", 1, 2],\n  //   [4, \"path\", 2, 2],\n\n  //   [1, \"path\", 4, 3],\n  //   [2, \"path\", 1, 3],\n  //   [3, \"path\", 2, 3],\n  //   [4, \"path\", 3, 3],\n\n  //   [1, \"path\", 1, 4],\n  //   [2, \"path\", 2, 4],\n  //   [3, \"path\", 3, 4],\n  //   [4, \"path\", 4, 4],\n\n  //   [1, \"path\", 2, 5],\n  //   [2, \"path\", 3, 5],\n  //   [3, \"path\", 4, 5],\n  //   [4, \"path\", 1, 5]\n  // ]);\n\n  // // Kick the legs out from under the cycle.\n\n  // verify(assert, prog, [\n  //   [4, \"edge\", 1, 0, -1]\n  // ], [\n  //   [4, \"path\", 1, 1, -1],\n\n  //   [4, \"path\", 2, 2, -1],\n  //   [3, \"path\", 1, 2, -1],\n  //   [2, \"path\", 1, 2, -1],\n  //   [1, \"path\", 1, 2, -1],\n\n  //   [4, \"path\", 3, 3, -1],\n  //   [3, \"path\", 2, 3, -1],\n  //   [2, \"path\", 2, 3, -1],\n  //   [1, \"path\", 2, 3, -1],\n\n  //   [4, \"path\", 4,  4, -1],\n\n  //   [4, \"path\", 1,  5, -1],\n  // ]);\n\n\n  assert.end();\n});\n\ntest(\"removal\", (assert) => {\n\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib}) => {\n    find({foo: \"bar\"});\n    return [\n      record({zomg: \"baz\"})\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  // trust, but\n  verify(assert, prog, [\n    [1, \"foo\", \"bar\"]\n  ], [\n    [2, \"zomg\", \"baz\", 1]\n  ]);\n\n  verify(assert, prog, [\n    [1, \"foo\", \"bar\", 0, -1]\n  ], [\n    [2, \"zomg\", \"baz\", 1, -1]\n  ], 1);\n\n  assert.end();\n});\n\ntest.skip(\"not\", (assert) => {\n\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib, not}) => {\n    let person = find({tag: \"person\"});\n    not(() => person.alive);\n    return [\n      person.add(\"dead\", \"true\")\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  // trust, but\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"]\n  ], [\n    [1, \"dead\", \"true\", 1]\n  ]);\n\n  assert.end();\n});\n\ntest(\"Nested attribute lookup\", (assert) => {\n\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib}) => {\n    let jeff = find({tag: \"bar\"});\n    return [\n      record({zomg: jeff.dog.weight})\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  verify(assert, prog, [\n    [1, \"tag\", \"bar\"],\n    [1, \"dog\", 2],\n    [2, \"weight\", 13],\n  ], [\n    [3, \"zomg\", 13, 1]\n  ])\n\n  assert.end();\n});\n\n\ntest(\"Basic not\", (assert) => {\n\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib, not}) => {\n    let person = find(\"person\");\n    not(() => {\n      person.age;\n    })\n    return [\n      person.add(\"tag\", \"old\")\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n  ], [\n    [1, \"tag\", \"old\", 1]\n  ])\n\n  verify(assert, prog, [\n    [1, \"age\", 20],\n  ], [\n    [1, \"tag\", \"old\", 1, -1]\n  ])\n\n  assert.end();\n});\n\ntest(\"Basic aggregate\", (assert) => {\n\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib, gather}) => {\n    let person = find(\"person\");\n    let count = gather(person).count();\n    return [\n      record(\"info\").add(\"total people\", count)\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [2, \"tag\", \"person\"],\n  ], [\n    [3, \"tag\", \"info\", 1],\n    [3, \"total people\", 2, 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\", 0, -1],\n  ], [\n    [3, \"total people\", 2, 1, -1],\n    [3, \"total people\", 1, 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [4, \"tag\", \"person\"],\n  ], [\n    [3, \"total people\", 1, 1, -1],\n    [3, \"total people\", 3, 1],\n  ])\n\n  assert.end();\n});\n\n\ntest(\"commit, remove, and recursion\", (assert) => {\n\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n\n  prog.commit(\"coolness\", ({find, not, record, choose}) => {\n    let click = find(\"click\", \"direct-target\");\n    let count = find(\"count\");\n    let current = count.count;\n    5 > current;\n    return [\n      count.add(\"count\", current + 1)\n    ]\n  })\n\n  prog.commit(\"foo\", ({find}) => {\n    let click = find(\"click\", \"direct-target\");\n    return [\n      click.remove(\"tag\", \"click\"),\n      click.remove(\"tag\", \"direct-target\"),\n    ];\n  })\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  verify(assert, prog, [\n    [1, \"tag\", \"count\"],\n    [1, \"count\", 0]\n  ], [\n  ])\n\n  verify(assert, prog, [\n    [2, \"tag\", \"click\"],\n    [2, \"tag\", \"direct-target\"]\n  ], [\n    [2, \"tag\", \"click\", 0, -1],\n    [2, \"tag\", \"direct-target\", 0, -1],\n    [1, \"count\", 1, 0],\n  ])\n\n  verify(assert, prog, [\n    [3, \"tag\", \"click\"],\n    [3, \"tag\", \"direct-target\"]\n  ], [\n    [3, \"tag\", \"click\", 0, -1],\n    [3, \"tag\", \"direct-target\", 0, -1],\n    [1, \"count\", 2, 0],\n  ])\n\n  assert.end();\n});\n\n\ntest(\"Remove: free AV\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.commit(\"coolness\", ({find, not, record, choose}) => {\n    let person = find(\"person\");\n    return [\n      person.remove()\n    ]\n  })\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [1, \"name\", \"chris\"],\n    [1, \"age\", 30],\n  ], [\n    [1, \"tag\", \"person\", 0, -1],\n    [1, \"name\", \"chris\", 0, -1],\n    [1, \"age\", 30, 0, -1],\n  ])\n\n  assert.end();\n});\n\n\ntest(\"Reference: arbitrary refs act like records\", (assert) => {\n\n  let prog = new Program(\"test\");\n\n  prog.commit(\"coolness\", ({find, not, record, union}) => {\n    let person = find(\"person\");\n    let [thing] = union(() => {\n      return find(\"person\");\n    }, () => {\n      return \"foo\";\n    })\n    return [\n      thing.remove()\n    ]\n  })\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n    [1, \"name\", \"chris\"],\n    [1, \"age\", 30],\n    [\"foo\", \"tag\", \"person\"],\n    [\"foo\", \"name\", \"chris\"],\n    [\"foo\", \"age\", 30],\n  ], [\n    [1, \"tag\", \"person\", 0, -1],\n    [1, \"name\", \"chris\", 0, -1],\n    [1, \"age\", 30, 0, -1],\n    [\"foo\", \"tag\", \"person\", 0, -1],\n    [\"foo\", \"name\", \"chris\", 0, -1],\n    [\"foo\", \"age\", 30, 0, -1],\n  ])\n\n  assert.end();\n});\n"
  },
  {
    "path": "test/performance.ts",
    "content": "import {Program} from \"../src/runtime/dsl2\";\nimport {verify, createChanges, time} from \"./util\";\nimport {HashIndex} from \"../src/runtime/indexes\";\nimport * as test from \"tape\";\n\ntest(\"test single block performance with 10000 transactions\", (assert) => {\n\n  // -----------------------------------------------------\n  // program\n  // -----------------------------------------------------\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib}) => {\n    let person = find(\"person\");\n    let text = `name: ${person.name}`;\n    return [\n      record(\"html/div\", {person, text})\n    ]\n  });\n\n  // -----------------------------------------------------\n  // verification\n  // -----------------------------------------------------\n\n  for(let ix = 0; ix < 1; ix++) {\n    prog.index = new HashIndex();\n    let size = 10000;\n    let changes = [];\n    for(let i = 0; i < size; i++) {\n      changes.push(createChanges(i, [[i - 1, \"name\", i - 1], [i, \"tag\", \"person\"]]))\n    }\n\n    let start = time();\n    for(let change of changes) {\n      prog.input(change);\n    }\n    let end = time(start);\n    assert.test(\"updates finished in \" + end, (assert) => {\n      assert.true(end < 1000, \"Took too long\");\n      assert.end();\n    })\n  }\n  assert.pass();\n  assert.end();\n});\n"
  },
  {
    "path": "test/stdlib/math.ts",
    "content": "import {Program} from \"../../src/runtime/dsl2\";\nimport {verify} from \"../util\";\nimport * as test from \"tape\";\n\ntest(\"stdlib:math:range: 1..5\", (assert) => {\n  let prog = new Program(\"test\");\n  prog.bind(\"test range\", ({find, lib:{math}, record}) => {\n    let ix = math.range(1, 5);\n    return [\n      record({ix})\n    ];\n  });\n\n  verify(assert, prog, [\n    [\"A\", \"tag\", \"turtle\"],\n  ], [\n    [11, \"ix\", 1, 1],\n    [12, \"ix\", 2, 1],\n    [13, \"ix\", 3, 1],\n    [14, \"ix\", 4, 1],\n    [15, \"ix\", 5, 1],\n  ]);\n  assert.end();\n});\n\ntest(\"stdlib:math:range: 3..-1\", (assert) => {\n  let prog = new Program(\"test\");\n  prog.bind(\"test range\", ({find, lib:{math}, record}) => {\n    let ix = math.range(3, -1);\n    return [\n      record({ix})\n    ];\n  });\n\n  verify(assert, prog, [\n    [\"A\", \"tag\", \"turtle\"],\n  ], [\n    [11, \"ix\", 3, 1],\n    [12, \"ix\", 2, 1],\n    [13, \"ix\", 1, 1],\n    [14, \"ix\", 0, 1],\n    [15, \"ix\", -1, 1],\n  ]);\n  assert.end();\n});\n\ntest(\"stdlib:math:range: x..y\", (assert) => {\n  let prog = new Program(\"test\");\n  prog.bind(\"test range\", ({find, lib:{math}}) => {\n    let endpoints = find(\"endpoints\");\n    let {x, y} = endpoints;\n    let ix = math.range(x, y);\n    return [\n      endpoints.add({ix})\n    ];\n  });\n\n  verify(assert, prog, [\n    [\"A\", \"tag\", \"endpoints\"],\n    [\"A\", \"x\", 1],\n    [\"A\", \"y\", 3],\n    [\"B\", \"tag\", \"endpoints\"],\n    [\"B\", \"x\", 2],\n    [\"B\", \"y\", 0],\n  ], [\n    [\"A\", \"ix\", 1, 1],\n    [\"A\", \"ix\", 2, 1],\n    [\"A\", \"ix\", 3, 1],\n    [\"B\", \"ix\", 0, 1],\n    [\"B\", \"ix\", 1, 1],\n    [\"B\", \"ix\", 2, 1],\n  ]);\n  assert.end();\n});\n\ntest(\"stdlib:math:range: bail on strings\", (assert) => {\n  let prog = new Program(\"test\");\n  prog.bind(\"test range\", ({find, lib:{math}}) => {\n    let endpoints = find(\"endpoints\");\n    let {x, y} = endpoints;\n    let ix = math.range(x, y);\n    return [\n      endpoints.add({ix})\n    ];\n  });\n\n  verify(assert, prog, [\n    [\"A\", \"tag\", \"endpoints\"],\n    [\"A\", \"x\", \"1\"],\n    [\"A\", \"y\", 3],\n    [\"B\", \"tag\", \"endpoints\"],\n    [\"B\", \"x\", 2],\n    [\"B\", \"y\", \"0\"],\n  ], [\n  ]);\n  assert.end();\n});\n"
  },
  {
    "path": "test/union.ts",
    "content": "import {Program} from \"../src/runtime/dsl2\";\nimport {verify, createVerifier, pprint} from \"./util\";\nimport * as test from \"tape\";\n\nvar programs = {\n  \"1 static\": () => {\n    let prog = new Program(\"1 static branch\");\n    prog.bind(\"simple block\", ({find, union, record}) => {\n      let foo = find(\"input\");\n      let [branch] = union(() => 1);\n      return [\n        record(\"result\", {branch})\n      ];\n    });\n    return prog;\n  },\n  \"2 static\": () => {\n    let prog = new Program(\"2 static branches\");\n    prog.bind(\"simple block\", ({find, union, record}) => {\n      let foo = find(\"input\");\n      let [branch] = union(\n        () => 1,\n        () => 2\n      );\n      return [\n        record(\"result\", {branch})\n      ];\n    });\n    return prog;\n  },\n\n  \"1 dynamic\": () => {\n    let prog = new Program(\"1 dynamic branch\");\n    prog.bind(\"simple block\", ({find, union, record}) => {\n      let foo = find(\"input\");\n      let [output] = union(() => foo.arg0);\n      return [\n        record(\"result\", {output})\n      ];\n    });\n    return prog;\n  },\n\n  \"2 dynamic\": () => {\n    let prog = new Program(\"2 dynamic branches\");\n    prog.bind(\"simple block\", ({find, union, record}) => {\n      let foo = find(\"input\");\n      let [output] = union(\n        () => {foo.arg0 == 1; return [\"one\"]},\n        () => foo.arg0\n      );\n      return [\n        record(\"result\", {output})\n      ];\n    });\n    return prog;\n  },\n};\n\n\n\nlet verifyBranches = createVerifier(programs);\n\n// -----------------------------------------------------\n// 1 Static branch\n// -----------------------------------------------------\n\ntest(\"Union: 1 static branch +A; -A; +A\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A; -A; +A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"branch\", 1, 1, -1]],\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]]\n  ]);\n});\n\ntest(\"Union: 1 static branch +A; -A; +B\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A; -A; +B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"branch\", 1, 1, -1]],\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]]\n  ]);\n});\n\n// @NOTE: Broken due to verify being too simple.\ntest(\"Union: 1 static branch +A; +A; -A\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A; +A; -A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [],\n    []\n  ]);\n});\n\n// @NOTE: Broken due to verify being too simple.\ntest(\"Union: 1 static branch +A; +A; -A; -A\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A; +A; -A; -A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [],\n    [],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"branch\", 1, 1, -1]],\n  ]);\n});\n\ntest(\"Union: 1 static branch +A +B; -A; +A\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A +B; -A; +A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [],\n    []\n  ]);\n});\n\ntest(\"Union: 1 static branch +A +B; -A -B\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A +B; -A -B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"branch\", 1, 1, -1]]\n  ]);\n});\n\ntest(\"Union: 1 static branch +A; -A +B\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A; -A +B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    []\n  ]);\n});\n\ntest(\"Union: 1 static branch +A, -A\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A, -A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1],\n     [2, \"tag\", \"result\", 2, -1], [2, \"branch\", 1, 2, -1]]\n  ]);\n});\n\ntest(\"Union: 1 static branch +A, -A +B\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A, -A +B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]]\n  ]);\n});\n\ntest(\"Union: 1 static branch +A, +B\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A, +B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]]\n  ]);\n});\n\ntest(\"Union: 1 static branch +A, +B; -A\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A, +B; -A\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"branch\", 1, 1, -1],\n     [2, \"tag\", \"result\", 2, +1], [2, \"branch\", 1, 2, +1]]\n  ]);\n});\n\ntest(\"Union: 1 static branch +A; -A, +B\", (assert) => {\n  verifyBranches(assert, \"1 static\", \"+A; -A, +B\", [\n    [[2, \"tag\", \"result\", 1, +1], [2, \"branch\", 1, 1, +1]],\n    [[2, \"tag\", \"result\", 1, -1], [2, \"branch\", 1, 1, -1],\n     [2, \"tag\", \"result\", 2, +1], [2, \"branch\", 1, 2, +1]]\n  ]);\n});\n\n// -----------------------------------------------------\n// 2 Static branches\n// -----------------------------------------------------\n\ntest(\"Union: 2 static branches +A; -A; +A\", (assert) => {\n  verifyBranches(assert, \"2 static\", \"+A; -A; +A\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"branch\", 1, 1, +1],\n     [2, \"tag\", \"result\", 1, +1], [2, \"branch\", 2, 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"branch\", 1, 1, -1],\n     [2, \"tag\", \"result\", 1, -1], [2, \"branch\", 2, 1, -1]],\n    [[1, \"tag\", \"result\", 1, +1], [1, \"branch\", 1, 1, +1],\n     [2, \"tag\", \"result\", 1, +1], [2, \"branch\", 2, 1, +1]]\n  ]);\n});\n\ntest(\"Union: 2 static branches +A; +B\", (assert) => {\n  verifyBranches(assert, \"2 static\", \"+A; +B\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"branch\", 1, 1, +1],\n     [2, \"tag\", \"result\", 1, +1], [2, \"branch\", 2, 1, +1]],\n    []\n  ]);\n});\n\ntest(\"Union: 2 static branches +A; +B; -A\", (assert) => {\n  verifyBranches(assert, \"2 static\", \"+A; +B; -A\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"branch\", 1, 1, +1],\n     [2, \"tag\", \"result\", 1, +1], [2, \"branch\", 2, 1, +1]],\n    [],\n    []\n  ]);\n});\n\ntest(\"Union: 2 static branches +A +B; -A; +A\", (assert) => {\n  verifyBranches(assert, \"2 static\", \"+A +B; -A; +A\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"branch\", 1, 1, +1],\n     [2, \"tag\", \"result\", 1, +1], [2, \"branch\", 2, 1, +1]],\n    [],\n    []\n  ]);\n});\n\ntest(\"Union: 2 static branches +A; +B; -A; -B\", (assert) => {\n  verifyBranches(assert, \"2 static\", \"+A; +B; -A; -B\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"branch\", 1, 1, +1],\n     [2, \"tag\", \"result\", 1, +1], [2, \"branch\", 2, 1, +1]],\n    [],\n    [],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"branch\", 1, 1, -1],\n     [2, \"tag\", \"result\", 1, -1], [2, \"branch\", 2, 1, -1]],\n  ]);\n});\n\n\ntest(\"Union: 2 static branches +A; +B, -A; -B\", (assert) => {\n  verifyBranches(assert, \"2 static\", \"+A; +B, -A; -B\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"branch\", 1, 1, +1],\n     [2, \"tag\", \"result\", 1, +1], [2, \"branch\", 2, 1, +1]],\n    [],\n    // These changes happen in round 2 because while the removal of B's results\n    // happen in round 1, A's results exist in round 1 and then are removed\n    // in round 2 as a result of A beind removed in round 1.\n    [[1, \"tag\", \"result\", 2, -1], [1, \"branch\", 1, 2, -1],\n     [2, \"tag\", \"result\", 2, -1], [2, \"branch\", 2, 2, -1]],\n  ]);\n});\n\n// -----------------------------------------------------\n// 1 dynamic branch\n// -----------------------------------------------------\n\ntest(\"Union: 1 dynamic branch +A:1; -A:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic\", \"+A:1; -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", 1, 1, -1]],\n  ]);\n});\n\ntest(\"Union: 1 dynamic branch +A:1; -A:1; +A:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic\", \"+A:1; -A:1; +A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", 1, 1, -1]],\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n  ]);\n});\n\ntest(\"Union: 1 dynamic branch +A:1; -A:1; +A:2\", (assert) => {\n  verifyBranches(assert, \"1 dynamic\", \"+A:1; -A:1; +A:2\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", 1, 1, -1]],\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 2, 1, +1]],\n  ]);\n});\n\ntest(\"Union: 1 dynamic branch +A:1; +A:2; -A:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic\", \"+A:1; +A:2; -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 2, 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", 1, 1, -1]],\n  ]);\n});\n\ntest(\"Union: 1 dynamic branch +A:1; +B:1; -A:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic\", \"+A:1; +B:1; -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n    [],\n    [],\n  ]);\n});\n\ntest(\"Union: 1 dynamic branch +A:1; +B:1; -A:1, -B:1\", (assert) => {\n  verifyBranches(assert, \"1 dynamic\", \"+A:1; +B:1; -A:1, -B:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", 1, 1, +1]],\n    [],\n    [[1, \"tag\", \"result\", 2, -1], [1, \"output\", 1, 2, -1]],\n  ]);\n});\n\n// -----------------------------------------------------\n// 2 dynamic branches\n// -----------------------------------------------------\n\ntest(\"Union: 2 dynamic branches +A:1; -A:1\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1; -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1],\n     [2, \"tag\", \"result\", 1, +1], [2, \"output\", 1, 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", \"one\", 1, -1],\n     [2, \"tag\", \"result\", 1, -1], [2, \"output\", 1, 1, -1]]\n  ]);\n});\n\ntest(\"Union: 2 dynamic branches +A:1; +B:1\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1; +B:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1],\n     [2, \"tag\", \"result\", 1, +1], [2, \"output\", 1, 1, +1]],\n    []\n  ]);\n});\n\ntest(\"Union: 2 dynamic branches +A:1, +B:1\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1, +B:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1],\n     [2, \"tag\", \"result\", 1, +1], [2, \"output\", 1, 1, +1]]\n  ]);\n});\n\ntest(\"Union: 2 dynamic branches +A:1, +B:1; -A:1\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1, +B:1; -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1],\n     [2, \"tag\", \"result\", 1, +1], [2, \"output\", 1, 1, +1]],\n    [[1, \"tag\", \"result\", 1, -1], [1, \"output\", \"one\", 1, -1],\n     [2, \"tag\", \"result\", 1, -1], [2, \"output\", 1, 1, -1],\n     [1, \"tag\", \"result\", 2, +1], [1, \"output\", \"one\", 2, +1],\n     [2, \"tag\", \"result\", 2, +1], [2, \"output\", 1, 2, +1]]\n  ]);\n});\n\ntest(\"Union: 2 dynamic branches +A:1 +B:2, -A:1\", (assert) => {\n  verifyBranches(assert, \"2 dynamic\", \"+A:1 +B:2, -A:1\", [\n    [[1, \"tag\", \"result\", 1, +1], [1, \"output\", \"one\", 1, +1],\n     [2, \"tag\", \"result\", 1, +1], [2, \"output\", 1, 1, +1],\n     [3, \"tag\", \"result\", 1, +1], [3, \"output\", 2, 1, +1],\n     [1, \"tag\", \"result\", 2, -1], [1, \"output\", \"one\", 2, -1],\n     [2, \"tag\", \"result\", 2, -1], [2, \"output\", 1, 2, -1]]\n  ]);\n});\n\ntest(\"Union: basic\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib, union}) => {\n    let person = find(\"person\");\n    let [info] = union(() => {\n      person.dog;\n      return \"cool\";\n    }, () => {\n      return \"not cool\";\n    });\n    return [\n      record(\"coolness\", {info})\n    ]\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n  ], [\n    [2, \"tag\", \"coolness\", 1],\n    [2, \"info\", \"not cool\", 1],\n  ])\n\n  verify(assert, prog, [\n    [1, \"dog\", \"spot\"],\n  ], [\n    [3, \"tag\", \"coolness\", 1],\n    [3, \"info\", \"cool\", 1],\n  ])\n\n  assert.end();\n});\n\n\ntest(\"Union: static moves\", (assert) => {\n\n  let prog = new Program(\"test\");\n  prog.bind(\"simple block\", ({find, record, lib, union}) => {\n    let person = find(\"person\");\n    let [info] = union(() => {\n      return \"cool\";\n    }, () => {\n      return \"not cool\";\n    });\n    return [\n      record(\"coolness\", {info})\n    ]\n  });\n\n  verify(assert, prog, [\n    [1, \"tag\", \"person\"],\n  ], [\n    [2, \"tag\", \"coolness\", 1],\n    [2, \"info\", \"not cool\", 1],\n    [3, \"tag\", \"coolness\", 1],\n    [3, \"info\", \"cool\", 1],\n  ])\n\n  assert.end();\n});\n"
  },
  {
    "path": "test/util.ts",
    "content": "import {Program} from \"../src/runtime/dsl2\";\nimport * as Runtime from \"../src/runtime/runtime\";\nimport * as test from \"tape\";\n\n// You can specify changes as either [e,a,v] or [e,a,v,round,count];\nexport type EAVTuple = [Runtime.RawValue, Runtime.RawValue, Runtime.RawValue];\nexport type EAVRCTuple = [Runtime.RawValue, Runtime.RawValue, Runtime.RawValue, number, number];\nexport type TestChange =  EAVTuple | EAVRCTuple;\n\nlet {GlobalInterner} = Runtime;\nlet TEST_INPUT_NODE = \"test-input-node\";\n\nexport function pprint(obj:any):string {\n  if(typeof obj === \"object\" && obj instanceof Array) {\n    return \"[\" + obj.map((v) => pprint(v)).join(\", \") + \"]\";\n\n  } else if(typeof obj === \"string\") {\n    return `\"${obj}\"`;\n  }\n  return \"\"+obj;\n}\n\nexport class EntityId {\n  constructor(public id:Runtime.ID) {}\n  toString() {\n    return \"$\" + this.id;\n  }\n}\n\nexport function o_o(val:Runtime.ID):EntityId|Runtime.RawValue|undefined {\n  let raw = GlobalInterner.reverse(val);\n  if(typeof raw === \"string\" && raw.indexOf(\"|\") !== -1) {\n    return new EntityId(val);\n  }\n\n  return raw;\n}\n\nexport function createChanges(transaction:number,eavns:TestChange[]) {\n  let changes:Runtime.Change[] = [];\n  for(let [e, a, v, round = 0, count = 1] of eavns as EAVRCTuple[]) {\n    changes.push(Runtime.Change.fromValues(e, a, v, TEST_INPUT_NODE, transaction, round, count));\n  }\n  return changes;\n}\n\nexport function verify(assert:test.Test, program:Program, input:any[], output:any[], transaction = 1) {\n  let ins = createChanges(transaction, input);\n  let outs = createChanges(transaction, output);\n\n  let all:(Runtime.Change|undefined)[] = outs;\n  let {changes, context} = program.input(ins)!;\n  let inputNode = GlobalInterner.get(TEST_INPUT_NODE);\n  changes = changes.filter((v) => v.n !== inputNode);\n  let msg = \"Fewer changes than expected\";\n  if(changes.length > all.length) {\n    msg = \"More changes than expected\";\n  }\n\n  if(changes.length !== all.length) {\n    assert.comment(\".    Actual: \" + pprint(changes.map((change) => {\n      let {e, a, v, round, count} = change;\n      return [o_o(e), o_o(a), o_o(v), round, count];\n    })));\n  }\n  assert.equal(changes.length, all.length, msg);\n\n  try {\n    context.distinctIndex.sanityCheck();\n  } catch(e) {\n    assert.fail(\"Distinct sanity check failed\");\n  }\n\n  // Because the changes handed to us in expected aren't going to have the same\n  // e's as what the program itself is going to generate, we're going to have to do\n  // some fancy matching to map from generated e's to expected e's. We'll need to\n  // store that mapping somewhere, so we have eMap:\n  let eMap:any = {};\n  let fullyResolved:any = {};\n  if(changes.length === all.length) {\n    // As we check all of the changes we got from running the input on the program,\n    // we need to update our eMap based on the expected changes that *could* match.\n    // Most of the time, the hope is that there's only one potential match, but when\n    // you're looking at something like tag, it's easy for there to be many records\n    // that get generated with the same tag, so we're going to do a decent amount of\n    // work here.\n    for(let actual of changes) {\n      let found = false;\n      // console.log(\"\\n\\nACTUAL\");\n      // console.log(\"    \", actual.toString());\n      // console.log(\"    \", actual);\n      let expectedIx = 0;\n      // check if we've found any potential matches for this e yet\n      let matches = eMap[actual.e];\n      // if we haven't found any matches yet, we need to collect some initial ones.\n      if(!matches) {\n        let potentials = [];\n        for(let expected of all) {\n          if(!expected) {\n            expectedIx++;\n            continue;\n          }\n          // if this expected *could* match ignoring e and n, then we'll store this as\n          // a potential mapping from the actual.e to the expected.e. We're also going\n          // to store this expected's index so that once we know for sure that this\n          // actual.e === expected.e we can clean out the expecteds that no one can claim\n          // anymore.\n          if(actual.equal(expected, true /*ignore the node*/, true /*ignore the e*/)) {\n            found = true;\n            potentials.push({e: expected.e, relatedChanges: [expectedIx]});\n          }\n          expectedIx++;\n        }\n        // if there was only one match, no one can ever have this expected - we've claimed it.\n        // As such, we need to remove it from the list;\n        if(potentials.length === 1) {\n          // We need to check that we haven't already resolved this match to some other actual value.\n          let e = potentials[0].e;\n          if(fullyResolved[e] !== undefined) {\n            assert.fail(`\\`${GlobalInterner.reverse(e)}\\` has already been resolved to \\`${GlobalInterner.reverse(fullyResolved[e])}\\`,` +\n                        ` but we are trying to resolve it to \\`${GlobalInterner.reverse(actual.e)}\\``);\n            break;\n          }\n          fullyResolved[e] = actual.e;\n          for(let ix of potentials[0].relatedChanges) {\n            all[ix] = undefined;\n          }\n        }\n        eMap[actual.e] = potentials;\n      } else if(matches.length === 1) {\n        // in the case where we've mapped our actual.e to our expected.e, we just check this\n        // current fact for a match where the expected.e is what we're looking for\n        for(let expected of all) {\n          if(!expected) {\n            expectedIx++;\n            continue;\n          }\n          if(expected.e === matches[0].e && actual.equal(expected, true, true)) {\n            found = true;\n            all[expectedIx] = undefined;\n          }\n          expectedIx++;\n        }\n      } else {\n        // since we have multiple potential matches, we need to see if this actual might reduce\n        // the set down for us. For each expected that's left, we'll check if expected.e matches\n        // one of our potentials, and if this expected would equal our actual if we ignored the\n        // e. If so, we keep this potential in the running. Any potentials the don't end up with\n        // a match get removed on account of us recreating the potential array from scratch here.\n        let potentials = [];\n        for(let expected of all) {\n          if(!expected) {\n            expectedIx++;\n            continue;\n          }\n          for(let match of matches) {\n            if(match.e === expected.e && !fullyResolved[match.e] && actual.equal(expected, true, true)) {\n              found = true;\n              potentials.push(match);\n              match.relatedChanges.push(expectedIx);\n            }\n          }\n          expectedIx++;\n        }\n        // If we only have one potential, we need to clean up after ourselves again. This time\n        // however, we could have had many relatedChanges that we need to clean up, so we'll loop\n        // through them and remove them from the list. They're our's now.\n        if(potentials.length === 1) {\n          // We need to check that we haven't already resolved this match to some other actual value.\n          let e = potentials[0].e;\n          if(fullyResolved[e] !== undefined) {\n            assert.fail(`\\`${GlobalInterner.reverse(e)}\\` has already been resolved to \\`${GlobalInterner.reverse(fullyResolved[e])}\\`,` +\n                        ` but we are trying to resolve it to \\`${GlobalInterner.reverse(actual.e)}\\``);\n            break;\n          }\n          fullyResolved[e] = actual.e;\n          let related = potentials[0].relatedChanges;\n          related.sort((a:number, b:number) => b - a);\n          for(let relatedIx of related) {\n            all[relatedIx] = undefined;\n          }\n          potentials[0].relatedChanges = []\n        }\n        eMap[actual.e] = potentials;\n      }\n\n      // console.log(\"    \", actual.e, \":\", eMap[actual.e]);\n      // console.log(\"    [\")\n      // for(let thing of all) {\n      //   console.log(\"        \", thing);\n      // }\n      // console.log(\"    ]\")\n\n      if(!found) assert.fail(\"No match found for: \" + actual.toString());\n      else assert.pass(\"Found match for: \" + actual.toString());\n    }\n  }\n}\n\nexport function time(start?:any): number | number[] | string {\n  if ( !start ) return process.hrtime();\n  let end = process.hrtime(start);\n  return ((end[0]*1000) + (end[1]/1000000)).toFixed(3);\n}\n\nexport function createInputs(inputString:string) {\n  let transactionInputs:EAVRCTuple[][] = [];\n  let transactions = inputString.split(\";\");\n  for(let transaction of transactions) {\n    let eavrcs:EAVRCTuple[] = [];\n    let roundNumber = 0;\n    for(let round of transaction.split(\",\")) {\n      for(let input of round.split(\" \")) {\n        if(!input) continue;\n\n        let count;\n        if(input[0] === \"+\") count = 1;\n        else if(input[0] === \"-\") count = -1;\n        else throw new Error(`Malformed input: ${input}`);\n\n        let args = input.slice(1).split(\":\");\n        let id = args.shift();\n        if(!id) throw new Error(`Malformed input: '${input}'`);\n\n        eavrcs.push([id, \"tag\", \"input\", roundNumber, count]);\n\n        let argIx = 0;\n        for(let arg of args) {\n          eavrcs.push([id, `arg${argIx}`, (isNaN(arg as any) ? arg : +arg), roundNumber, count]);\n          argIx++;\n        }\n      }\n      roundNumber += 1;\n    }\n    transactionInputs.push(eavrcs);\n  }\n\n  return transactionInputs;\n}\n\nexport function createVerifier<T extends {[name:string]: () => Program}>(programs:T) {\n  return function verifyInput(assert:test.Test, progName:(keyof T), inputString:string, expecteds:EAVRCTuple[][]) {\n    let prog = programs[progName]();\n    let inputs = createInputs(inputString);\n\n    if(expecteds.length !== inputs.length) {\n      assert.fail(\"Malformed test case\");\n      throw new Error(`Incorrect number of expecteds given the inputString Got ${expecteds.length}, needed: ${inputs.length}`);\n    }\n\n    let transactionNumber = 0;\n    for(let input of inputs) {\n      let expected = expecteds[transactionNumber];\n      assert.comment(\".  Verifying: \" + pprint(input) + \" -> \" + pprint(expected));\n      verify(assert, prog, input, expected);\n      transactionNumber++;\n    }\n    assert.end();\n    return prog;\n  };\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"outDir\": \"build\",\n    \"target\": \"es5\",\n    \"sourceMap\": true,\n    \"skipDefaultLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"experimentalDecorators\" : true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true\n  },\n  \"include\": [ \"src/**/*.ts\", \"src/**/*.js\", \"test/**/*.ts\", \"scripts/**/*.ts\" ]\n}\n"
  },
  {
    "path": "typings/codemirror/codemirror.d.ts",
    "content": "// Type definitions for CodeMirror\n// Project: https://github.com/marijnh/CodeMirror\n// Definitions by: mihailik <https://github.com/mihailik>\n// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped\n\ndeclare function CodeMirror(host: HTMLElement, options?: CodeMirror.EditorConfiguration): CodeMirror.Editor;\ndeclare function CodeMirror(callback: (host: HTMLElement) => void , options?: CodeMirror.EditorConfiguration): CodeMirror.Editor;\n\ndeclare namespace CodeMirror {\n    export var Doc : CodeMirror.DocConstructor;\n    export var Pos: CodeMirror.PositionConstructor;\n    export var Pass: any;\n\n    function fromTextArea(host: HTMLTextAreaElement, options?: EditorConfiguration): CodeMirror.EditorFromTextArea;\n\n    var version: string;\n\n    /** If you want to define extra methods in terms of the CodeMirror API, it is possible to use defineExtension.\n    This will cause the given value(usually a method) to be added to all CodeMirror instances created from then on. */\n    function defineExtension(name: string, value: any): void;\n\n    /** Like defineExtension, but the method will be added to the interface for Doc objects instead. */\n    function defineDocExtension(name: string, value: any): void;\n\n    /** Similarly, defineOption can be used to define new options for CodeMirror.\n    The updateFunc will be called with the editor instance and the new value when an editor is initialized,\n    and whenever the option is modified through setOption. */\n    function defineOption(name: string, default_: any, updateFunc: Function): void;\n\n    /** If your extention just needs to run some code whenever a CodeMirror instance is initialized, use CodeMirror.defineInitHook.\n    Give it a function as its only argument, and from then on, that function will be called (with the instance as argument)\n    whenever a new CodeMirror instance is initialized. */\n    function defineInitHook(func: Function): void;\n\n    /** Registers a helper value with the given name in the given namespace (type). This is used to define functionality\n    that may be looked up by mode. Will create (if it doesn't already exist) a property on the CodeMirror object for\n    the given type, pointing to an object that maps names to values. I.e. after doing\n    CodeMirror.registerHelper(\"hint\", \"foo\", myFoo), the value CodeMirror.hint.foo will point to myFoo. */\n    function registerHelper(namespace: string, name: string, helper: any): void;\n\n\n    function on(element: any, eventName: string, handler: Function): void;\n    function off(element: any, eventName: string, handler: Function): void;\n\n    /** Fired whenever a change occurs to the document. changeObj has a similar type as the object passed to the editor's \"change\" event,\n    but it never has a next property, because document change events are not batched (whereas editor change events are). */\n    function on(doc: Doc, eventName: 'change', handler: (instance: Doc, change: EditorChange) => void ): void;\n    function off(doc: Doc, eventName: 'change', handler: (instance: Doc, change: EditorChange) => void ): void;\n\n    /** See the description of the same event on editor instances. */\n    function on(doc: Doc, eventName: 'beforeChange', handler: (instance: Doc, change: EditorChangeCancellable) => void ): void;\n    function off(doc: Doc, eventName: 'beforeChange', handler: (instance: Doc, change: EditorChangeCancellable) => void ): void;\n\n    /** Fired whenever the cursor or selection in this document changes. */\n    function on(doc: Doc, eventName: 'cursorActivity', handler: (instance: CodeMirror.Editor) => void ): void;\n    function off(doc: Doc, eventName: 'cursorActivity', handler: (instance: CodeMirror.Editor) => void ): void;\n\n    /** Equivalent to the event by the same name as fired on editor instances. */\n    function on(doc: Doc, eventName: 'beforeSelectionChange', handler: (instance: CodeMirror.Editor, selection: { head: Position; anchor: Position; }) => void ): void;\n    function off(doc: Doc, eventName: 'beforeSelectionChange', handler: (instance: CodeMirror.Editor, selection: { head: Position; anchor: Position; }) => void ): void;\n\n    /** Will be fired when the line object is deleted. A line object is associated with the start of the line.\n    Mostly useful when you need to find out when your gutter markers on a given line are removed. */\n    function on(line: LineHandle, eventName: 'delete', handler: () => void ): void;\n    function off(line: LineHandle, eventName: 'delete', handler: () => void ): void;\n\n    /** Fires when the line's text content is changed in any way (but the line is not deleted outright).\n    The change object is similar to the one passed to change event on the editor object. */\n    function on(line: LineHandle, eventName: 'change', handler: (line: LineHandle, change: EditorChange) => void ): void;\n    function off(line: LineHandle, eventName: 'change', handler: (line: LineHandle, change: EditorChange) => void ): void;\n\n    /** Fired when the cursor enters the marked range. From this event handler, the editor state may be inspected but not modified,\n    with the exception that the range on which the event fires may be cleared. */\n    function on(marker: TextMarker, eventName: 'beforeCursorEnter', handler: () => void ): void;\n    function off(marker: TextMarker, eventName: 'beforeCursorEnter', handler: () => void ): void;\n\n    /** Fired when the range is cleared, either through cursor movement in combination with clearOnEnter or through a call to its clear() method.\n    Will only be fired once per handle. Note that deleting the range through text editing does not fire this event,\n    because an undo action might bring the range back into existence. */\n    function on(marker: TextMarker, eventName: 'clear', handler: () => void ): void;\n    function off(marker: TextMarker, eventName: 'clear', handler: () => void ): void;\n\n    /** Fired when the last part of the marker is removed from the document by editing operations. */\n    function on(marker: TextMarker, eventName: 'hide', handler: () => void ): void;\n    function off(marker: TextMarker, eventName: 'hide', handler: () => void ): void;\n\n    /** Fired when, after the marker was removed by editing, a undo operation brought the marker back. */\n    function on(marker: TextMarker, eventName: 'unhide', handler: () => void ): void;\n    function off(marker: TextMarker, eventName: 'unhide', handler: () => void ): void;\n\n    /** Fired whenever the editor re-adds the widget to the DOM. This will happen once right after the widget is added (if it is scrolled into view),\n    and then again whenever it is scrolled out of view and back in again, or when changes to the editor options\n    or the line the widget is on require the widget to be redrawn. */\n    function on(line: LineWidget, eventName: 'redraw', handler: () => void ): void;\n    function off(line: LineWidget, eventName: 'redraw', handler: () => void ): void;\n\n    /** Various CodeMirror-related objects emit events, which allow client code to react to various situations.\n    Handlers for such events can be registered with the on and off methods on the objects that the event fires on.\n    To fire your own events, use CodeMirror.signal(target, name, args...), where target is a non-DOM-node object. */\n    function signal(target: any, name: string, ...args: any[]): void;\n\n    interface Editor {\n\n        /** Tells you whether the editor currently has focus. */\n        hasFocus(): boolean;\n\n        /** Used to find the target position for horizontal cursor motion.start is a { line , ch } object,\n        amount an integer(may be negative), and unit one of the string \"char\", \"column\", or \"word\".\n        Will return a position that is produced by moving amount times the distance specified by unit.\n        When visually is true , motion in right - to - left text will be visual rather than logical.\n        When the motion was clipped by hitting the end or start of the document, the returned value will have a hitSide property set to true. */\n        findPosH(start: CodeMirror.Position, amount: number, unit: string, visually: boolean): { line: number; ch: number; hitSide?: boolean; };\n\n        /** Similar to findPosH , but used for vertical motion.unit may be \"line\" or \"page\".\n        The other arguments and the returned value have the same interpretation as they have in findPosH. */\n        findPosV(start: CodeMirror.Position, amount: number, unit: string): { line: number; ch: number; hitSide?: boolean; };\n\n\n        /** Change the configuration of the editor. option should the name of an option, and value should be a valid value for that option. */\n        setOption(option: string, value: any): void;\n\n        /** Retrieves the current value of the given option for this editor instance. */\n        getOption(option: string): any;\n\n        /** Attach an additional keymap to the editor.\n        This is mostly useful for add - ons that need to register some key handlers without trampling on the extraKeys option.\n        Maps added in this way have a higher precedence than the extraKeys and keyMap options, and between them,\n        the maps added earlier have a lower precedence than those added later, unless the bottom argument was passed,\n        in which case they end up below other keymaps added with this method. */\n        addKeyMap(map: any, bottom?: boolean): void;\n\n        /** Disable a keymap added with addKeyMap.Either pass in the keymap object itself , or a string,\n        which will be compared against the name property of the active keymaps. */\n        removeKeyMap(map: any): void;\n\n        /** Enable a highlighting overlay.This is a stateless mini - mode that can be used to add extra highlighting.\n        For example, the search add - on uses it to highlight the term that's currently being searched.\n        mode can be a mode spec or a mode object (an object with a token method). The options parameter is optional. If given, it should be an object.\n        Currently, only the opaque option is recognized. This defaults to off, but can be given to allow the overlay styling, when not null,\n        to override the styling of the base mode entirely, instead of the two being applied together. */\n        addOverlay(mode: any, options?: any): void;\n\n        /** Pass this the exact argument passed for the mode parameter to addOverlay to remove an overlay again. */\n        removeOverlay(mode: any): void;\n\n\n        /** Retrieve the currently active document from an editor. */\n        getDoc(): CodeMirror.Doc;\n\n        /** Attach a new document to the editor. Returns the old document, which is now no longer associated with an editor. */\n        swapDoc(doc: CodeMirror.Doc): CodeMirror.Doc;\n\n        /** Get the content of the current editor document. You can pass it an optional argument to specify the string to be used to separate lines (defaults to \"\\n\"). */\n        getValue(seperator?: string): string;\n\n        /** Set the content of the current editor document. */\n        setValue(content: string): void;\n\n        /** Sets the gutter marker for the given gutter (identified by its CSS class, see the gutters option) to the given value.\n        Value can be either null, to clear the marker, or a DOM element, to set it. The DOM element will be shown in the specified gutter next to the specified line. */\n        setGutterMarker(line: any, gutterID: string, value: HTMLElement): CodeMirror.LineHandle;\n\n        /** Remove all gutter markers in the gutter with the given ID. */\n        clearGutter(gutterID: string): void;\n\n        /** Set a CSS class name for the given line.line can be a number or a line handle.\n        where determines to which element this class should be applied, can can be one of \"text\" (the text element, which lies in front of the selection),\n        \"background\"(a background element that will be behind the selection),\n        or \"wrap\" (the wrapper node that wraps all of the line's elements, including gutter elements).\n        class should be the name of the class to apply. */\n        addLineClass(line: any, where: string, _class_: string): CodeMirror.LineHandle;\n\n        /** Remove a CSS class from a line.line can be a line handle or number.\n        where should be one of \"text\", \"background\", or \"wrap\"(see addLineClass).\n        class can be left off to remove all classes for the specified node, or be a string to remove only a specific class. */\n        removeLineClass(line: any, where: string, class_: string): CodeMirror.LineHandle;\n\n        /** Returns the line number, text content, and marker status of the given line, which can be either a number or a line handle. */\n        lineInfo(line: any): {\n            line: any;\n            handle: any;\n            text: string;\n            /** Object mapping gutter IDs to marker elements. */\n            gutterMarkers: any;\n            textClass: string;\n            bgClass: string;\n            wrapClass: string;\n            /** Array of line widgets attached to this line. */\n            widgets: any;\n        };\n\n        /** Puts node, which should be an absolutely positioned DOM node, into the editor, positioned right below the given { line , ch } position.\n        When scrollIntoView is true, the editor will ensure that the entire node is visible (if possible).\n        To remove the widget again, simply use DOM methods (move it somewhere else, or call removeChild on its parent). */\n        addWidget(pos: CodeMirror.Position, node: HTMLElement, scrollIntoView: boolean): void;\n\n        /** Adds a line widget, an element shown below a line, spanning the whole of the editor's width, and moving the lines below it downwards.\n        line should be either an integer or a line handle, and node should be a DOM node, which will be displayed below the given line.\n        options, when given, should be an object that configures the behavior of the widget.\n        Note that the widget node will become a descendant of nodes with CodeMirror-specific CSS classes, and those classes might in some cases affect it. */\n        addLineWidget(line: any, node: HTMLElement, options?: {\n            /** Whether the widget should cover the gutter. */\n            coverGutter?: boolean;\n            /** Whether the widget should stay fixed in the face of horizontal scrolling. */\n            noHScroll?: boolean;\n            /** Causes the widget to be placed above instead of below the text of the line. */\n            above?: boolean;\n            /** When true, will cause the widget to be rendered even if the line it is associated with is hidden. */\n            showIfHidden?: boolean;\n        }): CodeMirror.LineWidget;\n\n\n        /** Programatically set the size of the editor (overriding the applicable CSS rules).\n        width and height height can be either numbers(interpreted as pixels) or CSS units (\"100%\", for example).\n        You can pass null for either of them to indicate that that dimension should not be changed. */\n        setSize(width: any, height: any): void;\n\n        /** Scroll the editor to a given(pixel) position.Both arguments may be left as null or undefined to have no effect. */\n        scrollTo(x: number, y: number): void;\n\n        /** Get an { left , top , width , height , clientWidth , clientHeight } object that represents the current scroll position, the size of the scrollable area,\n        and the size of the visible area(minus scrollbars). */\n        getScrollInfo(): CodeMirror.ScrollInfo;\n\n        /** Scrolls the given element into view. pos is a { line , ch } position, referring to a given character, null, to refer to the cursor.\n        The margin parameter is optional. When given, it indicates the amount of pixels around the given area that should be made visible as well. */\n        scrollIntoView(pos: CodeMirror.Position, margin?: number): void;\n\n        /** Scrolls the given element into view. pos is a { left , top , right , bottom } object, in editor-local coordinates.\n        The margin parameter is optional. When given, it indicates the amount of pixels around the given area that should be made visible as well. */\n        scrollIntoView(pos: { left: number; top: number; right: number; bottom: number; }, margin: number): void;\n\n        /** Scrolls the given element into view. pos is a { line, ch } object, in editor-local coordinates.\n        The margin parameter is optional. When given, it indicates the amount of pixels around the given area that should be made visible as well. */\n        scrollIntoView(pos: { line: number, ch: number }, margin?: number): void;\n\n        /** Scrolls the given element into view. pos is a { from, to } object, in editor-local coordinates.\n        The margin parameter is optional. When given, it indicates the amount of pixels around the given area that should be made visible as well. */\n        scrollIntoView(pos: { from: CodeMirror.Position, to: CodeMirror.Position }, margin: number): void;\n\n        /** Returns an { left , top , bottom } object containing the coordinates of the cursor position.\n        If mode is \"local\" , they will be relative to the top-left corner of the editable document.\n        If it is \"page\" or not given, they are relative to the top-left corner of the page.\n        where is a boolean indicating whether you want the start(true) or the end(false) of the selection. */\n        cursorCoords(where: boolean, mode: string): { left: number; top: number; bottom: number; };\n\n        /** Returns an { left , top , bottom } object containing the coordinates of the cursor position.\n        If mode is \"local\" , they will be relative to the top-left corner of the editable document.\n        If it is \"page\" or not given, they are relative to the top-left corner of the page.\n        where specifies the precise position at which you want to measure. */\n        cursorCoords(where: CodeMirror.Position, mode: string): { left: number; top: number; bottom: number; };\n\n        /** Returns the position and dimensions of an arbitrary character.pos should be a { line , ch } object.\n        This differs from cursorCoords in that it'll give the size of the whole character,\n        rather than just the position that the cursor would have when it would sit at that position. */\n        charCoords(pos: CodeMirror.Position, mode?: string): { left: number; right: number; top: number; bottom: number; };\n\n        /** Given an { left , top } object , returns the { line , ch } position that corresponds to it.\n        The optional mode parameter determines relative to what the coordinates are interpreted. It may be \"window\" , \"page\"(the default) , or \"local\". */\n        coordsChar(object: { left: number; top: number; }, mode?: string): CodeMirror.Position;\n\n        /** Computes the line at the given pixel height. mode can be one of the same strings that coordsChar accepts. */\n        lineAtHeight(height: number, mode?: string): number;\n\n        /** Computes the height of the top of a line, in the coordinate system specified by mode (see coordsChar), which defaults to \"page\". When a line below the bottom of the document is specified, the returned value is the bottom of the last line in the document. */\n        heightAtLine(line: number|LineHandle, mode?: string): number;\n\n        /** Returns the line height of the default font for the editor. */\n        defaultTextHeight(): number;\n\n        /** Returns the pixel width of an 'x' in the default font for the editor.\n        (Note that for non - monospace fonts , this is mostly useless, and even for monospace fonts, non - ascii characters might have a different width). */\n        defaultCharWidth(): number;\n\n        /** Returns a { from , to } object indicating the start (inclusive) and end (exclusive) of the currently rendered part of the document.\n        In big documents, when most content is scrolled out of view, CodeMirror will only render the visible part, and a margin around it.\n        See also the viewportChange event. */\n        getViewport(): { from: number; to: number };\n\n        /** If your code does something to change the size of the editor element (window resizes are already listened for), or unhides it,\n        you should probably follow up by calling this method to ensure CodeMirror is still looking as intended. */\n        refresh(): void;\n\n\n        /** Retrieves information about the token the current mode found before the given position (a {line, ch} object). */\n        getTokenAt(pos: CodeMirror.Position): {\n            /** The character(on the given line) at which the token starts. */\n            start: number;\n            /** The character at which the token ends. */\n            end: number;\n            /** The token's string. */\n            string: string;\n            /** The token type the mode assigned to the token, such as \"keyword\" or \"comment\" (may also be null). */\n            type: string;\n            /** The mode's state at the end of this token. */\n            state: any;\n        };\n\n        /** Returns the mode's parser state, if any, at the end of the given line number.\n        If no line number is given, the state at the end of the document is returned.\n        This can be useful for storing parsing errors in the state, or getting other kinds of contextual information for a line. */\n        getStateAfter(line?: number): any;\n\n        /** CodeMirror internally buffers changes and only updates its DOM structure after it has finished performing some operation.\n        If you need to perform a lot of operations on a CodeMirror instance, you can call this method with a function argument.\n        It will call the function, buffering up all changes, and only doing the expensive update after the function returns.\n        This can be a lot faster. The return value from this method will be the return value of your function. */\n        operation<T>(fn: ()=> T): T;\n\n        /** Adjust the indentation of the given line.\n        The second argument (which defaults to \"smart\") may be one of:\n        \"prev\" Base indentation on the indentation of the previous line.\n        \"smart\" Use the mode's smart indentation if available, behave like \"prev\" otherwise.\n        \"add\" Increase the indentation of the line by one indent unit.\n        \"subtract\" Reduce the indentation of the line. */\n        indentLine(line: number, dir?: string): void;\n\n\n        /** Give the editor focus. */\n        focus(): void;\n\n        /** Returns the hidden textarea used to read input. */\n        getInputField(): HTMLTextAreaElement;\n\n        /** Returns the DOM node that represents the editor, and controls its size. Remove this from your tree to delete an editor instance. */\n        getWrapperElement(): HTMLElement;\n\n        /** Returns the DOM node that is responsible for the scrolling of the editor. */\n        getScrollerElement(): HTMLElement;\n\n        /** Fetches the DOM node that contains the editor gutters. */\n        getGutterElement(): HTMLElement;\n\n\n\n        /** Events are registered with the on method (and removed with the off method).\n        These are the events that fire on the instance object. The name of the event is followed by the arguments that will be passed to the handler.\n        The instance argument always refers to the editor instance. */\n        on(eventName: string, handler: (instance: CodeMirror.Editor) => void ): void;\n        off(eventName: string, handler: (instance: CodeMirror.Editor) => void ): void;\n\n        /** Fires when a user copies a selection from the editor. */\n        on(eventName: 'copy', handler: (instance: CodeMirror.Editor, event: any) => void ): void;\n        off(eventName: 'copy', handler: (instance: CodeMirror.Editor, event: any) => void ): void;\n\n        /** Fires when a user pastes a selection into the editor. */\n        on(eventName: 'paste', handler: (instance: CodeMirror.Editor, event: any) => void ): void;\n        off(eventName: 'paste', handler: (instance: CodeMirror.Editor, event: any) => void ): void;\n\n        /** Fires every time the content of the editor is changed. */\n        on(eventName: 'change', handler: (instance: CodeMirror.Editor, change: CodeMirror.EditorChangeLinkedList) => void ): void;\n        off(eventName: 'change', handler: (instance: CodeMirror.Editor, change: CodeMirror.EditorChangeLinkedList) => void ): void;\n\n        /** Like the \"change\" event, but batched per operation, passing an\n         * array containing all the changes that happened in the operation.\n         * This event is fired after the operation finished, and display\n         * changes it makes will trigger a new operation. */\n        on(eventName: 'changes', handler: (instance: CodeMirror.Editor, change: CodeMirror.EditorChangeLinkedList[]) => void ): void;\n        off(eventName: 'changes', handler: (instance: CodeMirror.Editor, change: CodeMirror.EditorChangeLinkedList[]) => void ): void;\n\n        /** This event is fired before a change is applied, and its handler may choose to modify or cancel the change.\n        The changeObj never has a next property, since this is fired for each individual change, and not batched per operation.\n        Note: you may not do anything from a \"beforeChange\" handler that would cause changes to the document or its visualization.\n        Doing so will, since this handler is called directly from the bowels of the CodeMirror implementation,\n        probably cause the editor to become corrupted. */\n        on(eventName: 'beforeChange', handler: (instance: CodeMirror.Editor, change: CodeMirror.EditorChangeCancellable) => void ): void;\n        off(eventName: 'beforeChange', handler: (instance: CodeMirror.Editor, change: CodeMirror.EditorChangeCancellable) => void ): void;\n\n        /** Will be fired when the cursor or selection moves, or any change is made to the editor content. */\n        on(eventName: 'cursorActivity', handler: (instance: CodeMirror.Editor) => void ): void;\n        off(eventName: 'cursorActivity', handler: (instance: CodeMirror.Editor) => void ): void;\n\n        /** This event is fired before the selection is moved. Its handler may modify the resulting selection head and anchor.\n        Handlers for this event have the same restriction as \"beforeChange\" handlers � they should not do anything to directly update the state of the editor. */\n        on(eventName: 'beforeSelectionChange', handler: (instance: CodeMirror.Editor, selection: { head: CodeMirror.Position; anchor: CodeMirror.Position; }) => void ): void;\n        off(eventName: 'beforeSelectionChange', handler: (instance: CodeMirror.Editor, selection: { head: CodeMirror.Position; anchor: CodeMirror.Position; }) => void ): void;\n\n        /** Fires whenever the view port of the editor changes (due to scrolling, editing, or any other factor).\n        The from and to arguments give the new start and end of the viewport. */\n        on(eventName: 'viewportChange', handler: (instance: CodeMirror.Editor, from: number, to: number) => void ): void;\n        off(eventName: 'viewportChange', handler: (instance: CodeMirror.Editor, from: number, to: number) => void ): void;\n\n        /** Fires when the editor gutter (the line-number area) is clicked. Will pass the editor instance as first argument,\n        the (zero-based) number of the line that was clicked as second argument, the CSS class of the gutter that was clicked as third argument,\n        and the raw mousedown event object as fourth argument. */\n        on(eventName: 'gutterClick', handler: (instance: CodeMirror.Editor, line: number, gutter: string, clickEvent: Event) => void ): void;\n        off(eventName: 'gutterClick', handler: (instance: CodeMirror.Editor, line: number, gutter: string, clickEvent: Event) => void ): void;\n\n        /** Fires whenever the editor is focused. */\n        on(eventName: 'focus', handler: (instance: CodeMirror.Editor) => void ): void;\n        off(eventName: 'focus', handler: (instance: CodeMirror.Editor) => void ): void;\n\n        /** Fires whenever the editor is unfocused. */\n        on(eventName: 'blur', handler: (instance: CodeMirror.Editor) => void ): void;\n        off(eventName: 'blur', handler: (instance: CodeMirror.Editor) => void ): void;\n\n        /** Fires when the editor is scrolled. */\n        on(eventName: 'scroll', handler: (instance: CodeMirror.Editor) => void ): void;\n        off(eventName: 'scroll', handler: (instance: CodeMirror.Editor) => void ): void;\n\n        /** Will be fired whenever CodeMirror updates its DOM display. */\n        on(eventName: 'update', handler: (instance: CodeMirror.Editor) => void ): void;\n        off(eventName: 'update', handler: (instance: CodeMirror.Editor) => void ): void;\n\n        /** Fired whenever a line is (re-)rendered to the DOM. Fired right after the DOM element is built, before it is added to the document.\n        The handler may mess with the style of the resulting element, or add event handlers, but should not try to change the state of the editor. */\n        on(eventName: 'renderLine', handler: (instance: CodeMirror.Editor, line: number, element: HTMLElement) => void ): void;\n        off(eventName: 'renderLine', handler: (instance: CodeMirror.Editor, line: number, element: HTMLElement) => void ): void;\n\n        /** Expose the state object, so that the Editor.state.completionActive property is reachable*/\n        state: any;\n    }\n\n    interface EditorFromTextArea extends Editor {\n\n        /** Copy the content of the editor into the textarea. */\n        save(): void;\n\n        /** Remove the editor, and restore the original textarea (with the editor's current content). */\n        toTextArea(): void;\n\n        /** Returns the textarea that the instance was based on. */\n        getTextArea(): HTMLTextAreaElement;\n    }\n\n    interface DocConstructor {\n        new (text: string, mode?: any, firstLineNumber?: number, lineSep?: string): Doc;\n        (text: string, mode?: any, firstLineNumber?: number, lineSep?: string): Doc;\n    }\n\n    interface Doc {\n        /** Get the current editor content. You can pass it an optional argument to specify the string to be used to separate lines (defaults to \"\\n\"). */\n        getValue(seperator?: string): string;\n\n        /** Set the editor content. */\n        setValue(content: string): void;\n\n        /** Get the text between the given points in the editor, which should be {line, ch} objects.\n        An optional third argument can be given to indicate the line separator string to use (defaults to \"\\n\"). */\n        getRange(from: Position, to: CodeMirror.Position, seperator?: string): string;\n\n        /** Replace the part of the document between from and to with the given string.\n        from and to must be {line, ch} objects. to can be left off to simply insert the string at position from. */\n        replaceRange(replacement: string, from: CodeMirror.Position, to: CodeMirror.Position, origin?:string): void;\n\n        /** Get the content of line n. */\n        getLine(n: number): string;\n\n        /** Set the content of line n. */\n        setLine(n: number, text: string): void;\n\n        /** Remove the given line from the document. */\n        removeLine(n: number): void;\n\n        /** Get the number of lines in the editor. */\n        lineCount(): number;\n\n        /** Get the first line of the editor. This will usually be zero but for linked sub-views,\n        or documents instantiated with a non-zero first line, it might return other values. */\n        firstLine(): number;\n\n        /** Get the last line of the editor. This will usually be lineCount() - 1, but for linked sub-views, it might return other values. */\n        lastLine(): number;\n\n        /** Fetches the line handle for the given line number. */\n        getLineHandle(num: number): CodeMirror.LineHandle;\n\n        /** Given a line handle, returns the current position of that line (or null when it is no longer in the document). */\n        getLineNumber(handle: CodeMirror.LineHandle): number;\n\n        /** Iterate over the whole document, and call f for each line, passing the line handle.\n        This is a faster way to visit a range of line handlers than calling getLineHandle for each of them.\n        Note that line handles have a text property containing the line's content (as a string). */\n        eachLine(f: (line: CodeMirror.LineHandle) => void ): void;\n\n        /** Iterate over the range from start up to (not including) end, and call f for each line, passing the line handle.\n        This is a faster way to visit a range of line handlers than calling getLineHandle for each of them.\n        Note that line handles have a text property containing the line's content (as a string). */\n        eachLine(start: number, end: number, f: (line: CodeMirror.LineHandle) => void ): void;\n\n        /** Set the editor content as 'clean', a flag that it will retain until it is edited, and which will be set again when such an edit is undone again.\n        Useful to track whether the content needs to be saved. */\n        markClean(): void;\n\n        /** Returns whether the document is currently clean (not modified since initialization or the last call to markClean). */\n        isClean(): boolean;\n\n\n\n        /** Get the currently selected code. */\n        getSelection(): string;\n\n        /** Replace the selection with the given string. By default, the new selection will span the inserted text.\n        The optional collapse argument can be used to change this � passing \"start\" or \"end\" will collapse the selection to the start or end of the inserted text. */\n        replaceSelection(replacement: string, collapse?: string): void;\n\n        /** start is a an optional string indicating which end of the selection to return.\n        It may be \"start\" , \"end\" , \"head\"(the side of the selection that moves when you press shift + arrow),\n        or \"anchor\"(the fixed side of the selection).Omitting the argument is the same as passing \"head\".A { line , ch } object will be returned. */\n        getCursor(start?: string): CodeMirror.Position;\n\n        /** Retrieves a list of all current selections. These will always be sorted, and never overlap (overlapping selections are merged).\n        Each object in the array contains anchor and head properties referring to {line, ch} objects. */\n        listSelections(): { anchor: CodeMirror.Position; head: CodeMirror.Position }[];\n\n        /** Return true if any text is selected. */\n        somethingSelected(): boolean;\n\n        /** Set the cursor position.You can either pass a single { line , ch } object , or the line and the character as two separate parameters. */\n        setCursor(pos: CodeMirror.Position): void;\n\n        /** Set the selection range.anchor and head should be { line , ch } objects.head defaults to anchor when not given. */\n        setSelection(anchor: CodeMirror.Position, head: CodeMirror.Position): void;\n\n        /** Similar to setSelection , but will, if shift is held or the extending flag is set,\n        move the head of the selection while leaving the anchor at its current place.\n        pos2 is optional , and can be passed to ensure a region (for example a word or paragraph) will end up selected\n        (in addition to whatever lies between that region and the current anchor). */\n        extendSelection(from: CodeMirror.Position, to?: CodeMirror.Position): void;\n\n        /** Sets or clears the 'extending' flag , which acts similar to the shift key,\n        in that it will cause cursor movement and calls to extendSelection to leave the selection anchor in place. */\n        setExtending(value: boolean): void;\n\n\n        /** Retrieve the editor associated with a document. May return null. */\n        getEditor(): CodeMirror.Editor;\n\n\n        /** Create an identical copy of the given doc. When copyHistory is true , the history will also be copied.Can not be called directly on an editor. */\n        copy(copyHistory: boolean): CodeMirror.Doc;\n\n        /** Create a new document that's linked to the target document. Linked documents will stay in sync (changes to one are also applied to the other) until unlinked. */\n        linkedDoc(options: {\n            /** When turned on, the linked copy will share an undo history with the original.\n            Thus, something done in one of the two can be undone in the other, and vice versa. */\n            sharedHist?: boolean;\n            from?: number;\n            /** Can be given to make the new document a subview of the original. Subviews only show a given range of lines.\n            Note that line coordinates inside the subview will be consistent with those of the parent,\n            so that for example a subview starting at line 10 will refer to its first line as line 10, not 0. */\n            to?: number;\n            /** By default, the new document inherits the mode of the parent. This option can be set to a mode spec to give it a different mode. */\n            mode: any;\n        }): CodeMirror.Doc;\n\n        /** Break the link between two documents. After calling this , changes will no longer propagate between the documents,\n        and, if they had a shared history, the history will become separate. */\n        unlinkDoc(doc: CodeMirror.Doc): void;\n\n        /** Will call the given function for all documents linked to the target document. It will be passed two arguments,\n        the linked document and a boolean indicating whether that document shares history with the target. */\n        iterLinkedDocs(fn: (doc: CodeMirror.Doc, sharedHist: boolean) => void ): void;\n\n        /** Undo one edit (if any undo events are stored). */\n        undo(): void;\n\n        /** Redo one undone edit. */\n        redo(): void;\n\n        /** Returns an object with {undo, redo } properties , both of which hold integers , indicating the amount of stored undo and redo operations. */\n        historySize(): { undo: number; redo: number; };\n\n        /** Clears the editor's undo history. */\n        clearHistory(): void;\n\n        /** Get a(JSON - serializeable) representation of the undo history. */\n        getHistory(): any;\n\n        /** Replace the editor's undo history with the one provided, which must be a value as returned by getHistory.\n        Note that this will have entirely undefined results if the editor content isn't also the same as it was when getHistory was called. */\n        setHistory(history: any): void;\n\n\n        /** Can be used to mark a range of text with a specific CSS class name. from and to should be { line , ch } objects. */\n        markText(from: CodeMirror.Position, to: CodeMirror.Position, options?: CodeMirror.TextMarkerOptions): TextMarker;\n\n        /** Inserts a bookmark, a handle that follows the text around it as it is being edited, at the given position.\n        A bookmark has two methods find() and clear(). The first returns the current position of the bookmark, if it is still in the document,\n        and the second explicitly removes the bookmark. */\n        setBookmark(pos: CodeMirror.Position, options?: {\n            /** Can be used to display a DOM node at the current location of the bookmark (analogous to the replacedWith option to markText). */\n            widget?: HTMLElement;\n\n            /** By default, text typed when the cursor is on top of the bookmark will end up to the right of the bookmark.\n            Set this option to true to make it go to the left instead. */\n            insertLeft?: boolean;\n        }): CodeMirror.TextMarker;\n\n        /** Returns an array of all the bookmarks and marked ranges found between the given positions. */\n        findMarks(from: CodeMirror.Position, to: CodeMirror.Position): TextMarker[];\n\n        /** Returns an array of all the bookmarks and marked ranges present at the given position. */\n        findMarksAt(pos: CodeMirror.Position): TextMarker[];\n\n        /** Returns an array containing all marked ranges in the document. */\n        getAllMarks(): CodeMirror.TextMarker[];\n\n\n        /** Gets the mode object for the editor. Note that this is distinct from getOption(\"mode\"), which gives you the mode specification,\n        rather than the resolved, instantiated mode object. */\n        getMode(): any;\n\n        /** Calculates and returns a { line , ch } object for a zero-based index whose value is relative to the start of the editor's text.\n        If the index is out of range of the text then the returned object is clipped to start or end of the text respectively. */\n        posFromIndex(index: number): CodeMirror.Position;\n\n        /** The reverse of posFromIndex. */\n        indexFromPos(object: CodeMirror.Position): number;\n\n        /** Expose the state object, so that the Doc.state.completionActive property is reachable*/\n        state: any;\n    }\n\n    interface LineHandle {\n        text: string;\n    }\n\n    interface ScrollInfo {\n        left: any;\n        top: any;\n        width: any;\n        height: any;\n        clientWidth: any;\n        clientHeight: any;\n    }\n\n    interface TextMarker {\n        /** Remove the mark. */\n        clear(): void;\n\n        /** Returns a {from, to} object (both holding document positions), indicating the current position of the marked range,\n        or undefined if the marker is no longer in the document. */\n        find(): CodeMirror.Range|CodeMirror.Position;\n        /**  Returns an object representing the options for the marker. If copyWidget is given true, it will clone the value of the replacedWith option, if any. */\n        getOptions(copyWidget: boolean): CodeMirror.TextMarkerOptions;\n    }\n\n    interface LineWidget {\n        /** Removes the widget. */\n        clear(): void;\n\n        /** Call this if you made some change to the widget's DOM node that might affect its height.\n        It'll force CodeMirror to update the height of the line that contains the widget. */\n        changed(): void;\n    }\n\n    interface EditorChange {\n        /** Position (in the pre-change coordinate system) where the change started. */\n        from: CodeMirror.Position;\n        /** Position (in the pre-change coordinate system) where the change ended. */\n        to: CodeMirror.Position;\n        /** Array of strings representing the text that replaced the changed range (split by line). */\n        text: string[];\n        /**  Text that used to be between from and to, which is overwritten by this change. */\n        removed: string[];\n        /**  String representing the origin of the change event and wether it can be merged with history */\n        origin: string;\n    }\n\n    interface EditorChangeLinkedList extends CodeMirror.EditorChange {\n        /** Points to another change object (which may point to another, etc). */\n        next?: CodeMirror.EditorChangeLinkedList;\n    }\n\n    interface EditorChangeCancellable extends CodeMirror.EditorChange {\n        /** may be used to modify the change. All three arguments to update are optional, and can be left off to leave the existing value for that field intact. */\n        update(from?: CodeMirror.Position, to?: CodeMirror.Position, text?: string): void;\n\n      cancel(): void;\n\n      canceled: boolean;\n    }\n\n    interface PositionConstructor {\n        new (line: number, ch?: number): Position;\n        (line: number, ch?: number): Position;\n    }\n\n    interface Range{\n        from: CodeMirror.Position;\n        to: CodeMirror.Position;\n    }\n\n    interface Position {\n        ch: number;\n        line: number;\n    }\n\n    interface EditorConfiguration {\n        /** string| The starting value of the editor. Can be a string, or a document object. */\n        value?: any;\n\n        /** string|object. The mode to use. When not given, this will default to the first mode that was loaded.\n        It may be a string, which either simply names the mode or is a MIME type associated with the mode.\n        Alternatively, it may be an object containing configuration options for the mode,\n        with a name property that names the mode (for example {name: \"javascript\", json: true}). */\n        mode?: any;\n\n        /** The theme to style the editor with. You must make sure the CSS file defining the corresponding .cm-s-[name] styles is loaded.\n        The default is \"default\". */\n        theme?: string;\n\n        /** How many spaces a block (whatever that means in the edited language) should be indented. The default is 2. */\n        indentUnit?: number;\n\n        /** Whether to use the context-sensitive indentation that the mode provides (or just indent the same as the line before). Defaults to true. */\n        smartIndent?: boolean;\n\n        /** The width of a tab character. Defaults to 4. */\n        tabSize?: number;\n\n        /** Whether, when indenting, the first N*tabSize spaces should be replaced by N tabs. Default is false. */\n        indentWithTabs?: boolean;\n\n        /** Configures whether the editor should re-indent the current line when a character is typed\n        that might change its proper indentation (only works if the mode supports indentation). Default is true. */\n        electricChars?: boolean;\n\n        /** Determines whether horizontal cursor movement through right-to-left (Arabic, Hebrew) text\n        is visual (pressing the left arrow moves the cursor left)\n        or logical (pressing the left arrow moves to the next lower index in the string, which is visually right in right-to-left text).\n        The default is false on Windows, and true on other platforms. */\n        rtlMoveVisually?: boolean;\n\n        /** Configures the keymap to use. The default is \"default\", which is the only keymap defined in codemirror.js itself.\n        Extra keymaps are found in the keymap directory. See the section on keymaps for more information. */\n        keyMap?: string;\n\n        /** Can be used to specify extra keybindings for the editor, alongside the ones defined by keyMap. Should be either null, or a valid keymap value. */\n        extraKeys?: any;\n\n        /** Whether CodeMirror should scroll or wrap for long lines. Defaults to false (scroll). */\n        lineWrapping?: boolean;\n\n        /** Whether to show line numbers to the left of the editor. */\n        lineNumbers?: boolean;\n\n        /** At which number to start counting lines. Default is 1. */\n        firstLineNumber?: number;\n\n        /** A function used to format line numbers. The function is passed the line number, and should return a string that will be shown in the gutter. */\n        lineNumberFormatter?: (line: number) => string;\n\n        /** Can be used to add extra gutters (beyond or instead of the line number gutter).\n        Should be an array of CSS class names, each of which defines a width (and optionally a background),\n        and which will be used to draw the background of the gutters.\n        May include the CodeMirror-linenumbers class, in order to explicitly set the position of the line number gutter\n        (it will default to be to the right of all other gutters). These class names are the keys passed to setGutterMarker. */\n        gutters?: string[];\n\n        /** Determines whether the gutter scrolls along with the content horizontally (false)\n        or whether it stays fixed during horizontal scrolling (true, the default). */\n        fixedGutter?: boolean;\n\n        /** boolean|string. This disables editing of the editor content by the user. If the special value \"nocursor\" is given (instead of simply true), focusing of the editor is also disallowed. */\n        readOnly?: any;\n\n        /**Whether the cursor should be drawn when a selection is active. Defaults to false. */\n        showCursorWhenSelecting?: boolean;\n\n        /** The maximum number of undo levels that the editor stores. Defaults to 40. */\n        undoDepth?: number;\n\n        /** The period of inactivity (in milliseconds) that will cause a new history event to be started when typing or deleting. Defaults to 500. */\n        historyEventDelay?: number;\n\n        /** The tab index to assign to the editor. If not given, no tab index will be assigned. */\n        tabindex?: number;\n\n        /** Can be used to make CodeMirror focus itself on initialization. Defaults to off.\n        When fromTextArea is used, and no explicit value is given for this option, it will be set to true when either the source textarea is focused,\n        or it has an autofocus attribute and no other element is focused. */\n        autofocus?: boolean;\n\n        /** Controls whether drag-and - drop is enabled. On by default. */\n        dragDrop?: boolean;\n\n        /** When given , this will be called when the editor is handling a dragenter , dragover , or drop event.\n        It will be passed the editor instance and the event object as arguments.\n        The callback can choose to handle the event itself , in which case it should return true to indicate that CodeMirror should not do anything further. */\n        onDragEvent?: (instance: CodeMirror.Editor, event: Event) => boolean;\n\n        /** This provides a rather low - level hook into CodeMirror's key handling.\n        If provided, this function will be called on every keydown, keyup, and keypress event that CodeMirror captures.\n        It will be passed two arguments, the editor instance and the key event.\n        This key event is pretty much the raw key event, except that a stop() method is always added to it.\n        You could feed it to, for example, jQuery.Event to further normalize it.\n        This function can inspect the key event, and handle it if it wants to.\n        It may return true to tell CodeMirror to ignore the event.\n        Be wary that, on some browsers, stopping a keydown does not stop the keypress from firing, whereas on others it does.\n        If you respond to an event, you should probably inspect its type property and only do something when it is keydown\n        (or keypress for actions that need character data). */\n        onKeyEvent?: (instance: CodeMirror.Editor, event: Event) => boolean;\n\n        /** Half - period in milliseconds used for cursor blinking. The default blink rate is 530ms. */\n        cursorBlinkRate?: number;\n\n        /** Determines the height of the cursor. Default is 1 , meaning it spans the whole height of the line.\n        For some fonts (and by some tastes) a smaller height (for example 0.85),\n        which causes the cursor to not reach all the way to the bottom of the line, looks better */\n        cursorHeight?: number;\n\n        /** Highlighting is done by a pseudo background - thread that will work for workTime milliseconds,\n        and then use timeout to sleep for workDelay milliseconds.\n        The defaults are 200 and 300, you can change these options to make the highlighting more or less aggressive. */\n        workTime?: number;\n\n        /** See workTime. */\n        workDelay?: number;\n\n        /** Indicates how quickly CodeMirror should poll its input textarea for changes(when focused).\n        Most input is captured by events, but some things, like IME input on some browsers, don't generate events that allow CodeMirror to properly detect it.\n        Thus, it polls. Default is 100 milliseconds. */\n        pollInterval?: number\n\n        /** By default, CodeMirror will combine adjacent tokens into a single span if they have the same class.\n        This will result in a simpler DOM tree, and thus perform better. With some kinds of styling(such as rounded corners),\n        this will change the way the document looks. You can set this option to false to disable this behavior. */\n        flattenSpans?: boolean;\n\n        /** When highlighting long lines, in order to stay responsive, the editor will give up and simply style\n        the rest of the line as plain text when it reaches a certain position. The default is 10000.\n        You can set this to Infinity to turn off this behavior. */\n        maxHighlightLength?: number;\n\n        /** Specifies the amount of lines that are rendered above and below the part of the document that's currently scrolled into view.\n        This affects the amount of updates needed when scrolling, and the amount of work that such an update does.\n        You should usually leave it at its default, 10. Can be set to Infinity to make sure the whole document is always rendered,\n        and thus the browser's text search works on it. This will have bad effects on performance of big documents. */\n        viewportMargin?: number;\n\n        /** Optional lint configuration to be used in conjunction with CodeMirror's linter addon. */\n        lint?: boolean | LintOptions;\n\n        /** Optional value to be used in conjunction with CodeMirror’s placeholder add-on. */\n      placeholder?: string;\n\n\n        /** Which scrollbar style to use. CodeMirror provides \"native\" and \"null\", with \"native\" being the default. Addons may add more styles. */\n        scrollbarStyle?: string;\n\n        // whether you can scroll past the end of the document\n        scrollPastEnd?: boolean\n    }\n\n    interface TextMarkerOptions {\n        /** Assigns a CSS class to the marked stretch of text. */\n        className?: string;\n\n        /** Determines whether text inserted on the left of the marker will end up inside or outside of it. */\n        inclusiveLeft?: boolean;\n\n        /** Like inclusiveLeft , but for the right side. */\n        inclusiveRight?: boolean;\n\n        /** Atomic ranges act as a single unit when cursor movement is concerned — i.e. it is impossible to place the cursor inside of them.\n        In atomic ranges, inclusiveLeft and inclusiveRight have a different meaning — they will prevent the cursor from being placed\n        respectively directly before and directly after the range. */\n        atomic?: boolean;\n\n        /** Collapsed ranges do not show up in the display.Setting a range to be collapsed will automatically make it atomic. */\n        collapsed?: boolean;\n\n        /** When enabled, will cause the mark to clear itself whenever the cursor enters its range.\n        This is mostly useful for text - replacement widgets that need to 'snap open' when the user tries to edit them.\n        The \"clear\" event fired on the range handle can be used to be notified when this happens. */\n        clearOnEnter?: boolean;\n\n        /** Determines whether the mark is automatically cleared when it becomes empty. Default is true. */\n        clearWhenEmpty?: boolean;\n\n        /** Use a given node to display this range.Implies both collapsed and atomic.\n        The given DOM node must be an inline element(as opposed to a block element). */\n        replacedWith?: HTMLElement;\n\n        /** When replacedWith is given, this determines whether the editor will\n         * capture mouse and drag events occurring in this widget. Default is\n         * false—the events will be left alone for the default browser handler,\n         * or specific handlers on the widget, to capture. */\n        handleMouseEvents?: boolean;\n\n        /** A read - only span can, as long as it is not cleared, not be modified except by calling setValue to reset the whole document.\n        Note: adding a read - only span currently clears the undo history of the editor,\n        because existing undo events being partially nullified by read - only spans would corrupt the history (in the current implementation). */\n        readOnly?: boolean;\n\n        /** When set to true (default is false), adding this marker will create an event in the undo history that can be individually undone(clearing the marker). */\n        addToHistory?: boolean;\n\n        /** Can be used to specify an extra CSS class to be applied to the leftmost span that is part of the marker. */\n        startStyle?: string;\n\n        /** Equivalent to startStyle, but for the rightmost span. */\n        endStyle?: string;\n\n        /** A string of CSS to be applied to the covered text. For example \"color: #fe3\". */\n        css?: string;\n\n        /** When given, will give the nodes created for this span a HTML title attribute with the given value. */\n        title?: string;\n\n        /** When the target document is linked to other documents, you can set shared to true to make the marker appear in all documents.\n        By default, a marker appears only in its target document. */\n        shared?: boolean;\n    }\n\n    interface StringStream {\n        lastColumnPos: number;\n        lastColumnValue: number;\n        lineStart: number;\n\n        /**\n         * Current position in the string.\n         */\n        pos: number;\n\n        /**\n         * Where the stream's position was when it was first passed to the token function.\n         */\n        start: number;\n\n        /**\n         * The current line's content.\n         */\n        string: string;\n\n        /**\n         * Number of spaces per tab character.\n         */\n        tabSize: number;\n\n        /**\n         * Returns true only if the stream is at the end of the line.\n         */\n        eol(): boolean;\n\n        /**\n         * Returns true only if the stream is at the start of the line.\n         */\n        sol(): boolean;\n\n        /**\n         * Returns the next character in the stream without advancing it. Will return an null at the end of the line.\n         */\n        peek(): string;\n\n        /**\n         * Returns the next character in the stream and advances it. Also returns null when no more characters are available.\n         */\n        next(): string;\n\n        /**\n         * match can be a character, a regular expression, or a function that takes a character and returns a boolean.\n         * If the next character in the stream 'matches' the given argument, it is consumed and returned.\n         * Otherwise, undefined is returned.\n         */\n        eat(match: string): string;\n        eat(match: RegExp): string;\n        eat(match: (char: string) => boolean): string;\n\n        /**\n         * Repeatedly calls eat with the given argument, until it fails. Returns true if any characters were eaten.\n         */\n        eatWhile(match: string): boolean;\n        eatWhile(match: RegExp): boolean;\n        eatWhile(match: (char: string) => boolean): boolean;\n\n        /**\n         * Shortcut for eatWhile when matching white-space.\n         */\n        eatSpace(): boolean;\n\n        /**\n         * Moves the position to the end of the line.\n         */\n        skipToEnd(): void;\n\n        /**\n         * Skips to the next occurrence of the given character, if found on the current line (doesn't advance the stream if\n         * the character does not occur on the line).\n         *\n         * Returns true if the character was found.\n         */\n        skipTo(ch: string): boolean;\n\n        /**\n         * Act like a multi-character eat - if consume is true or not given - or a look-ahead that doesn't update the stream\n         * position - if it is false. pattern can be either a string or a regular expression starting with ^. When it is a\n         * string, caseFold can be set to true to make the match case-insensitive. When successfully matching a regular\n         * expression, the returned value will be the array returned by match, in case you need to extract matched groups.\n         */\n        match(pattern: string, consume?: boolean, caseFold?: boolean): boolean;\n        match(pattern: RegExp, consume?: boolean): string[];\n\n        /**\n         * Backs up the stream n characters. Backing it up further than the start of the current token will cause things to\n         * break, so be careful.\n         */\n        backUp(n: number): void;\n\n        /**\n         * Returns the column (taking into account tabs) at which the current token starts.\n         */\n        column(): number;\n\n        /**\n         * Tells you how far the current line has been indented, in spaces. Corrects for tab characters.\n         */\n        indentation(): number;\n\n        /**\n         * Get the string between the start of the current token and the current stream position.\n         */\n        current(): string;\n    }\n\n    /**\n     * A Mode is, in the simplest case, a lexer (tokenizer) for your language — a function that takes a character stream as input,\n     * advances it past a token, and returns a style for that token. More advanced modes can also handle indentation for the language.\n     */\n    interface Mode<T> {\n        /**\n         * This function should read one token from the stream it is given as an argument, optionally update its state,\n         * and return a style string, or null for tokens that do not have to be styled. Multiple styles can be returned, separated by spaces.\n         */\n        token(stream: StringStream, state: T): string;\n\n        /**\n         * A function that produces a state object to be used at the start of a document.\n         */\n        startState?: () => T;\n        /**\n         * For languages that have significant blank lines, you can define a blankLine(state) method on your mode that will get called\n         * whenever a blank line is passed over, so that it can update the parser state.\n         */\n        blankLine?: (state: T) => void;\n        /**\n         * Given a state returns a safe copy of that state.\n         */\n        copyState?: (state: T) => T;\n\n        /**\n         * The indentation method should inspect the given state object, and optionally the textAfter string, which contains the text on\n         * the line that is being indented, and return an integer, the amount of spaces to indent.\n         */\n        indent?: (state: T, textAfter: string) => number;\n\n        /** The four below strings are used for working with the commenting addon. */\n        /**\n         * String that starts a line comment.\n         */\n        lineComment?: string;\n        /**\n         * String that starts a block comment.\n         */\n        blockCommentStart?: string;\n        /**\n         * String that ends a block comment.\n         */\n        blockCommentEnd?: string;\n        /**\n         * String to put at the start of continued lines in a block comment.\n         */\n        blockCommentLead?: string;\n\n        /**\n         * Trigger a reindent whenever one of the characters in the string is typed.\n         */\n        electricChars?: string\n        /**\n         * Trigger a reindent whenever the regex matches the part of the line before the cursor.\n         */\n        electricinput?: RegExp\n    }\n\n    /**\n     * A function that, given a CodeMirror configuration object and an optional mode configuration object, returns a mode object.\n     */\n    interface ModeFactory<T> {\n        (config: CodeMirror.EditorConfiguration, modeOptions?: any): Mode<T>\n    }\n\n    /**\n     * id will be the id for the defined mode. Typically, you should use this second argument to defineMode as your module scope function\n     * (modes should not leak anything into the global scope!), i.e. write your whole mode inside this function.\n     */\n    function defineMode(id: string, modefactory: ModeFactory<any>): void;\n\n    /**\n     * id will be the id for the defined mode. Typically, you should use this second argument to defineMode as your module scope function\n     * (modes should not leak anything into the global scope!), i.e. write your whole mode inside this function.\n     */\n    function defineMode<T>(id: string, modefactory: ModeFactory<T>): void;\n\n    /**\n     * The first argument is a configuration object as passed to the mode constructor function, and the second argument\n     * is a mode specification as in the EditorConfiguration mode option.\n     */\n    function getMode<T>(config: CodeMirror.EditorConfiguration, mode: any): Mode<T>;\n\n    /**\n     * Utility function from the overlay.js addon that allows modes to be combined. The mode given as the base argument takes care of\n     * most of the normal mode functionality, but a second (typically simple) mode is used, which can override the style of text.\n     * Both modes get to parse all of the text, but when both assign a non-null style to a piece of code, the overlay wins, unless\n     * the combine argument was true and not overridden, or state.overlay.combineTokens was true, in which case the styles are combined.\n     */\n    function overlayMode<T, S>(base: Mode<T>, overlay: Mode<S>, combine?: boolean): Mode<any>\n\n    /**\n     * async specifies that the lint process runs asynchronously. hasGutters specifies that lint errors should be displayed in the CodeMirror\n     * gutter, note that you must use this in conjunction with [ \"CodeMirror-lint-markers\" ] as an element in the gutters argument on\n     * initialization of the CodeMirror instance.\n     */\n    interface LintStateOptions {\n        async: boolean;\n        hasGutters: boolean;\n    }\n\n    /**\n     * Adds the getAnnotations callback to LintStateOptions which may be overridden by the user if they choose use their own\n     * linter.\n     */\n    interface LintOptions extends LintStateOptions {\n        getAnnotations: AnnotationsCallback;\n    }\n\n    /**\n     * A function that calls the updateLintingCallback with any errors found during the linting process.\n     */\n    interface AnnotationsCallback {\n        (content: string, updateLintingCallback: UpdateLintingCallback, options: LintStateOptions, codeMirror: Editor): void;\n    }\n\n    /**\n     * A function that, given an array of annotations, updates the CodeMirror linting GUI with those annotations\n     */\n    interface UpdateLintingCallback {\n        (codeMirror: Editor, annotations: Annotation[]): void;\n    }\n\n    /**\n     * An annotation contains a description of a lint error, detailing the location of the error within the code, the severity of the error,\n     * and an explaination as to why the error was thrown.\n     */\n    interface Annotation {\n        from: Position;\n        message?: string;\n        severity?: string;\n        to?: Position;\n    }\n\n    /**\n     * A function that calculates either a two-way or three-way merge between different sets of content.\n     */\n    function MergeView(element: HTMLElement, options?: MergeView.MergeViewEditorConfiguration): MergeView.MergeViewEditor;\n\n    namespace MergeView {\n      /**\n       * Options available to MergeView.\n       */\n      interface MergeViewEditorConfiguration extends EditorConfiguration {\n          /**\n           * Determines whether the original editor allows editing. Defaults to false.\n           */\n          allowEditingOriginals?: boolean;\n\n          /**\n           * When true stretches of unchanged text will be collapsed. When a number is given, this indicates the amount\n           * of lines to leave visible around such stretches (which defaults to 2). Defaults to false.\n           */\n          collapseIdentical?: boolean | number;\n\n          /**\n           * Sets the style used to connect changed chunks of code. By default, connectors are drawn. When this is set to \"align\",\n           * the smaller chunk is padded to align with the bigger chunk instead.\n           */\n          connect?: string;\n\n          /**\n           * Callback for when stretches of unchanged text are collapsed.\n           */\n          onCollapse?(mergeView: MergeViewEditor, line: number, size: number, mark: TextMarker): void;\n\n          /**\n           * Provides original version of the document to be shown on the right of the editor.\n           */\n          orig: any;\n\n          /**\n           * Provides original version of the document to be shown on the left of the editor.\n           * To create a 2-way (as opposed to 3-way) merge view, provide only one of origLeft and origRight.\n           */\n          origLeft?: any;\n\n          /**\n           * Provides original version of document to be shown on the right of the editor.\n           * To create a 2-way (as opposed to 3-way) merge view, provide only one of origLeft and origRight.\n           */\n          origRight?: any;\n\n          /**\n           * Determines whether buttons that allow the user to revert changes are shown. Defaults to true.\n           */\n          revertButtons?: boolean;\n\n          /**\n           * When true, changed pieces of text are highlighted. Defaults to true.\n           */\n          showDifferences?: boolean;\n      }\n\n      interface MergeViewEditor extends Editor {\n          /**\n           * Returns the editor instance.\n           */\n          editor(): Editor;\n\n          /**\n           * Left side of the merge view.\n           */\n          left: DiffView;\n          leftChunks(): MergeViewDiffChunk;\n          leftOriginal(): Editor;\n\n          /**\n           * Right side of the merge view.\n           */\n          right: DiffView;\n          rightChunks(): MergeViewDiffChunk;\n          rightOriginal(): Editor;\n\n          /**\n           * Sets whether or not the merge view should show the differences between the editor views.\n           */\n          setShowDifferences(showDifferences: boolean): void;\n      }\n\n      /**\n       * Tracks changes in chunks from oroginal to new.\n       */\n      interface MergeViewDiffChunk {\n          editFrom: number;\n          editTo: number;\n          origFrom: number;\n          origTo: number;\n      }\n\n      interface DiffView {\n          /**\n           * Forces the view to reload.\n           */\n          forceUpdate(): (mode: string) => void;\n\n          /**\n           * Sets whether or not the merge view should show the differences between the editor views.\n           */\n          setShowDifferences(showDifferences: boolean): void;\n      }\n    }\n\n  var commands:{[name:string]:(editor:Editor) => void}\n\n\n  // Extension typings addon/scroll/annotatescrollbar.js\n  namespace AnnotateScrollbar {\n    interface Options {\n      className?: string;\n      scrollButtonHeight?: number;\n      listenForChanges?: boolean;\n\n    }\n\n    class Annotation {\n      constructor(cm:Editor, options:Options)\n\n      computeScale():boolean\n      update(annotation:Range[])\n      redraw(compute:boolean)\n      clear()\n    }\n  }\n  interface Editor {\n    annotateScrollbar(options:AnnotateScrollbar.Options):AnnotateScrollbar.Annotation\n  }\n}\n\ndeclare module \"codemirror\" {\n  export = CodeMirror;\n}\n"
  },
  {
    "path": "typings/commonmark/commonmark.d.ts",
    "content": "// Type definitions for commonmark.js 0.22.1\n// Project: https://github.com/jgm/commonmark.js\n// Definitions by: Nico Jansen <https://github.com/nicojs>\n// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped\n\n\ndeclare namespace commonmark {\n\n    export interface NodeWalkingStep {\n        /**\n         * a boolean, which is true when we enter a Node from a parent or sibling, and false when we reenter it from a child\n         */\n        entering: boolean;\n        /**\n         * The node belonging to this step\n         */\n        node: Node;\n    }\n\n    export interface NodeWalker {\n        /**\n         * Returns an object with properties entering and node. Returns null when we have finished walking the tree.\n         */\n        next(): NodeWalkingStep;\n        /**\n         * Resets the iterator to resume at the specified node and setting for entering. (Normally this isn't needed unless you do destructive updates to the Node tree.)\n         */\n        resumeAt(node: Node, entering?: boolean): void;\n    }\n\n    export interface Position extends Array<Array<number>> {\n    }\n\n    export interface ListData {\n        type?: string,\n        tight?: boolean,\n        delimiter?: string,\n        bulletChar?: string\n    }\n\n    export class Node {\n        constructor(nodeType: string, sourcepos?: Position);\n        isContainer: boolean;\n\n        /**\n         * (read-only): one of Text, Softbreak, Hardbreak, Emph, Strong, Html, Link, Image, Code, Document, Paragraph, BlockQuote, Item, List, Heading, CodeBlock, HtmlBlock ThematicBreak.\n         */\n        type: string;\n        /**\n         * (read-only): a Node or null.\n         */\n        firstChild: Node;\n        /**\n         * (read-only): a Node or null.\n         */\n        lastChild: Node;\n        /**\n         * (read-only): a Node or null.\n         */\n        next: Node;\n        /**\n         * (read-only): a Node or null.\n         */\n        prev: Node;\n        /**\n         * (read-only): a Node or null.\n         */\n        parent: Node;\n        /**\n         * (read-only): an Array with the following form: [[startline, startcolumn], [endline, endcolumn]]\n         */\n        sourcepos: Position;\n        /**\n         *  the literal String content of the node or null.\n         */\n        literal: string;\n        /**\n         * link or image destination (String) or null.\n         */\n        destination: string;\n        /**\n         *  link or image title (String) or null.\n         */\n        title: string;\n        /**\n         * fenced code block info string (String) or null.\n         */\n        info: string;\n        /**\n         * heading level (Number).\n         */\n        level: number;\n        /**\n         * either Bullet or Ordered (or undefined).\n         */\n        listType: string;\n        /**\n         * true if list is tight\n         */\n        listTight: boolean;\n        /**\n         * a Number, the starting number of an ordered list.\n         */\n        listStart: number;\n        /**\n         * a String, either ) or . for an ordered list.\n         */\n        listDelimiter: string;\n        /**\n         * used only for CustomBlock or CustomInline.\n         */\n        onEnter: string;\n        /**\n         * used only for CustomBlock or CustomInline.\n         */\n        onExit: string;\n        /**\n         * Append a Node child to the end of the Node's children.\n         */\n        appendChild(child: Node): void;\n        /**\n         *  Prepend a Node child to the beginning of the Node's children.\n         */\n        prependChild(child: Node): void;\n        /**\n         *  Remove the Node from the tree, severing its links with siblings and parents, and closing up gaps as needed.\n         */\n        unlink(): void;\n        /**\n         * Insert a Node sibling after the Node.\n         */\n        insertAfter(sibling: Node): void;\n        /**\n         * Insert a Node sibling before the Node.\n         */\n        insertBefore(sibling: Node): void;\n        /**\n         * Returns a NodeWalker that can be used to iterate through the Node tree rooted in the Node\n         */\n        walker(): NodeWalker;\n        /**\n         * Setting the backing object of listType, listTight, listStat and listDelimiter directly.\n         * Not needed unless creating list nodes directly. Should be fixed from v>0.22.1\n         * https://github.com/jgm/commonmark.js/issues/74\n         */\n        _listData: ListData;\n    }\n\n    /**\n     * Instead of converting Markdown directly to HTML, as most converters do, commonmark.js parses Markdown to an AST (abstract syntax tree), and then renders this AST as HTML.\n     * This opens up the possibility of manipulating the AST between parsing and rendering. For example, one could transform emphasis into ALL CAPS.\n     */\n    export class Parser {\n        /**\n         * Constructs a new Parser\n         */\n        constructor(options?: ParserOptions);\n        parse(input: string): Node;\n    }\n\n    export interface ParserOptions {\n        /**\n         *  if true, straight quotes will be made curly, -- will be changed to an en dash, --- will be changed to an em dash, and ... will be changed to ellipses.\n         */\n        smart?: boolean;\n        time?: boolean;\n    }\n\n    export interface HtmlRenderingOptions extends XmlRenderingOptions {\n        /**\n         *  if true, raw HTML will not be passed through to HTML output (it will be replaced by comments), and potentially unsafe URLs in links and images (those beginning with javascript:, vbscript:, file:, and with a few exceptions data:) will be replaced with empty strings.\n         */\n        safe?: boolean;\n        /**\n         *  if true, straight quotes will be made curly, -- will be changed to an en dash, --- will be changed to an em dash, and ... will be changed to ellipses.\n         */\n        smart?: boolean;\n        /**\n         *  if true, source position information for block-level elements will be rendered in the data-sourcepos attribute (for HTML) or the sourcepos attribute (for XML).\n         */\n        sourcepos?: boolean;\n    }\n\n    export class HtmlRenderer {\n        constructor(options?: HtmlRenderingOptions)\n        render(root: Node): string;\n        /**\n         * Let's you override the softbreak properties of a renderer. So, to make soft breaks render as hard breaks in HTML:\n         * writer.softbreak = \"<br />\";\n         */\n        softbreak: string;\n        /**\n         * Override the function that will be used to escape (sanitize) the html output. Return value is used to add to the html output\n         * @param input the input to escape\n         * @param isAttributeValue indicates wheter or not the input value will be used as value of an html attribute.\n         */\n        escape: (input: string, isAttributeValue: boolean) => string;\n    }\n\n    export interface XmlRenderingOptions {\n        time?: boolean;\n        sourcepos?: boolean;\n    }\n\n    export class XmlRenderer {\n        constructor(options?: XmlRenderingOptions)\n        render(root: Node): string;\n    }\n\n}\n\ndeclare module 'commonmark' {\n    export = commonmark;\n}\n"
  }
]