├── .gitattributes ├── .gitignore ├── .npmignore ├── ATTRIBUTIONS.md ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── assets ├── css │ ├── app-preview.css │ ├── base.css │ ├── codemirror.css │ ├── editor.css │ ├── examples │ │ ├── crm.css │ │ └── todomvc.css │ ├── ide.css │ ├── ionicons.min.css │ ├── simplescrollbars.css │ └── trace.css ├── favicon.png └── fonts │ └── ionicons.ttf ├── bin └── eve.js ├── circle.yml ├── docker-compose.yml ├── index.html ├── package.json ├── src ├── bootstrap.ts ├── index.ts ├── loadWorker.js ├── microReact.ts ├── parser │ ├── errors.ts │ └── parser.ts ├── programs │ └── perfReport.ts ├── runtime │ ├── dsl2.ts │ ├── indexes.ts │ ├── performance.ts │ ├── runtime.ts │ ├── stdlib.ts │ └── trace.ts ├── system-polyfills.js ├── system.js ├── systemJSConfig.js ├── types.d.ts └── watchers │ ├── canvas.ts │ ├── compiler.ts │ ├── console.ts │ ├── dom.ts │ ├── editor.ts │ ├── file.ts │ ├── html.ts │ ├── index.ts │ ├── notify.ts │ ├── shape.ts │ ├── svg.ts │ ├── system.ts │ ├── tag-browser.ts │ ├── ui.ts │ └── watcher.ts ├── syntax_diagrams.html ├── test ├── aggregate.ts ├── all.ts ├── antijoin.ts ├── choose.ts ├── distinct.ts ├── foundation.ts ├── performance.ts ├── stdlib │ └── math.ts ├── union.ts └── util.ts ├── tsconfig.json └── typings ├── codemirror └── codemirror.d.ts └── commonmark └── commonmark.d.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | build/* linguist-vendored 2 | csrc/core/* linguist-vendored 3 | csrc/crypto/* linguist-vendored 4 | csrc/http/* linguist-vendored 5 | csrc/luapower/* linguist-vendored 6 | csrc/luautf8/* linguist-vendored 7 | csrc/unix/* linguist-vendored 8 | csrc/dns/* linguist-vendored 9 | examples/* linguist-vendored 10 | jssrc/codemirror.js linguist-vendored 11 | index.html linguist-vendored 12 | readme.md linguist-vendored 13 | *.css linguist-vendored 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | lua_modules/ 43 | 44 | *~ 45 | dist/ 46 | build/ 47 | node_modules/ 48 | .idea 49 | 50 | experimental/build/continuation_templates.h 51 | experimental/build/eve 52 | experimental/build/path.c 53 | experimental/build/Eve.xcodeproj/xcuserdata 54 | experimental/build/Eve.xcodeproj/*/xcuserdata 55 | experimental/build/lua 56 | experimental/build/luajit-2.0 57 | experimental/build/*.js 58 | 59 | experimental/build/*.js 60 | experimental/build/*.js.map 61 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/witheve/Eve/f680d331d827c7dfe3eaeb79a2a7ce86710d930b/.npmignore -------------------------------------------------------------------------------- /ATTRIBUTIONS.md: -------------------------------------------------------------------------------- 1 | # Software Attributions 2 | 3 | Eve 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. 4 | 5 | ------------------------------------------------------------------------------- 6 | 7 | ### @types/body-parser 8 | - TypeScript definitions for body-parser 9 | - By Santi Albo (https://github.com/santialbo/), VILIC VANE (https://vilic.info), Jonathan Häberle (https://github.com/dreampulse) 10 | - [MIT][MIT] License 11 | - https://www.npmjs.com/package/@types/body-parser 12 | 13 | ------------------------------------------------------------------------------- 14 | 15 | ### @types/commonmark 16 | - TypeScript definitions for commonmark.js 0.22.1 17 | - By Nico Jansen (https://github.com/nicojs) 18 | - [MIT][MIT] License 19 | - https://www.npmjs.com/package/@types/commonmark 20 | 21 | ------------------------------------------------------------------------------- 22 | 23 | ### @types/express 24 | - TypeScript definitions for Express 4.x 25 | - By by Boris Yankov https://github.com/borisyankov/. 26 | - [MIT][MIT] License 27 | - https://www.npmjs.com/package/@types/express 28 | 29 | ------------------------------------------------------------------------------- 30 | 31 | ### @types/glob 32 | - TypeScript definitions for Glob 5.0.10 33 | - By vvakame (https://github.com/vvakame) 34 | - [MIT][MIT] License 35 | - https://www.npmjs.com/package/@types/glob 36 | 37 | ------------------------------------------------------------------------------- 38 | 39 | ### @types/minimist 40 | - TypeScript definitions for minimist 1.1.3 41 | - By Bart van der Schoor (https://github.com/Bartvds), Necroskillz (https://github.com/Necroskillz) 42 | - [MIT][MIT] License 43 | - https://www.npmjs.com/package/@types/minimist 44 | 45 | ------------------------------------------------------------------------------- 46 | 47 | ### @types/mkdirp 48 | - TypeScript definitions for mkdirp 0.3.0 49 | - By Bart van der Schoor (https://github.com/Bartvds) 50 | - [MIT][MIT] License 51 | - https://www.npmjs.com/package/@types/mkdirp 52 | 53 | ------------------------------------------------------------------------------- 54 | 55 | ### @types/node 56 | - TypeScript definitions for Node.js v6.x 57 | - By Microsoft TypeScript (http://typescriptlang.org), DefinitelyTyped (https://github.com/DefinitelyTyped/DefinitelyTyped) 58 | - [MIT][MIT] License 59 | - https://www.npmjs.com/package/@types/node 60 | 61 | ------------------------------------------------------------------------------- 62 | 63 | ### @types/request 64 | - TypeScript definitions for request 65 | - By Carlos Ballesteros Velasco (https://github.com/soywiz), et. al. 66 | - [MIT][MIT] License 67 | - https://www.npmjs.com/package/@types/request 68 | 69 | ------------------------------------------------------------------------------- 70 | 71 | ### @types/tape 72 | - TypeScript definitions for tape v4.2.2 73 | - By Bart van der Schoor (https://github.com/Bartvds), Haoqun Jiang (https://github.com/sodatea) 74 | - [MIT][MIT] License 75 | - https://www.npmjs.com/package/@types/tape 76 | 77 | ------------------------------------------------------------------------------- 78 | 79 | ### @types/ws 80 | - TypeScript definitions for ws 81 | - By Paul Loyd (https://github.com/loyd) 82 | - [MIT][MIT] License 83 | - https://www.npmjs.com/package/@types/ws 84 | 85 | ------------------------------------------------------------------------------- 86 | 87 | ### codemirror.d.ts 88 | - TypeScript definitions for codemirror 89 | - By mihailik (https://github.com/mihailik) 90 | - [MIT][MIT] License 91 | - [https://github.com/DefinitelyTyped/DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/03b3450d08a0b0a1b9e820e581a52c8e6c21f32e/codemirror/codemirror.d.ts) 92 | 93 | ------------------------------------------------------------------------------- 94 | 95 | ### System.js 96 | - Universal dynamic module loader 97 | - [MIT][MIT] License 98 | - https://github.com/systemjs/systemjs 99 | 100 | ------------------------------------------------------------------------------- 101 | 102 | ### body-parser 103 | - Node.js body parsing middleware 104 | - [MIT][MIT] License 105 | - https://www.npmjs.com/package/body-parser 106 | 107 | ------------------------------------------------------------------------------- 108 | 109 | ### chevrotain 110 | - Chevrotain is a high performance fault tolerant javascript parsing DSL for building recursive decent parsers 111 | - [Apache 2.0][Apache] License 112 | - https://www.npmjs.com/package/chevrotain 113 | 114 | ------------------------------------------------------------------------------- 115 | 116 | ### commonmark 117 | - a strongly specified, highly compatible variant of Markdown 118 | - [BSD-2-Clause][BSD] License 119 | - https://www.npmjs.com/package/commonmark 120 | 121 | ------------------------------------------------------------------------------- 122 | 123 | ### express 124 | - Fast, unopinionated, minimalist web framework 125 | - [MIT][MIT] License 126 | - https://www.npmjs.com/package/express 127 | 128 | ------------------------------------------------------------------------------- 129 | 130 | ### glob 131 | - a little globber 132 | - by Isaac Schlueter (https://github.com/isaacs) 133 | - [ISC][ISC] License 134 | - https://www.npmjs.com/package/glob 135 | 136 | ------------------------------------------------------------------------------- 137 | 138 | ### minimist 139 | - parse argument options 140 | - [MIT][MIT] License 141 | - https://www.npmjs.com/package/minimist 142 | 143 | ------------------------------------------------------------------------------- 144 | 145 | ### mkdirp 146 | - Recursively mkdir, like mkdir -p 147 | - [MIT][MIT] License 148 | - https://www.npmjs.com/package/mkdirp 149 | 150 | ------------------------------------------------------------------------------- 151 | 152 | ### node-uuid 153 | - Rigorous implementation of RFC4122 (v1 and v4) UUIDs. 154 | - [MIT][MIT] License 155 | - https://www.npmjs.com/package/node-uuid 156 | 157 | ------------------------------------------------------------------------------- 158 | 159 | ### request 160 | - Simplified HTTP request client 161 | - [Apache 2.0][Apache] License 162 | - https://www.npmjs.com/package/request 163 | 164 | ------------------------------------------------------------------------------- 165 | 166 | ### typescript 167 | - TypeScript is a language for application scale JavaScript development 168 | - [Apache 2.0][Apache] License 169 | - https://www.npmjs.com/package/typescript 170 | 171 | ------------------------------------------------------------------------------- 172 | 173 | ### ws 174 | - simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455 175 | - [MIT][MIT] License 176 | - https://www.npmjs.com/package/ws 177 | 178 | ------------------------------------------------------------------------------- 179 | 180 | ### uuid.js 181 | - Simple, fast generation of RFC4122 UUIDS. 182 | - By Robert Kieffer (https://github.com/broofa) 183 | - [MIT][MIT] License 184 | - https://github.com/broofa/node-uuid 185 | 186 | ------------------------------------------------------------------------------- 187 | 188 | ### Codemirror 189 | - In-browser code editor 190 | - By Marijn Haverbeke (https://github.com/marijnh) 191 | - [MIT][MIT] License 192 | - https://github.com/codemirror/codemirror 193 | 194 | ------------------------------------------------------------------------------- 195 | 196 | ### Simple Scrollbars 197 | - Addon for CodeMirror 198 | - By Marijn Haverbeke (https://github.com/marijnh) 199 | - [MIT][MIT] License 200 | - https://codemirror.net/doc/manual.html#addon_simplescrollbars 201 | 202 | ------------------------------------------------------------------------------- 203 | 204 | ### Annotate Scrollbar 205 | - Addon for CodeMirror 206 | - By Marijn Haverbeke (https://github.com/marijnh) 207 | - [MIT][MIT] License 208 | - https://codemirror.net/doc/manual.html#addon_annotatescrollbar 209 | 210 | ------------------------------------------------------------------------------- 211 | 212 | ### ionicons 213 | The premium icon font for Ionic 214 | - [MIT][MIT] License 215 | - by Ben Sperry (https://twitter.com/benjsperry) 216 | - https://github.com/driftyco/ionicons 217 | 218 | ------------------------------------------------------------------------------- 219 | 220 | [BSD]: https://spdx.org/licenses/BSD-2-Clause 221 | [MIT]: https://spdx.org/licenses/MIT 222 | [Apache]: https://spdx.org/licenses/Apache-2.0 223 | [ISC]: https://spdx.org/licenses/ISC -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6-slim 2 | MAINTAINER Kodowa, Inc. 3 | ADD / /eve 4 | RUN chown -R node:node /eve 5 | USER node 6 | ENV HOME /eve 7 | WORKDIR /eve 8 | RUN npm install 9 | EXPOSE 8080 10 | CMD npm start -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | 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 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Eve logo 3 |

4 | 5 | --- 6 | 7 | Eve is a programming language based on years of research into building a human-first programming platform. 8 | 9 | **This repository hosts a preview of Eve v0.3 alpha, which is no longer under active development.** 10 | 11 | ## Getting Started with Eve v0.3 preview 12 | 13 | Install [Node](https://nodejs.org/en/download/) for your platform, then clone and build the [Eve starter repository](https://github.com/witheve/eve-starter): 14 | 15 | ``` 16 | git clone git@github.com:witheve/eve-starter.git 17 | cd eve-starter 18 | npm install 19 | ``` 20 | 21 | You can start the program switcher, which allows you to browse included example programs: 22 | 23 | ``` 24 | npm start 25 | ``` 26 | 27 | Or you can run a specific program by providing its path as an argument: 28 | 29 | ``` 30 | npm start -- path/to/program.js 31 | ``` 32 | 33 | ## Integrating Eve into an existing project 34 | 35 | You can get Eve as an npm package 36 | 37 | ``` 38 | npm install witheve@preview 39 | ``` 40 | 41 | Then import Eve to use it in your project 42 | 43 | ``` 44 | import {Program} from "witheve"; 45 | ``` 46 | 47 | ## Learning Eve 48 | 49 | You can learn about Eve with the following resources: 50 | 51 | - [Read the Quick Start Tutorial](http://play.witheve.com/) (use Chrome for best results) 52 | - [Syntax Quick Reference](https://witheve.github.io/assets/docs/SyntaxReference.pdf) 53 | - [Language Handbook (draft)](http://docs.witheve.com) 54 | 55 | Also, 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. 56 | 57 | ## Get Involved 58 | 59 | ### Join the Community 60 | 61 | The Eve community is small but constantly growing, and everyone is welcome! 62 | 63 | - Join or start a discussion on our [mailing list](https://groups.google.com/forum/#!forum/eve-talk). 64 | - Impact the future of Eve by getting involved with our [Request for Comments](https://github.com/witheve/rfcs) process. 65 | - Read our [development blog](http://incidentalcomplexity.com/). 66 | - Follow us on [Twitter](https://twitter.com/with_eve). 67 | 68 | ### How to Contribute 69 | 70 | The 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. 71 | 72 | ### How to File an Issue 73 | 74 | Please 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: 75 | 76 | 1. The steps needed to reproduce the bug 77 | 2. Your operating system and browser. 78 | 3. If applicable, the `.*eve` file that causes the bug. 79 | 80 | ## License 81 | 82 | Eve is licensed under the Apache 2.0 license, see [LICENSE](https://github.com/witheve/eve/blob/master/LICENSE) for details. 83 | 84 | ## Disclaimer 85 | 86 | Eve 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). 87 | 88 | As 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. 89 | -------------------------------------------------------------------------------- /assets/css/app-preview.css: -------------------------------------------------------------------------------- 1 | /* 2 | * app preview iframe wrapper stylesheets 3 | */ 4 | 5 | * { box-sizing:border-box; } 6 | body { background: rgb(47,47,49); color: #d8d8d8; font-family: Avenir, "Helvetica neue", sans-serif; display: flex; flex-direction: row; margin:0; width: 100%; } 7 | html, body, __root { height: 100%; } 8 | body { background: #f5f5f5; color: #555; } 9 | body { justify-content: stretch; } 10 | .__root { display: flex; flex-direction: column; align-self: stretch; } 11 | .application-root { overflow: auto; } 12 | .__root.application-root { order: 2; flex: 1 1 auto; color: #555; } 13 | .application-container { display: flex; flex-direction: column; flex: 1; } 14 | .program { position: relative; flex: 1; flex-direction: column; align-self: stretch; padding:20px; overflow: auto; display:flex; } -------------------------------------------------------------------------------- /assets/css/base.css: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | * UI Components * 3 | \******************************************************************************/ 4 | 5 | row { display: flex; flex-direction: row; } 6 | column {display: flex;flex-direction: column;align-items: stretch;} 7 | 8 | spacer { display: flex; flex: 1; } 9 | 10 | text { display: inline-block; white-space: pre-wrap; } 11 | 12 | button { 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; } 13 | 14 | button:before { align-self: center; } 15 | 16 | button:not(:empty):before { margin-right: 10px; } 17 | 18 | button { background: #f0f0f0; border-radius: 4px; border: 1px solid #e5e5e5; } 19 | button:hover { background: #e9e9e9; } 20 | button:active { background: #e0e0e0; } 21 | 22 | button.inset { border-radius: 4px; background: #EEE; border: 1px solid rgba(0, 0, 0, 0.1); } 23 | button.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); } 24 | button.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); } 25 | 26 | button.flat { background: transparent; border: none; border-radius: 0; } 27 | button.flat:not(.disabled):hover { background: rgba(0, 0, 0, 0.05); } 28 | button.flat:not(.disabled):active { background: rgba(0, 0, 0, 0.1); } 29 | 30 | .dark button.flat:not(.disabled):hover { background: rgba(255, 255, 255, 0.1); } 31 | .dark button.flat:not(.disabled):active { background: rgba(255, 255, 255, 0.2); } 32 | 33 | .ui-field-table {border-collapse: collapse;} 34 | .ui-field-table td {padding: 0;margin: 0;vertical-align: top; border: 1px solid #ccc;} 35 | .ui-field-table .ui-field-table-attribute {} 36 | .ui-field-table .ui-field-table-value-set {display: flex;flex-direction: column;} 37 | .ui-field-table .ui-field-table-cell { padding: 0 10; margin: 0; } 38 | .ui-field-table .ui-field-table-cell + .ui-field-table-cell { border-top: 1px solid #ccc; } 39 | 40 | .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;} 41 | .ui-field-table input::-webkit-input-placeholder { font-weight: 300; } 42 | .ui-field-table input.ui-field-table-attribute {padding-right: 0;} 43 | 44 | .ui-autocomplete { position: relative; flex: 0 0 auto; width: 200px; } 45 | .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); } 46 | .ui-autocomplete[open="true"] .ui-autocomplete-matches { border-top-width: 1px; } 47 | 48 | .ui-autocomplete-match { padding: 0 10px; height: 0; overflow: hidden; } 49 | .ui-autocomplete[open="true"] .ui-autocomplete-match { padding: 0 10px; height: 100%; overflow: hidden; } 50 | .ui-autocomplete-match:hover { background: #f9f9f9; } 51 | .ui-autocomplete-match:active { background: #f0f0f0; } 52 | 53 | /******************************************************************************\ 54 | * Shape * 55 | \******************************************************************************/ 56 | .shape-hexagon {position: relative;} 57 | .shape-hexagon > canvas {position: absolute;top: 0;left: 0;width: 100%;height: 100%;} 58 | .shape-hexagon > div { display: flex; flex-direction: column; justify-content: center; align-items: center; position: absolute; } 59 | 60 | /******************************************************************************\ 61 | * compiler 62 | \******************************************************************************/ 63 | 64 | .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; } 65 | .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;} 66 | .eve-compiler-error-sample { font-family:"Inconsolata", "Monaco", "Consolas", "Ubuntu Mono", monospace; margin-top:10px; background:#444; padding:10px; border-radius:3px; } 67 | .eve-compiler-error-message-line { font-size:20pt; color: #ff8c24; } 68 | .eve-compiler-error-message { color: #ff8c24; } 69 | .eve-compiler-error-content {padding: 5px; flex: 1;} 70 | 71 | /******************************************************************************\ 72 | * Notify 73 | \******************************************************************************/ 74 | @keyframes slide-down { 75 | 0% { padding: 0px 30px; max-height: 0; } 76 | 100% { padding: 5px 30px; max-height: 80px; } 77 | } 78 | 79 | .notify-wrapper { position: relative; max-height: 165px; overflow-y: hidden; } 80 | .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); } 81 | .notify-scroller { overflow-y: auto; max-height: 165px; overflow-y: auto; } 82 | .notify-root { border-bottom: 1px solid rgba(0, 0, 0, 0.1); } 83 | 84 | .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; } 85 | .notify-notice { align-items: center; } 86 | .notify-notice-wrapper + .notify-notice-wrapper { border-top: 1px solid rgba(0, 0, 0, 0.1); } 87 | .notify-notice-wrapper:before { margin-left: -30px; width: 30px; text-align: center; font-family: "Ionicons"; line-height: 1; } 88 | .notify-notice-wrapper[type="warning"] { background: rgba(255, 255, 0, 0.1); } 89 | .notify-notice-wrapper[type="warning"]:before {content: "\f100"; color: rgb(128, 128, 0); } 90 | .notify-notice-wrapper[type="error"] { background: rgba(255, 96, 96, 0.2); } 91 | .notify-notice-wrapper[type="error"]:before {content: "\f101"; color: rgb(255, 0, 0); } 92 | .notify-notice-wrapper .notify-dismiss { margin-right: -40px; width: 30px; margin-left: 10px; } 93 | -------------------------------------------------------------------------------- /assets/css/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | } 9 | 10 | /* PADDING */ 11 | 12 | .CodeMirror-lines { 13 | padding: 4px 0; /* Vertical padding around content */ 14 | } 15 | .CodeMirror pre { 16 | padding: 0 4px; /* Horizontal padding of content */ 17 | } 18 | 19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 20 | background-color: white; /* The little square between H and V scrollbars */ 21 | } 22 | 23 | /* GUTTER */ 24 | 25 | .CodeMirror-gutters { 26 | border-right: 1px solid #ddd; 27 | background-color: #f7f7f7; 28 | white-space: nowrap; 29 | } 30 | .CodeMirror-linenumbers {} 31 | .CodeMirror-linenumber { 32 | padding: 0 3px 0 5px; 33 | min-width: 20px; 34 | text-align: right; 35 | color: #999; 36 | white-space: nowrap; 37 | } 38 | 39 | .CodeMirror-guttermarker { color: black; } 40 | .CodeMirror-guttermarker-subtle { color: #999; } 41 | 42 | /* CURSOR */ 43 | 44 | .CodeMirror-cursor { 45 | border-left: 1px solid black; 46 | border-right: none; 47 | width: 0; 48 | } 49 | /* Shown when moving in bi-directional text */ 50 | .CodeMirror div.CodeMirror-secondarycursor { 51 | border-left: 1px solid silver; 52 | } 53 | .cm-fat-cursor .CodeMirror-cursor { 54 | width: auto; 55 | border: 0 !important; 56 | background: #7e7; 57 | } 58 | .cm-fat-cursor div.CodeMirror-cursors { 59 | z-index: 1; 60 | } 61 | 62 | .cm-animate-fat-cursor { 63 | width: auto; 64 | border: 0; 65 | -webkit-animation: blink 1.06s steps(1) infinite; 66 | -moz-animation: blink 1.06s steps(1) infinite; 67 | animation: blink 1.06s steps(1) infinite; 68 | background-color: #7e7; 69 | } 70 | @-moz-keyframes blink { 71 | 0% {} 72 | 50% { background-color: transparent; } 73 | 100% {} 74 | } 75 | @-webkit-keyframes blink { 76 | 0% {} 77 | 50% { background-color: transparent; } 78 | 100% {} 79 | } 80 | @keyframes blink { 81 | 0% {} 82 | 50% { background-color: transparent; } 83 | 100% {} 84 | } 85 | 86 | /* Can style cursor different in overwrite (non-insert) mode */ 87 | .CodeMirror-overwrite .CodeMirror-cursor {} 88 | 89 | .cm-tab { display: inline-block; text-decoration: inherit; } 90 | 91 | .CodeMirror-rulers { 92 | position: absolute; 93 | left: 0; right: 0; top: -50px; bottom: -20px; 94 | overflow: hidden; 95 | } 96 | .CodeMirror-ruler { 97 | border-left: 1px solid #ccc; 98 | top: 0; bottom: 0; 99 | position: absolute; 100 | } 101 | 102 | /* DEFAULT THEME */ 103 | 104 | .cm-s-default .cm-header {color: blue;} 105 | .cm-s-default .cm-quote {color: #090;} 106 | .cm-negative {color: #d44;} 107 | .cm-positive {color: #292;} 108 | .cm-header, .cm-strong {font-weight: bold;} 109 | .cm-em {font-style: italic;} 110 | .cm-link {text-decoration: underline;} 111 | .cm-strikethrough {text-decoration: line-through;} 112 | 113 | .cm-s-default .cm-keyword {color: #708;} 114 | .cm-s-default .cm-atom {color: #219;} 115 | .cm-s-default .cm-number {color: #164;} 116 | .cm-s-default .cm-def {color: #00f;} 117 | .cm-s-default .cm-variable, 118 | .cm-s-default .cm-punctuation, 119 | .cm-s-default .cm-property, 120 | .cm-s-default .cm-operator {} 121 | .cm-s-default .cm-variable-2 {color: #05a;} 122 | .cm-s-default .cm-variable-3 {color: #085;} 123 | .cm-s-default .cm-comment {color: #a50;} 124 | .cm-s-default .cm-string {color: #a11;} 125 | .cm-s-default .cm-string-2 {color: #f50;} 126 | .cm-s-default .cm-meta {color: #555;} 127 | .cm-s-default .cm-qualifier {color: #555;} 128 | .cm-s-default .cm-builtin {color: #30a;} 129 | .cm-s-default .cm-bracket {color: #997;} 130 | .cm-s-default .cm-tag {color: #170;} 131 | .cm-s-default .cm-attribute {color: #00c;} 132 | .cm-s-default .cm-hr {color: #999;} 133 | .cm-s-default .cm-link {color: #00c;} 134 | 135 | .cm-s-default .cm-error {color: #f00;} 136 | .cm-invalidchar {color: #f00;} 137 | 138 | .CodeMirror-composing { border-bottom: 2px solid; } 139 | 140 | /* Default styles for common addons */ 141 | 142 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 143 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 144 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 145 | .CodeMirror-activeline-background {background: #e8f2ff;} 146 | 147 | /* STOP */ 148 | 149 | /* The rest of this file contains styles related to the mechanics of 150 | the editor. You probably shouldn't touch them. */ 151 | 152 | .CodeMirror { 153 | position: relative; 154 | overflow: hidden; 155 | background: white; 156 | } 157 | 158 | .CodeMirror-scroll { 159 | overflow: scroll !important; /* Things will break if this is overridden */ 160 | /* 30px is the magic margin used to hide the element's real scrollbars */ 161 | /* See overflow: hidden in .CodeMirror */ 162 | margin-bottom: -30px; margin-right: -30px; 163 | padding-bottom: 30px; 164 | height: 100%; 165 | outline: none; /* Prevent dragging from highlighting the element */ 166 | position: relative; 167 | } 168 | .CodeMirror-sizer { 169 | position: relative; 170 | border-right: 30px solid transparent; 171 | } 172 | 173 | /* The fake, visible scrollbars. Used to force redraw during scrolling 174 | before actual scrolling happens, thus preventing shaking and 175 | flickering artifacts. */ 176 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 177 | position: absolute; 178 | z-index: 6; 179 | display: none; 180 | } 181 | .CodeMirror-vscrollbar { 182 | right: 0; top: 0; 183 | overflow-x: hidden; 184 | overflow-y: scroll; 185 | } 186 | .CodeMirror-hscrollbar { 187 | bottom: 0; left: 0; 188 | overflow-y: hidden; 189 | overflow-x: scroll; 190 | } 191 | .CodeMirror-scrollbar-filler { 192 | right: 0; bottom: 0; 193 | } 194 | .CodeMirror-gutter-filler { 195 | left: 0; bottom: 0; 196 | } 197 | 198 | .CodeMirror-gutters { 199 | position: absolute; left: 0; top: 0; 200 | min-height: 100%; 201 | z-index: 3; 202 | } 203 | .CodeMirror-gutter { 204 | white-space: normal; 205 | height: 100%; 206 | display: inline-block; 207 | vertical-align: top; 208 | margin-bottom: -30px; 209 | /* Hack to make IE7 behave */ 210 | *zoom:1; 211 | *display:inline; 212 | } 213 | .CodeMirror-gutter-wrapper { 214 | position: absolute; 215 | z-index: 4; 216 | background: none !important; 217 | border: none !important; 218 | } 219 | .CodeMirror-gutter-background { 220 | position: absolute; 221 | top: 0; bottom: 0; 222 | z-index: 4; 223 | } 224 | .CodeMirror-gutter-elt { 225 | position: absolute; 226 | cursor: default; 227 | z-index: 4; 228 | } 229 | .CodeMirror-gutter-wrapper { 230 | -webkit-user-select: none; 231 | -moz-user-select: none; 232 | user-select: none; 233 | } 234 | 235 | .CodeMirror-lines { 236 | cursor: text; 237 | min-height: 1px; /* prevents collapsing before first draw */ 238 | } 239 | .CodeMirror pre { 240 | /* Reset some styles that the rest of the page might have set */ 241 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 242 | border-width: 0; 243 | background: transparent; 244 | font-family: inherit; 245 | font-size: inherit; 246 | margin: 0; 247 | white-space: pre; 248 | word-wrap: normal; 249 | line-height: inherit; 250 | color: inherit; 251 | z-index: 2; 252 | position: relative; 253 | overflow: visible; 254 | -webkit-tap-highlight-color: transparent; 255 | -webkit-font-variant-ligatures: none; 256 | font-variant-ligatures: none; 257 | } 258 | .CodeMirror-wrap pre { 259 | word-wrap: break-word; 260 | white-space: pre-wrap; 261 | word-break: normal; 262 | } 263 | 264 | .CodeMirror-linebackground { 265 | position: absolute; 266 | left: 0; right: 0; top: 0; bottom: 0; 267 | z-index: 0; 268 | } 269 | 270 | .CodeMirror-linewidget { 271 | position: relative; 272 | z-index: 2; 273 | overflow: auto; 274 | } 275 | 276 | .CodeMirror-widget {} 277 | 278 | .CodeMirror-code { 279 | outline: none; 280 | } 281 | 282 | /* Force content-box sizing for the elements where we expect it */ 283 | .CodeMirror-scroll, 284 | .CodeMirror-sizer, 285 | .CodeMirror-gutter, 286 | .CodeMirror-gutters, 287 | .CodeMirror-linenumber { 288 | -moz-box-sizing: content-box; 289 | box-sizing: content-box; 290 | } 291 | 292 | .CodeMirror-measure { 293 | position: absolute; 294 | width: 100%; 295 | height: 0; 296 | overflow: hidden; 297 | visibility: hidden; 298 | } 299 | 300 | .CodeMirror-cursor { 301 | position: absolute; 302 | pointer-events: none; 303 | } 304 | .CodeMirror-measure pre { position: static; } 305 | 306 | div.CodeMirror-cursors { 307 | visibility: hidden; 308 | position: relative; 309 | z-index: 3; 310 | } 311 | div.CodeMirror-dragcursors { 312 | visibility: visible; 313 | } 314 | 315 | .CodeMirror-focused div.CodeMirror-cursors { 316 | visibility: visible; 317 | } 318 | 319 | .CodeMirror-selected { background: #d9d9d9; } 320 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 321 | .CodeMirror-crosshair { cursor: crosshair; } 322 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 323 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 324 | 325 | .cm-searching { 326 | background: #ffa; 327 | background: rgba(255, 255, 0, .4); 328 | } 329 | 330 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 331 | .CodeMirror span { *vertical-align: text-bottom; } 332 | 333 | /* Used to force a border model for a node */ 334 | .cm-force-border { padding-right: .1px; } 335 | 336 | @media print { 337 | /* Hide the cursor when printing */ 338 | .CodeMirror div.CodeMirror-cursors { 339 | visibility: hidden; 340 | } 341 | } 342 | 343 | /* See issue #2901 */ 344 | .cm-tab-wrap-hack:after { content: ''; } 345 | 346 | /* Help users use markselection to safely style text background */ 347 | span.CodeMirror-selectedtext { background: none; } 348 | -------------------------------------------------------------------------------- /assets/css/editor.css: -------------------------------------------------------------------------------- 1 | .editor-view {padding: 40px; align-items: stretch;height: 100%;background: white;} 2 | 3 | /****************************************************************************\ 4 | * Editor Nav 5 | \****************************************************************************/ 6 | 7 | .editor-view .editor-nav { margin-right: 20px; } 8 | .editor-view .editor-nav .editor-nav-tag { padding: 10px; } 9 | .editor-view .editor-nav .editor-nav-block { padding: 5px; padding-left: 20px; padding-right: 0; } 10 | 11 | 12 | 13 | /****************************************************************************\ 14 | * Editor Main 15 | \****************************************************************************/ 16 | 17 | .editor-view .editor-main { flex: 1; } 18 | .editor-view .editor-block-header { margin-bottom: 40px; } 19 | .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;} 20 | .editor-view .editor-block-title {font-size: 1.25em; margin-bottom: 0.25em;font-weight: 500; margin-bottom: 0.5em; } 21 | 22 | .editor-view .editor-block-storyboard { flex: 1; align-self: flex-start; } 23 | 24 | .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; } 25 | .editor-view .editor-block-frame.active, 26 | .editor-view .editor-block-frame:not([open="true"]).active:hover{ background: #f0f0f0; } 27 | 28 | .editor-view .editor-block-frame:not([open="true"]):hover { background: #fcfcfc; } 29 | .editor-view .editor-block-frame:not([open="true"]):active { background: #f0f0f0; } 30 | 31 | 32 | .editor-view .editor-new-frame { border-color: #ccc; color: #ccc; justify-content: space-around; } 33 | .editor-view .editor-new-frame:empty:after { display: flex; content: "+"; font-size: 3em; font-weight: 200;} 34 | 35 | .editor-view .editor-new-frame-type { align-self: stretch; color: #606060; } 36 | 37 | 38 | .editor-view .editor-block-content { flex: 1; } 39 | 40 | /****************************************************************************\ 41 | * Editor Node Tree 42 | \****************************************************************************/ 43 | 44 | .editor-node-tree { align-self: flex-start; padding-left: 44px; padding-top: 16px; } 45 | .editor-node-tree input {font-size: 1em; font-weight: inherit;font-family: inherit; border: none; color: inherit;} 46 | .editor-node-tree-node { position: relative; padding: 5px 0; margin-top: -16px; min-height: 60px; } 47 | .editor-node-tree > .editor-node-tree-node { min-height: 65px; } 48 | 49 | .editor-node-tree-node-new {margin-top: -16px; align-items: flex-start; } 50 | .editor-node-tree-node-new > .ui-autocomplete {margin-top: 10px;width: 100px;z-index: 2;} 51 | .editor-node-tree-node-new > .button {margin-top: 7px;} 52 | .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;} 53 | 54 | .editor-node-tree-node-pattern { margin-top: 11px; margin-left: 15px; } 55 | .editor-node-tree-node-pattern .editor-node-tree-node-pattern-name { margin-left: -15px; margin-bottom: 8px;} 56 | .editor-node-tree-node-pattern-field {position: relative; min-height: 22px; z-index: 1; } 57 | .editor-node-tree-node-pattern-field > text:not(:last-child):after {content: ":"} 58 | .editor-node-tree-node-pattern-value {padding:0;padding-left: 5px; max-width: 100px;} 59 | 60 | .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; } 61 | .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; } 62 | .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; } 63 | 64 | .editor-node-tree-node-controls { position: absolute; top: 38px; left: -21px; padding: 4px 0;} 65 | .editor-node-tree-node-controls > div.button {position:relative; margin:0; margin-right: -4px; ;z-index: 1;font-size:0.9em;} 66 | 67 | .editor-node-tree-node-controls:before {content: " ";display: block;position: absolute;top: 4px;bottom: 18px;right: -4px;border-left: 1px solid #ccc;} 68 | .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;} 69 | .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; } 70 | 71 | .editor-node-tree-node-field-new-attribute {width: 100px;z-index: 2;} 72 | 73 | /* Crazy tree positioning magic */ 74 | 75 | .editor-node-tree-node-hex { position: absolute; left: -44px; cursor: default; -webkit-user-select: none; } 76 | .editor-node-tree-node-pattern .editor-node-tree-node-hex { position: absolute; left: -59px; } 77 | .editor-node-tree-node-pattern .editor-node-tree-node-pattern .editor-node-tree-node-hex { position: absolute; left: -74px; } 78 | .editor-node-tree-node-pattern .editor-node-tree-node-pattern .editor-node-tree-node-pattern .editor-node-tree-node-hex { position: absolute; left: -89px; } 79 | .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; } 80 | /* okay, you win. */ 81 | 82 | .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;} 83 | 84 | .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;} 85 | .editor-node-tree > .editor-node-tree-node > .editor-node-tree-node-pattern > row > .editor-node-tree-node-pattern-name:before {display: none; } 86 | 87 | .editor-node-tree-node-pattern:before {display: block;content: " ";position: absolute; left: 1px; top: 38px; bottom: 6px;width: 0;border-left: 1px solid #ccc;} 88 | 89 | .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;} 90 | 91 | .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;} 92 | 93 | .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;} 94 | 95 | .editor-node-tree-subnodes:nth-child(2) { margin-top: 20px;} 96 | .editor-node-tree-subnodes:nth-child(3) { margin-top: 5px;} 97 | 98 | /* Animations */ 99 | .editor-node-tree-node-hex > canvas { transition: transform 0.2s ease-out; } 100 | .editor-node-tree-node[open="true"] > .editor-node-tree-node-hex > canvas { transform: rotateZ(60deg); } 101 | 102 | /****************************************************************************\ 103 | * Editor Molecule List 104 | \****************************************************************************/ 105 | .editor-molecule-list { position: relative; flex: 1; } 106 | .editor-molecule-list-molecule { position: relative; display: flex; flex: 0 0 auto; padding-bottom: 10px;} 107 | .editor-molecule-list-molecule-grid { position: relative;} 108 | .editor-molecule-list-molecule-cell { cursor: default; -webkit-user-select: none; } 109 | 110 | .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; } 111 | 112 | .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; } 113 | .editor-infrobox-field-new:before { width: 1.3em; line-height: 1.3em; text-align: center; background: white; font-weight: 100; } 114 | 115 | .editor-infobox-field-attribute { margin-left: -8px; width: auto; } 116 | .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;} 117 | 118 | .editor-infobox-node { margin-bottom: 10px; } 119 | 120 | .editor-infobox-node-header { margin: 0 9px; margin-bottom: 0.25rem; } 121 | .editor-infobox-node-name { font-size: 1.25em; font-weight: 500; color: #606060; } 122 | 123 | .editor-infobox-atom { width: 100%; } 124 | .editor-infobox .ui-field-table td { border: none; } 125 | .editor-infobox .ui-field-table td:first-child { width: 75px; } 126 | 127 | .editor-infobox .editor-paginator { padding-left: 0.5em; color: #909090; margin-top: 4px; } 128 | .editor-infobox .editor-paginator > div { margin-top: -2px; } 129 | 130 | /****************************************************************************\ 131 | * Editor Block Canvas 132 | \****************************************************************************/ 133 | .editor-view .editor-query-tree { width: 180px; margin-right: 10px; } 134 | 135 | 136 | /****************************************************************************\ 137 | * Data Editor 138 | \****************************************************************************/ 139 | .editor-block-data-canvas {position: relative;flex: 1;align-items: flex-start;} 140 | .editor-block-data-canvas .editor-data-toolbar { flex: 0 0 auto; background: #eee; border-radius: 3px; } 141 | .editor-block-data-canvas .editor-data-toolbar > .ui-button { display: flex; flex-direction: column; align-items: center; padding: 10px; color: #999; } 142 | .editor-block-data-canvas .editor-data-toolbar > .ui-button:before { font-size: 1.5em; color: #404040;} 143 | .editor-block-data-canvas .editor-data-toolbar > .ui-button.editor-active { color: #66f; } 144 | .editor-block-data-canvas .editor-data-toolbar > .ui-button.editor-active:before { color: #44f; } 145 | .editor-block-data-canvas .editor-data-toolbar > .editor-data-toolbar-select:before { transform: rotateZ(-30deg); } 146 | 147 | .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);} 148 | .editor-block-data-canvas .editor-data-node-infobox { padding: 10px; } 149 | .editor-block-data-canvas .editor-data-node-infobox + .editor-node-infobox { border-top: 1px solid gray; } 150 | .editor-block-data-canvas .editor-data-atom-infobox + .editor-atom-infobox { margin-top: 20px; } 151 | 152 | .editor-block-data-canvas .editor-data-node-header { font-weight: 500; text-align: center; } 153 | .editor-block-data-canvas .editor-data-field-row { min-width: 200px; min-height: 20px; margin-bottom: -1px; border: 1px solid #ccc; } 154 | .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;} 155 | .editor-block-data-canvas .editor-data-field-value-set {flex: 1 1 auto;/* max-width: 140px; */} 156 | .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;} 157 | 158 | .editor-block-data-canvas .editor-data-molecule-infobox input { font-size: 1em; font-weight: inherit;font-family: inherit; color: inherit;} 159 | .editor-block-data-canvas .editor-data-molecule-infobox input::-webkit-input-placeholder { font-weight: 300; } 160 | 161 | .editor-block-data-canvas .editor-data-field-new-row { background: #ddffdd; border-color: #44cc44; } 162 | 163 | .editor-block-data-canvas .editor-data-field-row.editor-data-erasing { background: #ffdddd; border-color: #cc4444; } 164 | .editor-block-data-canvas .editor-data-field-value.editor-data-erasing { background: #ffdddd; border-color: #cc4444; } 165 | 166 | 167 | 168 | /****************************************************************************\ 169 | * Shapes 170 | \****************************************************************************/ 171 | 172 | /* .shape-hexagon { position: relative; } */ 173 | /* .shape-hexagon .shape-hexagon-body { justify-content: center; align-items: center; } */ 174 | /* .shape-hexagon .shape-hexagon-cap { } */ 175 | 176 | /* .shape-hexagon .shape-hexagon-inner { } */ 177 | 178 | /* .shape-hex-grid {position: relative;left: 70px;} */ 179 | 180 | 181 | 182 | 183 | 184 | .hex-grid > .shape-hexagon-body, 185 | .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; } 186 | 187 | .hex-grid:hover > .shape-hexagon-body, 188 | .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; } 189 | 190 | 191 | .hex-grid:hover > .shape-hexagon-body { background: #ccc !important; } 192 | .hex-grid:hover > .shape-hexagon-cap.first { border-bottom-color: #ccc !important; } 193 | .hex-grid:hover > .shape-hexagon-cap.last { border-top-color: #ccc !important; } 194 | 195 | text { flex: 0 0 auto; } 196 | column { flex: 0 0 auto; } 197 | row { flex: 0 0 auto; } 198 | -------------------------------------------------------------------------------- /assets/css/examples/crm.css: -------------------------------------------------------------------------------- 1 | h3, h4 { font-weight:normal; font-size:16px; } 2 | 3 | .container { 4 | box-shadow: 0 3px 8px #bbb; 5 | width: 400px; 6 | height: 711px; 7 | background: white; 8 | font-family: sans-serif; 9 | font-size: 14px; 10 | display: flex; 11 | flex-direction: column; 12 | } 13 | 14 | .scroll { 15 | flex: 1; 16 | overflow: auto; 17 | display:flex; 18 | flex-direction:column; 19 | } 20 | 21 | .avatar { 22 | width: 128; 23 | height: 128; 24 | } 25 | 26 | .banner { 27 | background: linear-gradient(-90deg, rgb(74,64,136), rgb(0,158,224)); 28 | width: 100%; 29 | height: 150px; 30 | flex:none; 31 | } 32 | 33 | .avatar-container { 34 | border-radius: 6px; 35 | width: 128px; 36 | height: 128px; 37 | overflow: hidden; 38 | border: 5px solid white; 39 | margin: auto; 40 | margin-top: -60px; 41 | } 42 | 43 | .name2 { 44 | width: 100%; 45 | text-align: center; 46 | color: rgb(11,11,37); 47 | font-family: sans-serif; 48 | font-size: 20px; 49 | font-weight: bold; 50 | margin-top: 10px; 51 | } 52 | 53 | .info { 54 | width: 100%; 55 | text-align: center; 56 | color: rgb(147,167,178); 57 | color: #999; 58 | font-family: sans-serif; 59 | font-size: 14px; 60 | margin-top: 5px; 61 | } 62 | 63 | .navigation { 64 | display: flex; 65 | flex:none; 66 | border-top: 1px solid #eee; 67 | padding:5px 0; 68 | } 69 | 70 | .nav-button { 71 | position:relative; 72 | flex-grow: 1; 73 | text-align: center; 74 | height: 60px; 75 | cursor: pointer; 76 | color: #999; 77 | } 78 | 79 | .icon { 80 | font-size: 30px; 81 | padding: 3px; 82 | height: 35px; 83 | margin-top:2px; 84 | } 85 | 86 | .middle { 87 | border-right: 1px solid #eee; 88 | } 89 | 90 | .bubble { 91 | position: absolute; 92 | top:8px; 93 | left: 56px; 94 | color: white; 95 | font-weight: bold; 96 | } 97 | 98 | .content { 99 | margin-top: 10px; 100 | padding: 0px; 101 | flex-grow: 1; 102 | display: flex; 103 | flex-direction: column; 104 | } 105 | 106 | .convo { 107 | height: 280px; 108 | margin-bottom: 10px; 109 | padding: 10px 20px; 110 | overflow: auto; 111 | } 112 | 113 | .msg-avatar { 114 | height: 40px; 115 | width: 40px; 116 | margin-right: 15px; 117 | float: left; 118 | border-radius: 4px; 119 | border: 1px solid #ddd; 120 | } 121 | 122 | .msg-name { 123 | font-weight: bold; 124 | color: #555; 125 | margin-top:2px; 126 | margin-bottom:2px; 127 | } 128 | 129 | .msg { 130 | margin-bottom: 10px; 131 | min-height: 40px; 132 | flex:none; 133 | } 134 | 135 | .thread:first-child { margin-top:30px; border-top:1px solid #eee; } 136 | 137 | .thread { 138 | padding: 15px 20px; 139 | display: flex; 140 | flex-direction: row; 141 | border-bottom: 1px solid #eee; 142 | align-items: center; 143 | color: #999; 144 | } 145 | 146 | .thread img {} 147 | 148 | .thread-box { 149 | flex-grow: 1; 150 | } 151 | 152 | .archive { 153 | cursor: pointer; 154 | color: #999; 155 | font-size: 25px; 156 | } 157 | 158 | .contact-avatar { 159 | width: 80px; 160 | height: 80px; 161 | float: left; 162 | margin-right: 10px; 163 | border-radius: 4px; 164 | } 165 | 166 | .contact-name { 167 | font-weight: bold; 168 | font-size: 18px; 169 | margin-top:3px; 170 | margin-bottom: 5px; 171 | color: #666; 172 | } 173 | 174 | .contact { 175 | flex: none; 176 | padding: 15px 20px; 177 | border-bottom: 1px solid #eee; 178 | color: #999; 179 | } 180 | 181 | .contact:first-child { margin-top: 20px; border-top:1px solid #eee; } 182 | 183 | .recent-avatar { 184 | width: 40px; 185 | height: 40px; 186 | margin-right: 10px; 187 | } 188 | 189 | .msg-input { 190 | width: 360px; 191 | margin: 0 20px; 192 | border: 1px solid #ddd; 193 | border-radius: 2px; 194 | padding: 5px; 195 | font-size: 12pt; 196 | } 197 | 198 | .msg-time { 199 | color: rgb(175,175,175); 200 | margin-left: 10px; 201 | font-size: 12px; 202 | } 203 | 204 | .about { padding: 0 40px; padding-top: 10px; } 205 | 206 | .about-line { 207 | display: flex; 208 | flex-direction:column; 209 | margin-top:20px; 210 | } 211 | 212 | .about-label, .about h3 { 213 | color: rgb(152, 171, 181); 214 | color: #999; 215 | margin-bottom:6px; 216 | font-size: 14px; 217 | } 218 | 219 | .about h3 { margin-top:20px; margin-bottom:10px; } 220 | .about img { border-radius:4px; border:1px solid #ddd; } 221 | 222 | .plus { 223 | background-color: rgb(111, 165, 81); 224 | color: white; 225 | width: 16px; 226 | height: 16px; 227 | float: left; 228 | border-radius: 50%; 229 | text-align: center; 230 | font-size: 14px; 231 | font-weight: bold; 232 | margin-right: 5px; 233 | cursor: pointer; 234 | } 235 | 236 | a { 237 | color: rgb(0,158,224); 238 | line-height: 20px; 239 | text-decoration: none; 240 | border-bottom: 1px dashed rgb(0,158,224); 241 | } 242 | 243 | a:hover { 244 | color: rgb(91,89,164); 245 | } 246 | 247 | .more { 248 | padding: 0 40px; 249 | } 250 | 251 | .more h2 { font-weight: normal; font-size: 16pt; margin-top: 30px; margin-bottom: 10px; } 252 | 253 | .more ul { margin: 5px 0; } 254 | .more li { margin-bottom:8px; } 255 | 256 | .button { 257 | background-color: rgb(111, 165, 81); 258 | color: white; 259 | width: 80%; 260 | height: 25px; 261 | border-radius: 10px; 262 | text-align: center; 263 | font-size: 14px; 264 | font-weight: bold; 265 | margin-right: auto; 266 | margin-left: auto; 267 | margin-top: 10px; 268 | padding-top: 4px; 269 | cursor: pointer; 270 | } -------------------------------------------------------------------------------- /assets/css/examples/todomvc.css: -------------------------------------------------------------------------------- 1 | .program { 2 | margin: 0 auto; 3 | padding: 0; 4 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | line-height: 1.4em; 6 | background: #f5f5f5; 7 | color: #4d4d4d; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-font-smoothing: antialiased; 10 | font-smoothing: antialiased; 11 | font-weight: 300; 12 | } 13 | 14 | .todoapp { 15 | flex: 0 0 auto; 16 | min-width: 450px; 17 | max-width: 650px; 18 | height: auto; 19 | margin: 130px auto 40px auto; 20 | 21 | background: #fff; 22 | position: relative; 23 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 24 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 25 | } 26 | 27 | .todoapp button { 28 | margin: 0; 29 | padding: 0; 30 | border: 0; 31 | background: none; 32 | font-size: 100%; 33 | vertical-align: baseline; 34 | font-family: inherit; 35 | font-weight: inherit; 36 | color: inherit; 37 | -webkit-appearance: none; 38 | appearance: none; 39 | -webkit-font-smoothing: antialiased; 40 | -moz-font-smoothing: antialiased; 41 | font-smoothing: antialiased; 42 | } 43 | 44 | .todoapp button, 45 | .todoapp input[type="checkbox"] { 46 | outline: none; 47 | } 48 | 49 | .todoapp input::-webkit-input-placeholder { 50 | font-style: italic; 51 | font-weight: 300; 52 | color: #e6e6e6; 53 | } 54 | 55 | .todoapp input::-moz-placeholder { 56 | font-style: italic; 57 | font-weight: 300; 58 | color: #e6e6e6; 59 | } 60 | 61 | .todoapp input::input-placeholder { 62 | font-style: italic; 63 | font-weight: 300; 64 | color: #e6e6e6; 65 | } 66 | 67 | .todoapp h1 { 68 | position: absolute; 69 | top: -155px; 70 | width: 100%; 71 | font-size: 100px; 72 | font-weight: 100; 73 | text-align: center; 74 | color: rgba(175, 47, 47, 0.15); 75 | -webkit-text-rendering: optimizeLegibility; 76 | -moz-text-rendering: optimizeLegibility; 77 | text-rendering: optimizeLegibility; 78 | } 79 | 80 | .todoapp .hidden { 81 | display: none !important; 82 | } 83 | 84 | .todoapp .new-todo, 85 | .todoapp .edit { 86 | position: relative; 87 | margin: 0; 88 | width: 100%; 89 | font-size: 24px; 90 | font-family: inherit; 91 | font-weight: inherit; 92 | line-height: 1.4em; 93 | border: 0; 94 | outline: none; 95 | color: inherit; 96 | padding: 6px; 97 | border: 1px solid #999; 98 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 99 | box-sizing: border-box; 100 | -webkit-font-smoothing: antialiased; 101 | -moz-font-smoothing: antialiased; 102 | font-smoothing: antialiased; 103 | } 104 | 105 | .todoapp .new-todo { 106 | padding: 16px 16px 16px 60px; 107 | border: none; 108 | background: rgba(0, 0, 0, 0.003); 109 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 110 | } 111 | 112 | .todoapp .toggle-all { 113 | position: absolute; 114 | top: 12px; 115 | left: 14px; 116 | width: 60px; 117 | height: 34px; 118 | text-align: center; 119 | border: none; /* Mobile Safari */ 120 | } 121 | 122 | .todoapp .main { 123 | position: relative; 124 | z-index: 2; 125 | border-top: 1px solid #e6e6e6; 126 | } 127 | 128 | .todoapp .todo-list { 129 | margin: 0; 130 | padding: 0; 131 | list-style: none; 132 | } 133 | 134 | .todoapp .todo-list li { 135 | position: relative; 136 | padding-left: 40px; 137 | font-size: 24px; 138 | border-bottom: 1px solid #ededed; 139 | } 140 | 141 | .todoapp .todo-list li:last-child { 142 | border-bottom: none; 143 | } 144 | 145 | .todoapp .todo-list li.editing { 146 | display: flex; 147 | flex-direction: row; 148 | border-bottom: none; 149 | padding: 0; 150 | height: 44px; 151 | } 152 | 153 | .todoapp .todo-list li.editing .edit { 154 | display: flex; 155 | flex: 1; 156 | padding: 13px 15px 12px 15px; 157 | margin: -1px 0 0 43px; 158 | } 159 | 160 | .todoapp .todo-list li .toggle { 161 | text-align: center; 162 | width: 40px; 163 | /* auto, since non-WebKit browsers doesn't support input styling */ 164 | height: auto; 165 | position: absolute; 166 | top: 0; 167 | bottom: 0; 168 | left: 0px; 169 | border: none; /* Mobile Safari */ 170 | -webkit-appearance: none; 171 | appearance: none; 172 | } 173 | 174 | .todoapp .todo-list li .toggle:after { 175 | content: url('data:image/svg+xml;utf8,'); 176 | } 177 | 178 | .todoapp .todo-list li .toggle:checked:after { 179 | content: url('data:image/svg+xml;utf8,'); 180 | } 181 | 182 | .todoapp .todo-list li label { 183 | white-space: pre-line; 184 | word-break: break-all; 185 | margin-left: 12px; 186 | padding: 8px 6px; 187 | display: block; 188 | line-height: 1.2; 189 | transition: color 0.4s; 190 | } 191 | 192 | .todoapp .todo-list li.completed label { 193 | color: #d9d9d9; 194 | text-decoration: line-through; 195 | } 196 | 197 | .todoapp .todo-list li .destroy { 198 | display: none; 199 | position: absolute; 200 | top: 0; 201 | right: 10px; 202 | bottom: 0; 203 | width: 40px; 204 | height: 40px; 205 | margin: auto 0; 206 | font-size: 30px; 207 | color: #cc9a9a; 208 | margin-bottom: 11px; 209 | transition: color 0.2s ease-out; 210 | } 211 | 212 | .todoapp .todo-list li .destroy:hover { 213 | color: #af5b5e; 214 | } 215 | 216 | .todoapp .todo-list li .destroy:after { 217 | position: relative; 218 | content: "×"; 219 | top: 8px; 220 | } 221 | 222 | .todoapp .todo-list li:hover .destroy { 223 | display: block; 224 | } 225 | 226 | .todoapp footer { 227 | color: #777; 228 | padding: 10px 15px; 229 | height: 41px; 230 | text-align: center; 231 | border-top: 1px solid #e6e6e6; 232 | } 233 | 234 | .todoapp footer:before { 235 | content: ''; 236 | position: absolute; 237 | right: 0; 238 | bottom: 0; 239 | left: 0; 240 | height: 50px; 241 | overflow: hidden; 242 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 243 | 0 8px 0 -3px #f6f6f6, 244 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 245 | 0 16px 0 -6px #f6f6f6, 246 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 247 | } 248 | 249 | .todoapp .todo-count { 250 | float: left; 251 | text-align: left; 252 | } 253 | 254 | .todoapp .todo-count strong { 255 | font-weight: 300; 256 | } 257 | 258 | .todoapp .clear-completed, 259 | html .todoapp .clear-completed:active { 260 | float: right; 261 | position: relative; 262 | line-height: 20px; 263 | text-decoration: none; 264 | cursor: pointer; 265 | position: relative; 266 | } 267 | 268 | .todoapp .clear-completed:hover { 269 | text-decoration: underline; 270 | } 271 | 272 | 273 | .todoapp .filters { 274 | margin: 0; 275 | padding: 0; 276 | list-style: none; 277 | position: absolute; 278 | right: 0; 279 | left: 0; 280 | } 281 | 282 | .todoapp .filters li { 283 | display: inline; 284 | } 285 | 286 | .todoapp .filters li a { 287 | color: inherit; 288 | margin: 3px; 289 | padding: 3px 7px; 290 | text-decoration: none; 291 | border: 1px solid transparent; 292 | border-radius: 3px; 293 | } 294 | 295 | .todoapp .filters li a.selected, 296 | .todoapp .filters li a:hover { 297 | border-color: rgba(175, 47, 47, 0.1); 298 | } 299 | 300 | .todoapp .filters li a.selected { 301 | border-color: rgba(175, 47, 47, 0.2); 302 | } 303 | -------------------------------------------------------------------------------- /assets/css/simplescrollbars.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-simplescroll-horizontal div, .CodeMirror-simplescroll-vertical div { 2 | position: absolute; 3 | background: #ccc; 4 | -moz-box-sizing: border-box; 5 | box-sizing: border-box; 6 | border: 1px solid #bbb; 7 | border-radius: 2px; 8 | } 9 | 10 | .CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical { 11 | position: absolute; 12 | z-index: 6; 13 | background: #eee; 14 | } 15 | 16 | .CodeMirror-simplescroll-horizontal { 17 | bottom: 0; left: 0; 18 | height: 8px; 19 | } 20 | .CodeMirror-simplescroll-horizontal div { 21 | bottom: 0; 22 | height: 100%; 23 | } 24 | 25 | .CodeMirror-simplescroll-vertical { 26 | right: 0; top: 0; 27 | width: 8px; 28 | } 29 | .CodeMirror-simplescroll-vertical div { 30 | right: 0; 31 | width: 100%; 32 | } 33 | 34 | 35 | .CodeMirror-overlayscroll .CodeMirror-scrollbar-filler, .CodeMirror-overlayscroll .CodeMirror-gutter-filler { 36 | display: none; 37 | } 38 | 39 | .CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div { 40 | position: absolute; 41 | background: #bcd; 42 | border-radius: 3px; 43 | } 44 | 45 | .CodeMirror-overlayscroll-horizontal, .CodeMirror-overlayscroll-vertical { 46 | position: absolute; 47 | z-index: 6; 48 | } 49 | 50 | .CodeMirror-overlayscroll-horizontal { 51 | bottom: 0; left: 0; 52 | height: 6px; 53 | } 54 | .CodeMirror-overlayscroll-horizontal div { 55 | bottom: 0; 56 | height: 100%; 57 | } 58 | 59 | .CodeMirror-overlayscroll-vertical { 60 | right: 0; top: 0; 61 | width: 6px; 62 | } 63 | .CodeMirror-overlayscroll-vertical div { 64 | right: 0; 65 | width: 100%; 66 | } 67 | -------------------------------------------------------------------------------- /assets/css/trace.css: -------------------------------------------------------------------------------- 1 | .trace { position:absolute; bottom:20px; right:20px; width:75vw; background: #202020; color: #ccc; height:70vh; font-family: inconsolata; } 2 | .trace .searcher { overflow:auto; padding:10px; flex:none; } 3 | .trace button { flex:none; text-align:left; border:none; background: #444; margin-top:5px; cursor:pointer; color: #ccc; } 4 | .trace .change-link { flex:none; } 5 | 6 | .trace .vis { overflow:auto; } 7 | .trace .vis row { flex:none; } 8 | .trace .vis column { flex:none; } 9 | .trace .node { padding:10px; border:1px solid #777; flex:none; margin:5px; } 10 | 11 | .trace .block { margin:15px 0; } 12 | -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/witheve/Eve/f680d331d827c7dfe3eaeb79a2a7ce86710d930b/assets/favicon.png -------------------------------------------------------------------------------- /assets/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/witheve/Eve/f680d331d827c7dfe3eaeb79a2a7ce86710d930b/assets/fonts/ionicons.ttf -------------------------------------------------------------------------------- /bin/eve.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | 4 | var path = require("path"); 5 | var fs = require("fs"); 6 | var minimist = require("minimist"); 7 | 8 | var config = require("../build/src/config"); 9 | var Owner = config.Owner; 10 | var Mode = config.Mode; 11 | var server = require("../build/src/runtime/server"); 12 | 13 | const argv = minimist(process.argv.slice(2), {boolean: ["help", "version", "localControl", "server", "editor", "multiDoc"]}); 14 | 15 | // 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". 16 | function findRoot(root) { 17 | var pkg; 18 | root = root.split(path.sep); 19 | while(!pkg && root.length > 1) { 20 | var cur = root.join(path.sep); 21 | if(fs.existsSync(path.join(cur, "package.json"))) { 22 | return cur; 23 | } 24 | root.pop(); 25 | } 26 | } 27 | 28 | 29 | var port = argv["port"] || process.env.PORT || 8080; 30 | var runtimeOwner = argv["server"] ? Owner.server : Owner.client; 31 | var controlOwner = argv["localControl"] ? Owner.client : Owner.server; 32 | var editor = argv["editor"] || false; 33 | var multiDoc = argv["multiDoc"] || false; 34 | var filepath = argv["_"][0]; 35 | var internal = false; 36 | 37 | var root = findRoot(process.cwd()); 38 | var eveRoot = findRoot(__dirname); 39 | 40 | if(argv["help"]) { 41 | let pkg = require(path.join(eveRoot, "package.json")); 42 | console.log(` 43 | Eve ${pkg.version} 44 | 45 | Usage: eve [flags] [file] 46 | 47 | --help Display this message. 48 | --version Display installed version and exit. 49 | --server Execute code on the server rather than the client. 50 | --editor Display the editor (default if no file is specified). 51 | --port Change the port the Eve server listens to (default 8080). 52 | --localControl Entirely disable server interaction. File changes will be 53 | stored in localStorage. 54 | 55 | If the Eve binary is run in a project directory (a directory containing a 56 | package.json file), it will use that directory as your workspace. Otherwise 57 | Eve will use the built-in examples workspace. 58 | 59 | If a file is provided, Eve will run it in application-only mode unless the 60 | --editor flag is supplied. 61 | 62 | Please refer questions and comments to the mailing list: 63 | https://groups.google.com/forum/#!forum/eve-talk 64 | 65 | Please report bugs via GH issues: 66 | https://github.com/witheve/eve/issues 67 | `); 68 | process.exit(0); 69 | } 70 | if(argv["version"]) { 71 | let pkg = require(path.join(eveRoot, "package.json")); 72 | console.log(pkg.version); 73 | process.exit(0); 74 | } 75 | 76 | 77 | // If we're executing within the eve module/repo, we're running internally and should expose our examples, src, etc. 78 | // This should be handled down the road by some sort of a manifest in conjunction with the `root` rather than hardcoding. 79 | if(root === eveRoot) internal = true; 80 | else if(!root) { 81 | internal = true; 82 | // We shouldn't (and when globally installed, *can't*) taint the internal examples when running as an installed binary. 83 | // @TODO: In the future we should have a more flexible solution that can copy out the examples into your current workspace when edited. 84 | controlOwner = Owner.client; 85 | } 86 | 87 | 88 | // If we're not given an explicit filepath to run, assume the user wanted the editor (rather than a blank page). 89 | // Similarly, if we're running internally, send the user over to the quickstart, since they're likely testing the waters. 90 | if(!filepath) { 91 | editor = true; 92 | if(internal) filepath = eveRoot + "/" + "examples/quickstart.eve"; 93 | } else { 94 | filepath = path.resolve(filepath); 95 | if(process.platform.indexOf("win") === 0) { 96 | filepath = filepath.replace(/\\/g, "/"); 97 | } 98 | } 99 | 100 | let mode = Mode.workspace; 101 | if(filepath && !editor) mode = Mode.file 102 | 103 | var opts = {internal: internal, runtimeOwner: runtimeOwner, controlOwner: controlOwner, editor: editor, port: port, path: filepath, internal: internal, root: root, eveRoot: eveRoot, mode: mode, multiDoc: multiDoc}; 104 | config.init(opts); 105 | 106 | server.run(opts); 107 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 7.4.0 4 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | eve: 2 | build: . 3 | ports: 4 | - 18080:8080 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Eve 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "witheve", 3 | "version": "0.3.0-preview5", 4 | "description": "Programming designed for humans", 5 | "keywords": [ 6 | "language", 7 | "ide", 8 | "relational", 9 | "database", 10 | "dataflow" 11 | ], 12 | "homepage": "http://witheve.com", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/witheve/Eve" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/witheve/Eve/issues" 19 | }, 20 | "license": "Apache-2.0", 21 | "engines": { 22 | "node": ">=7.4.0" 23 | }, 24 | "main": "build/src/index.js", 25 | "types": "build/src/index.d.ts", 26 | "scripts": { 27 | "build": "tsc", 28 | "postinstall": "tsc", 29 | "prepublish": "tsc", 30 | "test": "node build/test/all.js | faucet", 31 | "start": "echo 'Please download the eve-starter repository to begin using Eve .'" 32 | }, 33 | "dependencies": { 34 | "@types/commonmark": "^0.22.29", 35 | "@types/dateformat": "^1.0.1", 36 | "@types/node": "^6.0.41", 37 | "@types/tape": "^4.2.28", 38 | "@types/uuid": "^2.0.29", 39 | "chevrotain": "0.28.2", 40 | "commonmark": "^0.27.0", 41 | "dateformat": "^2.0.0", 42 | "express": "^4.14.0", 43 | "falafel": "^2.0.0", 44 | "javascript-natural-sort": "^0.7.1", 45 | "setimmediate": "^1.0.5", 46 | "typescript": "^2.1.4", 47 | "uuid": "^3.0.1" 48 | }, 49 | "devDependencies": { 50 | "faucet": "0.0.1", 51 | "tape": "^4.6.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import "setimmediate"; 2 | import {Program} from "./runtime/dsl2"; 3 | import * as testUtil from "../test/util"; 4 | import "./parser/parser"; 5 | 6 | // let assert = {}; 7 | // function verify(assert:any, prog:Program, ins:any[], outs:any[]) { 8 | // prog.test(prog.nextTransactionId, ins); 9 | // } 10 | 11 | // function verifyIO(assert:any, progName:string, inputString:string, expecteds:testUtil.EAVRCTuple[][]) { 12 | // let inputs = testUtil.createInputs(inputString); 13 | // for(let input of inputs) { 14 | // prog.test(prog.nextTransactionId, input); 15 | // console.groupCollapsed("Expected"); 16 | // console.info(testUtil.pprint(expecteds)); 17 | // console.groupEnd(); 18 | // } 19 | // } 20 | 21 | // let prog = new Program("test"); 22 | 23 | // import "./programs/flappy"; 24 | // import "./programs/compiler"; 25 | // import "./programs/hover"; 26 | // import "./programs/canvas-demo"; 27 | // import "./programs/shape-demo"; 28 | // import "./programs/ui-demo"; 29 | // import "./programs/editor-demo"; 30 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export {Watcher, Program, appendAsEAVs, RawEAV, RawEAVC, RawValue, RawMap, RawRecord, createId, EAVDiffs, Diffs} from "./watchers/watcher"; 2 | export {parseDoc} from "./parser/parser"; 3 | 4 | export var watcherPath = "./build/src/watchers"; 5 | import * as watchers from "./watchers/index"; 6 | export {watchers}; 7 | -------------------------------------------------------------------------------- /src/loadWorker.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Web worker initialization 3 | //------------------------------------------------------------------- 4 | 5 | // We need to import uuid and commonmark before systemJS since the config tries 6 | // to read their global objects 7 | importScripts("/build/src/uuid.js", "/build/src/commonmark.js", "/build/src/system.js", "/build/src/systemJSConfig.js"); 8 | 9 | // while we're loading stuff in using systemJS we need to queue any messages 10 | // we may get from the browser 11 | let queue = []; 12 | 13 | // We set an initial onmessage here that just adds messages to the queue, 14 | // we'll override this with a real on message once the webworker code is loaded 15 | onmessage = function(event) { 16 | queue.push(event); 17 | } 18 | 19 | SystemJS.import("runtime/webworker").then(function(worker) { 20 | onmessage = worker.onmessage; 21 | for(let queued of queue) { 22 | onmessage(queued); 23 | } 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /src/parser/errors.ts: -------------------------------------------------------------------------------- 1 | //-------------------------------------------------------------- 2 | // Errors 3 | //-------------------------------------------------------------- 4 | 5 | import {exceptions, Token, EOF} from "chevrotain"; 6 | import * as parser from "./parser"; 7 | 8 | const SPAN_TYPE = "document_comment"; 9 | 10 | //-------------------------------------------------------------- 11 | // EveError 12 | //-------------------------------------------------------------- 13 | 14 | export class EveError { 15 | static ID = 0; 16 | 17 | type = "error"; 18 | id: string; 19 | blockId: string; 20 | message: string; 21 | start: number; 22 | stop: number; 23 | context?: any; 24 | spanId: string; 25 | 26 | constructor(blockId:string, start:number, stop:number, message:string, context?:any) { 27 | this.blockId = blockId; 28 | this.id = `${blockId}|error|${EveError.ID++}`; 29 | this.start = start; 30 | this.stop = stop; 31 | this.message = message; 32 | this.context = context; 33 | } 34 | 35 | injectSpan(spans:any, extraInfo:any) { 36 | spans.push(this.start, this.stop, SPAN_TYPE, this.id); 37 | extraInfo[this.id] = this; 38 | } 39 | } 40 | 41 | //-------------------------------------------------------------- 42 | // Parse error utils 43 | //-------------------------------------------------------------- 44 | 45 | function regexGroup(str:string, regex:RegExp, group = 1) { 46 | var matches = []; 47 | var match; 48 | while (match = regex.exec(str)) { 49 | matches.push(match[group]); 50 | } 51 | return matches; 52 | } 53 | 54 | function className(thing:any) { 55 | var funcNameRegex = /function (.{1,})\(/; 56 | var results = (funcNameRegex).exec((thing).constructor.toString()); 57 | return (results && results.length > 1) ? results[1] : ""; 58 | }; 59 | 60 | function lastTokenWithType(tokens:any, type:any) { 61 | let ix = tokens.length - 1; 62 | while(ix >= 0) { 63 | let cur = tokens[ix]; 64 | if(cur instanceof type) { 65 | return cur; 66 | } 67 | ix--; 68 | } 69 | } 70 | 71 | 72 | //-------------------------------------------------------------- 73 | // Parse errors 74 | //-------------------------------------------------------------- 75 | 76 | export function parserErrors(errors: any[], parseInfo: {blockId: string, blockStart: number, spans: any[], extraInfo: any, tokens: Token[]}) { 77 | let {blockId, blockStart, spans, extraInfo} = parseInfo; 78 | let normalized = []; 79 | let errorIx = 1; 80 | 81 | for(let error of errors) { 82 | let {token, context, message, resyncedTokens, name} = error; 83 | 84 | let eveError: EveError; 85 | if(name === "MismatchedTokenException") { 86 | eveError = mismatchedToken(error, parseInfo); 87 | } else if(name === "NotAllInputParsedException") { 88 | eveError = notAllInputParsed(error, parseInfo); 89 | } else { 90 | // console.log("UNHANDLED ERROR TYPE", name); 91 | let start = token.startOffset; 92 | let stop = token.startOffset + token.image.length; 93 | eveError = new EveError(blockId, start, stop, message, context); 94 | } 95 | 96 | eveError.injectSpan(spans, extraInfo); 97 | normalized.push(eveError); 98 | } 99 | return normalized; 100 | } 101 | 102 | //-------------------------------------------------------------- 103 | // MismatchedToken parse error 104 | //-------------------------------------------------------------- 105 | 106 | const MismatchRegex = /-->\s*(.*?)\s*<--/gi; 107 | 108 | function mismatchedToken(error:any, parseInfo:any) { 109 | const Pairs:any = { 110 | "CloseString": parser.OpenString, 111 | "CloseBracket": parser.OpenBracket, 112 | "CloseParen": parser.OpenParen, 113 | }; 114 | 115 | let {blockId, blockStart, spans, extraInfo, tokens} = parseInfo; 116 | let {token, context, message, resyncedTokens, name} = error; 117 | 118 | let blockEnd = tokens[tokens.length - 1].endOffset + 1; 119 | 120 | let [expectedType, foundType] = regexGroup(message, MismatchRegex); 121 | 122 | let start, stop; 123 | 124 | if(token instanceof EOF) { 125 | let pair = Pairs[expectedType] as any; 126 | if(pair) { 127 | token = lastTokenWithType(tokens, pair); 128 | message = messages.unclosedPair(expectedType); 129 | } else { 130 | token = tokens[tokens.length - 1]; 131 | } 132 | stop = blockEnd; 133 | } 134 | 135 | // We didn't find a matching pair, check if we're some other mistmatched bit of syntax. 136 | if(stop === undefined) { 137 | if(expectedType === "Tag") { 138 | if(token.label === "identifier") { 139 | message = messages.actionRawIdentifier(token.image); 140 | } else { 141 | message = messages.actionNonTag(token.image); 142 | } 143 | } 144 | } 145 | 146 | if(start === undefined) start = token.startOffset; 147 | if(stop === undefined) stop = token.startOffset + token.image.length; 148 | 149 | return new EveError(blockId, start, stop, message, context); 150 | } 151 | 152 | //-------------------------------------------------------------- 153 | // NotAllInputParsed parse error 154 | //-------------------------------------------------------------- 155 | 156 | const NotAllInputRegex = /found:\s*([^\s]+)/gi; 157 | const CloseChars:any = {")": true, "]": true}; 158 | 159 | function notAllInputParsed(error:any, parseInfo:any) { 160 | let {blockId, blockStart, spans, extraInfo, tokens} = parseInfo; 161 | let {token, context, message, resyncedTokens, name} = error; 162 | 163 | let blockEnd = tokens[tokens.length - 1].endOffset + 1; 164 | 165 | let [foundChar] = regexGroup(message, NotAllInputRegex); 166 | 167 | let start, stop; 168 | 169 | if(CloseChars[foundChar]) { 170 | message = messages.extraCloseChar(foundChar); 171 | } else { 172 | console.log("WEIRD STUFF AT THE END", context); 173 | } 174 | 175 | if(start === undefined) start = token.startOffset; 176 | if(stop === undefined) stop = token.startOffset + token.image.length; 177 | 178 | return new EveError(blockId, start, stop, message, context); 179 | } 180 | 181 | //-------------------------------------------------------------- 182 | // Build errors 183 | //-------------------------------------------------------------- 184 | 185 | export function unprovidedVariableGroup(block:any, variables:any) { 186 | let {id, start: blockStart} = block; 187 | let found; 188 | for(let variable of variables) { 189 | if(!variable.generated) { 190 | found = variable; 191 | break; 192 | } 193 | } 194 | if(!found) { 195 | found = variables[0]; 196 | } 197 | let [start, stop] = parser.nodeToBoundaries(found, blockStart); 198 | return new EveError(id, start, stop, messages.unprovidedVariable(found.name)); 199 | } 200 | 201 | export function blankScan(block:any, scan:any) { 202 | let {id, start: blockStart} = block; 203 | let [start, stop] = parser.nodeToBoundaries(scan, blockStart); 204 | return new EveError(id, start, stop, messages.blankScan()); 205 | } 206 | 207 | export function invalidLookupAction(block:any, action:any) { 208 | let {id, start: blockStart} = block; 209 | let [start, stop] = parser.nodeToBoundaries(action, blockStart); 210 | let missing = []; 211 | if(action.entity === undefined) missing.push("record"); 212 | if(action.attribute === undefined) missing.push("attribute"); 213 | if(action.value === undefined) missing.push("value"); 214 | return new EveError(id, start, stop, messages.invalidLookupAction(missing)); 215 | } 216 | 217 | export function unimplementedExpression(block:any, expression:any) { 218 | let {id, start: blockStart} = block; 219 | let [start, stop] = parser.nodeToBoundaries(expression, blockStart); 220 | return new EveError(id, start, stop, messages.unimplementedExpression(expression.op)); 221 | } 222 | 223 | export function incompatabileConstantEquality(block:any, left:any, right:any) { 224 | let {id, start: blockStart} = block; 225 | let [start] = parser.nodeToBoundaries(left, blockStart); 226 | let [_, stop] = parser.nodeToBoundaries(right, blockStart); 227 | return new EveError(id, start, stop, messages.neverEqual(left.value, right.value)); 228 | } 229 | 230 | export function incompatabileVariableToConstantEquality(block:any, variable:any, variableValue:any, constant:any) { 231 | let {id, start: blockStart} = block; 232 | let [start] = parser.nodeToBoundaries(variable, blockStart); 233 | let [_, stop] = parser.nodeToBoundaries(constant, blockStart); 234 | return new EveError(id, start, stop, messages.variableNeverEqual(variable, variableValue, constant.value)); 235 | } 236 | 237 | export function incompatabileTransitiveEquality(block:any, variable:any, value:any) { 238 | let {id, start: blockStart} = block; 239 | let [start, stop] = parser.nodeToBoundaries(variable, blockStart); 240 | return new EveError(id, start, stop, messages.variableNeverEqual(variable, variable.constant, value)); 241 | } 242 | 243 | export function unrecognisedFunctionAttribute(block:any, expression:any, attribute:any) { 244 | let {id, start: blockStart} = block; 245 | return new EveError(id, attribute.startOffset , attribute.endOffset, messages.unrecognisedFunctionAttribute(attribute.attribute, expression.op)); 246 | } 247 | 248 | //-------------------------------------------------------------- 249 | // Messages 250 | //-------------------------------------------------------------- 251 | 252 | const PairToName:any = { 253 | "CloseString": "quote", 254 | "CloseBracket": "bracket", 255 | "CloseParen": "paren", 256 | "]": "bracket", 257 | ")": "paren", 258 | "\"": "quote", 259 | } 260 | 261 | export var messages = { 262 | 263 | unclosedPair: (type:string) => `Looks like a close ${PairToName[type]} is missing`, 264 | 265 | extraCloseChar: (char:string) => `This close ${PairToName[char]} is missing an open ${PairToName[char]}`, 266 | 267 | unprovidedVariable: (varName:string) => `Nothing is providing a value for ${varName}`, 268 | unrecognisedFunctionAttribute: (attributeName:string, functionName:string) => `${attributeName} is not a recognised attribute for ${functionName}.`, 269 | 270 | unimplementedExpression: (op:string) => `There's no definition for the function ${op}`, 271 | 272 | blankScan: () => 'Lookup requires at least one attribute: record, attribute, value, or node', 273 | 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.`, 274 | 275 | neverEqual: (left:string, right:string) => `${left} can never equal ${right}`, 276 | variableNeverEqual: (variable:any, value:string, right:string) => `${variable.name} is equivalent to ${value}, which can't be equal to ${right}`, 277 | 278 | actionNonTag: (found:string) => `Looks like this should be a tag, try changing the ${found} to #${found}`, 279 | 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 #.` 280 | }; 281 | -------------------------------------------------------------------------------- /src/programs/perfReport.ts: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // Performance Report 3 | //--------------------------------------------------------------------- 4 | import {v4 as uuid} from "uuid"; 5 | import {Program} from "../watchers/watcher"; 6 | import {PerformanceTracker} from "../runtime/performance"; 7 | 8 | 9 | export class PerformanceReporter { 10 | constructor() { } 11 | 12 | blocksToEAVs(blocks:any, eavs:any[] = []) { 13 | for(let name in blocks) { 14 | let {counts, times} = blocks[name]; 15 | let blockId = uuid(); 16 | eavs.push( 17 | [blockId, "tag", "block"], 18 | [blockId, "name", name], 19 | ); 20 | for(let property in counts) { 21 | let count = counts[property]; 22 | let time = times[property]; 23 | let propertyId = uuid(); 24 | eavs.push( 25 | [blockId, "property", propertyId], 26 | [propertyId, "name", property], 27 | [propertyId, "count", count], 28 | [propertyId, "time", time] 29 | ); 30 | } 31 | } 32 | return eavs as any[]; 33 | } 34 | 35 | totalsToEAVs(counts:any, times:any, eavs:any[] = []) { 36 | let programId = uuid(); 37 | eavs.push( 38 | [programId, "tag", "total"], 39 | ); 40 | for(let property in counts) { 41 | let count = counts[property]; 42 | let time = times[property]; 43 | let propertyId = uuid(); 44 | eavs.push( 45 | [programId, "property", propertyId], 46 | [propertyId, "name", property], 47 | [propertyId, "count", count], 48 | [propertyId, "time", time] 49 | ); 50 | } 51 | } 52 | 53 | report(perf:PerformanceTracker) { 54 | let eavs = this.blocksToEAVs(perf.blocks); 55 | this.totalsToEAVs(perf.counts, perf.times, eavs); 56 | let me = new Program("Performance report"); 57 | me.attach("ui"); 58 | 59 | me.bind("calculate block percentage", ({find, record, lib: {math}}) => { 60 | let total = find("total"); 61 | total.property.name == "transaction" 62 | let block = find("block"); 63 | let property = block.property; 64 | property.name == "block"; 65 | let percent = math.round(property.time * 100 / total.property.time); 66 | return [ 67 | block.add("percent", percent) 68 | ] 69 | }); 70 | 71 | me.bind("draw total", ({find, record, lib: {math}}) => { 72 | let container = find("block-times") 73 | let total = find("total"); 74 | let property = total.property; 75 | let avg = math.toFixed(property.time / property.count, 4); 76 | let timeStr = math.toFixed(property.time, 2) 77 | return [ 78 | container.add("children", [ 79 | record("ui/row", {total}) 80 | .add("style", record({flex: "none", padding: "10px 20px", "margin-bottom":"10px", background: "#ccc"})) 81 | .add("sort", -10000).add("children", [ 82 | record("ui/column", {total, sort:0}).add("children", [ 83 | record("ui/text", {text:"Total", sort:0, style: record({"font-size": "16pt"})}), 84 | record("ui/row", {total, sort:1, style:record({"font-size":"14pt", margin:"10px 0"})}).add("children", [ 85 | record("ui/text", {text:property.count, sort:0}), 86 | record("ui/text", {text:timeStr, sort:1, style: record({margin: "0 10px"})}), 87 | record("ui/text", {text:avg, sort:2}), 88 | ]), 89 | ]), 90 | record("ui/spacer", {sort:1}), 91 | record("ui/text", {text:"100%", sort:1, style:record({"font-size":"20pt"})}) 92 | ]) 93 | ]) 94 | ] 95 | }); 96 | 97 | me.bind("draw blocks 2", ({find, record, lib: {math}}) => { 98 | let block = find("block"); 99 | let {name, property} = block; 100 | property.name == "block" 101 | let rev = -1; 102 | let avg = math.toFixed(property.time / property.count, 4); 103 | let timeStr = math.toFixed(property.time, 2) 104 | let foo = record({"font-size":"14pt", margin: "10px 0"}); 105 | return [ 106 | record("ui/column", "block-times", {style:record({"max-width":500}), sort:2}).add("children", [ 107 | record("ui/row", {block}) 108 | .add("style", record({flex: "none", padding: "10px 20px", "margin-bottom":"10px", background: "#ccc"})) 109 | .add("sort", rev * property.time).add("children", [ 110 | record("ui/column", {block, sort:0}).add("children", [ 111 | record("ui/text", {text:block.name, sort:0, style: record({"font-size": "16pt"})}), 112 | record("ui/row", {block, sort:1, style:foo}).add("children", [ 113 | record("ui/text", {text:property.count, sort:0}), 114 | record("ui/text", {text:timeStr, sort:1, style: record({margin: "0 10px"})}), 115 | record("ui/text", {text:avg, sort:2}), 116 | ]), 117 | record("ui/column", "props", {block, sort:2}) 118 | ]), 119 | record("ui/spacer", {sort:1}), 120 | record("ui/text", {text:block.percent + "%", sort:1, style:record({"font-size":"20pt"})}) 121 | ]) 122 | ]) 123 | ] 124 | }); 125 | 126 | me.bind("draw props", ({find, record, lib: {math}}) => { 127 | let container = find("props"); 128 | let {block} = container; 129 | let property = block.property; 130 | property.name != "block" 131 | let avg = math.toFixed(property.time / property.count, 4); 132 | let timeStr = math.toFixed(property.time, 2) 133 | return [ 134 | container.add("children", [ 135 | record("ui/row", {block, property, sort:property.name}).add("children", [ 136 | record("ui/text", {sort:0, text:property.name, style:record({"margin-right":"15px", width:"120px"})}), 137 | record("ui/text", {sort:1, text:property.count, style:record({width:50})}), 138 | record("ui/text", {sort:2, text:timeStr, style:record({margin: "0 10px", width:50})}), 139 | record("ui/text", {sort:3, text:avg, style:record({width:50})}), 140 | ]) 141 | ]) 142 | ] 143 | }); 144 | 145 | me.bind("Translate elements into html", ({find, record, union}) => { 146 | let elem = find("html/div"); 147 | return [elem.add("tag", "html/element").add("tagname", "div")]; 148 | }); 149 | 150 | // console.profile("perf"); 151 | me.inputEAVs(eavs); 152 | // console.profileEnd(); 153 | } 154 | 155 | } 156 | 157 | let 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}}}} 158 | -------------------------------------------------------------------------------- /src/runtime/performance.ts: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // Performance 3 | //--------------------------------------------------------------------- 4 | import {v4 as uuid} from "uuid"; 5 | // import {Program} from "./dsl2"; 6 | 7 | var globalsToTrack = ["transaction"]; 8 | var propertiesToTrack = ["block", "PresolveCheck", "GenericJoin"]; 9 | 10 | export type TimeReturn = number; 11 | 12 | export class PerformanceTracker { 13 | 14 | blocks:{[block:string]: { 15 | times: {[property:string]: number}, 16 | counts: {[property:string]: number}, 17 | }}; 18 | activeBlock:string; 19 | activeProperties:{[property:string]: TimeReturn}; 20 | times: {[property:string]: number}; 21 | counts: {[property:string]: number}; 22 | 23 | now: () => TimeReturn; 24 | elapsed: (start:TimeReturn) => TimeReturn; 25 | 26 | _makePropertyHolder(props = propertiesToTrack) { 27 | let neue:any = {}; 28 | for(let property of props) { 29 | neue[property] = 0; 30 | } 31 | return neue; 32 | } 33 | 34 | constructor() { 35 | this.reset(); 36 | this.now = now; 37 | this.elapsed = elapsed; 38 | } 39 | 40 | reset() { 41 | this.activeBlock = ""; 42 | this.activeProperties = {}; 43 | this.times = this._makePropertyHolder(globalsToTrack); 44 | this.counts = this._makePropertyHolder(globalsToTrack); 45 | this.blocks = {}; 46 | } 47 | 48 | block(name:string) { 49 | let {blocks} = this; 50 | let found = blocks[name]; 51 | if(!found) { 52 | found = blocks[name] = {counts: this._makePropertyHolder(), times: this._makePropertyHolder()}; 53 | } 54 | this.activeBlock = name; 55 | this.activeProperties["block"] = this.now(); 56 | found.counts["block"]++; 57 | } 58 | 59 | blockEnd(name:string) { 60 | let {blocks, activeBlock} = this; 61 | blocks[activeBlock].times["block"] += this.elapsed(this.activeProperties["block"]) 62 | this.activeBlock = ""; 63 | } 64 | 65 | blockTime(property:string) { 66 | let {blocks, activeBlock} = this; 67 | let found = blocks[activeBlock]; 68 | this.activeProperties[property] = this.now(); 69 | found.counts[property]++; 70 | } 71 | 72 | blockTimeEnd(property:string) { 73 | let {blocks, activeBlock} = this; 74 | let found = blocks[activeBlock]; 75 | found.times[property] += this.elapsed(this.activeProperties[property]); 76 | } 77 | 78 | time(property:string) { 79 | let {counts} = this; 80 | this.activeProperties[property] = this.now(); 81 | counts[property]++; 82 | } 83 | timeEnd(property:string) { 84 | let {times} = this; 85 | times[property] += this.elapsed(this.activeProperties[property]); 86 | } 87 | 88 | serialize() { 89 | return JSON.stringify({ 90 | times: this.times, 91 | counts: this.counts, 92 | blocks: this.blocks 93 | }) 94 | } 95 | } 96 | 97 | export class NoopPerformanceTracker extends PerformanceTracker { 98 | blocks:{[block:string]: { 99 | times: {[property:string]: number}, 100 | counts: {[property:string]: number}, 101 | }}; 102 | times: {[property:string]: number}; 103 | counts: {[property:string]: number}; 104 | 105 | now: () => TimeReturn; 106 | elapsed: (start:TimeReturn) => TimeReturn; 107 | 108 | constructor() { 109 | super(); 110 | this.now = () => 0; 111 | this.elapsed = (start:any) => 0; 112 | } 113 | reset() { } 114 | 115 | time(property:string) {} 116 | timeEnd(property:string) {} 117 | 118 | block(name:string) { this.activeBlock = name; } 119 | blockEnd(name:string) { this.activeBlock = ""; } 120 | 121 | blockTime(property:string) {} 122 | blockTimeEnd(property:string) {} 123 | } 124 | 125 | export var now: () => any; 126 | export var elapsed: (start:any) => any; 127 | if(global.process) { 128 | now = function(start?): any { 129 | return process.hrtime(); 130 | } 131 | elapsed = function(start:any): any { 132 | let end = process.hrtime(start); 133 | return ((end[0]*1000) + (end[1]/1000000)); 134 | } 135 | } else { 136 | now = function(start?): any { 137 | return performance.now(); 138 | } 139 | elapsed = function(start:any): any { 140 | let end = performance.now(); 141 | return end - start; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/runtime/stdlib.ts: -------------------------------------------------------------------------------- 1 | import {makeFunction, makeMultiFunction, RawValue, AggregateNode} from "./runtime"; 2 | import * as dateformat from "dateformat"; 3 | 4 | //-------------------------------------------------------------------- 5 | // Comparisons 6 | //-------------------------------------------------------------------- 7 | 8 | makeFunction({ 9 | name: "compare/>", 10 | args: {a: "number", b: "number"}, 11 | returns: {}, 12 | apply: (a:number, b:number) => { 13 | return (a > b) ? [] : undefined; 14 | } 15 | }); 16 | 17 | makeFunction({ 18 | name: "compare/>=", 19 | args: {a: "number", b: "number"}, 20 | returns: {}, 21 | apply: (a:number, b:number) => { 22 | return (a >= b) ? [] : undefined; 23 | } 24 | }); 25 | 26 | makeFunction({ 27 | name: "compare/<", 28 | args: {a: "number", b: "number"}, 29 | returns: {}, 30 | apply: (a:number, b:number) => { 31 | return (a < b) ? [] : undefined; 32 | } 33 | }); 34 | 35 | makeFunction({ 36 | name: "compare/<=", 37 | args: {a: "number", b: "number"}, 38 | returns: {}, 39 | apply: (a:number, b:number) => { 40 | return (a <= b) ? [] : undefined; 41 | } 42 | }); 43 | 44 | makeFunction({ 45 | name: "compare/!=", 46 | args: {a: "number", b: "number"}, 47 | returns: {}, 48 | apply: (a:number, b:number) => { 49 | return (a != b) ? [] : undefined; 50 | } 51 | }); 52 | 53 | makeFunction({ 54 | name: "compare/==", 55 | args: {a: "number", b: "number"}, 56 | returns: {}, 57 | apply: (a:number, b:number) => { 58 | return (a == b) ? [] : undefined; 59 | } 60 | }); 61 | 62 | //-------------------------------------------------------------------- 63 | // Math 64 | //-------------------------------------------------------------------- 65 | 66 | makeFunction({ 67 | name: "math/+", 68 | args: {a: "number", b: "number"}, 69 | returns: {result: "number"}, 70 | apply: (a:number, b:number) => { 71 | return [a + b]; 72 | } 73 | }); 74 | 75 | makeFunction({ 76 | name: "math/-", 77 | args: {a: "number", b: "number"}, 78 | returns: {result: "number"}, 79 | apply: (a:number, b:number) => { 80 | return [a - b]; 81 | } 82 | }); 83 | 84 | makeFunction({ 85 | name: "math/*", 86 | args: {a: "number", b: "number"}, 87 | returns: {result: "number"}, 88 | apply: (a:number, b:number) => { 89 | return [a * b]; 90 | } 91 | }); 92 | 93 | makeFunction({ 94 | name: "math//", 95 | args: {a: "number", b: "number"}, 96 | returns: {result: "number"}, 97 | apply: (a:number, b:number) => { 98 | return [a / b]; 99 | } 100 | }); 101 | 102 | makeFunction({ 103 | name: "math/floor", 104 | args: {value: "number"}, 105 | returns: {result: "number"}, 106 | apply: (value:number) => { 107 | return [Math.floor(value)]; 108 | } 109 | }); 110 | 111 | makeFunction({ 112 | name: "math/ceiling", 113 | args: {value: "number"}, 114 | returns: {result: "number"}, 115 | apply: (value:number) => { 116 | return [Math.ceil(value)]; 117 | } 118 | }); 119 | 120 | makeFunction({ 121 | name: "math/round", 122 | args: {value: "number"}, 123 | returns: {result: "number"}, 124 | apply: (value:number) => { 125 | return [Math.round(value)]; 126 | } 127 | }); 128 | 129 | makeFunction({ 130 | name: "math/sin", 131 | args: {degrees: "number"}, 132 | returns: {result: "number"}, 133 | apply: (degrees:number) => { 134 | return [Math.sin(degrees/180 * Math.PI)]; 135 | } 136 | }); 137 | 138 | makeFunction({ 139 | name: "math/cos", 140 | args: {degrees: "number"}, 141 | returns: {result: "number"}, 142 | apply: (degrees:number) => { 143 | return [Math.cos(degrees/180 * Math.PI)]; 144 | } 145 | }); 146 | 147 | makeFunction({ 148 | name: "math/tan", 149 | args: {degrees: "number"}, 150 | returns: {result: "number"}, 151 | apply: (degrees:number) => { 152 | return [Math.tan(degrees/180 * Math.PI)]; 153 | } 154 | }); 155 | 156 | makeFunction({ 157 | name: "math/max", 158 | args: {a: "number", b: "number"}, 159 | returns: {result: "number"}, 160 | apply: (a:number, b:number) => { 161 | return [Math.max(a, b)]; 162 | } 163 | }); 164 | 165 | makeFunction({ 166 | name: "math/min", 167 | args: {a: "number", b: "number"}, 168 | returns: {result: "number"}, 169 | apply: (a:number, b:number) => { 170 | return [Math.min(a, b)]; 171 | } 172 | }); 173 | 174 | makeFunction({ 175 | name: "math/mod", 176 | args: {value: "number", by: "number"}, 177 | returns: {result: "number"}, 178 | apply: (value:number, by:number) => { 179 | return [value % by]; 180 | } 181 | }); 182 | 183 | makeFunction({ 184 | name: "math/absolute", 185 | args: {value: "number"}, 186 | returns: {result: "number"}, 187 | apply: (value:number) => { 188 | return [Math.abs(value)]; 189 | } 190 | }); 191 | 192 | makeFunction({ 193 | name: "math/pow", 194 | args: {value: "number", exponent: "number"}, 195 | returns: {result: "number"}, 196 | apply: (value:number, exponent:number) => { 197 | return [Math.pow(value, exponent)]; 198 | } 199 | }); 200 | 201 | makeFunction({ 202 | name: "math/ln", 203 | args: {value: "number"}, 204 | returns: {result: "number"}, 205 | apply: (value:number) => { 206 | return [Math.log(value)]; 207 | } 208 | }); 209 | 210 | makeFunction({ 211 | name: "math/to-fixed", 212 | args: {value: "number", to: "number"}, 213 | returns: {result: "string"}, 214 | apply: (value:number, to:number) => { 215 | if(typeof value === "number") { 216 | return [value.toFixed(to)]; 217 | } 218 | } 219 | }); 220 | 221 | makeFunction({ 222 | name: "math/convert-base", 223 | args: {value: "number", to: "number"}, 224 | returns: {result: "string"}, 225 | apply: (value:number, to:number) => { 226 | if(typeof value === "number" && to > 1 && to < 37) { 227 | return [value.toString(to)]; 228 | } 229 | } 230 | }); 231 | 232 | makeMultiFunction({ 233 | name: "math/range", 234 | args: {start: "number", stop: "number"}, 235 | returns: {result: "string"}, 236 | estimate: function(context, prefix) { 237 | let {start, stop} = this.resolve(prefix); 238 | if(typeof start !== "number" || typeof stop !== "number") return 0; 239 | if(start > stop) { 240 | return start - stop; 241 | } else { 242 | return stop - start; 243 | } 244 | }, 245 | apply: (start:number, stop:number, step:number = 1) => { 246 | if(typeof start !== "number" || typeof stop !== "number" || typeof step !== "number") return; 247 | if(start > stop) { 248 | [stop, start] = [start, stop]; 249 | } 250 | 251 | let outputs = []; 252 | for(let ix = start; ix <= stop; ix += step) { 253 | outputs.push([ix]); 254 | } 255 | return outputs; 256 | } 257 | }); 258 | 259 | //-------------------------------------------------------------------- 260 | // String 261 | //-------------------------------------------------------------------- 262 | 263 | makeFunction({ 264 | name: "string/replace", 265 | args: {text: "string", replace: "string", with: "string"}, 266 | returns: {result: "string"}, 267 | apply: function(text:string, replace:string, _with:string) { 268 | let result = text.split(replace).join(_with); 269 | return [result]; 270 | } 271 | }); 272 | 273 | makeFunction({ 274 | name: "string/get", 275 | args: {text: "string", at: "number"}, 276 | returns: {result: "string"}, 277 | apply: function(text:string, at:number) { 278 | if(at > text.length) return; 279 | return [text[at - 1]]; 280 | } 281 | }); 282 | 283 | makeFunction({ 284 | name: "string/uppercase", 285 | args: {text: "string"}, 286 | returns: {result: "string"}, 287 | apply: function(text:string) { 288 | return [text.toLocaleUpperCase()]; 289 | } 290 | }); 291 | 292 | makeFunction({ 293 | name: "string/lowercase", 294 | args: {text: "string"}, 295 | returns: {result: "string"}, 296 | apply: function(text:string) { 297 | return [text.toLocaleLowerCase()]; 298 | } 299 | }); 300 | 301 | makeFunction({ 302 | name: "string/index-of", 303 | args: {text: "string", substring: "string"}, 304 | returns: {result: "number"}, 305 | apply: function(text:string, substring:string) { 306 | let ix = (""+text).indexOf(substring) + 1; 307 | if(ix == 0) return; 308 | return [ix]; 309 | } 310 | }); 311 | 312 | makeFunction({ 313 | name: "string/codepoint-length", 314 | args: {text: "string"}, 315 | returns: {result: "number"}, 316 | apply: function(text:string) { 317 | if(typeof text !== "string") return; 318 | return [text.length]; 319 | } 320 | }); 321 | 322 | 323 | //-------------------------------------------------------------------- 324 | // Random 325 | //-------------------------------------------------------------------- 326 | 327 | makeFunction({ 328 | name: "random/number", 329 | args: {seed: "any"}, 330 | returns: {result: "number"}, 331 | initialState: {}, 332 | apply: function(seed:RawValue) { 333 | let state = this.state; 334 | let result = state[seed]; 335 | if(result === undefined) { 336 | result = state[seed] = Math.random(); 337 | } 338 | return [result]; 339 | } 340 | }); 341 | 342 | //-------------------------------------------------------------------- 343 | // Logic 344 | //-------------------------------------------------------------------- 345 | 346 | makeFunction({ 347 | name: "logic/toggle", 348 | args: {value: "string"}, 349 | returns: {result: "string"}, 350 | apply: (value: string) => { 351 | if(value === "true") return ["false"]; 352 | if(value === "false") return ["true"]; 353 | return []; 354 | } 355 | }) 356 | 357 | //-------------------------------------------------------------------- 358 | // Date 359 | //-------------------------------------------------------------------- 360 | 361 | makeFunction({ 362 | name: "date/format", 363 | args: {timestamp: "number", format: "string"}, 364 | returns: {result: "string"}, 365 | apply: (timestamp: number, format: string) => { 366 | return [dateformat(timestamp, format)]; 367 | } 368 | }) 369 | 370 | //-------------------------------------------------------------------- 371 | // Eve internal 372 | //-------------------------------------------------------------------- 373 | 374 | makeFunction({ 375 | name: "eve/internal/gen-id", 376 | args: {}, 377 | variadic: true, 378 | returns: {result: "string"}, 379 | apply: (values:RawValue[]) => { 380 | // @FIXME: This is going to be busted in subtle cases. 381 | // If a record exists with a "1" and 1 value for the same 382 | // attribute, they'll collapse for gen-id, but won't join 383 | // elsewhere. This means aggregate cardinality will disagree with 384 | // action node cardinality. 385 | return [values.join("|")]; 386 | } 387 | }); 388 | 389 | makeFunction({ 390 | name: "eve/internal/concat", 391 | args: {}, 392 | variadic: true, 393 | returns: {result: "string"}, 394 | apply: (values:RawValue[]) => { 395 | return [values.join("")]; 396 | } 397 | }); 398 | 399 | //------------------------------------------------------------------------ 400 | // Aggregates 401 | //------------------------------------------------------------------------ 402 | 403 | export type SumAggregateState = {total:number}; 404 | export class SumAggregate extends AggregateNode { 405 | name = "Sum"; 406 | add(state:SumAggregateState, resolved:RawValue[]):any { 407 | state.total += resolved[0] as number; 408 | return state; 409 | } 410 | remove(state:SumAggregateState, resolved:RawValue[]):any { 411 | state.total -= resolved[0] as number; 412 | return state; 413 | } 414 | getResult(state:SumAggregateState):RawValue { 415 | return state.total; 416 | } 417 | newResultState():SumAggregateState { 418 | return {total: 0}; 419 | }; 420 | } 421 | -------------------------------------------------------------------------------- /src/system-polyfills.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SystemJS Promise Polyfill 3 | */ 4 | !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=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;p0?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;o0||"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 11 | 12 | // Class 13 | interface Path2D { 14 | addPath?(path: Path2D, transform?: SVGMatrix):void; 15 | closePath(): void; 16 | moveTo(x: number, y: number): void; 17 | lineTo(x: number, y: number): void; 18 | bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void; 19 | quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void; 20 | arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void; 21 | arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void; 22 | ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void; 23 | rect(x: number, y: number, width: number, height: number): void; 24 | } 25 | 26 | // Constructor 27 | interface Path2DConstructor { 28 | new (): Path2D; 29 | new (d: string): Path2D; 30 | new (path: Path2D, fillRule?: string): Path2D; 31 | prototype: Path2D; 32 | } 33 | //declare var Path2D: Path2DConstructor; 34 | 35 | // Extend Window 36 | interface Window { Path2D: Path2DConstructor; } 37 | 38 | // Extend CanvasRenderingContext2D 39 | interface CanvasRenderingContext2D { 40 | fill(path: Path2D): void; 41 | stroke(path: Path2D): void; 42 | clip(path: Path2D, fillRule?: string): void; 43 | } 44 | -------------------------------------------------------------------------------- /src/watchers/console.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import {Watcher} from "./watcher"; 3 | import {ID} from "../runtime/runtime"; 4 | 5 | export class ConsoleWatcher extends Watcher { 6 | 7 | setup() { 8 | if(console) { 9 | this.program 10 | .watch("Print to console log.", ({find, record}) => { 11 | let log = find("console/log"); 12 | return [log.add("text", log.text)] 13 | }) 14 | .asDiffs(({adds}) => { 15 | for(let [log, _, text] of adds) { 16 | console.log(text); 17 | } 18 | }) 19 | .watch("Print to console error.", ({find, record}) => { 20 | let log = find("console/error"); 21 | return [log.add("text", log.text)] 22 | }) 23 | .asDiffs(({adds}) => { 24 | for(let [log, _, text] of adds) { 25 | console.error(text); 26 | } 27 | }) 28 | .watch("Print to console warn.", ({find, record}) => { 29 | let log = find("console/warn"); 30 | return [log.add("text", log.text)] 31 | }) 32 | .asDiffs(({adds}) => { 33 | for(let [log, _, text] of adds) { 34 | console.warn(text); 35 | } 36 | }) 37 | } 38 | } 39 | } 40 | 41 | Watcher.register("console", ConsoleWatcher); 42 | -------------------------------------------------------------------------------- /src/watchers/file.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import {Watcher, RawEAV} from "./watcher"; 3 | import {ID} from "../runtime/runtime"; 4 | 5 | export class FileWatcher extends Watcher { 6 | 7 | setup() { 8 | let {program:me} = this; 9 | me.load(` 10 | Attach errors to the associated file 11 | ~~~ 12 | search 13 | error = [#file/error file code message] 14 | 15 | bind 16 | file.error += error 17 | ~~~ 18 | `) 19 | if(fs.readFile) { 20 | me.watch("Read a file.", ({find, record, choose}) => { 21 | let file = find("file/read"); 22 | let encoding = choose(() => file.encoding, () => "utf-8"); 23 | return [ 24 | record({file, path: file.path, encoding}) 25 | ] 26 | }) 27 | .asObjects<{file:ID, path:string, encoding:string}>(({adds, removes}) => { 28 | Object.keys(adds).forEach((id) => { 29 | let {file, path, encoding} = adds[id]; 30 | fs.readFile(path, {encoding}, function(err, contents){ 31 | if (!err) { 32 | me.inputEAVs([[file, "contents", contents]]); 33 | } else { 34 | let id = `${file}|error` 35 | let changes:RawEAV[] = []; 36 | changes.push( 37 | [id, "tag", "file/error"], 38 | [id, "file", file], 39 | [id, "code", `${err.code}`], 40 | [id, "message", `${err.message}`] 41 | ); 42 | me.inputEAVs(changes); 43 | } 44 | }); 45 | }) 46 | }) 47 | } 48 | 49 | if(fs.writeFile) { 50 | me.watch("Write a file.", ({find, record, choose}) => { 51 | let file = find("file/write"); 52 | let encoding = choose(() => file.encoding, () => "utf-8"); 53 | return [ 54 | record({file, path: file.path, encoding, contents: file.contents}) 55 | ] 56 | }) 57 | .asObjects<{file:ID, path:string, contents: string, encoding:string}>(({adds, removes}) => { 58 | Object.keys(adds).forEach((id) => { 59 | let {file, path, contents, encoding} = adds[id]; 60 | fs.writeFile(path, contents, {encoding: encoding}, function(err){ 61 | if (!err) { 62 | me.inputEAVs([[file, "tag", "file/complete"]]) 63 | } else { 64 | let id = `${file}|error` 65 | let changes:RawEAV[] = []; 66 | changes.push( 67 | [id, "tag", "file/error"], 68 | [id, "file", file], 69 | [id, "code", `${err.code}`], 70 | [id, "message", `${err.message}`] 71 | ); 72 | me.inputEAVs(changes); 73 | } 74 | }); 75 | }) 76 | }) 77 | } 78 | } 79 | } 80 | 81 | 82 | Watcher.register("file", FileWatcher); -------------------------------------------------------------------------------- /src/watchers/index.ts: -------------------------------------------------------------------------------- 1 | export {CanvasWatcher} from "./canvas"; 2 | export {CompilerWatcher} from "./compiler"; 3 | export {HTMLWatcher} from "./html"; 4 | export {NotifyWatcher} from "./notify"; 5 | export {FileWatcher} from "./file"; 6 | export {ConsoleWatcher} from "./console"; 7 | export {ShapeWatcher} from "./shape"; 8 | export {SVGWatcher} from "./svg"; 9 | export {SystemWatcher} from "./system"; 10 | export {TagBrowserWatcher} from "./tag-browser"; 11 | export {UIWatcher} from "./ui"; 12 | -------------------------------------------------------------------------------- /src/watchers/notify.ts: -------------------------------------------------------------------------------- 1 | import {Program, Watcher, RawValue, RawMap, RawEAVC} from "./watcher"; 2 | import {HTMLWatcher} from "./html"; 3 | 4 | export class Notice { 5 | element:HTMLElement = document.createElement("notice"); 6 | name:string; 7 | time:number; 8 | 9 | constructor(public program:Program, public id:RawValue, public type:RawValue) { 10 | let html = program.attach("html") as HTMLWatcher; 11 | html.addExternalRoot(id as string, this.element); 12 | } 13 | 14 | clear() { 15 | let parent = this.element.parentElement; 16 | if(parent) parent.removeChild(this.element); 17 | // @FIXME: html.removeExternalRoot. 18 | } 19 | } 20 | 21 | // @FIXME: do tihs with two program isolation instead of manual rendering? 22 | 23 | export class NotifyWatcher extends Watcher { 24 | html:HTMLWatcher; 25 | notices:RawMap = {}; 26 | root:HTMLElement; 27 | scroller:HTMLElement; 28 | wrapper:HTMLElement; 29 | 30 | setup() { 31 | this.html = this.program.attach("html") as HTMLWatcher; 32 | 33 | this.wrapper = document.createElement("div"); 34 | this.wrapper.className = "notify-wrapper"; 35 | document.body.appendChild(this.wrapper); 36 | this.scroller = document.createElement("div"); 37 | this.scroller.className = "notify-scroller"; 38 | this.wrapper.appendChild(this.scroller); 39 | 40 | this.root = document.createElement("column"); 41 | this.root.className = "notify-root ui-column"; 42 | this.scroller.appendChild(this.root); 43 | this.html.addExternalRoot("notify/root", this.root); 44 | 45 | this.program 46 | .bind("Notices that aren't dismissed are children of the notify root.", ({find, not}) => { 47 | let root = find("notify/root"); 48 | let wrapper = find("notify/notice-wrapper"); 49 | not(() => wrapper.notice.tag == "notify/dismissed"); 50 | return [root.add("children", wrapper)]; 51 | }) 52 | .bind("Decorate notices.", ({find, choose, record}) => { 53 | let notice = find("notify/notice"); 54 | let type = choose(() => notice.type, () => "info"); 55 | return [ 56 | record("notify/notice-wrapper", "ui/row", {notice, type}).add("children", [ 57 | notice.add("tag", "ui/row"), 58 | record("ui/spacer", {sort: 5, notice}) 59 | ]) 60 | ]; 61 | }) 62 | .bind("Notices which are dismissable get a button to do so.", ({find, record}) => { 63 | let notice = find("notify/notice", "notify/dismissible"); 64 | let wrapper = find("notify/notice-wrapper", {notice}); 65 | return [wrapper.add("children", [ 66 | record("notify/dismiss", "ui/button", {class: "flat", notice, sort: 15, icon: "close-round"}) 67 | ])]; 68 | }) 69 | .commit("Dismissed notices are marked.", ({find}) => { 70 | let notice = find("notify/notice"); 71 | find("html/event/click", {element: find("notify/dismiss", {notice})}); 72 | return [notice.remove().add("tag", "notify/dismissed")]; 73 | }) 74 | .bind("If a notice has a timestamp, display it.", ({find, lib:{date}, record}) => { 75 | let notice = find("notify/notice"); 76 | let wrapper = find("notify/notice-wrapper", {notice}); 77 | return [ 78 | wrapper.add("children", [ 79 | record("ui/text", {sort: 10, notice, text: date.format(notice.timestamp, "HH:MM:ss")}) 80 | ]) 81 | ]; 82 | }) 83 | .commit("Retract timestamps for bound notices that have ceased to be.", ({find}) => { 84 | let {notice} = find("notify/retract-timestamp"); 85 | return [notice.remove("timestamp")]; 86 | }) 87 | .watch("The notify watcher attaches a timestamp to notices without one.", ({find, not}) => { 88 | let notice = find("notify/notice"); 89 | not(() => notice.timestamp); 90 | return [notice.add("tag", "notify/notice")]; 91 | }) 92 | .asDiffs(({adds}) => { 93 | let timestamp = Date.now(); 94 | let eavs:RawEAVC[] = []; 95 | for(let [notice] of adds) eavs.push([notice, "timestamp", timestamp, 1]); 96 | if(eavs.length) this.program.inputEAVs(eavs); 97 | }) 98 | .watch("The notify watcher also cleans up those timestamps when the notice goes away.", ({find}) => { 99 | let notice = find("notify/notice"); 100 | return [notice.add("timestamp", notice.timestamp)]; 101 | }) 102 | .asDiffs(({removes}) => { 103 | if(!removes.length) return; 104 | let eavs:RawEAVC[] = []; 105 | eavs.push(["||notify/retract-timestamp", "tag", "notify/retract-timestamp", 1]); 106 | for(let [notice, _, timestamp] of removes) { 107 | eavs.push([notice, "timestamp", timestamp, -1]); 108 | //eavs.push(["||notify/retract-timestamp", "notice", notice, 1]); 109 | } 110 | if(eavs.length) this.program.inputEAVs(eavs); 111 | }) 112 | } 113 | } 114 | 115 | Watcher.register("notify", NotifyWatcher); 116 | -------------------------------------------------------------------------------- /src/watchers/shape.ts: -------------------------------------------------------------------------------- 1 | import {Watcher, RawValue, RawEAV, RawEAVC} from "./watcher"; 2 | 3 | export class ShapeWatcher extends Watcher { 4 | setup() { 5 | this.program.attach("html"); 6 | this.program.attach("canvas"); 7 | this.hexagon(); 8 | this.hexGrid(); 9 | 10 | this.squarePath(); 11 | this.hexagonPath(); 12 | } 13 | 14 | //-------------------------------------------------------------------- 15 | // #shape/hexagon 16 | //-------------------------------------------------------------------- 17 | hexagonHTML() { 18 | this.program 19 | .bind("Draw a hexagon", ({find, choose, record, lib: {math}}) => { 20 | let hex = find("shape/hexagon"); 21 | let {side} = hex; 22 | 23 | let tri_height = math.round(side * 0.5); // sin(30deg) 24 | let tri_width = math.round(side * 0.86603); // cos(30deg) 25 | let width = 2 * tri_width; 26 | 27 | let [background] = choose( 28 | () => {hex.tag == "shape/outline"; return hex.border}, 29 | () => hex.background 30 | ); 31 | 32 | let sideBorder = `${tri_width}px`; 33 | let activeBorder = `${tri_height}px`; 34 | 35 | return [ 36 | hex.add({tag: "html/element", tagname: "div", class: "shape-hexagon", style: record({width: `${width}px`}), children: [ 37 | record("shape/hexagon/cap", "html/element", {sort: 1, tagname: "div", class: ["shape-hexagon-cap", "first"], style: record({ 38 | width: 0, height: 0, 39 | "border": "0 solid transparent", 40 | "border-left-width": sideBorder, "border-right-width": sideBorder, 41 | "border-bottom-width": activeBorder, "border-bottom-color": background 42 | })}), 43 | record("shape/hexagon/body", "ui/column", {hex, sort: 2, style: record({height: `${side}px`, width: `${width}`, background}), class: "shape-hexagon-body"}), 44 | record("shape/hexagon/cap", "html/element", {sort: 3, tagname: "div", class: ["shape-hexagon-cap", "last"], style: record({ 45 | width: 0, height: 0, 46 | "border": "0 solid transparent", 47 | "border-left-width": sideBorder, "border-right-width": sideBorder, 48 | "border-top-width": activeBorder, "border-top-color": background 49 | })}), 50 | ]}) 51 | ]; 52 | }) 53 | 54 | .bind("Hexagons with border and thickness are outlined.", ({find}) => { 55 | let hex = find("shape/hexagon"); 56 | hex.border; 57 | hex.thickness; 58 | return [ 59 | hex.add("tag", "shape/outline") 60 | ]; 61 | }) 62 | 63 | .bind("An outlined hexagon contains another hexagon inset by thickness.", ({find, record}) => { 64 | let hex = find("shape/hexagon", "shape/outline"); 65 | let {thickness} = hex; 66 | let side = hex.side - thickness; 67 | let side_thickness = thickness * 0.86603; // cos(30deg) 68 | return [ 69 | hex.add("children", [ 70 | record("shape/hexagon", "shape/hexagon/inner", {outer: hex, sort: 4, side, background: hex.background, class: "shape-hexagon-inner", style: record({ 71 | position: "absolute", top: 0, left: 0, "margin-top": `${thickness}px`, "margin-left": `${side_thickness}` 72 | })}) 73 | ]) 74 | ]; 75 | }) 76 | 77 | .bind("Populate hexagon with content", ({find, not}) => { 78 | let hex_body = find("shape/hexagon/body"); 79 | not(() => hex_body.hex.tag == "shape/outline") 80 | let {content} = hex_body.hex; 81 | return [ 82 | hex_body.add("children", [ 83 | content 84 | ]) 85 | ]; 86 | }) 87 | 88 | .bind("Populate an outlined hexagon's inner with content", ({find}) => { 89 | let hex_inner = find("shape/hexagon/inner"); 90 | return [ 91 | hex_inner.add("content", hex_inner.outer.content) 92 | ]; 93 | }); 94 | } 95 | 96 | //-------------------------------------------------------------------- 97 | // #shape/hex-grid 98 | //-------------------------------------------------------------------- 99 | hexGrid() { 100 | // [#hex-grid cells side gap] 101 | this.program.bind("Decorate all the hex-grid cells as hexagons.", ({find, lib:{math}, record}) => { 102 | let hex_grid = find("shape/hex-grid"); 103 | 104 | let {side, gap} = hex_grid; 105 | let {cell} = hex_grid; 106 | let {x:x_ix, y:y_ix} = cell; 107 | 108 | let top_gap = gap * 0.86603; // sin(60deg) 109 | 110 | let tri_height = side * 0.5; 111 | let tri_width = side * 0.86603; 112 | 113 | let width = math.round(2 * tri_width + gap); 114 | let x_offset = math.round(math.mod(math.absolute(y_ix), 2) * width / 2); 115 | let height = math.round(side + tri_height + top_gap); 116 | 117 | let x = math.round(width * x_ix + x_offset); 118 | let y = math.round(height * y_ix); 119 | 120 | return [ 121 | hex_grid.add({tag: "html/element", tagname: "div", class: "shape-hex-grid"}), 122 | hex_grid.add("children", [ 123 | cell.add({ 124 | style: record({position: "absolute", left: `${x}px`, top: `${y}px`}) 125 | }) 126 | ]) 127 | ]; 128 | }); 129 | } 130 | 131 | hexagon() { 132 | this.program 133 | .bind("Decorate a shape/hexagon as a canvas.", ({find, choose, lib:{math}, record}) => { 134 | let hex = find("shape/hexagon"); 135 | let {side} = hex; 136 | let tri_height = side * 0.5; 137 | let tri_width = side * 0.86603; 138 | let [pad] = choose(() => hex.lineWidth, () => 0); 139 | let dpad = 2 * pad; 140 | let width = math.ceiling(2 * tri_width + dpad); 141 | let height = math.ceiling(2 * side + dpad); 142 | 143 | 144 | return [ 145 | hex.add({tag: "html/element", tagname: "div", style: record({width: `${width}px`, height: `${height}px`})}).add("children", [ 146 | record("canvas/root", {sort: 1, hex, width: `${width}px`, height: `${height}px`}).add("children", [ 147 | record("shape/hexagon-path", {sort: 1, hex, x: pad, y: pad, side}) 148 | ]), 149 | 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`})}) 150 | ]) 151 | ]; 152 | }) 153 | .bind("Copy hexagon content into it's appropriate container.", ({find, record}) => { 154 | let hex = find("shape/hexagon"); 155 | let container = find("shape/hexagon/content", {hex}); 156 | return [container.add("children", hex.content)]; 157 | }) 158 | .bind("Copy style properties onto hexagon path.", ({find, lookup}) => { 159 | let hex = find("shape/hexagon"); 160 | let path = find("shape/hexagon-path", {hex}); 161 | let {attribute, value} = lookup(hex); 162 | attribute != "tag"; 163 | attribute != "class"; 164 | attribute != "tagname"; 165 | attribute != "style"; 166 | attribute != "children"; 167 | attribute != "x"; 168 | attribute != "y"; 169 | return [path.add(attribute, value)]; 170 | }) 171 | } 172 | 173 | squarePath() { 174 | this.program.bind("Decorate a shape/square-path as a canvas/path", ({find, record}) => { 175 | let square = find("shape/square-path"); 176 | let {x, y, side} = square; 177 | return [ 178 | square.add({tag: "canvas/path"}).add("children", [ 179 | record({sort: 1, type: "rect", x, y, width: side, height: side}), 180 | ]) 181 | ]; 182 | }); 183 | } 184 | 185 | hexagonPath() { 186 | this.program.bind("Decorate shape/hexagon-path as a canvas/path", ({find, record}) => { 187 | let hex = find("shape/hexagon-path"); 188 | let {x, y, side} = hex; 189 | 190 | let tri_height = side * 0.5; 191 | let tri_width = side * 0.86603; 192 | let width = 2 * tri_width; 193 | 194 | let xl = x + width; 195 | let xm = x + tri_width; 196 | let y14 = y + tri_height; 197 | let y34 = y + 3 * tri_height; 198 | let yb = y + 2 * side; 199 | return [ 200 | hex.add({tag: "canvas/path"}).add("children", [ 201 | record({sort: 1, type: "moveTo", x: xm, y}), 202 | record({sort: 2, type: "lineTo", x: xl, y: y14}), 203 | record({sort: 3, type: "lineTo", x: xl, y: y34}), 204 | record({sort: 4, type: "lineTo", x: xm, y: yb}), 205 | record({sort: 5, type: "lineTo", x, y: y34}), 206 | record({sort: 6, type: "lineTo", x, y: y14}), 207 | record({sort: 7, type: "closePath"}), 208 | ]) 209 | ]; 210 | }) 211 | } 212 | } 213 | 214 | Watcher.register("shape", ShapeWatcher); 215 | -------------------------------------------------------------------------------- /src/watchers/svg.ts: -------------------------------------------------------------------------------- 1 | import {Watcher, RawValue, RawEAV, RawEAVC} from "./watcher"; 2 | import {DOMWatcher, ElemInstance} from "./dom"; 3 | import {HTMLWatcher} from "./html"; 4 | 5 | export interface Instance extends SVGElement {__element?: RawValue, __styles?: RawValue[], __sort?: RawValue} 6 | 7 | 8 | export class SVGWatcher extends DOMWatcher { 9 | tagPrefix = "svg"; 10 | html:HTMLWatcher; 11 | 12 | createInstance(id:RawValue, element:RawValue, tagname:RawValue):Instance { 13 | let elem:Instance = document.createElementNS("http://www.w3.org/2000/svg", tagname as string); 14 | elem.__element = element; 15 | elem.__styles = []; 16 | return elem; 17 | } 18 | 19 | getInstance(id:RawValue):Instance|undefined { 20 | if(this.instances[id]) return this.instances[id]; 21 | if(this.html.instances[id]) return this.html.instances[id] as any; 22 | } 23 | 24 | createRoot(id:RawValue) { 25 | // This is delegated to the HTML watcher for #svg/roots, and it's nonsensical to try to make any other svg element a root. 26 | return undefined; 27 | } 28 | 29 | addAttribute(instance:Instance, attribute:RawValue, value:RawValue):void { 30 | // @TODO: Namespace attributes as appropriate. 31 | instance.setAttribute(attribute as string, value as string); 32 | } 33 | 34 | removeAttribute(instance:Instance, attribute:RawValue, value:RawValue):void { 35 | // @TODO: Namespace attributes as appropriate. 36 | instance.removeAttribute(attribute as string); 37 | } 38 | 39 | setup() { 40 | this.html = this.program.attach("html") as HTMLWatcher; 41 | this.tagPrefix = "svg"; 42 | super.setup(); 43 | this.program 44 | .bind("Create an instance for each child of an svg/root.", ({find, record, lib}) => { 45 | let elem = find("svg/element"); 46 | let parentElem = find("svg/root", {children: elem}); 47 | let parent = find("html/instance", {element: parentElem}); 48 | 49 | return [ 50 | record("svg/instance", {element: elem, tagname: elem.tagname, parent}) 51 | ]; 52 | }) 53 | 54 | .bind("Decorate a svg roots as html.", ({find}) => { 55 | let elem = find("svg/root"); 56 | return [elem.add({tag: "html/element", tagname: "svg"})]; 57 | }) 58 | .bind("Decorate line as svg.", ({find}) => { 59 | let elem = find("svg/line"); 60 | return [elem.add({tag: "svg/element", tagname: "line"})]; 61 | }) 62 | .bind("Decorate circle as svg.", ({find}) => { 63 | let elem = find("svg/circle"); 64 | return [elem.add({tag: "svg/element", tagname: "circle"})]; 65 | }) 66 | .bind("Decorate rect as svg.", ({find}) => { 67 | let elem = find("svg/rect"); 68 | return [elem.add({tag: "svg/element", tagname: "rect"})]; 69 | }) 70 | .bind("Decorate text as svg.", ({find}) => { 71 | let elem = find("svg/text"); 72 | return [elem.add({tag: "svg/element", tagname: "text"})]; 73 | }) 74 | .bind("Decorate image as svg.", ({find}) => { 75 | let elem = find("svg/image"); 76 | return [elem.add({tag: "svg/element", tagname: "image"})]; 77 | }); 78 | } 79 | } 80 | 81 | Watcher.register("svg", SVGWatcher); 82 | -------------------------------------------------------------------------------- /src/watchers/system.ts: -------------------------------------------------------------------------------- 1 | import {Watcher} from "./watcher"; 2 | import {ID} from "../runtime/runtime"; 3 | 4 | export class SystemWatcher extends Watcher { 5 | timers:{[key:string]: {timer:any, prev:Date|undefined, tick:number}} = {}; 6 | 7 | getTime(changes:any[], timer:ID, tick:number, date?:Date) { 8 | let multiplicity = -1; 9 | if(!date) { 10 | multiplicity = 1; 11 | date = new Date(); 12 | } 13 | changes.push( 14 | [timer, "year", date.getFullYear(), multiplicity], 15 | [timer, "month", date.getMonth() + 1, multiplicity], 16 | [timer, "day", date.getDate(), multiplicity], 17 | [timer, "weekday", date.getDay() + 1, multiplicity], 18 | [timer, "hour", date.getHours(), multiplicity], 19 | [timer, "minute", date.getMinutes(), multiplicity], 20 | [timer, "second", date.getSeconds(), multiplicity], 21 | [timer, "millisecond", date.getMilliseconds(), multiplicity], 22 | [timer, "timestamp", date.getTime(), multiplicity], 23 | [timer, "tick", tick, multiplicity], 24 | ); 25 | return date; 26 | } 27 | 28 | setup() { 29 | let {program:me} = this; 30 | 31 | me.watch("setup timers", ({find, record}) => { 32 | let timer = find("system/timer"); 33 | return [ 34 | record({timer, resolution: timer.resolution}) 35 | ] 36 | }) 37 | 38 | me.asObjects<{timer:ID, resolution:number}>(({adds, removes}) => { 39 | Object.keys(adds).forEach((id) => { 40 | let {timer, resolution} = adds[id]; 41 | let prev:Date; 42 | let timerHandle = setInterval(() => { 43 | let {prev, tick} = this.timers[id]; 44 | let changes:any[] = []; 45 | if(prev) { 46 | this.getTime(changes, timer, tick, prev) 47 | } 48 | this.timers[id].tick = ++tick; 49 | this.timers[id].prev = this.getTime(changes, timer, tick); 50 | me.inputEAVs(changes); 51 | }, resolution); 52 | this.timers[id] = {timer:timerHandle, prev:undefined, tick:0}; 53 | }) 54 | console.log("GOT TIMER!", adds); 55 | }) 56 | } 57 | } 58 | 59 | Watcher.register("system", SystemWatcher); 60 | -------------------------------------------------------------------------------- /src/watchers/tag-browser.ts: -------------------------------------------------------------------------------- 1 | import {Watcher, Program, RawMap, RawValue, RawEAVC} from "./watcher"; 2 | 3 | import {UIWatcher} from "../watchers/ui"; 4 | 5 | interface Attrs extends RawMap {} 6 | 7 | function t(tag:string) { 8 | return `tag-browser/${tag}`; 9 | } 10 | 11 | function collapse(...args:T[]):T { 12 | let all:T = [] as any; 13 | for(let sublist of args) { 14 | for(let item of sublist) { 15 | all.push(item); 16 | } 17 | } 18 | return all; 19 | } 20 | 21 | export class TagBrowserWatcher extends Watcher { 22 | browser:Program = this.createTagBrowser(); 23 | 24 | setup() { 25 | this.program 26 | .watch("Export all tags", ({find, record}) => { 27 | let rec = find(); 28 | return [ 29 | record("child-tag", {"child-tag": rec.tag}) 30 | ]; 31 | }) 32 | .asDiffs((diffs) => { 33 | let eavs:RawEAVC[] = []; 34 | for(let [e, a, v] of diffs.removes) { 35 | eavs.push([e, a, v, -1]); 36 | } 37 | for(let [e, a, v] of diffs.adds) { 38 | eavs.push([e, a, v, 1]); 39 | } 40 | if(eavs.length) { 41 | this.browser.inputEAVs(eavs); 42 | } 43 | }) 44 | 45 | // .watch("Export records with active tags", ({find, lookup, record}) => { 46 | // let {"active-tag": activeTag} = find("tag-browser/active-tag"); 47 | // let rec = find({tag: activeTag}); 48 | // let {attribute, value} = lookup(rec); 49 | // return [ 50 | // rec.add("tag", "child-record").add(attribute, value) 51 | // ]; 52 | // }) 53 | 54 | .watch("Export all records", ({find, lookup, choose, record}) => { 55 | let rec = find(); 56 | let {attribute, value} = lookup(rec); 57 | // let [attrName] = choose( 58 | // () => { attribute == "tag"; return "child-tag"; }, 59 | // () => attribute 60 | // ); 61 | 62 | return [ 63 | rec.add("tag", "child-record").add(attribute, value) 64 | ]; 65 | }) 66 | .asDiffs((diffs) => { 67 | let eavs:RawEAVC[] = []; 68 | for(let [e, a, v] of diffs.removes) { 69 | // @NOTE: this breaks tag-browser inspecting tag-browser 70 | if(a === "tag" && v !== "child-record") a = "child-tag"; 71 | eavs.push([e, a, v, -1]); 72 | } 73 | for(let [e, a, v] of diffs.adds) { 74 | // @NOTE: this breaks tag-browser inspecting tag-browser 75 | if(a === "tag" && v !== "child-record") a = "child-tag"; 76 | eavs.push([e, a, v, 1]); 77 | } 78 | if(eavs.length) { 79 | this.browser.inputEAVs(eavs); 80 | } 81 | }) 82 | } 83 | 84 | createTagBrowser() { 85 | let prog = new Program("Tag Browser"); 86 | prog.attach("ui"); 87 | let {$style, $row, $column, $text, $elem} = UIWatcher.helpers; 88 | 89 | //-------------------------------------------------------------------- 90 | // Custom UI Components 91 | //-------------------------------------------------------------------- 92 | 93 | // Tag Button 94 | prog 95 | .bind("Tag button component", ({find, record}) => { 96 | let tagButton = find("tag-browser/tag"); 97 | let tag = tagButton.target; 98 | return [ 99 | tagButton.add({tag: "ui/button", class: "inset", text: tag, sort: tag}) 100 | ]; 101 | }) 102 | .commit("When a tag button is clicked, update the view target.", ({find, record}) => { 103 | let click = find("html/event/click"); 104 | let {element} = click; 105 | element.tag == "tag-browser/tag"; 106 | let view = find("tag-browser/view"); 107 | 108 | return [ 109 | view.remove("target").add("target", element.target) 110 | ]; 111 | }) 112 | // @FIXME: Work around for a bug that occurs when leaving a record open and then letting it be retracted and reasserted 113 | // .commit("When a tag button is clicked, clear any open child records", ({find, record}) => { 114 | // let click = find("html/event/click"); 115 | // let {element} = click; 116 | // element.tag == "tag-browser/tag"; 117 | // //let view = find("tag-browser/view"); 118 | // //view.target != element.target; 119 | 120 | // let openChildren = find("tag-browser/record"); 121 | // openChildren.open; 122 | 123 | // return [ 124 | // openChildren.remove("open") 125 | // ]; 126 | // }); 127 | 128 | // Record 129 | prog 130 | .bind("Record component (closed)", ({find, record}) => { 131 | let childRecord = find("tag-browser/record"); 132 | 133 | return [ 134 | childRecord.add({ 135 | tag: "ui/column", 136 | style: record({border: "1px solid gray", margin: 10, padding: 10}), 137 | rec: childRecord.target, 138 | 139 | children: [ 140 | record("ui/row", "tag-browser/record-header", {sort: 0, rec: childRecord.target}).add("children", [ 141 | record("html/element", {tagname: "div", class: "hexagon"}), 142 | record("ui/text", {rec: childRecord.target, text: childRecord.target.displayName}) 143 | ]) 144 | ] 145 | }) 146 | ]; 147 | }) 148 | 149 | .bind("Record component (open)", ({find, lookup, record}) => { 150 | let childRecord = find("tag-browser/record", {open: "true"}); 151 | let {attribute, value} = lookup(childRecord.target); 152 | attribute != "tag"; 153 | return [ 154 | childRecord.add({ 155 | children: [ 156 | record("tag-browser/record-attribute", {rec: childRecord.target, attr: attribute}).add("val", value) 157 | ] 158 | }) 159 | ]; 160 | }) 161 | 162 | .commit("Clicking a record toggles it open/closed", ({find, choose}) => { 163 | let recordHeader = find("tag-browser/record-header"); 164 | let record = find("tag-browser/record", {children: recordHeader}); 165 | find("html/event/click", {element: recordHeader}); 166 | let [open] = choose( 167 | () => { record.open == "true"; return "false"; }, 168 | () => "true" 169 | ) 170 | 171 | return [ 172 | record.remove("open").add("open", open) 173 | ]; 174 | }); 175 | 176 | // Record Attribute 177 | 178 | prog 179 | .bind("Record attribute component", ({find, choose, record}) => { 180 | let recordAttribute = find("tag-browser/record-attribute"); 181 | let {target} = find("tag-browser/view") 182 | 183 | let [attrName] = choose( 184 | () => { recordAttribute.attr == "child-tag"; return "tag"; }, 185 | () => recordAttribute.attr 186 | ); 187 | 188 | let {rec, val} = recordAttribute; 189 | return [ 190 | recordAttribute.add({tag: "ui/row", 191 | children: [ 192 | record("ui/text", {sort: 0, text: `${attrName}:`, rec, style: record({"margin-right": 10})}), 193 | record("ui/column", {sort: 1, rec, attrName}) // @FIXME: These attrs from the parent shouldn't need to be on the children. 194 | .add("children", record("tag-browser/record-value", {rec, attr: attrName, val, "active-input":"foo"})) 195 | ] 196 | }) 197 | ]; 198 | }); 199 | 200 | // Record Value 201 | prog 202 | .bind("Record value component (as tag)", ({find, record}) => { 203 | let recordValue = find("tag-browser/record-value"); 204 | let {val} = recordValue; 205 | let childTag = find("child-tag", {"child-tag": val}); 206 | return [ 207 | recordValue.add({tag: "tag-browser/tag", target: val}) 208 | ]; 209 | }) 210 | 211 | .bind("Record value component (as record)", ({find, record}) => { 212 | let recordValue = find("tag-browser/record-value"); 213 | let childRecord = find("child-record"); 214 | childRecord == recordValue.val; 215 | return [ 216 | recordValue.add({tag: "tag-browser/record", target: childRecord}) 217 | ]; 218 | }) 219 | 220 | .bind("Record value component (as raw value)", ({find, not, record}) => { 221 | let recordValue = find("tag-browser/record-value"); 222 | // @FIXME: Not is *still* busted 223 | not(() => recordValue.tag == "tag-browser/tag"); 224 | not(() => recordValue.tag == "tag-browser/record"); 225 | let {val} = recordValue; 226 | return [ 227 | recordValue.add({tag: "ui/text", sort: val, text: val}) 228 | ]; 229 | }) 230 | 231 | // Tag Cloud 232 | prog.bind("List all tags in the tag cloud.", ({find, not, record}) => { 233 | let cloud = find("tag-browser/cloud"); 234 | let rec = find("child-tag"); 235 | let tag = rec["child-tag"]; 236 | 237 | return [ 238 | cloud.add("children", record("tag-browser/tag", {target: tag})) 239 | ]; 240 | }); 241 | 242 | // Tag View 243 | prog 244 | .bind("Show the targeted tag", ({find, record}) => { 245 | let view = find("tag-browser/view"); 246 | let target = view.target; 247 | 248 | return [ 249 | view.add("children", record("ui/text", {text: `Current tag: ${target}`, sort: 0})) 250 | ] 251 | }) 252 | .bind("Show records with the targeted tag", ({find, not, record}) => { 253 | let view = find("tag-browser/view"); 254 | let targetedRecord = find("child-record", {"child-tag": view.target}); 255 | 256 | return [ 257 | view.add("children", record("ui/row", { 258 | sort: 1, 259 | style: record({"flex-wrap": "wrap", "align-items": "flex-start"}), 260 | }).add("children", record("tag-browser/record", {target: targetedRecord, "active-tag": view.target}))) 261 | ]; 262 | }) 263 | .watch("When the view target changes, mark it as the active tag in the child program", ({find, record}) => { 264 | let view = find("tag-browser/view"); 265 | 266 | return [ 267 | record("tag-browser/active-tag", {"active-tag": view.target}) 268 | ]; 269 | }) 270 | .asDiffs((diffs) => { 271 | let eavs:RawEAVC[] = []; 272 | for(let [e, a, v] of diffs.removes) { 273 | eavs.push([e, a, v, -1]); 274 | } 275 | for(let [e, a, v] of diffs.adds) { 276 | eavs.push([e, a, v, 1]); 277 | } 278 | if(eavs.length) { 279 | this.program.inputEAVs(eavs); 280 | } 281 | }); 282 | 283 | // Display Name aliasing 284 | prog 285 | .bind("Alias display names", ({find, choose}) => { 286 | let record = find("child-record"); 287 | let [name] = choose( 288 | //() => record.displayName, 289 | () => record.name, 290 | () => "???" 291 | ); 292 | return [ 293 | record.add("displayName", name) 294 | ]; 295 | }) 296 | 297 | // Create root UI 298 | let changes = collapse( 299 | $column({tag: t("root")}, [ 300 | $row({tag: t("cloud"), style: $style({"flex-wrap": "wrap"})}, []), 301 | $column({tag: t("view")}, []) 302 | ]), 303 | 304 | $elem("html/element", { 305 | tagname: "style", 306 | text: ` 307 | .hexagon { 308 | width: 22px; 309 | height: 22px; 310 | margin-right: 5px; 311 | vertical-align: middle; 312 | border-radius: 100px; 313 | border: 2px solid #AAA; 314 | } 315 | ` 316 | }) 317 | ); 318 | prog.inputEAVs(changes); 319 | 320 | return prog; 321 | } 322 | } 323 | 324 | Watcher.register("tag browser", TagBrowserWatcher); 325 | -------------------------------------------------------------------------------- /src/watchers/watcher.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | export {RawValue, RawEAV, RawEAVC} from "../runtime/runtime"; 3 | import {ID, GlobalInterner, RawValue, RawEAV, RawEAVC, Change, createArray, ExportHandler} from "../runtime/runtime"; 4 | export {Program} from "../runtime/dsl2"; 5 | import {Program, LinearFlowFunction} from "../runtime/dsl2"; 6 | import {v4 as uuid} from "uuid"; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Watcher 10 | //------------------------------------------------------------------------------ 11 | 12 | export class Watcher { 13 | protected static _registry:{[id:string]: typeof Watcher} = {}; 14 | 15 | static register(id:string, watcher:typeof Watcher) { 16 | if(this._registry[id]) { 17 | if(this._registry[id] === watcher) return; 18 | throw new Error(`Attempting to overwrite existing watcher with id '${id}'`); 19 | } 20 | this._registry[id] = watcher; 21 | } 22 | 23 | static unregister(id:string) { 24 | delete this._registry[id]; 25 | } 26 | 27 | static get(id:string) { 28 | let watcher = this._registry[id]; 29 | if(watcher) return watcher; 30 | } 31 | 32 | get program() { return this._program; } 33 | 34 | constructor(protected _program:Program) { 35 | this.setup(); 36 | } 37 | 38 | setup() {} 39 | } 40 | 41 | //------------------------------------------------------------------------------ 42 | // Exporter 43 | //------------------------------------------------------------------------------ 44 | 45 | export interface Map {[key:number]: V}; 46 | export interface RawMap {[key:string]: V, [key:number]: V}; 47 | export interface RawRecord extends RawMap {} 48 | 49 | export interface Diffs {adds: V, removes: V}; 50 | export interface EAVDiffs extends Diffs {} 51 | export interface ObjectDiffs extends Diffs> {} 52 | 53 | export type DiffConsumer = (diffs:EAVDiffs) => void; 54 | export type ObjectConsumer = (diffs:ObjectDiffs) => void; 55 | 56 | export class Exporter { 57 | protected _diffTriggers:Map = {}; 58 | protected _objectTriggers:Map[]> = {}; 59 | protected _blocks:ID[] = []; 60 | 61 | triggerOnDiffs(blockId:ID, handler:DiffConsumer):void { 62 | if(!this._diffTriggers[blockId]) this._diffTriggers[blockId] = createArray(); 63 | if(this._diffTriggers[blockId].indexOf(handler) === -1) { 64 | this._diffTriggers[blockId].push(handler); 65 | } 66 | if(this._blocks.indexOf(blockId) === -1) { 67 | this._blocks.push(blockId); 68 | } 69 | } 70 | 71 | triggerOnObjects(blockId:ID, handler:ObjectConsumer):void { 72 | if(!this._objectTriggers[blockId]) this._objectTriggers[blockId] = createArray(); 73 | if(this._objectTriggers[blockId].indexOf(handler) === -1) { 74 | this._objectTriggers[blockId].push(handler); 75 | } 76 | if(this._blocks.indexOf(blockId) === -1) { 77 | this._blocks.push(blockId); 78 | } 79 | } 80 | 81 | accumulateChangesAs(changes:Change[]) { 82 | let adds:RawMap = {}; 83 | let removes:RawMap = {}; 84 | 85 | for(let change of changes) { 86 | let {e, a, v, count} = change.reverse(); 87 | if(count === 1) { 88 | let record = adds[e] = adds[e] || Object.create(null); 89 | if(record[a]) throw new Error("@FIXME: accumulateChanges supports only a single value per attribute."); 90 | record[a] = v; 91 | } else { 92 | let record = removes[e] = removes[e] || Object.create(null); 93 | if(record[a]) throw new Error("@FIXME: accumulateChanges supports only a single value per attribute."); 94 | record[a] = v; 95 | } 96 | } 97 | 98 | return {adds, removes}; 99 | } 100 | 101 | handle:ExportHandler = (blockChanges) => { 102 | for(let blockId of this._blocks) { 103 | let changes = blockChanges[blockId]; 104 | if(changes && changes.length) { 105 | let diffTriggers = this._diffTriggers[blockId]; 106 | if(diffTriggers) { 107 | let output:EAVDiffs = {adds: [], removes: []}; 108 | for(let change of changes) { 109 | let eav = change.toRawEAV(); 110 | if(change.count > 0) { 111 | output.adds.push(eav); 112 | } else { 113 | output.removes.push(eav); 114 | } 115 | } 116 | 117 | for(let trigger of diffTriggers) { 118 | trigger(output); 119 | } 120 | } 121 | 122 | let objectTriggers = this._objectTriggers[blockId]; 123 | if(objectTriggers) { 124 | let output:ObjectDiffs<{}> = this.accumulateChangesAs<{}>(changes); 125 | for(let trigger of objectTriggers) { 126 | trigger(output); 127 | } 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | //------------------------------------------------------------------------------ 135 | // Convenience Diff Handlers 136 | //------------------------------------------------------------------------------ 137 | 138 | export function _isId(value?:RawValue):boolean { 139 | return (""+value).indexOf("|") != -1; 140 | } 141 | 142 | export function maybeIntern(value?:RawValue):ID|RawValue|undefined { 143 | if(value === undefined) return value; 144 | let interned = _isId(value) ? GlobalInterner.get(value) : undefined 145 | return interned !== undefined ? interned : value; 146 | } 147 | 148 | export function asJS(value?:RawValue):number|string|boolean|undefined { 149 | if(typeof value === "number") return value; 150 | if(value === "true") return true; 151 | if(value === "false") return false; 152 | return value; 153 | } 154 | 155 | export function forwardDiffs(destination:Program, name:string = "Unnamed", debug = false) { 156 | return (diffs:EAVDiffs) => { 157 | let eavs:RawEAVC[] = []; 158 | for(let [e, a, v] of diffs.removes) { 159 | eavs.push([e, a, v, -1]); 160 | } 161 | for(let [e, a, v] of diffs.adds) { 162 | eavs.push([e, a, v, 1]); 163 | } 164 | if(eavs.length) { 165 | if(debug) { 166 | console.log("FWD", name, "=>", destination.name); 167 | console.log(eavs.map((c) => `[${c.map(maybeIntern).join(", ")}]`).join("\n")); 168 | } 169 | destination.inputEAVs(eavs); 170 | } 171 | }; 172 | } 173 | 174 | //-------------------------------------------------------------------- 175 | // Watcher / Program Utils 176 | //-------------------------------------------------------------------- 177 | 178 | export function createId() { 179 | return "|" + uuid(); 180 | } 181 | 182 | export function isRawValue(x:any): x is RawValue { 183 | return x !== undefined && (typeof x === "string" || typeof x === "number"); 184 | } 185 | 186 | export function isRawValueArray(x:any): x is RawValue[] { 187 | if(x && x.constructor === Array) { 188 | for(let value of x) { 189 | if(!isRawValue(value)) return false; 190 | } 191 | return true; 192 | } 193 | return false; 194 | } 195 | 196 | export function isRawEAVArray(x:any): x is RawEAV[] { 197 | if(x && x.constructor === Array) { 198 | for(let value of x) { 199 | if(!isRawValueArray(value)) return false; 200 | if(value.length !== 3) return false; 201 | } 202 | return true; 203 | } 204 | return false; 205 | } 206 | 207 | export function isRawEAVArraySet(x:any): x is RawEAV[][] { 208 | return x && x.constructor === Array && isRawEAVArray(x[0]); 209 | } 210 | 211 | export function isRecord(x:any): x is Attrs { 212 | return x && typeof x === "object" && x.constructor !== Array; 213 | } 214 | 215 | export function isRecordSet(x:any): x is Attrs[] { 216 | return x && x.constructor === Array && isRecord(x[0]); 217 | } 218 | 219 | export interface Attrs extends RawMap {} 220 | export function appendAsEAVs(eavs:any[], record: Attrs, id = createId()) { 221 | for(let attr in record) { 222 | let value = record[attr]; 223 | if(isRawValue(value)) { 224 | eavs.push([id, attr, value]); 225 | 226 | } else if(isRawValueArray(value)) { 227 | // We have a set of scalars 228 | for(let val of value) eavs.push([id, attr, val]); 229 | 230 | } else if(isRawEAVArray(value)) { 231 | // We have a single nested sub-object (i.e. a set of EAVs). 232 | let childEAVs = value; 233 | let [childId] = childEAVs[0]; 234 | eavs.push([id, attr, childId]); 235 | for(let childEAV of childEAVs) eavs.push(childEAV); 236 | 237 | } else if(isRawEAVArraySet(value)) { 238 | // We have a set of nested sub-objects. 239 | for(let childEAVs of value) { 240 | let [childId] = childEAVs[0]; 241 | eavs.push([id, attr, childId]); 242 | for(let childEAV of childEAVs) eavs.push(childEAV); 243 | } 244 | } else if(isRecord(value)) { 245 | let ix = eavs.length; 246 | appendAsEAVs(eavs, value); 247 | let [childId] = eavs[ix]; 248 | eavs.push([id, attr, childId]); 249 | } else if(isRecordSet(value)) { 250 | for(let record of value) { 251 | let ix = eavs.length; 252 | appendAsEAVs(eavs, record); 253 | let [childId] = eavs[ix]; 254 | eavs.push([id, attr, childId]); 255 | } 256 | } 257 | } 258 | 259 | return eavs; 260 | } 261 | -------------------------------------------------------------------------------- /syntax_diagrams.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 25 | -------------------------------------------------------------------------------- /test/all.ts: -------------------------------------------------------------------------------- 1 | import "./foundation"; 2 | import "./distinct"; 3 | import "./antijoin"; 4 | import "./choose"; 5 | import "./union"; 6 | import "./aggregate"; 7 | import "./stdlib/math"; 8 | // import "./performance"; 9 | -------------------------------------------------------------------------------- /test/antijoin.ts: -------------------------------------------------------------------------------- 1 | import {Program} from "../src/runtime/dsl2"; 2 | import {verify, createVerifier} from "./util"; 3 | import * as test from "tape"; 4 | 5 | function createProgram() { 6 | let prog = new Program("test"); 7 | prog.bind("simple block", ({find, record, not}) => { 8 | let left = find("left"); 9 | not(() => { 10 | find("right", {left}) 11 | }) 12 | return [ 13 | record("success") 14 | ] 15 | }); 16 | return prog; 17 | } 18 | 19 | test("Antijoin: simple left", (assert) => { 20 | let prog = createProgram(); 21 | 22 | verify(assert, prog, [ 23 | [1, "tag", "left"] 24 | ], [ 25 | [2, "tag", "success", 1] 26 | ]) 27 | 28 | assert.end(); 29 | }); 30 | 31 | test("Antijoin: simple right", (assert) => { 32 | let prog = createProgram(); 33 | 34 | verify(assert, prog, [ 35 | [1, "tag", "right"] 36 | ], [ 37 | // nothing 38 | ]) 39 | 40 | verify(assert, prog, [ 41 | [1, "left", 2] 42 | ], [ 43 | //nothing 44 | ]) 45 | 46 | verify(assert, prog, [ 47 | [2, "tag", "left"] 48 | ], [ 49 | //nothing 50 | ]) 51 | 52 | assert.end(); 53 | }); 54 | 55 | test("Antijoin: simple left then right", (assert) => { 56 | let prog = createProgram(); 57 | 58 | verify(assert, prog, [ 59 | [2, "tag", "left"] 60 | ], [ 61 | [3, "tag", "success", 1] 62 | ]) 63 | 64 | verify(assert, prog, [ 65 | [1, "tag", "right"], 66 | [1, "left", 2], 67 | ], [ 68 | [3, "tag", "success", 1, -1] 69 | ]) 70 | 71 | assert.end(); 72 | }); 73 | 74 | 75 | test("Antijoin: simple left then right same transaction", (assert) => { 76 | let prog = createProgram(); 77 | 78 | verify(assert, prog, [ 79 | [2, "tag", "left"], 80 | [1, "tag", "right", 1], 81 | [1, "left", 2, 1], 82 | ], [ 83 | [3, "tag", "success", 1], 84 | [3, "tag", "success", 2, -1] 85 | ]) 86 | 87 | assert.end(); 88 | }); 89 | 90 | test("Antijoin: simple right then left same transaction", (assert) => { 91 | let prog = createProgram(); 92 | 93 | verify(assert, prog, [ 94 | [1, "tag", "right"], 95 | [1, "left", 2], 96 | [2, "tag", "left", 1], 97 | ], [ 98 | // nothing 99 | ]) 100 | 101 | assert.end(); 102 | }); 103 | 104 | test("Antijoin: right -> left -> -right", (assert) => { 105 | let prog = createProgram(); 106 | 107 | verify(assert, prog, [ 108 | [1, "tag", "right"], 109 | [1, "left", 2], 110 | 111 | [2, "tag", "left", 1], 112 | 113 | [1, "tag", "right", 2, -1], 114 | [1, "left", 2, 2, -1], 115 | ], [ 116 | [3, "tag", "success", 3], 117 | ]) 118 | 119 | assert.end(); 120 | }); 121 | 122 | test("Antijoin: right -> right -> left -> -right", (assert) => { 123 | let prog = createProgram(); 124 | 125 | verify(assert, prog, [ 126 | [1, "tag", "right"], 127 | [1, "left", 2], 128 | 129 | [4, "tag", "right", 5], 130 | [4, "left", 2, 5], 131 | ], [ 132 | // nothing 133 | ]) 134 | 135 | verify(assert, prog, [ 136 | [2, "tag", "left", 2], 137 | [1, "tag", "right", 3, -1], 138 | [1, "left", 2, 3, -1], 139 | ], [ 140 | [3, "tag", "success", 4], 141 | [3, "tag", "success", 6, -1], 142 | ]) 143 | 144 | assert.end(); 145 | }); 146 | 147 | test("Antijoin: right -> -right -> right -> left", (assert) => { 148 | let prog = createProgram(); 149 | 150 | verify(assert, prog, [ 151 | [1, "tag", "right"], 152 | [1, "tag", "right", 3, -1], 153 | [4, "tag", "right", 5], 154 | 155 | [1, "left", 2], 156 | [1, "left", 2, 3, -1], 157 | [4, "left", 2, 5], 158 | ], [ 159 | // nothing 160 | ]) 161 | 162 | verify(assert, prog, [ 163 | [2, "tag", "left"], 164 | ], [ 165 | [3, "tag", "success", 4], 166 | [3, "tag", "success", 6, -1], 167 | ]) 168 | 169 | assert.end(); 170 | }); 171 | 172 | test("Antijoin: left -> right -> -right", (assert) => { 173 | let prog = createProgram(); 174 | 175 | verify(assert, prog, [ 176 | [2, "tag", "left"], 177 | [4, "tag", "right", 5], 178 | [4, "left", 2, 5], 179 | ], [ 180 | // nothing 181 | [3, "tag", "success", 1], 182 | [3, "tag", "success", 6, -1], 183 | ]) 184 | 185 | verify(assert, prog, [ 186 | [4, "tag", "right", 5, -1], 187 | [4, "left", 2, 5, -1], 188 | ], [ 189 | [3, "tag", "success", 6, 1], 190 | ]) 191 | 192 | assert.end(); 193 | }); 194 | 195 | test("Antijoin: right -> right -> left", (assert) => { 196 | let prog = createProgram(); 197 | 198 | verify(assert, prog, [ 199 | [1, "tag", "right", 4], 200 | [1, "left", 2, 4], 201 | [4, "tag", "right", 5], 202 | [4, "left", 2, 5], 203 | ], [ 204 | // nothing 205 | ]) 206 | 207 | verify(assert, prog, [ 208 | [2, "tag", "left"], 209 | ], [ 210 | [3, "tag", "success", 1], 211 | [3, "tag", "success", 5, -1], 212 | ]) 213 | 214 | assert.end(); 215 | }); 216 | 217 | let programs = { 218 | "simple": () => { 219 | let prog = new Program("simple"); 220 | prog.bind("simple block", ({find, not, record}) => { 221 | let input = find("input"); 222 | not(() => input.arg0) 223 | return [ 224 | record("result") 225 | ]; 226 | }); 227 | return prog; 228 | }, 229 | "dynamic": () => { 230 | let prog = new Program("simple"); 231 | prog.bind("simple block", ({find, not, record}) => { 232 | let input = find("input"); 233 | not(() => input.arg0) 234 | return [ 235 | record("result", {output: input}) 236 | ]; 237 | }); 238 | return prog; 239 | }, 240 | }; 241 | 242 | let verifyIO = createVerifier(programs); 243 | 244 | // ----------------------------------------------------- 245 | // simple 246 | // ----------------------------------------------------- 247 | 248 | test("AntiJoin: simple +A; -A; +A", (assert) => { 249 | verifyIO(assert, "simple", "+A; -A; +A", [ 250 | [[2, "tag", "result", 1, +1]], 251 | [[2, "tag", "result", 1, -1]], 252 | [[2, "tag", "result", 1, +1]] 253 | ]); 254 | }); 255 | 256 | test("AntiJoin: simple +A; +B; -A; -B", (assert) => { 257 | verifyIO(assert, "simple", "+A; +B; -A; -B", [ 258 | [[2, "tag", "result", 1, +1]], 259 | [], 260 | [], 261 | [[2, "tag", "result", 1, -1]], 262 | ]); 263 | }); 264 | 265 | test("AntiJoin: simple +A, -A, +A", (assert) => { 266 | verifyIO(assert, "simple", "+A, -A, +A", [ 267 | [[2, "tag", "result", 1, +1], 268 | [2, "tag", "result", 2, -1], 269 | [2, "tag", "result", 3, +1]] 270 | ]); 271 | }); 272 | 273 | test("AntiJoin: simple +A, +B, -A, -B", (assert) => { 274 | verifyIO(assert, "simple", "+A, +B, -A, -B", [ 275 | [[2, "tag", "result", 1, +1], 276 | [2, "tag", "result", 4, -1]], 277 | ]); 278 | }); 279 | 280 | test("AntiJoin: simple +A; +A:1; -A:1; -A", (assert) => { 281 | verifyIO(assert, "simple", "+A; +A:1; -A:1; -A", [ 282 | [[2, "tag", "result", 1, +1]], 283 | [[2, "tag", "result", 1, -1]], 284 | [[2, "tag", "result", 1, +1]], 285 | [[2, "tag", "result", 1, -1]] 286 | ]); 287 | }); 288 | 289 | test("AntiJoin: simple +A; +A:1; +A:2; -A:1; -A:2", (assert) => { 290 | verifyIO(assert, "simple", "+A; +A:1; +A:2; -A:1; -A:2", [ 291 | [[2, "tag", "result", 1, +1]], 292 | [[2, "tag", "result", 1, -1]], 293 | [], 294 | [], 295 | [[2, "tag", "result", 1, +1]] 296 | ]); 297 | }); 298 | 299 | test("AntiJoin: simple +A; +B:1; -A; +C", (assert) => { 300 | verifyIO(assert, "simple", "+A; +B:1; -A; +C", [ 301 | [[2, "tag", "result", 1, +1]], 302 | [], 303 | [[2, "tag", "result", 1, -1]], 304 | [[2, "tag", "result", 1, +1]] 305 | ]); 306 | }); 307 | 308 | test("AntiJoin: simple +A; +B:1; +C; +A:1", (assert) => { 309 | verifyIO(assert, "simple", "+A; +B:1; +C; +A:1", [ 310 | [[2, "tag", "result", 1, +1]], 311 | [], 312 | [], 313 | [] 314 | ]); 315 | }); 316 | 317 | // ----------------------------------------------------- 318 | // dynamic 319 | // ----------------------------------------------------- 320 | 321 | test("AntiJoin: dynamic +A; -A; +A", (assert) => { 322 | verifyIO(assert, "dynamic", "+A; -A; +A", [ 323 | [[2, "tag", "result", 1, +1], [2, "output", "A", 1, +1]], 324 | [[2, "tag", "result", 1, -1], [2, "output", "A", 1, -1]], 325 | [[2, "tag", "result", 1, +1], [2, "output", "A", 1, +1]] 326 | ]); 327 | }); 328 | 329 | test("AntiJoin: dynamic +A; +B; -A; -B", (assert) => { 330 | verifyIO(assert, "dynamic", "+A; +B; -A; -B", [ 331 | [[2, "tag", "result", 1, +1], [2, "output", "A", 1, +1]], 332 | [[2, "tag", "result", 1, +1], [2, "output", "B", 1, +1]], 333 | [[2, "tag", "result", 1, -1], [2, "output", "A", 1, -1]], 334 | [[2, "tag", "result", 1, -1], [2, "output", "B", 1, -1]], 335 | ]); 336 | }); 337 | -------------------------------------------------------------------------------- /test/distinct.ts: -------------------------------------------------------------------------------- 1 | import {DistinctIndex} from "../src/runtime/indexes"; 2 | import {Change, Iterator} from "../src/runtime/runtime"; 3 | import {verify} from "./util"; 4 | import * as test from "tape"; 5 | 6 | function roundCountsToChanges(rcs:number[][]) { 7 | let changes = []; 8 | for(let [round, count] of rcs) { 9 | changes.push(new Change(1,2,3,4,1,round,count)); 10 | } 11 | return changes; 12 | } 13 | 14 | function distinctTest(assert:any, roundCounts: number[][], expected: any) { 15 | let index = new DistinctIndex(); 16 | 17 | let changes = roundCountsToChanges(roundCounts); 18 | 19 | let final:any = {}; 20 | for(let change of changes) { 21 | let neueChanges = new Iterator(); 22 | index.distinct(change, neueChanges); 23 | let neue; 24 | while((neue = neueChanges.next())) { 25 | final[neue.round] = (final[neue.round] || 0) + neue.count; 26 | } 27 | } 28 | 29 | let badKeys:any = {}; 30 | 31 | for(let key in expected) { 32 | let finalValue = final[key]; 33 | let expectedValue = expected[key]; 34 | if(finalValue || expectedValue) { 35 | let valid = finalValue == expectedValue; 36 | assert.true(valid, `round ${key} :: expected ${expected[key]}, actual ${final[key]}`); 37 | if(!valid) { 38 | badKeys[key] = true; 39 | } 40 | } 41 | } 42 | for(let key in final) { 43 | if(badKeys[key]) continue; 44 | let finalValue = final[key]; 45 | let expectedValue = expected[key]; 46 | if(finalValue || expectedValue) { 47 | let valid = finalValue == expectedValue; 48 | assert.true(valid, `round ${key} :: expected ${expected[key]}, actual ${final[key]}`); 49 | if(!valid) { 50 | badKeys[key] = true; 51 | } 52 | } 53 | } 54 | } 55 | 56 | interface DistinctTest { 57 | only:(name:string, roundCounts:number[][], expected:{[round:number]: number}) => void 58 | (name:string, roundCounts:number[][], expected:{[round:number]: number}): void 59 | } 60 | 61 | let distinct:DistinctTest = ((name:string, roundCounts:number[][], expected:{[round:number]: number}) => { 62 | test(`Distinct: ${name}`, (assert) => { 63 | distinctTest(assert, roundCounts, expected); 64 | assert.end(); 65 | }); 66 | }) as any; 67 | 68 | 69 | 70 | distinct.only = function distinctOnly(name:string, roundCounts:number[][], expected:any) { 71 | test.only(`Distinct: ${name}`, (assert) => { 72 | distinctTest(assert, roundCounts, expected); 73 | assert.end(); 74 | }); 75 | } 76 | 77 | distinct("basic", [ 78 | [1,1], 79 | [2,-1], 80 | 81 | [1, 1], 82 | [3, -1], 83 | ], { 84 | 1: 1, 85 | 3: -1 86 | }) 87 | 88 | //------------------------------------------------------------ 89 | // Chris's section 90 | //------------------------------------------------------------ 91 | 92 | distinct("basic 2", [ 93 | [1, 1], 94 | [2, -1], 95 | 96 | [3,1], 97 | [4,-1], 98 | ], { 99 | 1: 1, 100 | 2: -1, 101 | 3: 1, 102 | 4: -1, 103 | }) 104 | 105 | distinct("basic 2 in reverse order", [ 106 | [3,1], 107 | [4,-1], 108 | 109 | [1, 1], 110 | [2, -1], 111 | ], { 112 | 1: 1, 113 | 2: -1, 114 | 3: 1, 115 | 4: -1, 116 | }) 117 | 118 | distinct("basic 2 undone", [ 119 | [1, 1], 120 | [2, -1], 121 | 122 | [3,1], 123 | [4,-1], 124 | 125 | [1, -1], 126 | [2, 1], 127 | ], { 128 | 3: 1, 129 | 4: -1, 130 | }) 131 | 132 | distinct("multiple counts", [ 133 | [1, 1], 134 | [1, 1], 135 | [1, 1], 136 | [2, -1], 137 | [2, -1], 138 | [2, -1], 139 | 140 | [3,1], 141 | [3,1], 142 | [3,1], 143 | [4,-1], 144 | [4,-1], 145 | [4,-1], 146 | ], { 147 | 1: 1, 148 | 2: -1, 149 | 3: 1, 150 | 4: -1, 151 | }) 152 | 153 | distinct("multiple counts reversed", [ 154 | [3,1], 155 | [3,1], 156 | [3,1], 157 | [4,-1], 158 | [4,-1], 159 | [4,-1], 160 | 161 | [1, 1], 162 | [1, 1], 163 | [1, 1], 164 | [2, -1], 165 | [2, -1], 166 | [2, -1], 167 | ], { 168 | 1: 1, 169 | 2: -1, 170 | 3: 1, 171 | 4: -1, 172 | }) 173 | 174 | distinct("multiple counts interleaved", [ 175 | [3,1], 176 | [4,-1], 177 | [3,1], 178 | [4,-1], 179 | [3,1], 180 | [4,-1], 181 | 182 | [1, 1], 183 | [2, -1], 184 | [1, 1], 185 | [2, -1], 186 | [1, 1], 187 | [2, -1], 188 | ], { 189 | 1: 1, 190 | 2: -1, 191 | 3: 1, 192 | 4: -1, 193 | }) 194 | 195 | distinct("multiple counts negatives first", [ 196 | [2, -1], 197 | [2, -1], 198 | [2, -1], 199 | [1, 1], 200 | [1, 1], 201 | [1, 1], 202 | 203 | [4,-1], 204 | [4,-1], 205 | [4,-1], 206 | [3,1], 207 | [3,1], 208 | [3,1], 209 | ], { 210 | 1: 1, 211 | 2: -1, 212 | 3: 1, 213 | 4: -1, 214 | }) 215 | 216 | distinct("multiple counts undone", [ 217 | [1, 1], 218 | [1, 1], 219 | [1, 1], 220 | [2, -1], 221 | [2, -1], 222 | [2, -1], 223 | 224 | [3,1], 225 | [3,1], 226 | [3,1], 227 | [4,-1], 228 | [4,-1], 229 | [4,-1], 230 | 231 | [1, -1], 232 | [1, -1], 233 | [1, -1], 234 | [2, 1], 235 | [2, 1], 236 | [2, 1], 237 | ], { 238 | 3: 1, 239 | 4: -1, 240 | }) 241 | 242 | distinct("multiple counts undone interleaved", [ 243 | [1, 1], 244 | [1, 1], 245 | [1, 1], 246 | [2, -1], 247 | [2, -1], 248 | [2, -1], 249 | 250 | [1, -1], 251 | [1, -1], 252 | [1, -1], 253 | 254 | [3,1], 255 | [3,1], 256 | [3,1], 257 | [4,-1], 258 | [4,-1], 259 | [4,-1], 260 | 261 | [2, 1], 262 | [2, 1], 263 | [2, 1], 264 | ], { 265 | 3: 1, 266 | 4: -1, 267 | }) 268 | 269 | distinct("multiple disparate counts", [ 270 | [1, 1], 271 | [1, 1], 272 | [1, 1], 273 | [2, -1], 274 | [2, -1], 275 | [2, -1], 276 | 277 | [3,1], 278 | [4,-1], 279 | ], { 280 | 1: 1, 281 | 2: -1, 282 | 3: 1, 283 | 4: -1, 284 | }) 285 | 286 | distinct("multiple disparate counts with extra removes", [ 287 | [1, 1], 288 | [1, 1], 289 | [1, 1], 290 | 291 | [2, -1], 292 | [2, -1], 293 | [2, -1], 294 | 295 | [1, -1], 296 | [1, -1], 297 | [1, -1], 298 | 299 | [2, 1], 300 | [2, 1], 301 | [2, 1], 302 | 303 | [3,1], 304 | [4,-1], 305 | ], { 306 | 3: 1, 307 | 4: -1, 308 | }) 309 | 310 | //------------------------------------------------------------ 311 | // Josh's section 312 | //------------------------------------------------------------ 313 | 314 | distinct("simple round promotion", [ 315 | [8, 1], 316 | [9, -1], 317 | 318 | [5, 1], 319 | [6, -1], 320 | [8, -1], 321 | [9, 1] 322 | ], { 323 | 5: 1, 324 | 6: -1, 325 | 8: 0, 326 | 9: 0 327 | }); 328 | 329 | distinct("full promotion", [ 330 | [9, 1], 331 | [9, 1], 332 | [10, -1], 333 | [10, -1], 334 | 335 | [9, 1], 336 | [9, 1], 337 | [10, -1], 338 | [10, -1], 339 | 340 | [9, -1], 341 | [10, 1], 342 | [9, -1], 343 | [10, 1], 344 | 345 | [9, -1], 346 | [10, 1], 347 | [9, -1], 348 | [10, 1] 349 | ], { 350 | 9: 0, 351 | 10: 0 352 | }) 353 | 354 | distinct("positive full promotion", [ 355 | [7, 1], 356 | [8, -1], 357 | [8, 1], 358 | [7, 1], 359 | [8, -1], 360 | [4, 1], 361 | [8, -1], 362 | [7, 1], 363 | [8, -1], 364 | [8, 1], 365 | [5, -1], 366 | [7, -3], 367 | [8, 1], 368 | [8, 3], 369 | [5, 1], 370 | [8, 1], 371 | [8, -2], 372 | [8, -1], 373 | ], { 374 | 4: 1, 375 | }) 376 | -------------------------------------------------------------------------------- /test/performance.ts: -------------------------------------------------------------------------------- 1 | import {Program} from "../src/runtime/dsl2"; 2 | import {verify, createChanges, time} from "./util"; 3 | import {HashIndex} from "../src/runtime/indexes"; 4 | import * as test from "tape"; 5 | 6 | test("test single block performance with 10000 transactions", (assert) => { 7 | 8 | // ----------------------------------------------------- 9 | // program 10 | // ----------------------------------------------------- 11 | 12 | let prog = new Program("test"); 13 | prog.bind("simple block", ({find, record, lib}) => { 14 | let person = find("person"); 15 | let text = `name: ${person.name}`; 16 | return [ 17 | record("html/div", {person, text}) 18 | ] 19 | }); 20 | 21 | // ----------------------------------------------------- 22 | // verification 23 | // ----------------------------------------------------- 24 | 25 | for(let ix = 0; ix < 1; ix++) { 26 | prog.index = new HashIndex(); 27 | let size = 10000; 28 | let changes = []; 29 | for(let i = 0; i < size; i++) { 30 | changes.push(createChanges(i, [[i - 1, "name", i - 1], [i, "tag", "person"]])) 31 | } 32 | 33 | let start = time(); 34 | for(let change of changes) { 35 | prog.input(change); 36 | } 37 | let end = time(start); 38 | assert.test("updates finished in " + end, (assert) => { 39 | assert.true(end < 1000, "Took too long"); 40 | assert.end(); 41 | }) 42 | } 43 | assert.pass(); 44 | assert.end(); 45 | }); 46 | -------------------------------------------------------------------------------- /test/stdlib/math.ts: -------------------------------------------------------------------------------- 1 | import {Program} from "../../src/runtime/dsl2"; 2 | import {verify} from "../util"; 3 | import * as test from "tape"; 4 | 5 | test("stdlib:math:range: 1..5", (assert) => { 6 | let prog = new Program("test"); 7 | prog.bind("test range", ({find, lib:{math}, record}) => { 8 | let ix = math.range(1, 5); 9 | return [ 10 | record({ix}) 11 | ]; 12 | }); 13 | 14 | verify(assert, prog, [ 15 | ["A", "tag", "turtle"], 16 | ], [ 17 | [11, "ix", 1, 1], 18 | [12, "ix", 2, 1], 19 | [13, "ix", 3, 1], 20 | [14, "ix", 4, 1], 21 | [15, "ix", 5, 1], 22 | ]); 23 | assert.end(); 24 | }); 25 | 26 | test("stdlib:math:range: 3..-1", (assert) => { 27 | let prog = new Program("test"); 28 | prog.bind("test range", ({find, lib:{math}, record}) => { 29 | let ix = math.range(3, -1); 30 | return [ 31 | record({ix}) 32 | ]; 33 | }); 34 | 35 | verify(assert, prog, [ 36 | ["A", "tag", "turtle"], 37 | ], [ 38 | [11, "ix", 3, 1], 39 | [12, "ix", 2, 1], 40 | [13, "ix", 1, 1], 41 | [14, "ix", 0, 1], 42 | [15, "ix", -1, 1], 43 | ]); 44 | assert.end(); 45 | }); 46 | 47 | test("stdlib:math:range: x..y", (assert) => { 48 | let prog = new Program("test"); 49 | prog.bind("test range", ({find, lib:{math}}) => { 50 | let endpoints = find("endpoints"); 51 | let {x, y} = endpoints; 52 | let ix = math.range(x, y); 53 | return [ 54 | endpoints.add({ix}) 55 | ]; 56 | }); 57 | 58 | verify(assert, prog, [ 59 | ["A", "tag", "endpoints"], 60 | ["A", "x", 1], 61 | ["A", "y", 3], 62 | ["B", "tag", "endpoints"], 63 | ["B", "x", 2], 64 | ["B", "y", 0], 65 | ], [ 66 | ["A", "ix", 1, 1], 67 | ["A", "ix", 2, 1], 68 | ["A", "ix", 3, 1], 69 | ["B", "ix", 0, 1], 70 | ["B", "ix", 1, 1], 71 | ["B", "ix", 2, 1], 72 | ]); 73 | assert.end(); 74 | }); 75 | 76 | test("stdlib:math:range: bail on strings", (assert) => { 77 | let prog = new Program("test"); 78 | prog.bind("test range", ({find, lib:{math}}) => { 79 | let endpoints = find("endpoints"); 80 | let {x, y} = endpoints; 81 | let ix = math.range(x, y); 82 | return [ 83 | endpoints.add({ix}) 84 | ]; 85 | }); 86 | 87 | verify(assert, prog, [ 88 | ["A", "tag", "endpoints"], 89 | ["A", "x", "1"], 90 | ["A", "y", 3], 91 | ["B", "tag", "endpoints"], 92 | ["B", "x", 2], 93 | ["B", "y", "0"], 94 | ], [ 95 | ]); 96 | assert.end(); 97 | }); 98 | -------------------------------------------------------------------------------- /test/util.ts: -------------------------------------------------------------------------------- 1 | import {Program} from "../src/runtime/dsl2"; 2 | import * as Runtime from "../src/runtime/runtime"; 3 | import * as test from "tape"; 4 | 5 | // You can specify changes as either [e,a,v] or [e,a,v,round,count]; 6 | export type EAVTuple = [Runtime.RawValue, Runtime.RawValue, Runtime.RawValue]; 7 | export type EAVRCTuple = [Runtime.RawValue, Runtime.RawValue, Runtime.RawValue, number, number]; 8 | export type TestChange = EAVTuple | EAVRCTuple; 9 | 10 | let {GlobalInterner} = Runtime; 11 | let TEST_INPUT_NODE = "test-input-node"; 12 | 13 | export function pprint(obj:any):string { 14 | if(typeof obj === "object" && obj instanceof Array) { 15 | return "[" + obj.map((v) => pprint(v)).join(", ") + "]"; 16 | 17 | } else if(typeof obj === "string") { 18 | return `"${obj}"`; 19 | } 20 | return ""+obj; 21 | } 22 | 23 | export class EntityId { 24 | constructor(public id:Runtime.ID) {} 25 | toString() { 26 | return "$" + this.id; 27 | } 28 | } 29 | 30 | export function o_o(val:Runtime.ID):EntityId|Runtime.RawValue|undefined { 31 | let raw = GlobalInterner.reverse(val); 32 | if(typeof raw === "string" && raw.indexOf("|") !== -1) { 33 | return new EntityId(val); 34 | } 35 | 36 | return raw; 37 | } 38 | 39 | export function createChanges(transaction:number,eavns:TestChange[]) { 40 | let changes:Runtime.Change[] = []; 41 | for(let [e, a, v, round = 0, count = 1] of eavns as EAVRCTuple[]) { 42 | changes.push(Runtime.Change.fromValues(e, a, v, TEST_INPUT_NODE, transaction, round, count)); 43 | } 44 | return changes; 45 | } 46 | 47 | export function verify(assert:test.Test, program:Program, input:any[], output:any[], transaction = 1) { 48 | let ins = createChanges(transaction, input); 49 | let outs = createChanges(transaction, output); 50 | 51 | let all:(Runtime.Change|undefined)[] = outs; 52 | let {changes, context} = program.input(ins)!; 53 | let inputNode = GlobalInterner.get(TEST_INPUT_NODE); 54 | changes = changes.filter((v) => v.n !== inputNode); 55 | let msg = "Fewer changes than expected"; 56 | if(changes.length > all.length) { 57 | msg = "More changes than expected"; 58 | } 59 | 60 | if(changes.length !== all.length) { 61 | assert.comment(". Actual: " + pprint(changes.map((change) => { 62 | let {e, a, v, round, count} = change; 63 | return [o_o(e), o_o(a), o_o(v), round, count]; 64 | }))); 65 | } 66 | assert.equal(changes.length, all.length, msg); 67 | 68 | try { 69 | context.distinctIndex.sanityCheck(); 70 | } catch(e) { 71 | assert.fail("Distinct sanity check failed"); 72 | } 73 | 74 | // Because the changes handed to us in expected aren't going to have the same 75 | // e's as what the program itself is going to generate, we're going to have to do 76 | // some fancy matching to map from generated e's to expected e's. We'll need to 77 | // store that mapping somewhere, so we have eMap: 78 | let eMap:any = {}; 79 | let fullyResolved:any = {}; 80 | if(changes.length === all.length) { 81 | // As we check all of the changes we got from running the input on the program, 82 | // we need to update our eMap based on the expected changes that *could* match. 83 | // Most of the time, the hope is that there's only one potential match, but when 84 | // you're looking at something like tag, it's easy for there to be many records 85 | // that get generated with the same tag, so we're going to do a decent amount of 86 | // work here. 87 | for(let actual of changes) { 88 | let found = false; 89 | // console.log("\n\nACTUAL"); 90 | // console.log(" ", actual.toString()); 91 | // console.log(" ", actual); 92 | let expectedIx = 0; 93 | // check if we've found any potential matches for this e yet 94 | let matches = eMap[actual.e]; 95 | // if we haven't found any matches yet, we need to collect some initial ones. 96 | if(!matches) { 97 | let potentials = []; 98 | for(let expected of all) { 99 | if(!expected) { 100 | expectedIx++; 101 | continue; 102 | } 103 | // if this expected *could* match ignoring e and n, then we'll store this as 104 | // a potential mapping from the actual.e to the expected.e. We're also going 105 | // to store this expected's index so that once we know for sure that this 106 | // actual.e === expected.e we can clean out the expecteds that no one can claim 107 | // anymore. 108 | if(actual.equal(expected, true /*ignore the node*/, true /*ignore the e*/)) { 109 | found = true; 110 | potentials.push({e: expected.e, relatedChanges: [expectedIx]}); 111 | } 112 | expectedIx++; 113 | } 114 | // if there was only one match, no one can ever have this expected - we've claimed it. 115 | // As such, we need to remove it from the list; 116 | if(potentials.length === 1) { 117 | // We need to check that we haven't already resolved this match to some other actual value. 118 | let e = potentials[0].e; 119 | if(fullyResolved[e] !== undefined) { 120 | assert.fail(`\`${GlobalInterner.reverse(e)}\` has already been resolved to \`${GlobalInterner.reverse(fullyResolved[e])}\`,` + 121 | ` but we are trying to resolve it to \`${GlobalInterner.reverse(actual.e)}\``); 122 | break; 123 | } 124 | fullyResolved[e] = actual.e; 125 | for(let ix of potentials[0].relatedChanges) { 126 | all[ix] = undefined; 127 | } 128 | } 129 | eMap[actual.e] = potentials; 130 | } else if(matches.length === 1) { 131 | // in the case where we've mapped our actual.e to our expected.e, we just check this 132 | // current fact for a match where the expected.e is what we're looking for 133 | for(let expected of all) { 134 | if(!expected) { 135 | expectedIx++; 136 | continue; 137 | } 138 | if(expected.e === matches[0].e && actual.equal(expected, true, true)) { 139 | found = true; 140 | all[expectedIx] = undefined; 141 | } 142 | expectedIx++; 143 | } 144 | } else { 145 | // since we have multiple potential matches, we need to see if this actual might reduce 146 | // the set down for us. For each expected that's left, we'll check if expected.e matches 147 | // one of our potentials, and if this expected would equal our actual if we ignored the 148 | // e. If so, we keep this potential in the running. Any potentials the don't end up with 149 | // a match get removed on account of us recreating the potential array from scratch here. 150 | let potentials = []; 151 | for(let expected of all) { 152 | if(!expected) { 153 | expectedIx++; 154 | continue; 155 | } 156 | for(let match of matches) { 157 | if(match.e === expected.e && !fullyResolved[match.e] && actual.equal(expected, true, true)) { 158 | found = true; 159 | potentials.push(match); 160 | match.relatedChanges.push(expectedIx); 161 | } 162 | } 163 | expectedIx++; 164 | } 165 | // If we only have one potential, we need to clean up after ourselves again. This time 166 | // however, we could have had many relatedChanges that we need to clean up, so we'll loop 167 | // through them and remove them from the list. They're our's now. 168 | if(potentials.length === 1) { 169 | // We need to check that we haven't already resolved this match to some other actual value. 170 | let e = potentials[0].e; 171 | if(fullyResolved[e] !== undefined) { 172 | assert.fail(`\`${GlobalInterner.reverse(e)}\` has already been resolved to \`${GlobalInterner.reverse(fullyResolved[e])}\`,` + 173 | ` but we are trying to resolve it to \`${GlobalInterner.reverse(actual.e)}\``); 174 | break; 175 | } 176 | fullyResolved[e] = actual.e; 177 | let related = potentials[0].relatedChanges; 178 | related.sort((a:number, b:number) => b - a); 179 | for(let relatedIx of related) { 180 | all[relatedIx] = undefined; 181 | } 182 | potentials[0].relatedChanges = [] 183 | } 184 | eMap[actual.e] = potentials; 185 | } 186 | 187 | // console.log(" ", actual.e, ":", eMap[actual.e]); 188 | // console.log(" [") 189 | // for(let thing of all) { 190 | // console.log(" ", thing); 191 | // } 192 | // console.log(" ]") 193 | 194 | if(!found) assert.fail("No match found for: " + actual.toString()); 195 | else assert.pass("Found match for: " + actual.toString()); 196 | } 197 | } 198 | } 199 | 200 | export function time(start?:any): number | number[] | string { 201 | if ( !start ) return process.hrtime(); 202 | let end = process.hrtime(start); 203 | return ((end[0]*1000) + (end[1]/1000000)).toFixed(3); 204 | } 205 | 206 | export function createInputs(inputString:string) { 207 | let transactionInputs:EAVRCTuple[][] = []; 208 | let transactions = inputString.split(";"); 209 | for(let transaction of transactions) { 210 | let eavrcs:EAVRCTuple[] = []; 211 | let roundNumber = 0; 212 | for(let round of transaction.split(",")) { 213 | for(let input of round.split(" ")) { 214 | if(!input) continue; 215 | 216 | let count; 217 | if(input[0] === "+") count = 1; 218 | else if(input[0] === "-") count = -1; 219 | else throw new Error(`Malformed input: ${input}`); 220 | 221 | let args = input.slice(1).split(":"); 222 | let id = args.shift(); 223 | if(!id) throw new Error(`Malformed input: '${input}'`); 224 | 225 | eavrcs.push([id, "tag", "input", roundNumber, count]); 226 | 227 | let argIx = 0; 228 | for(let arg of args) { 229 | eavrcs.push([id, `arg${argIx}`, (isNaN(arg as any) ? arg : +arg), roundNumber, count]); 230 | argIx++; 231 | } 232 | } 233 | roundNumber += 1; 234 | } 235 | transactionInputs.push(eavrcs); 236 | } 237 | 238 | return transactionInputs; 239 | } 240 | 241 | export function createVerifier Program}>(programs:T) { 242 | return function verifyInput(assert:test.Test, progName:(keyof T), inputString:string, expecteds:EAVRCTuple[][]) { 243 | let prog = programs[progName](); 244 | let inputs = createInputs(inputString); 245 | 246 | if(expecteds.length !== inputs.length) { 247 | assert.fail("Malformed test case"); 248 | throw new Error(`Incorrect number of expecteds given the inputString Got ${expecteds.length}, needed: ${inputs.length}`); 249 | } 250 | 251 | let transactionNumber = 0; 252 | for(let input of inputs) { 253 | let expected = expecteds[transactionNumber]; 254 | assert.comment(". Verifying: " + pprint(input) + " -> " + pprint(expected)); 255 | verify(assert, prog, input, expected); 256 | transactionNumber++; 257 | } 258 | assert.end(); 259 | return prog; 260 | }; 261 | } 262 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "outDir": "build", 7 | "target": "es5", 8 | "sourceMap": true, 9 | "skipDefaultLibCheck": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "experimentalDecorators" : true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true 14 | }, 15 | "include": [ "src/**/*.ts", "src/**/*.js", "test/**/*.ts", "scripts/**/*.ts" ] 16 | } 17 | -------------------------------------------------------------------------------- /typings/commonmark/commonmark.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for commonmark.js 0.22.1 2 | // Project: https://github.com/jgm/commonmark.js 3 | // Definitions by: Nico Jansen 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | 6 | 7 | declare namespace commonmark { 8 | 9 | export interface NodeWalkingStep { 10 | /** 11 | * a boolean, which is true when we enter a Node from a parent or sibling, and false when we reenter it from a child 12 | */ 13 | entering: boolean; 14 | /** 15 | * The node belonging to this step 16 | */ 17 | node: Node; 18 | } 19 | 20 | export interface NodeWalker { 21 | /** 22 | * Returns an object with properties entering and node. Returns null when we have finished walking the tree. 23 | */ 24 | next(): NodeWalkingStep; 25 | /** 26 | * 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.) 27 | */ 28 | resumeAt(node: Node, entering?: boolean): void; 29 | } 30 | 31 | export interface Position extends Array> { 32 | } 33 | 34 | export interface ListData { 35 | type?: string, 36 | tight?: boolean, 37 | delimiter?: string, 38 | bulletChar?: string 39 | } 40 | 41 | export class Node { 42 | constructor(nodeType: string, sourcepos?: Position); 43 | isContainer: boolean; 44 | 45 | /** 46 | * (read-only): one of Text, Softbreak, Hardbreak, Emph, Strong, Html, Link, Image, Code, Document, Paragraph, BlockQuote, Item, List, Heading, CodeBlock, HtmlBlock ThematicBreak. 47 | */ 48 | type: string; 49 | /** 50 | * (read-only): a Node or null. 51 | */ 52 | firstChild: Node; 53 | /** 54 | * (read-only): a Node or null. 55 | */ 56 | lastChild: Node; 57 | /** 58 | * (read-only): a Node or null. 59 | */ 60 | next: Node; 61 | /** 62 | * (read-only): a Node or null. 63 | */ 64 | prev: Node; 65 | /** 66 | * (read-only): a Node or null. 67 | */ 68 | parent: Node; 69 | /** 70 | * (read-only): an Array with the following form: [[startline, startcolumn], [endline, endcolumn]] 71 | */ 72 | sourcepos: Position; 73 | /** 74 | * the literal String content of the node or null. 75 | */ 76 | literal: string; 77 | /** 78 | * link or image destination (String) or null. 79 | */ 80 | destination: string; 81 | /** 82 | * link or image title (String) or null. 83 | */ 84 | title: string; 85 | /** 86 | * fenced code block info string (String) or null. 87 | */ 88 | info: string; 89 | /** 90 | * heading level (Number). 91 | */ 92 | level: number; 93 | /** 94 | * either Bullet or Ordered (or undefined). 95 | */ 96 | listType: string; 97 | /** 98 | * true if list is tight 99 | */ 100 | listTight: boolean; 101 | /** 102 | * a Number, the starting number of an ordered list. 103 | */ 104 | listStart: number; 105 | /** 106 | * a String, either ) or . for an ordered list. 107 | */ 108 | listDelimiter: string; 109 | /** 110 | * used only for CustomBlock or CustomInline. 111 | */ 112 | onEnter: string; 113 | /** 114 | * used only for CustomBlock or CustomInline. 115 | */ 116 | onExit: string; 117 | /** 118 | * Append a Node child to the end of the Node's children. 119 | */ 120 | appendChild(child: Node): void; 121 | /** 122 | * Prepend a Node child to the beginning of the Node's children. 123 | */ 124 | prependChild(child: Node): void; 125 | /** 126 | * Remove the Node from the tree, severing its links with siblings and parents, and closing up gaps as needed. 127 | */ 128 | unlink(): void; 129 | /** 130 | * Insert a Node sibling after the Node. 131 | */ 132 | insertAfter(sibling: Node): void; 133 | /** 134 | * Insert a Node sibling before the Node. 135 | */ 136 | insertBefore(sibling: Node): void; 137 | /** 138 | * Returns a NodeWalker that can be used to iterate through the Node tree rooted in the Node 139 | */ 140 | walker(): NodeWalker; 141 | /** 142 | * Setting the backing object of listType, listTight, listStat and listDelimiter directly. 143 | * Not needed unless creating list nodes directly. Should be fixed from v>0.22.1 144 | * https://github.com/jgm/commonmark.js/issues/74 145 | */ 146 | _listData: ListData; 147 | } 148 | 149 | /** 150 | * 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. 151 | * This opens up the possibility of manipulating the AST between parsing and rendering. For example, one could transform emphasis into ALL CAPS. 152 | */ 153 | export class Parser { 154 | /** 155 | * Constructs a new Parser 156 | */ 157 | constructor(options?: ParserOptions); 158 | parse(input: string): Node; 159 | } 160 | 161 | export interface ParserOptions { 162 | /** 163 | * 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. 164 | */ 165 | smart?: boolean; 166 | time?: boolean; 167 | } 168 | 169 | export interface HtmlRenderingOptions extends XmlRenderingOptions { 170 | /** 171 | * 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. 172 | */ 173 | safe?: boolean; 174 | /** 175 | * 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. 176 | */ 177 | smart?: boolean; 178 | /** 179 | * 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). 180 | */ 181 | sourcepos?: boolean; 182 | } 183 | 184 | export class HtmlRenderer { 185 | constructor(options?: HtmlRenderingOptions) 186 | render(root: Node): string; 187 | /** 188 | * Let's you override the softbreak properties of a renderer. So, to make soft breaks render as hard breaks in HTML: 189 | * writer.softbreak = "
"; 190 | */ 191 | softbreak: string; 192 | /** 193 | * Override the function that will be used to escape (sanitize) the html output. Return value is used to add to the html output 194 | * @param input the input to escape 195 | * @param isAttributeValue indicates wheter or not the input value will be used as value of an html attribute. 196 | */ 197 | escape: (input: string, isAttributeValue: boolean) => string; 198 | } 199 | 200 | export interface XmlRenderingOptions { 201 | time?: boolean; 202 | sourcepos?: boolean; 203 | } 204 | 205 | export class XmlRenderer { 206 | constructor(options?: XmlRenderingOptions) 207 | render(root: Node): string; 208 | } 209 | 210 | } 211 | 212 | declare module 'commonmark' { 213 | export = commonmark; 214 | } 215 | --------------------------------------------------------------------------------