├── .browserslistrc
├── .eslintrc.js
├── .gitignore
├── Examples.md
├── LICENSE
├── README.md
├── babel.config.js
├── deploy.sh
├── package-lock.json
├── package.json
├── public
├── android-icon-144x144.png
├── android-icon-192x192.png
├── android-icon-36x36.png
├── android-icon-48x48.png
├── android-icon-72x72.png
├── android-icon-96x96.png
├── apple-icon-114x114.png
├── apple-icon-120x120.png
├── apple-icon-144x144.png
├── apple-icon-152x152.png
├── apple-icon-180x180.png
├── apple-icon-57x57.png
├── apple-icon-60x60.png
├── apple-icon-72x72.png
├── apple-icon-76x76.png
├── apple-icon-precomposed.png
├── apple-icon.png
├── browserconfig.xml
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon-96x96.png
├── favicon.ico
├── index.html
├── manifest.json
├── ms-icon-144x144.png
├── ms-icon-150x150.png
├── ms-icon-310x310.png
└── ms-icon-70x70.png
├── src
├── App.vue
├── CodeEditor.vue
├── LSystem.js
├── LineCollection.js
├── NoWebGL.vue
├── Turtle.js
├── assets
│ └── logo.png
├── bus.js
├── components
│ └── HelloWorld.vue
├── createScene.js
├── editor.styl
├── getCodeModel.js
├── grammar
│ └── parser.js
├── lmode.js
├── main.js
└── shared.styl
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | 'extends': [
7 | 'plugin:vue/essential',
8 | 'eslint:recommended'
9 | ],
10 | parserOptions: {
11 | parser: 'babel-eslint'
12 | },
13 | ignorePatterns: [
14 | "w-gl/*.js"
15 | ],
16 | rules: {
17 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
18 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
19 | 'no-unused-vars': 1,
20 | 'no-mixed-spaces-and-tabs': 0
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/Examples.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | These systems are generated by [Thomas Munoz](https://github.com/Epholys) and come from
4 | https://epholys.itch.io/lsys
5 |
6 | [Cog](https://anvaka.github.io/lsystem/?code=%2F%2F%20by%20Thomas%20Munoz%0A%2F%2F%20from%20https%3A%2F%2Fgithub.com%2FEpholys%2Fprocgen%0Aaxiom%3A%20F-F-F-F-F-F-F-F%0Arules%3A%20%0A%20F%20%3D%3E%20-F%2B%2BF-%0A%0Adepth%3A%205%0AstepsPerFrame%3A%2010%0Aangle%3A%2045%0A%0Aactions%3A%0A%20%20F%20%3D%3E%20draw%28%29)
7 |
8 |
9 | [Algae](https://anvaka.github.io/lsystem/?code=%2F%2F%20by%20Thomas%20Munoz%0A%2F%2F%20from%20https%3A%2F%2Fgithub.com%2FEpholys%2Fprocgen%0Aaxiom%3A%20F%0Arules%3A%20%0A%20F%20%3D%3E%20FF-%5B-F%2BF%2BF%5D%2BF%5B%2B%2BF-F-F%5D%0A%0Adepth%3A%205%0AstepsPerFrame%3A%201000%0Adirection%3A%20%5B0%2C%201%2C%200%5D%0Aangle%3A%2022.5%0A%0Aactions%3A%0A%20%20F%20%3D%3E%20draw%28%29)
10 |
11 | [Twilight](https://anvaka.github.io/lsystem/?code=%2F%2F%20by%20Thomas%20Munoz%0A%2F%2F%20from%20https%3A%2F%2Fgithub.com%2FEpholys%2Fprocgen%0Aaxiom%3A%20X%0Arules%3A%20%0A%20Y%20%3D%3E%20%5B%2BX%5D%0A%20F%20%3D%3E%20FF%0A%20X%20%3D%3E%20F%5B-FX%5D%5B%2BY%5D%5BFFY%5B--FX%5D%5BFX%5D%5B%2B%2BFX%5D%5DFYF%5B%2BFX%5D%0A%0Adepth%3A%205%0A%0AstepsPerFrame%3A%201000%0Adirection%3A%20%5B0%2C%201%2C%200%5D%0Aangle%3A%2016.5%0A%0Aactions%3A%0A%20%20F%20%3D%3E%20draw%28%29)
12 |
13 | [Mandala](https://anvaka.github.io/lsystem/?code=%2F%2F%20by%20Thomas%20Munoz%0A%2F%2F%20from%20https%3A%2F%2Fgithub.com%2FEpholys%2Fprocgen%0Aaxiom%3A%20X---------X-------X%0Arules%3A%20%0A%20%20X%20%3D%3E%20%5BG%5D%5B%2BG%5D%5B-G%5D%5B%2B%2BG%5D%5B--G%5D%5B%2B%2B%2BG%5D%5B---G%5D%5B%2B%2B%2B%2BG%5D%5B----G%5D%0A%20%20G%20%3D%3E%20GG%5B--G%5D%5B%2B%2BG%5D%0A%0Adepth%3A%206%0A%0AstepsPerFrame%3A%201000%0Adirection%3A%20%5B0%2C%201%2C%200%5D%0Aangle%3A%2015%0A%0Aactions%3A%0A%20%20G%20%3D%3E%20draw%28%29)
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-2025 Andrei Kashcha
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # L-System
2 |
3 | This website allows you to edit [L-System](https://en.wikipedia.org/wiki/L-system) and immediately see results online:
4 |
5 | 
6 |
7 | https://anvaka.github.io/lsystem
8 |
9 | ## High level details
10 |
11 | The rendering is done with WebGL use can explore generated system similar to a map (with pan-zoom interaction).
12 |
13 | To change camera angle hold `Option` (or `Alt`) key and drag with mouse, or slide two fingers up/down.
14 |
15 | You can click `Randomize` button to generate a well-known L-System and tweak it. While `randomize`
16 | functionality provides basic L-Systems, I'd love to find a better way to introduce variety here. Please let
17 | me know if you have suggestions how to improve random L-System generation logic, so that it produces appealing
18 | results.
19 |
20 | ## Syntax/L-Systems details
21 |
22 | L-Systems are described very well on [Paul Bourke's](http://paulbourke.net/fractals/lsys/) website.
23 |
24 | ### Sections in the editor
25 |
26 | Each section can be entered in the text editor, followed by a semicolon. For example:
27 |
28 | ```
29 | axiom: X
30 | rules:
31 | X => F+F
32 | ```
33 |
34 | Here we entered two sections: `axiom` and `rules`. The list of available sections, along with their
35 | meanings is below:
36 |
37 | * `axiom` - initial state of the system
38 | * `rules` - list of rewrite rules that are applied on each iteration
39 | * `depth` - how deep we are allowed to go recursively
40 | * `angle` - if specified, this argument governs rotation angle. Can be overridden with actions
41 | * `actions` - list of graphic commands that are triggered by a matching character in the evolved system.
42 | * `width` - width in pixels of the drawn line
43 | * `color` - color of the line. Accepts names and hex. E.g. `blue`, works the same as `#0000ff`
44 | * `stepsPerFrame` - how many steps we are allowed to render per single frame. If set to -1 the scene is rendered immediately. This could be dangerous on deep systems, as the entire system traversal may exhaust the browser's resources.
45 | * `direction` - three numbers separated by coma `x, y, z` that set initial direction
46 | * `position` three numbers separated by coma `x, y, z` that set initial position
47 |
48 | ### Actions
49 |
50 | Actions are associated with each symbol in the `rules` section. When processor finds matching action
51 | it executes it.
52 |
53 | For example, let's say we have the following system:
54 |
55 | ```
56 | axiom: X
57 | rules:
58 | X => F+FX
59 |
60 | depth: 4
61 | actions:
62 | F => draw(10)
63 | + => rotate(90)
64 | ```
65 |
66 | Once the system is unwrapped, we get the following string:
67 |
68 | ```
69 | F+FF+FF+FF+F
70 | ```
71 |
72 | Each `F` has an associated action `draw(10)` which means "draw 10 units in current direction".
73 | Each `+` has an associated action `rotate(90)` which means "rotate 90 degrees".
74 |
75 | Can you guess what `F+FF+FF+FF+F` will render? To see the final result, [click here](https://anvaka.github.io/lsystem/?code=axiom%3A%20X%0Arules%3A%0A%20%20X%20%3D%3E%20F%2BFX%0A%0Adepth%3A%204%0Aactions%3A%0A%20%20F%20%3D%3E%20draw%2810%29%0A%20%20%2B%20%3D%3E%20rotate%2890%29)
76 |
77 | Here is the list of all available actions:
78 |
79 | `draw(x)` draw `x` units in current direction
80 | `move(x)` move `x` units in current direction without drawing
81 | `rotate(deg)` rotate current direction `deg` degrees around `Z` axis
82 | `rotateX(deg)` rotate current direction `deg` degrees around `X` axis
83 | `rotateY(deg)` rotate current direction `deg` degrees around `Y` axis
84 | `push()` saves current render state onto stack
85 | `pop()` restores previously saved render state
86 |
87 | By default the following actions are added automatically:
88 | actions:
89 |
90 | ```
91 | F => draw(10)
92 | f => move(10)
93 | + => rotate(60)
94 | - => rotate(-60)
95 | [ => push()
96 | ] => pop()
97 | ```
98 |
99 |
100 | ## Local development
101 |
102 | ```
103 | npm install
104 | ```
105 |
106 | ### Compiles and hot-reloads for development
107 | ```
108 | npm start
109 | ```
110 |
111 | ### Compiles and minifies for production
112 | ```
113 | npm run build
114 | ```
115 |
116 | # License
117 |
118 | [MIT](https://github.com/anvaka/lsystem/blob/master/LICENSE.md)
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | rm -rf ./dist
3 | npm run build
4 | cd ./dist
5 | git init
6 | git add .
7 | git commit -m 'push to gh-pages'
8 | git push --force git@github.com:anvaka/lsystem.git main:gh-pages
9 | cd ../
10 | git tag `date "+release-%Y%m%d%H%M%S"`
11 | git push --tags
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "l-system",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "start": "vue-cli-service serve",
8 | "build": "vue-cli-service build",
9 | "lint": "vue-cli-service lint"
10 | },
11 | "dependencies": {
12 | "codemirror": "^5.54.0",
13 | "core-js": "^3.6.4",
14 | "ngraph.events": "^1.2.1",
15 | "query-state": "^4.3.0",
16 | "stylus": "^0.54.7",
17 | "stylus-loader": "^3.0.2",
18 | "tinycolor2": "^1.4.1",
19 | "vue": "^2.6.11",
20 | "vue-codemirror-lite": "^1.0.4",
21 | "w-gl": "^0.21.0"
22 | },
23 | "devDependencies": {
24 | "@vue/cli-plugin-babel": "~4.3.0",
25 | "@vue/cli-plugin-eslint": "~4.3.0",
26 | "@vue/cli-service": "~4.3.0",
27 | "babel-eslint": "^10.1.0",
28 | "eslint": "^6.7.2",
29 | "eslint-plugin-vue": "^6.2.2",
30 | "vue-template-compiler": "^2.6.11"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/public/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/android-icon-144x144.png
--------------------------------------------------------------------------------
/public/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/android-icon-192x192.png
--------------------------------------------------------------------------------
/public/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/android-icon-36x36.png
--------------------------------------------------------------------------------
/public/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/android-icon-48x48.png
--------------------------------------------------------------------------------
/public/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/android-icon-72x72.png
--------------------------------------------------------------------------------
/public/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/android-icon-96x96.png
--------------------------------------------------------------------------------
/public/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/apple-icon-114x114.png
--------------------------------------------------------------------------------
/public/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/apple-icon-120x120.png
--------------------------------------------------------------------------------
/public/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/apple-icon-144x144.png
--------------------------------------------------------------------------------
/public/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/apple-icon-152x152.png
--------------------------------------------------------------------------------
/public/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/apple-icon-180x180.png
--------------------------------------------------------------------------------
/public/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/apple-icon-57x57.png
--------------------------------------------------------------------------------
/public/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/apple-icon-60x60.png
--------------------------------------------------------------------------------
/public/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/apple-icon-72x72.png
--------------------------------------------------------------------------------
/public/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/apple-icon-76x76.png
--------------------------------------------------------------------------------
/public/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/public/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/apple-icon.png
--------------------------------------------------------------------------------
/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | #ffffff
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/favicon-96x96.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | L-System Explorer
29 |
47 |
48 |
49 |
50 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "L System",
3 | "icons": [
4 | {
5 | "src": "android-icon-36x36.png",
6 | "sizes": "36x36",
7 | "type": "image\/png",
8 | "density": "0.75"
9 | },
10 | {
11 | "src": "android-icon-48x48.png",
12 | "sizes": "48x48",
13 | "type": "image\/png",
14 | "density": "1.0"
15 | },
16 | {
17 | "src": "android-icon-72x72.png",
18 | "sizes": "72x72",
19 | "type": "image\/png",
20 | "density": "1.5"
21 | },
22 | {
23 | "src": "android-icon-96x96.png",
24 | "sizes": "96x96",
25 | "type": "image\/png",
26 | "density": "2.0"
27 | },
28 | {
29 | "src": "android-icon-144x144.png",
30 | "sizes": "144x144",
31 | "type": "image\/png",
32 | "density": "3.0"
33 | },
34 | {
35 | "src": "android-icon-192x192.png",
36 | "sizes": "192x192",
37 | "type": "image\/png",
38 | "density": "4.0"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/public/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/ms-icon-144x144.png
--------------------------------------------------------------------------------
/public/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/ms-icon-150x150.png
--------------------------------------------------------------------------------
/public/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/ms-icon-310x310.png
--------------------------------------------------------------------------------
/public/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/public/ms-icon-70x70.png
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
69 |
70 |
71 |
72 |
110 |
111 |
251 |
--------------------------------------------------------------------------------
/src/CodeEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/LSystem.js:
--------------------------------------------------------------------------------
1 | import Turtle from './Turtle';
2 | import tinycolor from 'tinycolor2';
3 |
4 | // We don't want to crash the browser. If the expanded system is larger than this
5 | // we will stop iteration
6 | const LIMIT = 1000000;
7 |
8 | export default class LSystem {
9 | constructor(scene, systemSettings) {
10 | coerceTypes(systemSettings);
11 | this.rules = systemSettings.rules;
12 | this.actions = compileActions(systemSettings, this);
13 | this.turtle = new Turtle(scene, systemSettings);
14 | this.stepsPerFrame = systemSettings.stepsPerFrame || 42;
15 | this.drawQueue = [];
16 | this.lastDrawnIndex = -1;
17 |
18 | const depth = Number.isFinite(systemSettings.depth) ? systemSettings.depth : 5;
19 | let start = systemSettings.start;
20 | if (start === undefined) start = Object.keys(this.rules)[0];
21 | if (start === undefined) throw new Error('System does not have neither rewrite rules nor start state');
22 | this.start = start;
23 | this.depth = depth;
24 | this.simulate(); // TODO: Might exhaust system resources
25 | this.iterator = this.renderIterator();
26 | }
27 |
28 | dispose() {
29 | this.turtle.dispose();
30 | }
31 |
32 | frame() {
33 | let next, steps = 0;
34 | do {
35 | next = this.iterator.next();
36 | } while (!next.done && steps++ < this.stepsPerFrame );
37 | return !next.done;
38 | }
39 |
40 | simulate() {
41 | let production = this.start;
42 | let index = 0;
43 | while (index < this.depth && production.length < LIMIT) {
44 | production = this.iterateRules(production);
45 | index += 1;
46 | }
47 |
48 | this.production = production;
49 | this.complete = production.length < LIMIT;
50 | }
51 |
52 | iterateRules(rule) {
53 | let result = [];
54 | for (let op of rule) {
55 | let rewriteRule = this.rules[op];
56 | if (rewriteRule !== undefined) result.push(rewriteRule);
57 | else result.push(op);
58 | }
59 | return result.join('');
60 | }
61 |
62 | *renderIterator() {
63 | for (let op of this.production) {
64 | if (this.actions[op]) {
65 | if (this.stepsPerFrame < 0) {
66 | yield* this.actions[op]();
67 | } else {
68 | yield this.actions[op]();
69 | }
70 | }
71 | }
72 | }
73 | }
74 |
75 | function compileActions(systemSettings, lSystem) {
76 | let actions = {};
77 |
78 | let defaultRotationAngle = Number.isFinite(systemSettings.angle) ? systemSettings.angle : 60;
79 |
80 | let mergedActions = {
81 | 'F': {name: 'draw', args: []},
82 | 'f': {name: 'move', args: []},
83 | '+': {name: 'rotate', args: [defaultRotationAngle]},
84 | '-': {name: 'rotate', args: [-defaultRotationAngle]},
85 | '&': {name: 'swapAngle', args: [-defaultRotationAngle]},
86 | '[': {name: 'push', args: []},
87 | ']': {name: 'pop', args: []},
88 | ...systemSettings.actions
89 | }
90 |
91 | Object.keys(mergedActions).forEach(key => {
92 | let value = mergedActions[key];
93 | let turtleAction = turtleCanDo(value, lSystem);
94 | if (turtleAction) {
95 | actions[key] = turtleAction;
96 | } else {
97 | throw new Error("Turtle does not know how to do '" + value + "'");
98 | }
99 | });
100 |
101 | return actions;
102 | }
103 |
104 | function turtleCanDo(command, lSystem) {
105 | if (command.name.indexOf('rotate') === 0) {
106 | let angle = command.args[0]
107 | if (!Number.isFinite(angle)) throw new Error('`rotate() needs one float argument');
108 |
109 | switch (command.name) {
110 | case 'rotateX': return function() { lSystem.turtle.rotateX(angle) };
111 | case 'rotateY': return function() { lSystem.turtle.rotateY(angle) };
112 | }
113 |
114 | return function() { lSystem.turtle.rotateZ(angle) };
115 | }
116 |
117 | if (command.name === 'draw') {
118 | let length = getLength(command.args[0], 'draw');
119 | return function() {
120 | lSystem.turtle.draw(length)
121 | }
122 | }
123 |
124 | if (command.name.match(/chcolor|setColor/i)) {
125 | let color = command.args[0];
126 | return function() {
127 | lSystem.turtle.setColor(color)
128 | }
129 | }
130 |
131 | if (command.name === 'push') return function() {lSystem.turtle.push()}
132 | if (command.name === 'pop') return function() {lSystem.turtle.pop()}
133 | if (command.name === 'move') {
134 | let length = getLength(command.args[0], 'move');
135 | return function() {lSystem.turtle.move(length)}
136 | }
137 | if (command.name.match(/swapAngle/i)) {
138 | return function() {lSystem.turtle.swapAngle()}
139 | }
140 | }
141 |
142 |
143 | function getLength(value, name) {
144 | let length = 10;
145 | if (value !== undefined) {
146 | length = value;
147 | if (!Number.isFinite(length)) throw new Error(name +'(l) expects `l` to be a float number. Got: ' + value);
148 | }
149 | return length;
150 | }
151 |
152 |
153 | function coerceTypes(system) {
154 | if (system.angle !== undefined) system.angle = Number.parseFloat(system.angle);
155 | if (system.width !== undefined) system.width = Number.parseFloat(system.width);
156 | if (system.depth !== undefined) system.depth = Number.parseFloat(system.depth);
157 | if (system.color !== undefined) {
158 | let rgba = tinycolor(system.color).toRgb();
159 | system.color = (rgba.r << 24) | (rgba.g << 16) | (rgba.b << 8) | (rgba.a * 255 | 0)
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/LineCollection.js:
--------------------------------------------------------------------------------
1 | import {GLCollection, defineProgram, InstancedAttribute, ColorAttribute} from 'w-gl';
2 |
3 | export default class LineCollection extends GLCollection {
4 | constructor(gl, options = {}) {
5 | let program = defineProgram({
6 | gl,
7 | vertex: `
8 | uniform mat4 modelViewProjection;
9 | uniform float width;
10 | uniform vec2 resolution;
11 |
12 | attribute vec4 color;
13 | attribute vec3 from, to;
14 | attribute vec2 point;
15 |
16 | varying vec4 vColor;
17 | varying vec2 vPoint;
18 |
19 | void main() {
20 | vec4 clip0 = modelViewProjection * vec4(from, 1.0);
21 | vec4 clip1 = modelViewProjection * vec4(to, 1.0);
22 |
23 | vec2 screen0 = resolution * (0.5 * clip0.xy/clip0.w + 0.5);
24 | vec2 screen1 = resolution * (0.5 * clip1.xy/clip1.w + 0.5);
25 |
26 | vec2 xBasis = normalize(screen1 - screen0);
27 | vec2 yBasis = vec2(-xBasis.y, xBasis.x);
28 |
29 | // Offset the original points:
30 | vec2 pt0 = screen0 + width * point.x * yBasis;
31 | vec2 pt1 = screen1 + width * point.x * yBasis;
32 |
33 | vec2 pt = mix(pt0, pt1, point.y);
34 | vec4 clip = mix(clip0, clip1, point.y);
35 |
36 | gl_Position = vec4(clip.w * (2.0 * pt/resolution - 1.0), clip.z, clip.w);
37 | vColor = color.abgr; // mix(.abgr, aToColor.abgr, aPosition.y);
38 | }`,
39 |
40 | fragment: `
41 | precision highp float;
42 | varying vec4 vColor;
43 |
44 | void main() {
45 | gl_FragColor = vColor;
46 | }`,
47 | attributes: {
48 | color: new ColorAttribute()
49 | },
50 | instanced: {
51 | point: new InstancedAttribute([
52 | -0.5, 0, -0.5, 1, 0.5, 1, // First 2D triangle of the quad
53 | -0.5, 0, 0.5, 1, 0.5, 0 // Second 2D triangle of the quad
54 | ])
55 | }
56 | });
57 | super(program);
58 | this.width = options.width || 2;
59 | this.multiColorSegment = true;
60 | }
61 |
62 | draw(_, drawContext) {
63 | if (!this.uniforms) {
64 | this.uniforms = {
65 | modelViewProjection: this.modelViewProjection,
66 | width: this.width,
67 | resolution: [drawContext.width, drawContext.height]
68 | }
69 | }
70 | this.uniforms.resolution[0] = drawContext.width;
71 | this.uniforms.resolution[1] = drawContext.height;
72 | this.program.draw(this.uniforms);
73 | }
74 |
75 | // implement lineRenderTrait to allow SVG export via w-gl
76 | forEachLine(cb) {
77 | let count = this.program.getCount()
78 | for (let i = 0; i < count; ++i) {
79 | let vertex = this.program.get(i);
80 | let from = { x: vertex.from[0], y: vertex.from[1], z: vertex.from[2], color: vertex.color }
81 | let to = { x: vertex.to[0], y: vertex.to[1], z: vertex.to[2], color: vertex.color }
82 | cb(from, to);
83 | }
84 | }
85 |
86 | getLineColor(from) {
87 | let count = this.program.getCount()
88 | let c = from ?
89 | from.color :
90 | count > 0 ? this.program.get(0).color : 0xFFFFFFFF;
91 |
92 | return [
93 | ((c >> 24) & 0xFF) / 255,
94 | ((c >> 16) & 0xFF) / 255,
95 | ((c >> 8) & 0xFF) / 255,
96 | ((c >> 0) & 0xFF) / 255,
97 | ]
98 | }
99 | }
--------------------------------------------------------------------------------
/src/NoWebGL.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WebGL is not enabled :(
4 |
5 | To render large amount of data fast, this website uses WebGL ,
6 | which seem to be not supported by the device that you are using.
7 |
8 |
Please try a different device to play with this website
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/Turtle.js:
--------------------------------------------------------------------------------
1 | import LineCollection from './LineCollection';
2 | import tinycolor from 'tinycolor2';
3 |
4 | export default class Turtle {
5 | constructor(scene, options = {}) {
6 | this.position = options.position || [0, 0, 0];
7 | checkArray('position', this.position);
8 | this.direction = options.direction || [1, 0, 0];
9 | normalize(this.direction);
10 | checkArray('direction', this.direction);
11 |
12 | this.lines = new LineCollection(scene.getGL(), { capacity: 1024, width: options.width || 2 });
13 | this.scene = scene;
14 | this.color = options.color || 0xFFFFFFFF;
15 |
16 | this.scene.appendChild(this.lines);
17 | this.stack = [];
18 | this.invertZYAngle = false;
19 | }
20 |
21 | push() {
22 | this.stack.push({
23 | color: this.color,
24 | direction: [this.direction[0], this.direction[1], this.direction[2]],
25 | position: [this.position[0], this.position[1], this.position[2]],
26 | })
27 | }
28 |
29 | pop() {
30 | let state = this.stack.pop();
31 | this.color = state.color;
32 | this.direction = state.direction;
33 | this.position = state.position;
34 | }
35 |
36 | dispose() {
37 | this.scene.removeChild(this.lines);
38 | }
39 |
40 | move(distance) {
41 | let p = this.position;
42 | let n = this.direction;
43 | let x = p[0] + distance * n[0];
44 | let y = p[1] + distance * n[1];
45 | let z = p[2] + distance * n[2];
46 | p[0] = x; p[1] = y; p[2] = z;
47 | }
48 |
49 | draw(distance) {
50 | let p = this.position;
51 | let n = this.direction;
52 | let x = p[0] + distance * n[0];
53 | let y = p[1] + distance * n[1];
54 | let z = p[2] + distance * n[2];
55 | this.lines.add({
56 | from: p,
57 | to: [x, y, z],
58 | color: this.color
59 | });
60 |
61 | p[0] = x; p[1] = y; p[2] = z;
62 | this.scene.renderFrame();
63 | }
64 |
65 | setColor(newColorValue) {
66 | if (newColorValue === undefined) {
67 | throw new Error('setColor() expects color value, got undefined');
68 | }
69 | const rgba = tinycolor(typeof newColorValue === 'number' ?
70 | Object.values(tinycolor.names)[newColorValue] : newColorValue
71 | ).toRgb();
72 | this.color = (rgba.r << 24) | (rgba.g << 16) | (rgba.b << 8) | (rgba.a * 255 | 0)
73 | }
74 |
75 | rotateZ(angleInDegrees) {
76 | if (this.invertZYAngle) angleInDegrees *= -1;
77 | let rad = Math.PI * angleInDegrees / 180;
78 | let n = this.direction;
79 |
80 | let x = Math.cos(rad) * n[0] - Math.sin(rad) * n[1];
81 | let y = Math.sin(rad) * n[0] + Math.cos(rad) * n[1];
82 |
83 | n[0] = x;
84 | n[1] = y;
85 | }
86 |
87 | rotateY(angleInDegrees) {
88 | if (this.invertZYAngle) angleInDegrees *= -1;
89 | let rad = Math.PI * angleInDegrees / 180;
90 | let n = this.direction;
91 |
92 | let x = Math.cos(rad) * n[0] - Math.sin(rad) * n[2];
93 | let z = Math.sin(rad) * n[0] + Math.cos(rad) * n[2];
94 |
95 | n[0] = x;
96 | n[2] = z;
97 | }
98 |
99 | rotateX(angleInDegrees) {
100 | let rad = Math.PI * angleInDegrees / 180;
101 | let n = this.direction;
102 |
103 | let y = Math.cos(rad) * n[1] - Math.sin(rad) * n[2];
104 | let z = Math.sin(rad) * n[1] + Math.cos(rad) * n[2];
105 |
106 | n[1] = y;
107 | n[2]= z;
108 | }
109 |
110 | swapAngle() {
111 | this.invertZYAngle = !this.invertZYAngle;
112 | }
113 | }
114 |
115 | function checkArray(arrayName, array, ) {
116 | if (!Array.isArray(array)) {
117 | throw new Error('Array is expected for `' + arrayName + '`');
118 | }
119 | if (array.length === 2) {
120 | array.push(0);
121 | }
122 | if (array.length !== 3) {
123 | throw new Error('Array `' + arrayName + '` should have 2 or 3 coordinates, got ' + array.length);
124 | }
125 | }
126 | function normalize(arr) {
127 | let l = Math.hypot(arr[0], arr[1], arr[2]);
128 | arr[0] /= l;
129 | arr[1] /= l;
130 | arr[2] /= l;
131 | }
132 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvaka/lsystem/7f0431d64d0b0cfc9ff2fb8416f0e797c36a4593/src/assets/logo.png
--------------------------------------------------------------------------------
/src/bus.js:
--------------------------------------------------------------------------------
1 | import eventify from 'ngraph.events';
2 | const bus = eventify({});
3 | export default bus;
--------------------------------------------------------------------------------
/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation .
8 |
9 |
Installed CLI Plugins
10 |
14 |
Essential Links
15 |
22 |
Ecosystem
23 |
30 |
31 |
32 |
33 |
41 |
42 |
43 |
59 |
--------------------------------------------------------------------------------
/src/createScene.js:
--------------------------------------------------------------------------------
1 | import {createScene, createGuide, toSVG} from 'w-gl';
2 | import LSystem from './LSystem';
3 |
4 | export default function createLScene(canvas) {
5 | let scene = createScene(canvas);
6 | let guide = createGuide(scene, {
7 | lineColor: 0x0d3f71ff
8 | });
9 |
10 | scene.setClearColor(12/255, 41/255, 82/255, 1)
11 | let initialSceneSize = 40;
12 | scene.setViewBox({
13 | left: -initialSceneSize,
14 | top: -initialSceneSize,
15 | right: initialSceneSize,
16 | bottom: initialSceneSize,
17 | });
18 |
19 | let canDrawMore = false;
20 | let lSystem = [];
21 | let disposeLater;
22 | let raf = requestAnimationFrame(frame);
23 |
24 | return {
25 | dispose,
26 | setSystem,
27 | saveToSVG,
28 | isComplete,
29 | }
30 |
31 | function saveToSVG(fileName) {
32 | let svg = toSVG(scene, {
33 | open() {
34 | return ``;
35 | }
36 | });
37 | let blob = new Blob([svg], {type: "image/svg+xml"});
38 | let url = window.URL.createObjectURL(blob);
39 | // For some reason, safari doesn't like when download happens on the same
40 | // event loop cycle. Pushing it to the next one.
41 | setTimeout(() => {
42 | let a = document.createElement("a");
43 | a.href = url;
44 | a.download = fileName;
45 | a.click();
46 | revokeLater(url);
47 | }, 30)
48 | }
49 |
50 | function revokeLater(url) {
51 | // In iOS immediately revoked URLs cause "WebKitBlobResource error 1." error
52 | // Setting a timeout to revoke URL in the future fixes the error:
53 | setTimeout(() => {
54 | window.URL.revokeObjectURL(url);
55 | }, 45000);
56 | }
57 |
58 | function setSystem(newSystem) {
59 | cancelAnimationFrame(raf);
60 | if (disposeLater) {
61 | lSystem.forEach(l => disposeLater.push(l));
62 | } else {
63 | disposeLater = lSystem;
64 | }
65 |
66 | if (!Array.isArray(newSystem)) {
67 | newSystem = [newSystem]
68 | }
69 | let wantGuideToBeHidden = false;
70 | lSystem = [];
71 | newSystem.forEach(systemSettings => {
72 | if (systemSettings.hideGrid) {
73 | wantGuideToBeHidden = true;
74 | }
75 | lSystem.push(new LSystem(scene, systemSettings));
76 | });
77 |
78 | if (wantGuideToBeHidden && guide) {
79 | guide.dispose();
80 | guide = null;
81 | } else if (!wantGuideToBeHidden && !guide) {
82 | guide = createGuide(scene);
83 | }
84 |
85 | raf = requestAnimationFrame(frame);
86 | }
87 |
88 | function isComplete() {
89 | return lSystem.every(x => x.complete);
90 | }
91 |
92 | function frame() {
93 | canDrawMore = false;
94 | lSystem.forEach(drawSystem);
95 | if (canDrawMore) {
96 | raf = requestAnimationFrame(frame);
97 | }
98 | if (disposeLater) {
99 | disposeLater.forEach(l => l.dispose())
100 | disposeLater = null;
101 | }
102 | }
103 |
104 | function drawSystem(system) {
105 | canDrawMore |= system.frame();
106 | }
107 |
108 | function dispose() {
109 | cancelAnimationFrame(raf);
110 | scene.dispose();
111 | }
112 | }
--------------------------------------------------------------------------------
/src/editor.styl:
--------------------------------------------------------------------------------
1 | // (C) Copyright (c) 2014 fergaldoyle
2 | // https://github.com/fergaldoyle/brackets-visual-studio-dark/blob/master/LICENSE
3 | //
4 | // Adjusted to vector fields need by https://github.com/anvaka
5 |
6 | @import "./shared.styl";
7 |
8 | standardText = #DDD;
9 | maxHeight = 320px;
10 |
11 | .CodeMirror {
12 | height: auto;
13 | max-height: maxHeight;
14 | font-size: 14px;
15 | z-index: 0;
16 | background-color: window-background;
17 | border: 1px solid window-background;
18 |
19 | .CodeMirror-gutter-elt{
20 | background: transparent;
21 | }
22 |
23 | .CodeMirror-scroll {
24 | height: auto;
25 | max-height: maxHeight;
26 | }
27 | .CodeMirror-linenumber,
28 | .CodeMirror-scroll,
29 | .CodeMirror-gutters {
30 | color: standardText;
31 | font-family: Consolas,'SourceCodePro-Medium',monaco,monospace;
32 | }
33 |
34 | .CodeMirror-linenumber {
35 | color: #2B91AF;
36 | }
37 |
38 | .CodeMirror-selected {
39 | background-color: #264F78;
40 | }
41 |
42 | .cm-matchhighlight {
43 | //background-color: #123E70;
44 | }
45 |
46 | .cm-matchingtag,
47 | .CodeMirror-matchingtag {
48 | background-color: #113D6F;
49 | //box-shadow: 0px 0px 2px #ADC0D3;
50 | }
51 |
52 | .cm-matchingbracket,
53 | .CodeMirror-matchingbracket {
54 | background-color: #113D6F;
55 | box-shadow: 0px 0px 3px #99B0C7;
56 | color: standardText !important;
57 | }
58 |
59 |
60 | .CodeMirror-cursor {
61 | border-left: 1px solid standardText;
62 | z-index: 3;
63 | }
64 |
65 | .CodeMirror-activeline-background {
66 | outline: 2px solid #dddddd;
67 | }
68 | // Languages
69 | // common
70 | .cm-m-lsystem,
71 | .cm-variable,
72 | .cm-variable-2,
73 | .cm-variable-3 {
74 | color: standardText;
75 | }
76 |
77 | .cm-pragma {
78 | font-weight: bold;
79 | color: #0afff8;
80 | }
81 |
82 | .cm-operator {
83 | color: #B4B4B4;
84 | }
85 |
86 | .cm-comment {
87 | color: #57A64A;
88 | font-style: italic;
89 | }
90 |
91 | .cm-string {
92 | color: #D69D85;
93 | }
94 |
95 | .cm-attribute {
96 | color: #9CDCFE;
97 | }
98 |
99 | .cm-number {
100 | color: #B5CEA8;
101 | }
102 |
103 | .cm-def {
104 | color: #4EC9B0;
105 | }
106 |
107 | .cm-meta {
108 | color: #9B9B9B;
109 | }
110 |
111 | .cm-keyword,
112 | .cm-tag, .cm-atom {
113 | color: #569CD6;
114 | }
115 |
116 | .cm-bracket {
117 | color: #808080;
118 | }
119 |
120 | .cm-m-clike.cm-builtin {
121 | color: #569CD6;
122 | }
123 |
124 | .cm-m-clike.cm-variable-2 {
125 | //color: #d26bca;
126 | }
127 | // search highlight
128 | .CodeMirror-searching {
129 | color: #ddd !important;
130 | }
131 |
132 | .CodeMirror-searching.searching-last,
133 | .CodeMirror-searching.searching-first {
134 | border-radius: 1px;
135 | background-color: #653306;
136 | box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.8);
137 | padding: 1px 0;
138 | }
139 |
140 | .CodeMirror-searching.searching-current-match {
141 | background-color: #515C6A;
142 | }
143 | }
144 |
145 | .CodeMirror-focused {
146 | border: 1px dashed white;
147 | background: #13294f;
148 | }
--------------------------------------------------------------------------------
/src/getCodeModel.js:
--------------------------------------------------------------------------------
1 | import parseExpression from './grammar/parser';
2 | const queryState = require('query-state');
3 |
4 | var qs = queryState({
5 | code: getInitialCode()
6 | }, {
7 | useSearch: true
8 | });
9 |
10 | // Create a Parser object from our grammar.
11 | let standardCollection = [
12 | `// dragon curve
13 | axiom: X
14 | rules:
15 | X => X+YF+
16 | Y => -FX-Y
17 |
18 | depth: 10
19 | angle: 90`,
20 |
21 | `// William McWorters Terdragon
22 | axiom: F
23 | rules:
24 | F => F+F-F
25 |
26 | depth: 8
27 | angle: 120`,
28 |
29 | `// William McWorters Pentl
30 | axiom: F-F-F-F-F
31 | rules:
32 | F => cF-F-F++dF+F-F
33 |
34 | depth: 4
35 | angle: 72
36 | actions:
37 | c => setColor("mediumpurple")
38 | d => setColor("violet") `,
39 |
40 | `// William McWorters Pentant
41 | axiom: X-X-X-X-X
42 | rules:
43 | F =>
44 | X => dFX-FX-FX+FY+FY+FX-FX
45 | Y => cFY+FY-FX-FX-FY+FY+FY
46 |
47 | depth: 3
48 | angle: 72
49 | actions:
50 | c => setColor("goldenrod")
51 | d => setColor("gold") `,
52 |
53 | `// William McWorter Sierπnski Carpet
54 | axiom: F
55 | rules:
56 | F => cF+F-F-F-f+dF+F+F-F
57 | f => fff
58 |
59 | angle: 90
60 | depth: 4
61 | actions:
62 | c => setColor("goldenrod")
63 | d => setColor("gold") `,
64 |
65 | `// Hexagonal Gosper
66 | axiom: X
67 | rules:
68 | X => X+YF++YF-FX--FXFX-YF+
69 | Y => -FX+YFYF++YF+FX--FX-Y
70 |
71 | angle: 60`,
72 |
73 | `// Peano curve
74 | axiom: X
75 | rules:
76 | X => XFYFX-F-YFXFY+F+XFYFX
77 | Y => YFXFY+F+XFYFX-F-YFXFY
78 |
79 | depth:4
80 | angle: 90`,
81 |
82 | `// Gary Teachout Pean-c
83 | axiom: FX
84 | rules:
85 | F =>
86 | X => FX-FY-FX+FY+FX+FY+FX+FY+FX-FY-FX-FY-FX-FY-FX+FY+FX
87 | Y => FY
88 |
89 | depth: 3
90 | angle: 45`,
91 |
92 | `// Square Sierpinski
93 | axiom: F+XF+F+XF
94 | rules:
95 | X => XF-F+F-XF+F+XF-F+F-X
96 |
97 | depth: 4
98 | angle: 90 `,
99 |
100 | `// Tree
101 | axiom: X
102 | rules:
103 | F => FF
104 | X => F-[[X]+X]+F[+FX]-X
105 |
106 | direction: [0, 1, 0]
107 | angle: 22.5`,
108 | `// Tree with color
109 | axiom: X
110 | rules:
111 | F => FF
112 | X => F-[[X]+X]+cF[+dFX]-X
113 |
114 | color:brown
115 | direction: [0, 1, 0]
116 | angle: 22.5a
117 | actions:
118 | c => setColor('green')
119 | d => setColor('lightgreen')`,
120 |
121 | `// Bush, after P. Bourke
122 | axiom: Y
123 | rules:
124 | X => X[-FFF]c[+FFF]FX
125 | Y => dYFXe[+Y][-Y]
126 |
127 | color: brown
128 | direction: [0, 1, 0]
129 | angle: 22.5
130 | actions:
131 | c => setColor('green')
132 | d => setColor('lime')
133 | e => setColor('brown')`,
134 |
135 | ` // Grains, after P. Bourke
136 | axiom: Y
137 | rules:
138 | X => dX[-F+FF]cg[+F-FF]dFX
139 | Y => eYFX[+Y][-Y]
140 |
141 | depth:5
142 | direction: [0, 1, 0]
143 | angle: 27
144 | actions:
145 | c => setColor('green')
146 | d => setColor('lime')
147 | e => setColor('lightgreen')
148 | g => rotate(0.3)`,
149 |
150 | ` // Grains 2, after P. Bourke
151 | axiom: Y
152 | rules:
153 | X => cX[-FF-F]g[+FF+F]dFX
154 | Y => eYFX[+Y][-Y]XFY
155 |
156 | depth:4
157 | direction: [0, 1, 0]
158 | angle: 27
159 | actions:
160 | c => setColor('green')
161 | d => setColor('lime')
162 | e => setColor('gold')
163 | g => rotate(0.2)`,
164 |
165 | `// Bush, P. Bourke
166 | axiom: F
167 | rules:
168 | F => FF+[c+F-F-F]-[-F+F+dF]
169 |
170 | color: green
171 | direction: [0, 1, 0]
172 | angle: 21
173 | depth:4
174 | actions:
175 | c => setColor('green')
176 | d => setColor('lime')`,
177 |
178 | `// P. Bourkes Pentaplexy
179 | axiom: F++F++F++F++F
180 | rules:
181 | F => cF++F++F+++++dF-F++F
182 |
183 | depth:3
184 | angle: 36
185 | actions:
186 | c => setColor('mediumpurple')
187 | d => setColor('violet')`,
188 |
189 | `// poetasters shrub
190 | axiom: F
191 | rules:
192 | F => Fe[+cFF]Fd[-FF]cF
193 |
194 | color:brown
195 | direction: [0, 1, 0]
196 | angle: 322
197 | depth: 4
198 | actions:
199 | c => setColor('green')
200 | d => setColor('lightgreen')
201 | e => setColor('brown')`,
202 |
203 | `// poetasters weed, after, P. Bourke
204 | axiom: F
205 | rules:
206 | F -> F-[XY]+[XY]F+[XY]-[XY]
207 | X -> +dFY
208 | Y -> -cFX
209 |
210 | color: brown
211 | direction: [0, 1, 0.5]
212 | angle: 22.5
213 | depth:5
214 | actions:
215 | c => setColor('green')
216 | d => setColor('lime')`,
217 |
218 | `// Unlikely bush (after Bourke)
219 | axiom: F
220 | rules:
221 | F => eF[+cFF][-FF]cF[-F]d[+F]F
222 |
223 | color: brown
224 | direction: [0, 1, 0]
225 | angle: 330
226 | depth:3
227 | actions:
228 | c => setColor('green')
229 | d => setColor('lime')
230 | e => setColor('brown')`,
231 |
232 | `// Weed, P. Bourke
233 | axiom: F
234 | rules:
235 | F -> FF-[XY]+[XY]
236 | X -> +cFY
237 | Y -> -dFX
238 |
239 | color: brown
240 | direction: [0, 1, 1]
241 | angle: 22.5
242 | actions:
243 | c => setColor('green')
244 | d => setColor('lime')`,
245 |
246 | `// P. Bourke after Saupe
247 | axiom: VZFFF
248 | rules:
249 | V -> [+++W][---W]YV
250 | W -> +X[-W]Z
251 | X -> -W[+X]Z
252 | Y -> YZ
253 | Z -> [-FcFF][+FdFF]F
254 |
255 | color: green
256 | depth:8
257 | direction: [0, 1, 0]
258 | angle: 20
259 | actions:
260 | c => setColor('lightgreen')
261 | d => setColor('lime')`,
262 |
263 | `// poetaster's curly, inspired by McWorter
264 | axiom: F+F-F+F
265 | rules:
266 | F => eF-F-F++[cF+F-dF[GGG]][GGG]
267 | G => c--g--g--g--g--g--g--g
268 |
269 | depth: 3
270 | angle: 17
271 | width:2
272 | direction: [-0.5,0.7,-0.5]
273 | actions:
274 | c => setColor("palegreen")
275 | d => setColor("violet")
276 | e => setColor("green")
277 | g => draw(2)`,
278 |
279 | `// aquatic plant
280 | axiom: F
281 | rules:
282 | F -> FFc[-F++F]d[+F--F]e++F--F
283 |
284 | color: brown
285 | direction: [0, 1, 0.5]
286 | angle: 27
287 | depth:4
288 | actions:
289 | c => setColor('green')
290 | d => setColor('lime')
291 | e => setColor('goldenrod')`,
292 |
293 | `// another aquatic
294 | axiom: F
295 | rules:
296 | F => FMNOMBxPNMyO
297 | M => e[-F++F++]
298 | N => d[+F--F--]
299 | O => c++F--F
300 | P => d--F++F
301 |
302 | color: brown
303 | direction: [0, 1, -1]
304 | angle: 27
305 | depth:5
306 | actions:
307 | c => setColor('green')
308 | d => setColor('lime')
309 | e => setColor('goldenrod')
310 | x => rotateX(2)
311 | y => rotateY(-3)`,
312 |
313 | `// poetasters third aquatic
314 | axiom: F
315 | rules:
316 | F -> FMNxQRyQR[O-O-O-O-0]
317 | M => d[++FF+FF+]
318 | N => d[--FF-FF-]
319 | O => e[F-F-F++dF+F-F]
320 | Q => c++F--F
321 | R => c--F++F
322 |
323 | color: brown
324 | direction: [0, 1, -1]
325 | angle: 17
326 | depth:4
327 | actions:
328 | c => setColor('green')
329 | d => setColor('lime')
330 | e => setColor('goldenrod')
331 | x => rotateX(2)
332 | y => rotateY(-1.5)`,
333 |
334 | `// poetasters sallow thorn
335 | axiom: F
336 | rules:
337 | F -> FMNxQRyQROP
338 | M => d[++FF+FF+]
339 | N => d[--FF-FF-]
340 | O => e[-F++F++]
341 | P => e[+F--F--]
342 | Q => c++F--F
343 | R => c--F++F
344 |
345 | color: brown
346 | direction: [0.5, 1, -1]
347 | angle: 27
348 | depth:5
349 | actions:
350 | c => setColor('green')
351 | d => setColor('lime')
352 | e => setColor('goldenrod')
353 | x => rotateX(2)
354 | y => rotateY(-1.5)`,
355 |
356 | `// Pean-c flower after Gary Teachout
357 | axiom: FXhFXiFX
358 | rules:
359 | F =>
360 | X => [FX-FY][-cFX-FY-FX][ZZ]-dFY-FX+FY+FX
361 | Y => FY
362 | Z => -cFX-FY-FX
363 |
364 | color: green
365 | depth: 3
366 | angle: 340
367 | width: 2
368 | direction: [1,1,1]
369 | actions:
370 | c => setColor("violet")
371 | d => setColor("lime")
372 | h => rotate(5)
373 | i => rotate(-3)`,
374 |
375 | `// poetasters succulent 1
376 | axiom: A
377 | rules:
378 | A =>[FL]gAhg[FLA]
379 | F => cSF
380 | S => dFL
381 | L => c[F+F+F]fe[F-F-F]
382 |
383 | color:green
384 | direction: [0, 1, 0.5]
385 | width: 4
386 | angle: 17
387 | depth: 7
388 | actions:
389 | c => setColor('green')
390 | d => setColor('lime')
391 | e => setColor('lightgreen')
392 | g => rotate(4.5)
393 | h => rotate(-3)`,
394 |
395 | `// Pyramids, Anthony Hanmer ADH258a
396 | axiom: F++F++F+++F--F--F
397 | rules:
398 | F => cFF++F++F++dFFF
399 |
400 | color: gold
401 | angle: 60
402 | depth:3
403 | actions:
404 | c => setColor('gold')
405 | d => setColor('goldenrod')`,
406 |
407 | `// Hilbert curve
408 | axiom: X
409 | rules:
410 | X => -YF+XFX+FY-
411 | Y => +XF-YFY-FX+
412 |
413 | angle: 90`,
414 |
415 | `// Levey Curve
416 | axiom: F++F++F++F
417 | rules:
418 | F => -dF++cF-
419 | angle: 45
420 | depth: 12
421 | actions:
422 | c => setColor("goldenrod")
423 | d => setColor("gold")`,
424 |
425 | `// blocks
426 | axiom: F+F+F+F
427 | rules:
428 | F => F-f+FF-FF-FF-FFf-FFFF
429 | f => ffffff
430 |
431 | angle: 90
432 | depth: 3 `,
433 |
434 | `// aztec blocks
435 | axiom: F-F-F-F
436 | rules:
437 | F => F-cf+FF-F-FF-Ff-FF+df-FF+F+FF+Ff+FFF
438 | f => ffffff
439 |
440 | angle: 90
441 | depth: 2
442 | actions:
443 | c => setColor("goldenrod")
444 | d => setColor("gold")`,
445 |
446 | `// Color Mosaic
447 | axiom: F+F+F+F
448 | rules:
449 | F => dFF+F+cF+F+FF
450 |
451 | color: green
452 | depth: 3
453 | angle: 90
454 | actions:
455 | c => setColor('lime')
456 | d => setColor('green')`,
457 |
458 | `// 3 Blocks
459 | axiom: F^^F^^F
460 | rules:
461 | F => F-fff^F^^F^^F&&fff-FFF
462 | f => fff
463 |
464 | depth: 3
465 | actions:
466 | - => rotate(-90)
467 | ^ => rotate(60)
468 | & => rotate(-60) `,
469 | `// Leaf
470 | axiom: Y---Y
471 | rules:
472 | X => F-FF-F--[--X]F-FF-F--F-FF-F--
473 | Y => f-F+X+F-fY
474 |
475 | depth: 8
476 | angle: 60`, `// esum
477 | axiom: X+X+X+X+X+X+X+X
478 | rules:
479 | X => [F[-X++Y]]
480 | Y => [F[-Y--X]]
481 | F => F
482 |
483 | depth: 6
484 | angle: -45`,
485 | `// Penrose tiling
486 | axiom: [N]++[N]++[N]++[N]++[N]
487 | rules:
488 | M => OF++PF----NF[-OF----MF]++
489 | N => +OF--PF[---MF--NF]+
490 | O => -MF++NF[+++OF++PF]-
491 | P => --OF++++MF[+PF++++NF]--NF
492 | F =>
493 |
494 | depth: 4
495 | angle: 36
496 | `
497 | ]
498 |
499 | export default function getCodeModel(scene) {
500 | let model = {
501 | setCode,
502 | error: null,
503 | randomize,
504 | code: qs.get('code')
505 | }
506 | let lastPickedIndex = -1;
507 |
508 | setCode(model.code);
509 |
510 | return model;
511 |
512 | function randomize() {
513 | let index;
514 | do { index = pickRandomIndex(standardCollection) } while (index === lastPickedIndex);
515 | lastPickedIndex = index;
516 | let code = standardCollection[lastPickedIndex];
517 | setCode(code);
518 | model.ignoreNextUpdate = true;
519 | model.code = code;
520 | }
521 |
522 | function setCode(newCode) {
523 | newCode = newCode.trim();
524 | if (!newCode) {
525 | model.error = 'Enter a system description above'
526 | return;
527 | }
528 | try {
529 | let system = parseExpression(newCode);
530 | if (system) {
531 | // TODO: I messed up with grammar, and seems like string value takes precedence over
532 | // axiom clause. A little hack here to put them back on the same page until better fix:
533 | if (system.axiom) system.start = system.axiom;
534 | scene.setSystem(system);
535 | if (scene.isComplete()) {
536 | model.error = null;
537 | } else {
538 | model.error = 'The system limit reached.\nRendering first 1,000,000 characters'
539 | }
540 | qs.set('code', newCode);
541 | } else {
542 | model.error = 'Could not parse the input string';
543 | }
544 | } catch (e) {
545 | model.error = e.message;
546 | }
547 | }
548 | }
549 |
550 | function getInitialCode() {
551 | // todo: query state?
552 | return `axiom: X
553 | rules:
554 | X => -YF+XFX+FY-
555 | Y => +XF-YFY-FX+
556 |
557 | depth: 5
558 | stepsPerFrame: 10
559 | width: 2
560 | color: #FFFFFF
561 |
562 | actions:
563 | - => rotate(-90)
564 | + => rotate(90)
565 | F => draw()
566 | `
567 | }
568 |
569 |
570 | function getRandomSystem(length) {
571 | let states = 'FXY';
572 | let res = [];
573 | let lastCh = '';
574 | while (res.length < length) {
575 | let ch = pickChar();
576 | if (ch === '+' && lastCh === '-') ch = '-'
577 | else if (ch === '-' && lastCh === '+') ch = '+';
578 | else if (ch === 'F' && lastCh === 'F') ch = Math.random() < 0.5 ? 'X': 'Y';
579 | res.push(ch);
580 | lastCh = ch;
581 | }
582 |
583 | for (let ch of 'FXY') {
584 | if (res.indexOf(ch) < 0) {
585 | res[Math.floor(Math.random() * res.length)] = ch;
586 | }
587 | }
588 |
589 | let X = res.join('');
590 | let Y = res.reverse().map( x => {
591 | if (x === '+') return '-';
592 | if (x === '-') return '+';
593 | if (x === 'Y') return 'X';
594 | if (x === 'X') return 'Y';
595 | return x;
596 | } ).join('')
597 |
598 | let angle = Math.random() < 0.5 ? 60 : 90;
599 | if (angle === 90 && ( X.match(/(\+\+|\+-|--)/) || Y.match(/(\+\+|\+-|--)/))) angle = 45;
600 | return {X, Y, angle}
601 |
602 | function pickChar() {
603 | let r = Math.random();
604 | if (r < 0.78) {
605 | r = Math.random();
606 | return states[Math.floor(states.length * r)]
607 | } else {
608 | return r < 0.89 ? '+' : '-'
609 | }
610 | }
611 | }
612 |
613 | function pickRandomIndex(arr) {
614 | return Math.floor(Math.random() * arr.length);
615 | }
616 |
--------------------------------------------------------------------------------
/src/grammar/parser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This is a very simple parser to parse our "L-system" language
3 | *
4 | * It works as a simple state machine, which has two rules:
5 | *
6 | * "ExpectKeyValue" and "ExpectRewriteValue". In each state it consumes
7 | * a line from the input and either transitions to the next state
8 | * or adds new key/values to the current state.
9 | */
10 | const separator = /:|->|=>|=|→/
11 | const rules = /rules|production rules/i;
12 | const actions = /actions/i;
13 | const ws = /[ \t]/g;
14 | const numericKey = /angle|depth|width|stepsPerFrame/i;
15 | const vectorKey = /direction|position/i;
16 | const parens = /[[\]()]/;
17 |
18 | /**
19 | * Read rewrite rule.
20 | */
21 | class ExpectRewriteValue {
22 | constructor(addTo, parent, valueParser) {
23 | this.parent = parent;
24 | this.addTo = addTo;
25 | this.valueParser = valueParser || (result => result);
26 | }
27 |
28 | process(line) {
29 | let result = line.split(separator)
30 | if (result.length === 1) throw new Error('Expected `_ => value`, found ' + line);
31 |
32 | let key = result[0].trim();
33 | if (key.length !== 1) {
34 | // we are done with this rule
35 | return this.parent.process(line);
36 | }
37 | let v = result.slice(1).join('').replace(ws, '');
38 | this.addTo[key] = this.valueParser(v);
39 | return this;
40 | }
41 | }
42 |
43 | function extractAction(stringValue) {
44 | let m = stringValue.match(/(\w+?)\s*\((.+?)?\)/);
45 | if (!m) {
46 | throw new Error('Expected `action()` call, got: ' + stringValue);
47 | }
48 | let name = m[1];
49 | let args;
50 | if (!m[2]) args = [];
51 | else {
52 | args = m[2].split(',').map(v => {
53 | let stringValue = extractStringValue(v);
54 | if (stringValue !== undefined) return stringValue;
55 | return Number.parseFloat(v.trim());
56 | });
57 | }
58 | return {name, args};
59 | }
60 |
61 | function extractStringValue(value) {
62 | let matchedString = (value && value.match(/\s*(?:'(.*?)'|"(.*?)")\s*/));
63 | if (matchedString) {
64 | return matchedString[1] || matchedString[2];
65 | }
66 | }
67 |
68 | /**
69 | * Expect arbitrary key/value line.
70 | */
71 | class ExpectKeyValue {
72 | constructor(addTo) {
73 | this.addTo = addTo;
74 | }
75 |
76 | process(line) {
77 | let result = line.split(separator)
78 | if (result.length === 1) throw new Error('Expected `key: value`, found ' + line);
79 | let key = result[0].trim();
80 | let value = result.slice(1).join('').trim();
81 | if (key.match(rules)) {
82 | this.addTo.rules = {};
83 | return new ExpectRewriteValue(this.addTo.rules, this);
84 | } else if (key.match(actions)) {
85 | this.addTo.actions = {};
86 | return new ExpectRewriteValue(this.addTo.actions, this, extractAction);
87 | } else if (key.match(numericKey)) {
88 | let v = Number.parseFloat(value);
89 | if (!Number.isFinite(v)) {
90 | throw new Error('Expected a number value for `' + key + '`');
91 | }
92 | this.addTo[key] = v;
93 | return this;
94 | } else if (key.match(vectorKey)) {
95 | this.addTo[key] = parseVector(value, key)
96 | return this;
97 | }
98 |
99 | this.addTo[key] = value;
100 | return this;
101 | }
102 | }
103 |
104 | function parseVector(v, vectorName) {
105 | return v.replace(parens, '').replace(ws, '').split(',').map((component, position) => {
106 | if (!component) {
107 | throw new Error('Missing vector component at position ' + position +' for `' + vectorName + '`');
108 | }
109 | let x = Number.parseFloat(component);
110 | if (!Number.isFinite(x)) {
111 | throw new Error('Vectors are expected to be numbers. Found `' + component + '` in `' + v + '`');
112 | }
113 | return x;
114 | })
115 | }
116 |
117 | export default function parse(str) {
118 | let lines = str.split('\n');
119 | let currentState = new ExpectKeyValue({});
120 |
121 | lines.forEach((line, lineNumber) => {
122 | line = line.trim();
123 | // ignore comments and empty lines
124 | if (line === '' || line.startsWith('//')) return;
125 | try {
126 | currentState = currentState.process(line);
127 | } catch (e) {
128 | throw new Error(e.message + '\n\n at line ' + lineNumber);
129 | }
130 | });
131 |
132 | while (currentState.parent) {
133 | currentState = currentState.parent;
134 | }
135 | return currentState.addTo;
136 | }
137 |
--------------------------------------------------------------------------------
/src/lmode.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
4 | // Distributed under an MIT license: https://codemirror.net/LICENSE
5 |
6 | // Hacked together to barely support l-system definition
7 | // by Andrei Kashcha (anvaka@)
8 |
9 | module.exports = function(CodeMirror) {
10 | "use strict";
11 |
12 | CodeMirror.defineMode("javascript", function(config, parserConfig) {
13 | var indentUnit = config.indentUnit;
14 | var statementIndent = parserConfig.statementIndent;
15 | var jsonMode = parserConfig.json;
16 | var lSystemMode = parserConfig.lsystem;
17 | var isTS = parserConfig.typescript;
18 | var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
19 | var ruleCharRE = /[a-zA-Z+_^?~<>|&*%\][-]/;
20 |
21 | // Tokenizer
22 |
23 | var keywords = function(){
24 | function kw(type) {return {type: type, style: "keyword"};}
25 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
26 | var operator = kw("operator"), atom = {type: "atom", style: "atom"};
27 |
28 | return {
29 | axiom: kw('axiom'),
30 | angle: kw('angle'),
31 | start: kw('start'),
32 | direction: kw('direction'),
33 | position: kw('position'),
34 | hideGrid: kw('hideGrid'),
35 | rules: kw('rules'),
36 | depth: kw('depth'),
37 | stepsPerFrame: kw('stepsPerFrame'),
38 | actions: kw('actions'),
39 | color: kw('color'),
40 | width: kw('width'),
41 | };
42 | }();
43 | let nope = {test: () => false}
44 |
45 | var isOperatorChar = nope;
46 |
47 | function readRegexp(stream) {
48 | var escaped = false, next, inSet = false;
49 | while ((next = stream.next()) != null) {
50 | if (!escaped) {
51 | if (next == "/" && !inSet) return;
52 | if (next == "[") inSet = true;
53 | else if (inSet && next == "]") inSet = false;
54 | }
55 | escaped = !escaped && next == "\\";
56 | }
57 | }
58 |
59 | // Used as scratch variables to communicate multiple values without
60 | // consing up tons of objects.
61 | var type, content;
62 | function ret(tp, style, cont) {
63 | type = tp; content = cont;
64 | return style;
65 | }
66 | function tokenBase(stream, state) {
67 | var ch = stream.next();
68 | if (ch == '"' || ch == "'") {
69 | state.tokenize = tokenString(ch);
70 | return state.tokenize(stream, state);
71 | } else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
72 | return ret("number", "number");
73 | } else if (ch == "." && stream.match("..")) {
74 | return ret("spread", "meta");
75 | } else if (/[{}\(\),;\:\.]/.test(ch)) {
76 | return ret(ch);
77 | } else if (ch == "=" && stream.eat(">")) {
78 | return ret("=>", "operator");
79 | } else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
80 | return ret("number", "number");
81 | } else if (/\d/.test(ch)) {
82 | stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
83 | return ret("number", "number");
84 | } else if (ch == "/") {
85 | if (stream.eat("*")) {
86 | state.tokenize = tokenComment;
87 | return tokenComment(stream, state);
88 | } else if (stream.eat("/")) {
89 | stream.skipToEnd();
90 | return ret("comment", "comment");
91 | } else if (expressionAllowed(stream, state, 1)) {
92 | readRegexp(stream);
93 | stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
94 | return ret("regexp", "string-2");
95 | } else {
96 | stream.eat("=");
97 | return ret("operator", "operator", stream.current());
98 | }
99 | } else if (ch == "`") {
100 | state.tokenize = tokenQuasi;
101 | return tokenQuasi(stream, state);
102 | } else if (ch == "#" && stream.peek() == "!") {
103 | stream.skipToEnd();
104 | return ret("meta", "meta");
105 | } else if (ch == "#" && stream.eatWhile(wordRE)) {
106 | return ret("variable", "property")
107 | } else if (ch == "<" && stream.match("!--") || ch == "-" && stream.match("->")) {
108 | stream.skipToEnd()
109 | return ret("comment", "comment")
110 | } else if (isOperatorChar.test(ch)) {
111 | if (ch != ">" || !state.lexical || state.lexical.type != ">") {
112 | if (stream.eat("=")) {
113 | if (ch == "!" || ch == "=") stream.eat("=")
114 | } else if (/[<>*+\-]/.test(ch)) {
115 | stream.eat(ch)
116 | if (ch == ">") stream.eat(ch)
117 | }
118 | }
119 | if (ch == "?" && stream.eat(".")) return ret(".")
120 | return ret("operator", "operator", stream.current());
121 | } else if (wordRE.test(ch)) {
122 | stream.eatWhile(wordRE);
123 | var word = stream.current()
124 | if (state.lastType != ".") {
125 | if (keywords.propertyIsEnumerable(word)) {
126 | var kw = keywords[word]
127 | return ret(kw.type, kw.style, word)
128 | }
129 | if (word == "async" && stream.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/, false))
130 | return ret("async", "keyword", word)
131 | }
132 | return ret("variable", "variable", word)
133 | } else if (ruleCharRE.test(ch)) {
134 | var arrow = stream.string.indexOf("=>", stream.start);
135 | if (arrow > -1) {
136 | var word = stream.current()
137 | return ret("def", "def", word)
138 | }
139 | }
140 | }
141 |
142 | function tokenString(quote) {
143 | return function(stream, state) {
144 | var escaped = false, next;
145 | while ((next = stream.next()) != null) {
146 | if (next == quote && !escaped) break;
147 | escaped = !escaped && next == "\\";
148 | }
149 | if (!escaped) state.tokenize = tokenBase;
150 | return ret("string", "string");
151 | };
152 | }
153 |
154 | function tokenComment(stream, state) {
155 | var maybeEnd = false, ch;
156 | while (ch = stream.next()) {
157 | if (ch == "/" && maybeEnd) {
158 | state.tokenize = tokenBase;
159 | break;
160 | }
161 | maybeEnd = (ch == "*");
162 | }
163 | return ret("comment", "comment");
164 | }
165 |
166 | function tokenQuasi(stream, state) {
167 | var escaped = false, next;
168 | while ((next = stream.next()) != null) {
169 | if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
170 | state.tokenize = tokenBase;
171 | break;
172 | }
173 | escaped = !escaped && next == "\\";
174 | }
175 | return ret("quasi", "string-2", stream.current());
176 | }
177 |
178 | var brackets = "([{}])";
179 | // This is a crude lookahead trick to try and notice that we're
180 | // parsing the argument patterns for a fat-arrow function before we
181 | // actually hit the arrow token. It only works if the arrow is on
182 | // the same line as the arguments and there's no strange noise
183 | // (comments) in between. Fallback is to only notice when we hit the
184 | // arrow, and not declare the arguments as locals for the arrow
185 | // body.
186 | function findFatArrow(stream, state) {
187 | if (state.fatArrowAt) state.fatArrowAt = null;
188 | var arrow = stream.string.indexOf("=>", stream.start);
189 | if (arrow < 0) return;
190 |
191 | if (isTS) { // Try to skip TypeScript return type declarations after the arguments
192 | var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
193 | if (m) arrow = m.index
194 | }
195 |
196 | var depth = 0, sawSomething = false;
197 | for (var pos = arrow - 1; pos >= 0; --pos) {
198 | var ch = stream.string.charAt(pos);
199 | var bracket = brackets.indexOf(ch);
200 | if (bracket >= 0 && bracket < 3) {
201 | if (!depth) { ++pos; break; }
202 | if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
203 | } else if (bracket >= 3 && bracket < 6) {
204 | ++depth;
205 | } else if (wordRE.test(ch)) {
206 | sawSomething = true;
207 | } else if (/["'\/`]/.test(ch)) {
208 | for (;; --pos) {
209 | if (pos == 0) return
210 | var next = stream.string.charAt(pos - 1)
211 | if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
212 | }
213 | } else if (sawSomething && !depth) {
214 | ++pos;
215 | break;
216 | }
217 | }
218 | if (sawSomething && !depth) state.fatArrowAt = pos;
219 | }
220 |
221 | // Parser
222 |
223 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
224 |
225 | function JSLexical(indented, column, type, align, prev, info) {
226 | this.indented = indented;
227 | this.column = column;
228 | this.type = type;
229 | this.prev = prev;
230 | this.info = info;
231 | if (align != null) this.align = align;
232 | }
233 |
234 | function inScope(state, varname) {
235 | for (var v = state.localVars; v; v = v.next)
236 | if (v.name == varname) return true;
237 | for (var cx = state.context; cx; cx = cx.prev) {
238 | for (var v = cx.vars; v; v = v.next)
239 | if (v.name == varname) return true;
240 | }
241 | }
242 |
243 | function parseJS(state, style, type, content, stream) {
244 | var cc = state.cc;
245 | // Communicate our context to the combinators.
246 | // (Less wasteful than consing up a hundred closures on every call.)
247 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
248 |
249 | if (!state.lexical.hasOwnProperty("align"))
250 | state.lexical.align = true;
251 |
252 | while(true) {
253 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
254 | if (combinator(type, content)) {
255 | while(cc.length && cc[cc.length - 1].lex)
256 | cc.pop()();
257 | if (cx.marked) return cx.marked;
258 | if (type == "variable" && inScope(state, content)) return "variable-2";
259 | return style;
260 | }
261 | }
262 | }
263 |
264 | // Combinator utils
265 |
266 | var cx = {state: null, column: null, marked: null, cc: null};
267 | function pass() {
268 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
269 | }
270 | function cont() {
271 | pass.apply(null, arguments);
272 | return true;
273 | }
274 | function inList(name, list) {
275 | for (var v = list; v; v = v.next) if (v.name == name) return true
276 | return false;
277 | }
278 | function register(varname) {
279 | var state = cx.state;
280 | cx.marked = "def";
281 | if (state.context) {
282 | if (state.lexical.info == "var" && state.context && state.context.block) {
283 | // FIXME function decls are also not block scoped
284 | var newContext = registerVarScoped(varname, state.context)
285 | if (newContext != null) {
286 | state.context = newContext
287 | return
288 | }
289 | } else if (!inList(varname, state.localVars)) {
290 | state.localVars = new Var(varname, state.localVars)
291 | return
292 | }
293 | }
294 | // Fall through means this is global
295 | if (parserConfig.globalVars && !inList(varname, state.globalVars))
296 | state.globalVars = new Var(varname, state.globalVars)
297 | }
298 | function registerVarScoped(varname, context) {
299 | if (!context) {
300 | return null
301 | } else if (context.block) {
302 | var inner = registerVarScoped(varname, context.prev)
303 | if (!inner) return null
304 | if (inner == context.prev) return context
305 | return new Context(inner, context.vars, true)
306 | } else if (inList(varname, context.vars)) {
307 | return context
308 | } else {
309 | return new Context(context.prev, new Var(varname, context.vars), false)
310 | }
311 | }
312 |
313 | function isModifier(name) {
314 | return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
315 | }
316 |
317 | // Combinators
318 |
319 | function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
320 | function Var(name, next) { this.name = name; this.next = next }
321 |
322 | var defaultVars = new Var("this", new Var("arguments", null))
323 | function pushcontext() {
324 | cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
325 | cx.state.localVars = defaultVars
326 | }
327 | function pushblockcontext() {
328 | cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
329 | cx.state.localVars = null
330 | }
331 | function popcontext() {
332 | cx.state.localVars = cx.state.context.vars
333 | cx.state.context = cx.state.context.prev
334 | }
335 | popcontext.lex = true
336 | function pushlex(type, info) {
337 | var result = function() {
338 | var state = cx.state, indent = state.indented;
339 | if (state.lexical.type == "stat") indent = state.lexical.indented;
340 | else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
341 | indent = outer.indented;
342 | state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
343 | };
344 | result.lex = true;
345 | return result;
346 | }
347 | function poplex() {
348 | var state = cx.state;
349 | if (state.lexical.prev) {
350 | if (state.lexical.type == ")")
351 | state.indented = state.lexical.indented;
352 | state.lexical = state.lexical.prev;
353 | }
354 | }
355 | poplex.lex = true;
356 |
357 | function expect(wanted) {
358 | function exp(type) {
359 | if (type == wanted) return cont();
360 | else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
361 | else return cont(exp);
362 | };
363 | return exp;
364 | }
365 |
366 | function statement(type, value) {
367 | if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
368 | if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
369 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
370 | if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
371 | if (type == "debugger") return cont(expect(";"));
372 | if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
373 | if (type == ";") return cont();
374 | if (type == "if") {
375 | if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
376 | cx.state.cc.pop()();
377 | return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
378 | }
379 | if (type == "function") return cont(functiondef);
380 | if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
381 | if (type == "class" || (isTS && value == "interface")) {
382 | cx.marked = "keyword"
383 | return cont(pushlex("form", type == "class" ? type : value), className, poplex)
384 | }
385 | if (type == "variable") {
386 | if (isTS && value == "declare") {
387 | cx.marked = "keyword"
388 | return cont(statement)
389 | } else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
390 | cx.marked = "keyword"
391 | if (value == "enum") return cont(enumdef);
392 | else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
393 | else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
394 | } else if (isTS && value == "namespace") {
395 | cx.marked = "keyword"
396 | return cont(pushlex("form"), expression, statement, poplex)
397 | } else if (isTS && value == "abstract") {
398 | cx.marked = "keyword"
399 | return cont(statement)
400 | } else {
401 | return cont(pushlex("stat"), maybelabel);
402 | }
403 | }
404 | if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
405 | block, poplex, poplex, popcontext);
406 | if (type == "case") return cont(expression, expect(":"));
407 | if (type == "default") return cont(expect(":"));
408 | if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
409 | if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
410 | if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
411 | if (type == "async") return cont(statement)
412 | if (value == "@") return cont(expression, statement)
413 | return pass(pushlex("stat"), expression, expect(";"), poplex);
414 | }
415 | function maybeCatchBinding(type) {
416 | if (type == "(") return cont(funarg, expect(")"))
417 | }
418 | function expression(type, value) {
419 | return expressionInner(type, value, false);
420 | }
421 | function expressionNoComma(type, value) {
422 | return expressionInner(type, value, true);
423 | }
424 | function parenExpr(type) {
425 | if (type != "(") return pass()
426 | return cont(pushlex(")"), maybeexpression, expect(")"), poplex)
427 | }
428 | function expressionInner(type, value, noComma) {
429 | if (cx.state.fatArrowAt == cx.stream.start) {
430 | var body = noComma ? arrowBodyNoComma : arrowBody;
431 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
432 | else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
433 | }
434 |
435 | var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
436 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
437 | if (type == "function") return cont(functiondef, maybeop);
438 | if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
439 | if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
440 | if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
441 | if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
442 | if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
443 | if (type == "{") return contCommasep(objprop, "}", null, maybeop);
444 | if (type == "quasi") return pass(quasi, maybeop);
445 | if (type == "new") return cont(maybeTarget(noComma));
446 | if (type == "import") return cont(expression);
447 | return cont();
448 | }
449 | function maybeexpression(type) {
450 | if (type.match(/[;\}\)\],]/)) return pass();
451 | return pass(expression);
452 | }
453 |
454 | function maybeoperatorComma(type, value) {
455 | if (type == ",") return cont(maybeexpression);
456 | return maybeoperatorNoComma(type, value, false);
457 | }
458 | function maybeoperatorNoComma(type, value, noComma) {
459 | var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
460 | var expr = noComma == false ? expression : expressionNoComma;
461 | if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
462 | if (type == "operator") {
463 | if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
464 | if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false))
465 | return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
466 | if (value == "?") return cont(expression, expect(":"), expr);
467 | return cont(expr);
468 | }
469 | if (type == "quasi") { return pass(quasi, me); }
470 | if (type == ";") return;
471 | if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
472 | if (type == ".") return cont(property, me);
473 | if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
474 | if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
475 | if (type == "regexp") {
476 | cx.state.lastType = cx.marked = "operator"
477 | cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
478 | return cont(expr)
479 | }
480 | }
481 | function quasi(type, value) {
482 | if (type != "quasi") return pass();
483 | if (value.slice(value.length - 2) != "${") return cont(quasi);
484 | return cont(expression, continueQuasi);
485 | }
486 | function continueQuasi(type) {
487 | if (type == "}") {
488 | cx.marked = "string-2";
489 | cx.state.tokenize = tokenQuasi;
490 | return cont(quasi);
491 | }
492 | }
493 | function arrowBody(type) {
494 | findFatArrow(cx.stream, cx.state);
495 | return pass(type == "{" ? statement : expression);
496 | }
497 | function arrowBodyNoComma(type) {
498 | findFatArrow(cx.stream, cx.state);
499 | return pass(type == "{" ? statement : expressionNoComma);
500 | }
501 | function maybeTarget(noComma) {
502 | return function(type) {
503 | if (type == ".") return cont(noComma ? targetNoComma : target);
504 | else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
505 | else return pass(noComma ? expressionNoComma : expression);
506 | };
507 | }
508 | function target(_, value) {
509 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
510 | }
511 | function targetNoComma(_, value) {
512 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
513 | }
514 | function maybelabel(type) {
515 | if (type == ":") return cont(poplex, statement);
516 | return pass(maybeoperatorComma, expect(";"), poplex);
517 | }
518 | function property(type) {
519 | if (type == "variable") {cx.marked = "property"; return cont();}
520 | }
521 | function objprop(type, value) {
522 | if (type == "async") {
523 | cx.marked = "property";
524 | return cont(objprop);
525 | } else if (type == "variable" || cx.style == "keyword") {
526 | cx.marked = "property";
527 | if (value == "get" || value == "set") return cont(getterSetter);
528 | var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
529 | if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
530 | cx.state.fatArrowAt = cx.stream.pos + m[0].length
531 | return cont(afterprop);
532 | } else if (type == "number" || type == "string") {
533 | cx.marked = (cx.style + " property");
534 | return cont(afterprop);
535 | } else if (type == "jsonld-keyword") {
536 | return cont(afterprop);
537 | } else if (isTS && isModifier(value)) {
538 | cx.marked = "keyword"
539 | return cont(objprop)
540 | } else if (type == "[") {
541 | return cont(expression, maybetype, expect("]"), afterprop);
542 | } else if (type == "spread") {
543 | return cont(expressionNoComma, afterprop);
544 | } else if (value == "*") {
545 | cx.marked = "keyword";
546 | return cont(objprop);
547 | } else if (type == ":") {
548 | return pass(afterprop)
549 | }
550 | }
551 | function getterSetter(type) {
552 | if (type != "variable") return pass(afterprop);
553 | cx.marked = "property";
554 | return cont(functiondef);
555 | }
556 | function afterprop(type) {
557 | if (type == ":") return cont(expressionNoComma);
558 | if (type == "(") return pass(functiondef);
559 | }
560 | function commasep(what, end, sep) {
561 | function proceed(type, value) {
562 | if (sep ? sep.indexOf(type) > -1 : type == ",") {
563 | var lex = cx.state.lexical;
564 | if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
565 | return cont(function(type, value) {
566 | if (type == end || value == end) return pass()
567 | return pass(what)
568 | }, proceed);
569 | }
570 | if (type == end || value == end) return cont();
571 | if (sep && sep.indexOf(";") > -1) return pass(what)
572 | return cont(expect(end));
573 | }
574 | return function(type, value) {
575 | if (type == end || value == end) return cont();
576 | return pass(what, proceed);
577 | };
578 | }
579 | function contCommasep(what, end, info) {
580 | for (var i = 3; i < arguments.length; i++)
581 | cx.cc.push(arguments[i]);
582 | return cont(pushlex(end, info), commasep(what, end), poplex);
583 | }
584 | function block(type) {
585 | if (type == "}") return cont();
586 | return pass(statement, block);
587 | }
588 | function maybetype(type, value) {
589 | if (isTS) {
590 | if (type == ":") return cont(typeexpr);
591 | if (value == "?") return cont(maybetype);
592 | }
593 | }
594 | function maybetypeOrIn(type, value) {
595 | if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
596 | }
597 | function mayberettype(type) {
598 | if (isTS && type == ":") {
599 | if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
600 | else return cont(typeexpr)
601 | }
602 | }
603 | function isKW(_, value) {
604 | if (value == "is") {
605 | cx.marked = "keyword"
606 | return cont()
607 | }
608 | }
609 | function typeexpr(type, value) {
610 | if (value == "keyof" || value == "typeof" || value == "infer") {
611 | cx.marked = "keyword"
612 | return cont(value == "typeof" ? expressionNoComma : typeexpr)
613 | }
614 | if (type == "variable" || value == "void") {
615 | cx.marked = "type"
616 | return cont(afterType)
617 | }
618 | if (value == "|" || value == "&") return cont(typeexpr)
619 | if (type == "string" || type == "number" || type == "atom") return cont(afterType);
620 | if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
621 | if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType)
622 | if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
623 | if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
624 | }
625 | function maybeReturnType(type) {
626 | if (type == "=>") return cont(typeexpr)
627 | }
628 | function typeprop(type, value) {
629 | if (type == "variable" || cx.style == "keyword") {
630 | cx.marked = "property"
631 | return cont(typeprop)
632 | } else if (value == "?" || type == "number" || type == "string") {
633 | return cont(typeprop)
634 | } else if (type == ":") {
635 | return cont(typeexpr)
636 | } else if (type == "[") {
637 | return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
638 | } else if (type == "(") {
639 | return pass(functiondecl, typeprop)
640 | }
641 | }
642 | function typearg(type, value) {
643 | if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
644 | if (type == ":") return cont(typeexpr)
645 | if (type == "spread") return cont(typearg)
646 | return pass(typeexpr)
647 | }
648 | function afterType(type, value) {
649 | if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
650 | if (value == "|" || type == "." || value == "&") return cont(typeexpr)
651 | if (type == "[") return cont(typeexpr, expect("]"), afterType)
652 | if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
653 | if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
654 | }
655 | function maybeTypeArgs(_, value) {
656 | if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
657 | }
658 | function typeparam() {
659 | return pass(typeexpr, maybeTypeDefault)
660 | }
661 | function maybeTypeDefault(_, value) {
662 | if (value == "=") return cont(typeexpr)
663 | }
664 | function vardef(_, value) {
665 | if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
666 | return pass(pattern, maybetype, maybeAssign, vardefCont);
667 | }
668 | function pattern(type, value) {
669 | if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
670 | if (type == "variable") { register(value); return cont(); }
671 | if (type == "spread") return cont(pattern);
672 | if (type == "[") return contCommasep(eltpattern, "]");
673 | if (type == "{") return contCommasep(proppattern, "}");
674 | }
675 | function proppattern(type, value) {
676 | if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
677 | register(value);
678 | return cont(maybeAssign);
679 | }
680 | if (type == "variable") cx.marked = "property";
681 | if (type == "spread") return cont(pattern);
682 | if (type == "}") return pass();
683 | if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
684 | return cont(expect(":"), pattern, maybeAssign);
685 | }
686 | function eltpattern() {
687 | return pass(pattern, maybeAssign)
688 | }
689 | function maybeAssign(_type, value) {
690 | if (value == "=") return cont(expressionNoComma);
691 | }
692 | function vardefCont(type) {
693 | if (type == ",") return cont(vardef);
694 | }
695 | function maybeelse(type, value) {
696 | if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
697 | }
698 | function forspec(type, value) {
699 | if (value == "await") return cont(forspec);
700 | if (type == "(") return cont(pushlex(")"), forspec1, poplex);
701 | }
702 | function forspec1(type) {
703 | if (type == "var") return cont(vardef, forspec2);
704 | if (type == "variable") return cont(forspec2);
705 | return pass(forspec2)
706 | }
707 | function forspec2(type, value) {
708 | if (type == ")") return cont()
709 | if (type == ";") return cont(forspec2)
710 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
711 | return pass(expression, forspec2)
712 | }
713 | function functiondef(type, value) {
714 | if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
715 | if (type == "variable") {register(value); return cont(functiondef);}
716 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
717 | if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
718 | }
719 | function functiondecl(type, value) {
720 | if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
721 | if (type == "variable") {register(value); return cont(functiondecl);}
722 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
723 | if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
724 | }
725 | function typename(type, value) {
726 | if (type == "keyword" || type == "variable") {
727 | cx.marked = "type"
728 | return cont(typename)
729 | } else if (value == "<") {
730 | return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
731 | }
732 | }
733 | function funarg(type, value) {
734 | if (value == "@") cont(expression, funarg)
735 | if (type == "spread") return cont(funarg);
736 | if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
737 | if (isTS && type == "this") return cont(maybetype, maybeAssign)
738 | return pass(pattern, maybetype, maybeAssign);
739 | }
740 | function classExpression(type, value) {
741 | // Class expressions may have an optional name.
742 | if (type == "variable") return className(type, value);
743 | return classNameAfter(type, value);
744 | }
745 | function className(type, value) {
746 | if (type == "variable") {register(value); return cont(classNameAfter);}
747 | }
748 | function classNameAfter(type, value) {
749 | if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
750 | if (value == "extends" || value == "implements" || (isTS && type == ",")) {
751 | if (value == "implements") cx.marked = "keyword";
752 | return cont(isTS ? typeexpr : expression, classNameAfter);
753 | }
754 | if (type == "{") return cont(pushlex("}"), classBody, poplex);
755 | }
756 | function classBody(type, value) {
757 | if (type == "async" ||
758 | (type == "variable" &&
759 | (value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
760 | cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false))) {
761 | cx.marked = "keyword";
762 | return cont(classBody);
763 | }
764 | if (type == "variable" || cx.style == "keyword") {
765 | cx.marked = "property";
766 | return cont(classfield, classBody);
767 | }
768 | if (type == "number" || type == "string") return cont(classfield, classBody);
769 | if (type == "[")
770 | return cont(expression, maybetype, expect("]"), classfield, classBody)
771 | if (value == "*") {
772 | cx.marked = "keyword";
773 | return cont(classBody);
774 | }
775 | if (isTS && type == "(") return pass(functiondecl, classBody)
776 | if (type == ";" || type == ",") return cont(classBody);
777 | if (type == "}") return cont();
778 | if (value == "@") return cont(expression, classBody)
779 | }
780 | function classfield(type, value) {
781 | if (value == "?") return cont(classfield)
782 | if (type == ":") return cont(typeexpr, maybeAssign)
783 | if (value == "=") return cont(expressionNoComma)
784 | var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
785 | return pass(isInterface ? functiondecl : functiondef)
786 | }
787 | function afterExport(type, value) {
788 | if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
789 | if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
790 | if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
791 | return pass(statement);
792 | }
793 | function exportField(type, value) {
794 | if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
795 | if (type == "variable") return pass(expressionNoComma, exportField);
796 | }
797 | function afterImport(type) {
798 | if (type == "string") return cont();
799 | if (type == "(") return pass(expression);
800 | return pass(importSpec, maybeMoreImports, maybeFrom);
801 | }
802 | function importSpec(type, value) {
803 | if (type == "{") return contCommasep(importSpec, "}");
804 | if (type == "variable") register(value);
805 | if (value == "*") cx.marked = "keyword";
806 | return cont(maybeAs);
807 | }
808 | function maybeMoreImports(type) {
809 | if (type == ",") return cont(importSpec, maybeMoreImports)
810 | }
811 | function maybeAs(_type, value) {
812 | if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
813 | }
814 | function maybeFrom(_type, value) {
815 | if (value == "from") { cx.marked = "keyword"; return cont(expression); }
816 | }
817 | function arrayLiteral(type) {
818 | if (type == "]") return cont();
819 | return pass(commasep(expressionNoComma, "]"));
820 | }
821 | function enumdef() {
822 | return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
823 | }
824 | function enummember() {
825 | return pass(pattern, maybeAssign);
826 | }
827 |
828 | function isContinuedStatement(state, textAfter) {
829 | return state.lastType == "operator" || state.lastType == "," ||
830 | isOperatorChar.test(textAfter.charAt(0)) ||
831 | /[,.]/.test(textAfter.charAt(0));
832 | }
833 |
834 | function expressionAllowed(stream, state, backUp) {
835 | return state.tokenize == tokenBase &&
836 | /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
837 | (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
838 | }
839 |
840 | // Interface
841 |
842 | return {
843 | startState: function(basecolumn) {
844 | var state = {
845 | tokenize: tokenBase,
846 | lastType: "sof",
847 | cc: [],
848 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
849 | localVars: parserConfig.localVars,
850 | context: parserConfig.localVars && new Context(null, null, false),
851 | indented: basecolumn || 0
852 | };
853 | if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
854 | state.globalVars = parserConfig.globalVars;
855 | return state;
856 | },
857 |
858 | token: function(stream, state) {
859 | if (stream.sol()) {
860 | if (!state.lexical.hasOwnProperty("align"))
861 | state.lexical.align = false;
862 | state.indented = stream.indentation();
863 | findFatArrow(stream, state);
864 | }
865 | if (state.tokenize != tokenComment && stream.eatSpace()) return null;
866 | var style = state.tokenize(stream, state);
867 | if (type == "comment") return style;
868 | state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
869 | return parseJS(state, style, type, content, stream);
870 | },
871 |
872 | indent: function(state, textAfter) {
873 | if (state.tokenize == tokenComment) return CodeMirror.Pass;
874 | if (state.tokenize != tokenBase) return 0;
875 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
876 | // Kludge to prevent 'maybelse' from blocking lexical scope pops
877 | if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
878 | var c = state.cc[i];
879 | if (c == poplex) lexical = lexical.prev;
880 | else if (c != maybeelse) break;
881 | }
882 | while ((lexical.type == "stat" || lexical.type == "form") &&
883 | (firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
884 | (top == maybeoperatorComma || top == maybeoperatorNoComma) &&
885 | !/^[,\.=+\-*:?[\(]/.test(textAfter))))
886 | lexical = lexical.prev;
887 | if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
888 | lexical = lexical.prev;
889 | var type = lexical.type, closing = firstChar == type;
890 |
891 | if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
892 | else if (type == "form" && firstChar == "{") return lexical.indented;
893 | else if (type == "form") return lexical.indented + indentUnit;
894 | else if (type == "stat")
895 | return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
896 | else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
897 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
898 | else if (lexical.align) return lexical.column + (closing ? 0 : 1);
899 | else return lexical.indented + (closing ? 0 : indentUnit);
900 | },
901 |
902 | electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
903 | blockCommentStart: jsonMode ? null : "/*",
904 | blockCommentEnd: jsonMode ? null : "*/",
905 | blockCommentContinue: jsonMode ? null : " * ",
906 | lineComment: jsonMode ? null : "//",
907 | fold: "brace",
908 | closeBrackets: "()[]{}''\"\"``",
909 |
910 | helperType: jsonMode ? "json" : "javascript",
911 | jsonldMode: false,
912 | jsonMode: jsonMode,
913 |
914 | expressionAllowed: expressionAllowed,
915 |
916 | skipExpression: function(state) {
917 | var top = state.cc[state.cc.length - 1]
918 | if (top == expression || top == expressionNoComma) state.cc.pop()
919 | }
920 | };
921 | });
922 |
923 | CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
924 |
925 | CodeMirror.defineMIME("text/javascript", "javascript");
926 | CodeMirror.defineMIME("text/ecmascript", "javascript");
927 | CodeMirror.defineMIME("application/javascript", "javascript");
928 | CodeMirror.defineMIME("application/x-javascript", "javascript");
929 | CodeMirror.defineMIME("application/ecmascript", "javascript");
930 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
931 | CodeMirror.defineMIME("application/lsystem", {name: "javascript", json: true, lsystem: true});
932 | CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
933 | CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
934 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
935 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
936 | }
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import NoWebGL from './NoWebGL';
4 | import {isWebGLEnabled} from 'w-gl';
5 |
6 | Vue.config.productionTip = false
7 |
8 | let canRender = isWebGLEnabled(document.querySelector('#canvas'));
9 |
10 | new Vue({
11 | render: h => h(canRender ? App : NoWebGL),
12 | }).$mount('#app')
13 |
--------------------------------------------------------------------------------
/src/shared.styl:
--------------------------------------------------------------------------------
1 |
2 | window-background = rgba(6, 24, 56, 1.0);
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | publicPath: '',
3 | };
--------------------------------------------------------------------------------