├── .github
└── FUNDING.yml
├── .gitignore
├── README.md
├── cljs-src
└── j2c
│ └── core.cljs
├── deps.edn
├── package.json
├── public
├── bundle.js
├── bundle.js.map
├── codemirror.css
├── compiler.js
├── examples
│ ├── 01.primitives.js
│ ├── 02.variables.js
│ ├── 03.functions.js
│ ├── 04.conditionals.js
│ ├── 05.operators.js
│ ├── 06.array.js
│ ├── 07.object.js
│ ├── 08.try-catch.js
│ ├── 09.threading.js
│ ├── basic.js
│ └── react.js
├── index.html
├── main.css
└── vendor
│ ├── clojure.js
│ ├── codemirror.js
│ ├── javascript.js
│ ├── react-dom.production.min.js
│ └── react.production.min.js
├── src
├── ast-builders.js
├── ast-transforms.js
├── ast-types
│ ├── javascript.js
│ └── jsx.js
├── cljs-gen.js
├── cljs-types.js
├── code-generators.js
├── index.js
├── js2cljs.js
├── syntax-builder.js
├── ui.js
└── utils.js
├── test.js
└── yarn.lock
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: roman01la
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cpcache
3 | .cache
4 | out
5 | *.iml
6 | .idea
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | _If you like what I do, consider supporting my work via donation_
2 |
3 | [](https://www.buymeacoffee.com/romanliutikov)
4 |
5 | # JavaScript to ClojureScript translator
6 |
7 | This tool tries to translate as much JavaScript code into ClojureScript as it can. Keep in mind that it might fail or the result will be non-idiomatic Clojure code due to substantial differences between languages.
8 |
9 | _e.g. Clojure explicitly distincts global and local vars, but JavaScript does not_
10 |
11 | ```clojure
12 | (def x 1) ;; global
13 | (let [x 2] ;; local
14 | (/ x 2))
15 | ```
16 |
17 | _Clojure's data structures are immutable by defalt_
18 |
19 | ```clojure
20 | (let [x {}]
21 | [(assoc x :y 1) x])
22 | ;; [{:y 1} {}]
23 | ```
24 |
25 | Use for educational purpose.
26 |
27 | ## How to contribute
28 | If something is not translated properly, file an issue
29 |
--------------------------------------------------------------------------------
/cljs-src/j2c/core.cljs:
--------------------------------------------------------------------------------
1 | (ns j2c.core
2 | (:require [cljs.js :as cljs]
3 | [cljs.spec.alpha :as s]))
4 |
5 | (s/def :hiccup/form
6 | (s/or
7 | :string string?
8 | :number number?
9 | :element :hiccup/element))
10 |
11 | (s/def :hiccup/element
12 | (s/cat
13 | :tag #(or (keyword? %) (symbol? %) (fn? %))
14 | :attrs (s/? map?)
15 | :children (s/* :hiccup/form)))
16 |
17 | (defn parse-hiccup [hiccup]
18 | (s/conform :hiccup/form hiccup))
19 |
20 | (defmulti html first)
21 |
22 | (defmethod html :default [[_ v]]
23 | v)
24 |
25 | (defmethod html :element [[_ {:keys [tag attrs children]}]]
26 | (apply js/React.createElement
27 | (if (keyword? tag)
28 | (name tag)
29 | tag)
30 | (clj->js attrs)
31 | (map html children)))
32 |
33 | (defn ^:export compileHiccup [form]
34 | (html (parse-hiccup form)))
35 |
36 | ;; ================
37 |
38 | (defn ^:export evalExpr
39 | ([source cb]
40 | (cljs/compile-str (cljs/empty-state) source 'cljs.user
41 | {:eval cljs/js-eval}
42 | (fn [{:keys [error value]}]
43 | (if-not error
44 | (cb nil value)
45 | (cb (.. error -cause -stack)))))))
46 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:deps {org.clojure/clojure {:mvn/version "1.10.3"}
2 | org.clojure/clojurescript {:mvn/version "1.10.879"}}
3 | :paths ["cljs-src"]}
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js2cljs",
3 | "license": "MIT",
4 | "scripts": {
5 | "dev": "parcel watch -d public -o bundle.js src/ui.js",
6 | "prod": "parcel build -d public -o bundle.js src/ui.js",
7 | "cljs": "clojure -m cljs.main -O simple -o public/compiler.js -c j2c.core -v"
8 | },
9 | "devDependencies": {
10 | "babel-types": "6.26.0",
11 | "babylon": "6.18.0",
12 | "invariant": "2.2.4",
13 | "pako": "^2.0.4",
14 | "parcel": "1.12.3",
15 | "zprint-clj": "0.2.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/public/codemirror.css:
--------------------------------------------------------------------------------
1 | /* BASICS */
2 |
3 | .CodeMirror {
4 | /* Set height, width, borders, and global font properties here */
5 | font-family: monospace;
6 | height: 300px;
7 | color: black;
8 | direction: ltr;
9 | }
10 |
11 | /* PADDING */
12 |
13 | .CodeMirror-lines {
14 | padding: 4px 0; /* Vertical padding around content */
15 | }
16 | .CodeMirror pre {
17 | padding: 0 4px; /* Horizontal padding of content */
18 | }
19 |
20 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
21 | background-color: white; /* The little square between H and V scrollbars */
22 | }
23 |
24 | /* GUTTER */
25 |
26 | .CodeMirror-gutters {
27 | border-right: 1px solid #ddd;
28 | background-color: #f7f7f7;
29 | white-space: nowrap;
30 | }
31 | .CodeMirror-linenumbers {}
32 | .CodeMirror-linenumber {
33 | padding: 0 3px 0 5px;
34 | min-width: 20px;
35 | text-align: right;
36 | color: #999;
37 | white-space: nowrap;
38 | }
39 |
40 | .CodeMirror-guttermarker { color: black; }
41 | .CodeMirror-guttermarker-subtle { color: #999; }
42 |
43 | /* CURSOR */
44 |
45 | .CodeMirror-cursor {
46 | border-left: 1px solid black;
47 | border-right: none;
48 | width: 0;
49 | }
50 | /* Shown when moving in bi-directional text */
51 | .CodeMirror div.CodeMirror-secondarycursor {
52 | border-left: 1px solid silver;
53 | }
54 | .cm-fat-cursor .CodeMirror-cursor {
55 | width: auto;
56 | border: 0 !important;
57 | background: #7e7;
58 | }
59 | .cm-fat-cursor div.CodeMirror-cursors {
60 | z-index: 1;
61 | }
62 | .cm-fat-cursor-mark {
63 | background-color: rgba(20, 255, 20, 0.5);
64 | -webkit-animation: blink 1.06s steps(1) infinite;
65 | -moz-animation: blink 1.06s steps(1) infinite;
66 | animation: blink 1.06s steps(1) infinite;
67 | }
68 | .cm-animate-fat-cursor {
69 | width: auto;
70 | border: 0;
71 | -webkit-animation: blink 1.06s steps(1) infinite;
72 | -moz-animation: blink 1.06s steps(1) infinite;
73 | animation: blink 1.06s steps(1) infinite;
74 | background-color: #7e7;
75 | }
76 | @-moz-keyframes blink {
77 | 0% {}
78 | 50% { background-color: transparent; }
79 | 100% {}
80 | }
81 | @-webkit-keyframes blink {
82 | 0% {}
83 | 50% { background-color: transparent; }
84 | 100% {}
85 | }
86 | @keyframes blink {
87 | 0% {}
88 | 50% { background-color: transparent; }
89 | 100% {}
90 | }
91 |
92 | /* Can style cursor different in overwrite (non-insert) mode */
93 | .CodeMirror-overwrite .CodeMirror-cursor {}
94 |
95 | .cm-tab { display: inline-block; text-decoration: inherit; }
96 |
97 | .CodeMirror-rulers {
98 | position: absolute;
99 | left: 0; right: 0; top: -50px; bottom: -20px;
100 | overflow: hidden;
101 | }
102 | .CodeMirror-ruler {
103 | border-left: 1px solid #ccc;
104 | top: 0; bottom: 0;
105 | position: absolute;
106 | }
107 |
108 | /* DEFAULT THEME */
109 |
110 | .cm-s-default .cm-header {color: blue;}
111 | .cm-s-default .cm-quote {color: #090;}
112 | .cm-negative {color: #d44;}
113 | .cm-positive {color: #292;}
114 | .cm-header, .cm-strong {font-weight: bold;}
115 | .cm-em {font-style: italic;}
116 | .cm-link {text-decoration: underline;}
117 | .cm-strikethrough {text-decoration: line-through;}
118 |
119 | .cm-s-default .cm-keyword {color: #708;}
120 | .cm-s-default .cm-atom {color: #219;}
121 | .cm-s-default .cm-number {color: #164;}
122 | .cm-s-default .cm-def {color: #00f;}
123 | .cm-s-default .cm-variable,
124 | .cm-s-default .cm-punctuation,
125 | .cm-s-default .cm-property,
126 | .cm-s-default .cm-operator {}
127 | .cm-s-default .cm-variable-2 {color: #05a;}
128 | .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
129 | .cm-s-default .cm-comment {color: #a50;}
130 | .cm-s-default .cm-string {color: #a11;}
131 | .cm-s-default .cm-string-2 {color: #f50;}
132 | .cm-s-default .cm-meta {color: #555;}
133 | .cm-s-default .cm-qualifier {color: #555;}
134 | .cm-s-default .cm-builtin {color: #30a;}
135 | .cm-s-default .cm-bracket {color: #997;}
136 | .cm-s-default .cm-tag {color: #170;}
137 | .cm-s-default .cm-attribute {color: #00c;}
138 | .cm-s-default .cm-hr {color: #999;}
139 | .cm-s-default .cm-link {color: #00c;}
140 |
141 | .cm-s-default .cm-error {color: #f00;}
142 | .cm-invalidchar {color: #f00;}
143 |
144 | .CodeMirror-composing { border-bottom: 2px solid; }
145 |
146 | /* Default styles for common addons */
147 |
148 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
149 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
150 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
151 | .CodeMirror-activeline-background {background: #e8f2ff;}
152 |
153 | /* STOP */
154 |
155 | /* The rest of this file contains styles related to the mechanics of
156 | the editor. You probably shouldn't touch them. */
157 |
158 | .CodeMirror {
159 | position: relative;
160 | overflow: hidden;
161 | background: white;
162 | }
163 |
164 | .CodeMirror-scroll {
165 | overflow: scroll !important; /* Things will break if this is overridden */
166 | /* 30px is the magic margin used to hide the element's real scrollbars */
167 | /* See overflow: hidden in .CodeMirror */
168 | margin-bottom: -30px; margin-right: -30px;
169 | padding-bottom: 30px;
170 | height: 100%;
171 | outline: none; /* Prevent dragging from highlighting the element */
172 | position: relative;
173 | }
174 | .CodeMirror-sizer {
175 | position: relative;
176 | border-right: 30px solid transparent;
177 | }
178 |
179 | /* The fake, visible scrollbars. Used to force redraw during scrolling
180 | before actual scrolling happens, thus preventing shaking and
181 | flickering artifacts. */
182 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
183 | position: absolute;
184 | z-index: 6;
185 | display: none;
186 | }
187 | .CodeMirror-vscrollbar {
188 | right: 0; top: 0;
189 | overflow-x: hidden;
190 | overflow-y: scroll;
191 | }
192 | .CodeMirror-hscrollbar {
193 | bottom: 0; left: 0;
194 | overflow-y: hidden;
195 | overflow-x: scroll;
196 | }
197 | .CodeMirror-scrollbar-filler {
198 | right: 0; bottom: 0;
199 | }
200 | .CodeMirror-gutter-filler {
201 | left: 0; bottom: 0;
202 | }
203 |
204 | .CodeMirror-gutters {
205 | position: absolute; left: 0; top: 0;
206 | min-height: 100%;
207 | z-index: 3;
208 | }
209 | .CodeMirror-gutter {
210 | white-space: normal;
211 | height: 100%;
212 | display: inline-block;
213 | vertical-align: top;
214 | margin-bottom: -30px;
215 | }
216 | .CodeMirror-gutter-wrapper {
217 | position: absolute;
218 | z-index: 4;
219 | background: none !important;
220 | border: none !important;
221 | }
222 | .CodeMirror-gutter-background {
223 | position: absolute;
224 | top: 0; bottom: 0;
225 | z-index: 4;
226 | }
227 | .CodeMirror-gutter-elt {
228 | position: absolute;
229 | cursor: default;
230 | z-index: 4;
231 | }
232 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent }
233 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
234 |
235 | .CodeMirror-lines {
236 | cursor: text;
237 | min-height: 1px; /* prevents collapsing before first draw */
238 | }
239 | .CodeMirror pre {
240 | /* Reset some styles that the rest of the page might have set */
241 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
242 | border-width: 0;
243 | background: transparent;
244 | font-family: inherit;
245 | font-size: inherit;
246 | margin: 0;
247 | white-space: pre;
248 | word-wrap: normal;
249 | line-height: inherit;
250 | color: inherit;
251 | z-index: 2;
252 | position: relative;
253 | overflow: visible;
254 | -webkit-tap-highlight-color: transparent;
255 | -webkit-font-variant-ligatures: contextual;
256 | font-variant-ligatures: contextual;
257 | }
258 | .CodeMirror-wrap pre {
259 | word-wrap: break-word;
260 | white-space: pre-wrap;
261 | word-break: normal;
262 | }
263 |
264 | .CodeMirror-linebackground {
265 | position: absolute;
266 | left: 0; right: 0; top: 0; bottom: 0;
267 | z-index: 0;
268 | }
269 |
270 | .CodeMirror-linewidget {
271 | position: relative;
272 | z-index: 2;
273 | padding: 0.1px; /* Force widget margins to stay inside of the container */
274 | }
275 |
276 | .CodeMirror-widget {}
277 |
278 | .CodeMirror-rtl pre { direction: rtl; }
279 |
280 | .CodeMirror-code {
281 | outline: none;
282 | }
283 |
284 | /* Force content-box sizing for the elements where we expect it */
285 | .CodeMirror-scroll,
286 | .CodeMirror-sizer,
287 | .CodeMirror-gutter,
288 | .CodeMirror-gutters,
289 | .CodeMirror-linenumber {
290 | -moz-box-sizing: content-box;
291 | box-sizing: content-box;
292 | }
293 |
294 | .CodeMirror-measure {
295 | position: absolute;
296 | width: 100%;
297 | height: 0;
298 | overflow: hidden;
299 | visibility: hidden;
300 | }
301 |
302 | .CodeMirror-cursor {
303 | position: absolute;
304 | pointer-events: none;
305 | }
306 | .CodeMirror-measure pre { position: static; }
307 |
308 | div.CodeMirror-cursors {
309 | visibility: hidden;
310 | position: relative;
311 | z-index: 3;
312 | }
313 | div.CodeMirror-dragcursors {
314 | visibility: visible;
315 | }
316 |
317 | .CodeMirror-focused div.CodeMirror-cursors {
318 | visibility: visible;
319 | }
320 |
321 | .CodeMirror-selected { background: #d9d9d9; }
322 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
323 | .CodeMirror-crosshair { cursor: crosshair; }
324 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
325 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
326 |
327 | .cm-searching {
328 | background-color: #ffa;
329 | background-color: rgba(255, 255, 0, .4);
330 | }
331 |
332 | /* Used to force a border model for a node */
333 | .cm-force-border { padding-right: .1px; }
334 |
335 | @media print {
336 | /* Hide the cursor when printing */
337 | .CodeMirror div.CodeMirror-cursors {
338 | visibility: hidden;
339 | }
340 | }
341 |
342 | /* See issue #2901 */
343 | .cm-tab-wrap-hack:after { content: ''; }
344 |
345 | /* Help users use markselection to safely style text background */
346 | span.CodeMirror-selectedtext { background: none; }
347 |
--------------------------------------------------------------------------------
/public/examples/01.primitives.js:
--------------------------------------------------------------------------------
1 | true;
2 | false;
3 | null;
4 | undefined;
5 | 1;
6 | 1.34;
7 | 1e8;
8 | Infinity;
9 | NaN;
10 | ("hello");
11 | `multi
12 | line`;
13 | /^[a-z]/iu;
14 |
--------------------------------------------------------------------------------
/public/examples/02.variables.js:
--------------------------------------------------------------------------------
1 | var x = 1;
2 |
3 | const z = 3;
4 |
5 | {
6 | let y = 2;
7 | }
8 |
--------------------------------------------------------------------------------
/public/examples/03.functions.js:
--------------------------------------------------------------------------------
1 | function hello(msg) {
2 | return msg;
3 | }
4 |
5 | const hello = function(msg) {
6 | return msg;
7 | };
8 |
9 | const hello = function myfn(msg) {
10 | return msg;
11 | };
12 |
13 | const hello = msg => msg;
14 |
15 | hello(123);
16 |
--------------------------------------------------------------------------------
/public/examples/04.conditionals.js:
--------------------------------------------------------------------------------
1 | if (1 > 0) {
2 | 1;
3 | }
4 |
5 | if (1 > 0) {
6 | 1;
7 | } else {
8 | 2;
9 | }
10 |
11 | if (1 > 0) {
12 | 1;
13 | } else if (1 > 9) {
14 | 2;
15 | } else {
16 | 3;
17 | }
18 |
19 | switch (0) {
20 | case 1:
21 | 1;
22 | case 2:
23 | 2;
24 | default:
25 | 3;
26 | }
27 |
--------------------------------------------------------------------------------
/public/examples/05.operators.js:
--------------------------------------------------------------------------------
1 | x = 1;
2 |
3 | 1 == 2;
4 | 1 != 2;
5 | 1 === 2;
6 | 1 !== 2;
7 |
8 | 1 > 2;
9 | 1 >= 2;
10 | 1 < 2;
11 | 1 <= 2;
12 |
13 | true && false;
14 | true || false;
15 |
--------------------------------------------------------------------------------
/public/examples/06.array.js:
--------------------------------------------------------------------------------
1 | const coll = [1, 2, 3];
2 |
3 | coll[0];
4 |
5 | coll[0] = 9;
6 |
--------------------------------------------------------------------------------
/public/examples/07.object.js:
--------------------------------------------------------------------------------
1 | const x = {
2 | a: 1,
3 | hi(msg) {
4 | console.log(msg);
5 | }
6 | };
7 |
8 | x.hi("hey");
9 |
10 | x.a = { b: 1 };
11 |
12 | x.a.b = 3;
13 |
14 | console.log(x.a.b);
15 |
--------------------------------------------------------------------------------
/public/examples/08.try-catch.js:
--------------------------------------------------------------------------------
1 | try {
2 | window.y();
3 | } catch (err) {
4 | console.error("ERROR", err);
5 | } finally {
6 | console.log("done");
7 | }
8 |
--------------------------------------------------------------------------------
/public/examples/09.threading.js:
--------------------------------------------------------------------------------
1 | fetch("https://api.github.com/users/roman01la/repos")
2 | .then(res => res.json())
3 | .then(json => {
4 | console.log(JSON.stringify(json[0]));
5 | })
6 | .catch(err => console.log(err));
7 |
--------------------------------------------------------------------------------
/public/examples/basic.js:
--------------------------------------------------------------------------------
1 | function dist(p1, p2) {
2 | const a = p1.x - p2.x;
3 | const b = p1.y - p2.y;
4 |
5 | return Math.sqrt(a * a + b * b);
6 | }
7 |
8 | {
9 | // explicit block scope
10 | const p1 = { x: 1, y: -9 };
11 | const p2 = { x: -4, y: 13 };
12 |
13 | const d = dist(p1, p2);
14 |
15 | console.log("Distance: " + d);
16 |
17 | if (d > 0) {
18 | const apxd = Math.round(d);
19 | console.log("Distance is positive!", "≈" + apxd);
20 | }
21 | }
22 |
23 | // cond
24 | if (0 > 1) {
25 | console.log("0 > 1");
26 | } else if (1 < 0) {
27 | console.log("0 > 1");
28 | } else if (9 < -9) {
29 | console.log("9 < -9");
30 | }
31 |
32 | // case
33 | switch (1) {
34 | case 4:
35 | const h = 1;
36 | console.log(h);
37 | break;
38 | case 3:
39 | console.log(3);
40 | break;
41 | default:
42 | console.log("default case");
43 | }
44 |
--------------------------------------------------------------------------------
/public/examples/react.js:
--------------------------------------------------------------------------------
1 | const App = () => {
2 | const [state, setState] = React.useState({ value: "", repos: [] });
3 |
4 | function handleSubmit(e, uname) {
5 | e.preventDefault();
6 | fetch("https://api.github.com/users/" + uname + "/repos")
7 | .then(res => res.json())
8 | .then(json => {
9 | setState({ ...state, repos: json });
10 | })
11 | .catch(err => console.log(err));
12 | }
13 |
14 | return html(
15 |
16 |
26 | {"repos fetched: " + state.repos.length}
27 |
28 | );
29 | };
30 |
31 | ReactDOM.render(
32 | React.createElement(App),
33 | document.getElementById("react-root")
34 | );
35 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | JavaScript to ClojureScript translator
7 |
11 |
12 |
13 |
14 |
15 |
16 |
34 |
35 |
36 |
37 |
38 |
JavaScript
39 |
40 |
41 |
42 |
ClojureScript
43 |
44 |
45 |
46 |
Compiled ClojureScript
47 |
48 |
49 |
53 |
57 |
58 |
59 |
60 |
61 |
62 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/public/main.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | }
4 |
5 | html * {
6 | box-sizing: inherit;
7 | }
8 |
9 | body {
10 | width: 100%;
11 | height: 100vh;
12 | overflow: hidden;
13 | margin: 0;
14 | padding: 0;
15 | font: normal 15px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
16 | Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
17 | letter-spacing: 0;
18 | -webkit-font-smoothing: antialiased;
19 | -moz-osx-font-smoothing: grayscale;
20 | -moz-font-feature-settings: "liga" on;
21 | text-rendering: optimizeLegibility;
22 | }
23 |
24 | #root {
25 | width: 100%;
26 | height: 100vh;
27 | }
28 |
29 | #root .col {
30 | max-width: 50%;
31 | }
32 |
33 | .row {
34 | flex: 1;
35 | display: flex;
36 | flex-direction: row;
37 | }
38 |
39 | .col {
40 | flex: 1;
41 | display: flex;
42 | flex-direction: column;
43 | border: 0.5px solid #e2e2e2;
44 | position: relative;
45 | }
46 | .react-root {
47 | border-top: 0.5px solid #e2e2e2;
48 | position: relative;
49 | }
50 |
51 | .col > div {
52 | flex: 1;
53 | max-height: 100%;
54 | }
55 |
56 | .CodeMirror {
57 | height: 100%;
58 | font-family: "Fira Code", monospace;
59 | }
60 |
61 | .popup-overlay {
62 | position: absolute;
63 | top: 0;
64 | left: 0;
65 | width: 100vw;
66 | height: 100vh;
67 | background: rgba(0, 0, 0, 0.8);
68 | z-index: 6;
69 | }
70 |
71 | .popup {
72 | position: absolute;
73 | top: 50%;
74 | left: 50%;
75 | transform: translate3d(-50%, -50%, 0);
76 | width: 460px;
77 | background: #fff;
78 | z-index: 6;
79 | border-radius: 5px;
80 | box-shadow: 0 4px 64px rgba(0, 0, 0, 0.2);
81 | padding: 32px;
82 | }
83 |
84 | .popup h1 {
85 | margin: 0;
86 | font-size: 24px;
87 | font-weight: normal;
88 | }
89 | .label {
90 | position: absolute;
91 | top: 4px;
92 | right: 4px;
93 | font-size: 9px;
94 | z-index: 5;
95 | color: #a3a3a3;
96 | text-transform: uppercase;
97 | font-weight: 500;
98 | background: #fff;
99 | padding: 2px 4px;
100 | }
101 |
102 | a {
103 | color: #3163ff;
104 | }
105 |
106 | .app-header a {
107 | color: #fff;
108 | }
109 |
110 | .app-header {
111 | width: 100%;
112 | padding: 8px 16px;
113 | background: #40a9c2;
114 | color: #fff;
115 | font-size: 11px;
116 | display: flex;
117 | align-items: center;
118 | justify-content: space-between;
119 | }
120 |
121 | .selector {
122 | margin: 0 0 0 16px;
123 | }
124 |
125 | @media (max-width: 760px) {
126 | .row {
127 | flex-direction: column;
128 | }
129 | #root .col {
130 | max-width: 100%;
131 | }
132 | }
133 |
134 | .tabs .btn.active {
135 | font-weight: 600;
136 | }
137 |
--------------------------------------------------------------------------------
/public/vendor/clojure.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | /**
5 | * Author: Hans Engel
6 | * Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun)
7 | */
8 |
9 | (function(mod) {
10 | if (typeof exports == "object" && typeof module == "object") // CommonJS
11 | mod(require("../../lib/codemirror"));
12 | else if (typeof define == "function" && define.amd) // AMD
13 | define(["../../lib/codemirror"], mod);
14 | else // Plain browser env
15 | mod(CodeMirror);
16 | })(function(CodeMirror) {
17 | "use strict";
18 |
19 | CodeMirror.defineMode("clojure", function (options) {
20 | var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", CHARACTER = "string-2",
21 | ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword", VAR = "variable";
22 | var INDENT_WORD_SKIP = options.indentUnit || 2;
23 | var NORMAL_INDENT_UNIT = options.indentUnit || 2;
24 |
25 | function makeKeywords(str) {
26 | var obj = {}, words = str.split(" ");
27 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
28 | return obj;
29 | }
30 |
31 | var atoms = makeKeywords("true false nil");
32 |
33 | var keywords = makeKeywords(
34 | "defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest " +
35 | "slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn " +
36 | "do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync " +
37 | "doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars " +
38 | "binding gen-class gen-and-load-class gen-and-save-class handler-case handle");
39 |
40 | var builtins = makeKeywords(
41 | "* *' *1 *2 *3 *agent* *allow-unresolved-vars* *assert* *clojure-version* *command-line-args* *compile-files* " +
42 | "*compile-path* *compiler-options* *data-readers* *e *err* *file* *flush-on-newline* *fn-loader* *in* " +
43 | "*math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-readably* *read-eval* " +
44 | "*source-path* *unchecked-math* *use-context-classloader* *verbose-defrecords* *warn-on-reflection* + +' - -' -> " +
45 | "->> ->ArrayChunk ->Vec ->VecNode ->VecSeq -cache-protocol-fn -reset-methods .. / < <= = == > >= EMPTY-NODE accessor " +
46 | "aclone add-classpath add-watch agent agent-error agent-errors aget alength alias all-ns alter alter-meta! " +
47 | "alter-var-root amap ancestors and apply areduce array-map aset aset-boolean aset-byte aset-char aset-double " +
48 | "aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 " +
49 | "bases bean bigdec bigint biginteger binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set " +
50 | "bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array booleans bound-fn bound-fn* bound? butlast " +
51 | "byte byte-array bytes case cat cast char char-array char-escape-string char-name-string char? chars chunk chunk-append " +
52 | "chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors " +
53 | "clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement completing concat cond condp " +
54 | "conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec dec' decimal? " +
55 | "declare dedupe default-data-readers definline definterface defmacro defmethod defmulti defn defn- defonce defprotocol " +
56 | "defrecord defstruct deftype delay delay? deliver denominator deref derive descendants destructure disj disj! dissoc " +
57 | "dissoc! distinct distinct? doall dorun doseq dosync dotimes doto double double-array doubles drop drop-last " +
58 | "drop-while eduction empty empty? ensure enumeration-seq error-handler error-mode eval even? every-pred every? ex-data ex-info " +
59 | "extend extend-protocol extend-type extenders extends? false? ffirst file-seq filter filterv find find-keyword " +
60 | "find-ns find-protocol-impl find-protocol-method find-var first flatten float float-array float? floats flush fn fn? " +
61 | "fnext fnil for force format frequencies future future-call future-cancel future-cancelled? future-done? future? " +
62 | "gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator group-by hash " +
63 | "hash-combine hash-map hash-set identical? identity if-let if-not ifn? import in-ns inc inc' init-proxy instance? " +
64 | "int int-array integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt keep " +
65 | "keep-indexed key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file " +
66 | "load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array " +
67 | "make-hierarchy map map-indexed map? mapcat mapv max max-key memfn memoize merge merge-with meta method-sig methods " +
68 | "min min-key mod munge name namespace namespace-munge neg? newline next nfirst nil? nnext not not-any? not-empty " +
69 | "not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias " +
70 | "ns-unmap nth nthnext nthrest num number? numerator object-array odd? or parents partial partition partition-all " +
71 | "partition-by pcalls peek persistent! pmap pop pop! pop-thread-bindings pos? pr pr-str prefer-method prefers " +
72 | "primitives-classnames print print-ctor print-dup print-method print-simple print-str printf println println-str " +
73 | "prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues " +
74 | "quot rand rand-int rand-nth random-sample range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern " +
75 | "re-seq read read-line read-string realized? reduce reduce-kv reductions ref ref-history-count ref-max-history " +
76 | "ref-min-history ref-set refer refer-clojure reify release-pending-sends rem remove remove-all-methods " +
77 | "remove-method remove-ns remove-watch repeat repeatedly replace replicate require reset! reset-meta! resolve rest " +
78 | "restart-agent resultset-seq reverse reversible? rseq rsubseq satisfies? second select-keys send send-off seq seq? " +
79 | "seque sequence sequential? set set-error-handler! set-error-mode! set-validator! set? short short-array shorts " +
80 | "shuffle shutdown-agents slurp some some-fn sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? " +
81 | "special-symbol? spit split-at split-with str string? struct struct-map subs subseq subvec supers swap! symbol " +
82 | "symbol? sync take take-last take-nth take-while test the-ns thread-bound? time to-array to-array-2d trampoline transduce " +
83 | "transient tree-seq true? type unchecked-add unchecked-add-int unchecked-byte unchecked-char unchecked-dec " +
84 | "unchecked-dec-int unchecked-divide-int unchecked-double unchecked-float unchecked-inc unchecked-inc-int " +
85 | "unchecked-int unchecked-long unchecked-multiply unchecked-multiply-int unchecked-negate unchecked-negate-int "+
86 | "unchecked-remainder-int unchecked-short unchecked-subtract unchecked-subtract-int underive unquote " +
87 | "unquote-splicing update update-in update-proxy use val vals var-get var-set var? vary-meta vec vector vector-of " +
88 | "vector? volatile! volatile? vreset! vswap! when when-first when-let when-not while with-bindings with-bindings* with-in-str with-loading-context " +
89 | "with-local-vars with-meta with-open with-out-str with-precision with-redefs with-redefs-fn xml-seq zero? zipmap " +
90 | "*default-data-reader-fn* as-> cond-> cond->> reduced reduced? send-via set-agent-send-executor! " +
91 | "set-agent-send-off-executor! some-> some->>");
92 |
93 | var indentKeys = makeKeywords(
94 | // Built-ins
95 | "ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto " +
96 | "locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type " +
97 | "try catch " +
98 |
99 | // Binding forms
100 | "let letfn binding loop for doseq dotimes when-let if-let " +
101 |
102 | // Data structures
103 | "defstruct struct-map assoc " +
104 |
105 | // clojure.test
106 | "testing deftest " +
107 |
108 | // contrib
109 | "handler-case handle dotrace deftrace");
110 |
111 | var tests = {
112 | digit: /\d/,
113 | digit_or_colon: /[\d:]/,
114 | hex: /[0-9a-f]/i,
115 | sign: /[+-]/,
116 | exponent: /e/i,
117 | keyword_char: /[^\s\(\[\;\)\]]/,
118 | symbol: /[\w*+!\-\._?:<>\/\xa1-\uffff]/,
119 | block_indent: /^(?:def|with)[^\/]+$|\/(?:def|with)/
120 | };
121 |
122 | function stateStack(indent, type, prev) { // represents a state stack object
123 | this.indent = indent;
124 | this.type = type;
125 | this.prev = prev;
126 | }
127 |
128 | function pushStack(state, indent, type) {
129 | state.indentStack = new stateStack(indent, type, state.indentStack);
130 | }
131 |
132 | function popStack(state) {
133 | state.indentStack = state.indentStack.prev;
134 | }
135 |
136 | function isNumber(ch, stream){
137 | // hex
138 | if ( ch === '0' && stream.eat(/x/i) ) {
139 | stream.eatWhile(tests.hex);
140 | return true;
141 | }
142 |
143 | // leading sign
144 | if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) {
145 | stream.eat(tests.sign);
146 | ch = stream.next();
147 | }
148 |
149 | if ( tests.digit.test(ch) ) {
150 | stream.eat(ch);
151 | stream.eatWhile(tests.digit);
152 |
153 | if ( '.' == stream.peek() ) {
154 | stream.eat('.');
155 | stream.eatWhile(tests.digit);
156 | } else if ('/' == stream.peek() ) {
157 | stream.eat('/');
158 | stream.eatWhile(tests.digit);
159 | }
160 |
161 | if ( stream.eat(tests.exponent) ) {
162 | stream.eat(tests.sign);
163 | stream.eatWhile(tests.digit);
164 | }
165 |
166 | return true;
167 | }
168 |
169 | return false;
170 | }
171 |
172 | // Eat character that starts after backslash \
173 | function eatCharacter(stream) {
174 | var first = stream.next();
175 | // Read special literals: backspace, newline, space, return.
176 | // Just read all lowercase letters.
177 | if (first && first.match(/[a-z]/) && stream.match(/[a-z]+/, true)) {
178 | return;
179 | }
180 | // Read unicode character: \u1000 \uA0a1
181 | if (first === "u") {
182 | stream.match(/[0-9a-z]{4}/i, true);
183 | }
184 | }
185 |
186 | return {
187 | startState: function () {
188 | return {
189 | indentStack: null,
190 | indentation: 0,
191 | mode: false
192 | };
193 | },
194 |
195 | token: function (stream, state) {
196 | if (state.indentStack == null && stream.sol()) {
197 | // update indentation, but only if indentStack is empty
198 | state.indentation = stream.indentation();
199 | }
200 |
201 | // skip spaces
202 | if (state.mode != "string" && stream.eatSpace()) {
203 | return null;
204 | }
205 | var returnType = null;
206 |
207 | switch(state.mode){
208 | case "string": // multi-line string parsing mode
209 | var next, escaped = false;
210 | while ((next = stream.next()) != null) {
211 | if (next == "\"" && !escaped) {
212 |
213 | state.mode = false;
214 | break;
215 | }
216 | escaped = !escaped && next == "\\";
217 | }
218 | returnType = STRING; // continue on in string mode
219 | break;
220 | default: // default parsing mode
221 | var ch = stream.next();
222 |
223 | if (ch == "\"") {
224 | state.mode = "string";
225 | returnType = STRING;
226 | } else if (ch == "\\") {
227 | eatCharacter(stream);
228 | returnType = CHARACTER;
229 | } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) {
230 | returnType = ATOM;
231 | } else if (ch == ";") { // comment
232 | stream.skipToEnd(); // rest of the line is a comment
233 | returnType = COMMENT;
234 | } else if (isNumber(ch,stream)){
235 | returnType = NUMBER;
236 | } else if (ch == "(" || ch == "[" || ch == "{" ) {
237 | var keyWord = '', indentTemp = stream.column(), letter;
238 | /**
239 | Either
240 | (indent-word ..
241 | (non-indent-word ..
242 | (;something else, bracket, etc.
243 | */
244 |
245 | if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) {
246 | keyWord += letter;
247 | }
248 |
249 | if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) ||
250 | tests.block_indent.test(keyWord))) { // indent-word
251 | pushStack(state, indentTemp + INDENT_WORD_SKIP, ch);
252 | } else { // non-indent word
253 | // we continue eating the spaces
254 | stream.eatSpace();
255 | if (stream.eol() || stream.peek() == ";") {
256 | // nothing significant after
257 | // we restart indentation the user defined spaces after
258 | pushStack(state, indentTemp + NORMAL_INDENT_UNIT, ch);
259 | } else {
260 | pushStack(state, indentTemp + stream.current().length, ch); // else we match
261 | }
262 | }
263 | stream.backUp(stream.current().length - 1); // undo all the eating
264 |
265 | returnType = BRACKET;
266 | } else if (ch == ")" || ch == "]" || ch == "}") {
267 | returnType = BRACKET;
268 | if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : (ch == "]" ? "[" :"{"))) {
269 | popStack(state);
270 | }
271 | } else if ( ch == ":" ) {
272 | stream.eatWhile(tests.symbol);
273 | return ATOM;
274 | } else {
275 | stream.eatWhile(tests.symbol);
276 |
277 | if (keywords && keywords.propertyIsEnumerable(stream.current())) {
278 | returnType = KEYWORD;
279 | } else if (builtins && builtins.propertyIsEnumerable(stream.current())) {
280 | returnType = BUILTIN;
281 | } else if (atoms && atoms.propertyIsEnumerable(stream.current())) {
282 | returnType = ATOM;
283 | } else {
284 | returnType = VAR;
285 | }
286 | }
287 | }
288 |
289 | return returnType;
290 | },
291 |
292 | indent: function (state) {
293 | if (state.indentStack == null) return state.indentation;
294 | return state.indentStack.indent;
295 | },
296 |
297 | closeBrackets: {pairs: "()[]{}\"\""},
298 | lineComment: ";;"
299 | };
300 | });
301 |
302 | CodeMirror.defineMIME("text/x-clojure", "clojure");
303 | CodeMirror.defineMIME("text/x-clojurescript", "clojure");
304 | CodeMirror.defineMIME("application/edn", "clojure");
305 |
306 | });
307 |
--------------------------------------------------------------------------------
/public/vendor/javascript.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | "use strict";
13 |
14 | CodeMirror.defineMode("javascript", function(config, parserConfig) {
15 | var indentUnit = config.indentUnit;
16 | var statementIndent = parserConfig.statementIndent;
17 | var jsonldMode = parserConfig.jsonld;
18 | var jsonMode = parserConfig.json || jsonldMode;
19 | var isTS = parserConfig.typescript;
20 | var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
21 |
22 | // Tokenizer
23 |
24 | var keywords = function(){
25 | function kw(type) {return {type: type, style: "keyword"};}
26 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
27 | var operator = kw("operator"), atom = {type: "atom", style: "atom"};
28 |
29 | return {
30 | "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
31 | "return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
32 | "debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
33 | "function": kw("function"), "catch": kw("catch"),
34 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
35 | "in": operator, "typeof": operator, "instanceof": operator,
36 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
37 | "this": kw("this"), "class": kw("class"), "super": kw("atom"),
38 | "yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
39 | "await": C
40 | };
41 | }();
42 |
43 | var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
44 | var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
45 |
46 | function readRegexp(stream) {
47 | var escaped = false, next, inSet = false;
48 | while ((next = stream.next()) != null) {
49 | if (!escaped) {
50 | if (next == "/" && !inSet) return;
51 | if (next == "[") inSet = true;
52 | else if (inSet && next == "]") inSet = false;
53 | }
54 | escaped = !escaped && next == "\\";
55 | }
56 | }
57 |
58 | // Used as scratch variables to communicate multiple values without
59 | // consing up tons of objects.
60 | var type, content;
61 | function ret(tp, style, cont) {
62 | type = tp; content = cont;
63 | return style;
64 | }
65 | function tokenBase(stream, state) {
66 | var ch = stream.next();
67 | if (ch == '"' || ch == "'") {
68 | state.tokenize = tokenString(ch);
69 | return state.tokenize(stream, state);
70 | } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
71 | return ret("number", "number");
72 | } else if (ch == "." && stream.match("..")) {
73 | return ret("spread", "meta");
74 | } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
75 | return ret(ch);
76 | } else if (ch == "=" && stream.eat(">")) {
77 | return ret("=>", "operator");
78 | } else if (ch == "0" && stream.match(/^(?:x[\da-f]+|o[0-7]+|b[01]+)n?/i)) {
79 | return ret("number", "number");
80 | } else if (/\d/.test(ch)) {
81 | stream.match(/^\d*(?:n|(?:\.\d*)?(?:[eE][+\-]?\d+)?)?/);
82 | return ret("number", "number");
83 | } else if (ch == "/") {
84 | if (stream.eat("*")) {
85 | state.tokenize = tokenComment;
86 | return tokenComment(stream, state);
87 | } else if (stream.eat("/")) {
88 | stream.skipToEnd();
89 | return ret("comment", "comment");
90 | } else if (expressionAllowed(stream, state, 1)) {
91 | readRegexp(stream);
92 | stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
93 | return ret("regexp", "string-2");
94 | } else {
95 | stream.eat("=");
96 | return ret("operator", "operator", stream.current());
97 | }
98 | } else if (ch == "`") {
99 | state.tokenize = tokenQuasi;
100 | return tokenQuasi(stream, state);
101 | } else if (ch == "#") {
102 | stream.skipToEnd();
103 | return ret("error", "error");
104 | } else if (isOperatorChar.test(ch)) {
105 | if (ch != ">" || !state.lexical || state.lexical.type != ">") {
106 | if (stream.eat("=")) {
107 | if (ch == "!" || ch == "=") stream.eat("=")
108 | } else if (/[<>*+\-]/.test(ch)) {
109 | stream.eat(ch)
110 | if (ch == ">") stream.eat(ch)
111 | }
112 | }
113 | return ret("operator", "operator", stream.current());
114 | } else if (wordRE.test(ch)) {
115 | stream.eatWhile(wordRE);
116 | var word = stream.current()
117 | if (state.lastType != ".") {
118 | if (keywords.propertyIsEnumerable(word)) {
119 | var kw = keywords[word]
120 | return ret(kw.type, kw.style, word)
121 | }
122 | if (word == "async" && stream.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/, false))
123 | return ret("async", "keyword", word)
124 | }
125 | return ret("variable", "variable", word)
126 | }
127 | }
128 |
129 | function tokenString(quote) {
130 | return function(stream, state) {
131 | var escaped = false, next;
132 | if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
133 | state.tokenize = tokenBase;
134 | return ret("jsonld-keyword", "meta");
135 | }
136 | while ((next = stream.next()) != null) {
137 | if (next == quote && !escaped) break;
138 | escaped = !escaped && next == "\\";
139 | }
140 | if (!escaped) state.tokenize = tokenBase;
141 | return ret("string", "string");
142 | };
143 | }
144 |
145 | function tokenComment(stream, state) {
146 | var maybeEnd = false, ch;
147 | while (ch = stream.next()) {
148 | if (ch == "/" && maybeEnd) {
149 | state.tokenize = tokenBase;
150 | break;
151 | }
152 | maybeEnd = (ch == "*");
153 | }
154 | return ret("comment", "comment");
155 | }
156 |
157 | function tokenQuasi(stream, state) {
158 | var escaped = false, next;
159 | while ((next = stream.next()) != null) {
160 | if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
161 | state.tokenize = tokenBase;
162 | break;
163 | }
164 | escaped = !escaped && next == "\\";
165 | }
166 | return ret("quasi", "string-2", stream.current());
167 | }
168 |
169 | var brackets = "([{}])";
170 | // This is a crude lookahead trick to try and notice that we're
171 | // parsing the argument patterns for a fat-arrow function before we
172 | // actually hit the arrow token. It only works if the arrow is on
173 | // the same line as the arguments and there's no strange noise
174 | // (comments) in between. Fallback is to only notice when we hit the
175 | // arrow, and not declare the arguments as locals for the arrow
176 | // body.
177 | function findFatArrow(stream, state) {
178 | if (state.fatArrowAt) state.fatArrowAt = null;
179 | var arrow = stream.string.indexOf("=>", stream.start);
180 | if (arrow < 0) return;
181 |
182 | if (isTS) { // Try to skip TypeScript return type declarations after the arguments
183 | var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
184 | if (m) arrow = m.index
185 | }
186 |
187 | var depth = 0, sawSomething = false;
188 | for (var pos = arrow - 1; pos >= 0; --pos) {
189 | var ch = stream.string.charAt(pos);
190 | var bracket = brackets.indexOf(ch);
191 | if (bracket >= 0 && bracket < 3) {
192 | if (!depth) { ++pos; break; }
193 | if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
194 | } else if (bracket >= 3 && bracket < 6) {
195 | ++depth;
196 | } else if (wordRE.test(ch)) {
197 | sawSomething = true;
198 | } else if (/["'\/]/.test(ch)) {
199 | return;
200 | } else if (sawSomething && !depth) {
201 | ++pos;
202 | break;
203 | }
204 | }
205 | if (sawSomething && !depth) state.fatArrowAt = pos;
206 | }
207 |
208 | // Parser
209 |
210 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
211 |
212 | function JSLexical(indented, column, type, align, prev, info) {
213 | this.indented = indented;
214 | this.column = column;
215 | this.type = type;
216 | this.prev = prev;
217 | this.info = info;
218 | if (align != null) this.align = align;
219 | }
220 |
221 | function inScope(state, varname) {
222 | for (var v = state.localVars; v; v = v.next)
223 | if (v.name == varname) return true;
224 | for (var cx = state.context; cx; cx = cx.prev) {
225 | for (var v = cx.vars; v; v = v.next)
226 | if (v.name == varname) return true;
227 | }
228 | }
229 |
230 | function parseJS(state, style, type, content, stream) {
231 | var cc = state.cc;
232 | // Communicate our context to the combinators.
233 | // (Less wasteful than consing up a hundred closures on every call.)
234 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
235 |
236 | if (!state.lexical.hasOwnProperty("align"))
237 | state.lexical.align = true;
238 |
239 | while(true) {
240 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
241 | if (combinator(type, content)) {
242 | while(cc.length && cc[cc.length - 1].lex)
243 | cc.pop()();
244 | if (cx.marked) return cx.marked;
245 | if (type == "variable" && inScope(state, content)) return "variable-2";
246 | return style;
247 | }
248 | }
249 | }
250 |
251 | // Combinator utils
252 |
253 | var cx = {state: null, column: null, marked: null, cc: null};
254 | function pass() {
255 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
256 | }
257 | function cont() {
258 | pass.apply(null, arguments);
259 | return true;
260 | }
261 | function inList(name, list) {
262 | for (var v = list; v; v = v.next) if (v.name == name) return true
263 | return false;
264 | }
265 | function register(varname) {
266 | var state = cx.state;
267 | cx.marked = "def";
268 | if (state.context) {
269 | if (state.lexical.info == "var" && state.context && state.context.block) {
270 | // FIXME function decls are also not block scoped
271 | var newContext = registerVarScoped(varname, state.context)
272 | if (newContext != null) {
273 | state.context = newContext
274 | return
275 | }
276 | } else if (!inList(varname, state.localVars)) {
277 | state.localVars = new Var(varname, state.localVars)
278 | return
279 | }
280 | }
281 | // Fall through means this is global
282 | if (parserConfig.globalVars && !inList(varname, state.globalVars))
283 | state.globalVars = new Var(varname, state.globalVars)
284 | }
285 | function registerVarScoped(varname, context) {
286 | if (!context) {
287 | return null
288 | } else if (context.block) {
289 | var inner = registerVarScoped(varname, context.prev)
290 | if (!inner) return null
291 | if (inner == context.prev) return context
292 | return new Context(inner, context.vars, true)
293 | } else if (inList(varname, context.vars)) {
294 | return context
295 | } else {
296 | return new Context(context.prev, new Var(varname, context.vars), false)
297 | }
298 | }
299 |
300 | function isModifier(name) {
301 | return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
302 | }
303 |
304 | // Combinators
305 |
306 | function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
307 | function Var(name, next) { this.name = name; this.next = next }
308 |
309 | var defaultVars = new Var("this", new Var("arguments", null))
310 | function pushcontext() {
311 | cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
312 | cx.state.localVars = defaultVars
313 | }
314 | function pushblockcontext() {
315 | cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
316 | cx.state.localVars = null
317 | }
318 | function popcontext() {
319 | cx.state.localVars = cx.state.context.vars
320 | cx.state.context = cx.state.context.prev
321 | }
322 | popcontext.lex = true
323 | function pushlex(type, info) {
324 | var result = function() {
325 | var state = cx.state, indent = state.indented;
326 | if (state.lexical.type == "stat") indent = state.lexical.indented;
327 | else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
328 | indent = outer.indented;
329 | state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
330 | };
331 | result.lex = true;
332 | return result;
333 | }
334 | function poplex() {
335 | var state = cx.state;
336 | if (state.lexical.prev) {
337 | if (state.lexical.type == ")")
338 | state.indented = state.lexical.indented;
339 | state.lexical = state.lexical.prev;
340 | }
341 | }
342 | poplex.lex = true;
343 |
344 | function expect(wanted) {
345 | function exp(type) {
346 | if (type == wanted) return cont();
347 | else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
348 | else return cont(exp);
349 | };
350 | return exp;
351 | }
352 |
353 | function statement(type, value) {
354 | if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
355 | if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
356 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
357 | if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
358 | if (type == "debugger") return cont(expect(";"));
359 | if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
360 | if (type == ";") return cont();
361 | if (type == "if") {
362 | if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
363 | cx.state.cc.pop()();
364 | return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
365 | }
366 | if (type == "function") return cont(functiondef);
367 | if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
368 | if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), className, poplex); }
369 | if (type == "variable") {
370 | if (isTS && value == "declare") {
371 | cx.marked = "keyword"
372 | return cont(statement)
373 | } else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
374 | cx.marked = "keyword"
375 | if (value == "enum") return cont(enumdef);
376 | else if (value == "type") return cont(typeexpr, expect("operator"), typeexpr, expect(";"));
377 | else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
378 | } else if (isTS && value == "namespace") {
379 | cx.marked = "keyword"
380 | return cont(pushlex("form"), expression, block, poplex)
381 | } else if (isTS && value == "abstract") {
382 | cx.marked = "keyword"
383 | return cont(statement)
384 | } else {
385 | return cont(pushlex("stat"), maybelabel);
386 | }
387 | }
388 | if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
389 | block, poplex, poplex, popcontext);
390 | if (type == "case") return cont(expression, expect(":"));
391 | if (type == "default") return cont(expect(":"));
392 | if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
393 | if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
394 | if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
395 | if (type == "async") return cont(statement)
396 | if (value == "@") return cont(expression, statement)
397 | return pass(pushlex("stat"), expression, expect(";"), poplex);
398 | }
399 | function maybeCatchBinding(type) {
400 | if (type == "(") return cont(funarg, expect(")"))
401 | }
402 | function expression(type, value) {
403 | return expressionInner(type, value, false);
404 | }
405 | function expressionNoComma(type, value) {
406 | return expressionInner(type, value, true);
407 | }
408 | function parenExpr(type) {
409 | if (type != "(") return pass()
410 | return cont(pushlex(")"), expression, expect(")"), poplex)
411 | }
412 | function expressionInner(type, value, noComma) {
413 | if (cx.state.fatArrowAt == cx.stream.start) {
414 | var body = noComma ? arrowBodyNoComma : arrowBody;
415 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
416 | else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
417 | }
418 |
419 | var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
420 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
421 | if (type == "function") return cont(functiondef, maybeop);
422 | if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
423 | if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
424 | if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
425 | if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
426 | if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
427 | if (type == "{") return contCommasep(objprop, "}", null, maybeop);
428 | if (type == "quasi") return pass(quasi, maybeop);
429 | if (type == "new") return cont(maybeTarget(noComma));
430 | if (type == "import") return cont(expression);
431 | return cont();
432 | }
433 | function maybeexpression(type) {
434 | if (type.match(/[;\}\)\],]/)) return pass();
435 | return pass(expression);
436 | }
437 |
438 | function maybeoperatorComma(type, value) {
439 | if (type == ",") return cont(expression);
440 | return maybeoperatorNoComma(type, value, false);
441 | }
442 | function maybeoperatorNoComma(type, value, noComma) {
443 | var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
444 | var expr = noComma == false ? expression : expressionNoComma;
445 | if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
446 | if (type == "operator") {
447 | if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
448 | if (isTS && value == "<" && cx.stream.match(/^([^>]|<.*?>)*>\s*\(/, false))
449 | return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
450 | if (value == "?") return cont(expression, expect(":"), expr);
451 | return cont(expr);
452 | }
453 | if (type == "quasi") { return pass(quasi, me); }
454 | if (type == ";") return;
455 | if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
456 | if (type == ".") return cont(property, me);
457 | if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
458 | if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
459 | if (type == "regexp") {
460 | cx.state.lastType = cx.marked = "operator"
461 | cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
462 | return cont(expr)
463 | }
464 | }
465 | function quasi(type, value) {
466 | if (type != "quasi") return pass();
467 | if (value.slice(value.length - 2) != "${") return cont(quasi);
468 | return cont(expression, continueQuasi);
469 | }
470 | function continueQuasi(type) {
471 | if (type == "}") {
472 | cx.marked = "string-2";
473 | cx.state.tokenize = tokenQuasi;
474 | return cont(quasi);
475 | }
476 | }
477 | function arrowBody(type) {
478 | findFatArrow(cx.stream, cx.state);
479 | return pass(type == "{" ? statement : expression);
480 | }
481 | function arrowBodyNoComma(type) {
482 | findFatArrow(cx.stream, cx.state);
483 | return pass(type == "{" ? statement : expressionNoComma);
484 | }
485 | function maybeTarget(noComma) {
486 | return function(type) {
487 | if (type == ".") return cont(noComma ? targetNoComma : target);
488 | else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
489 | else return pass(noComma ? expressionNoComma : expression);
490 | };
491 | }
492 | function target(_, value) {
493 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
494 | }
495 | function targetNoComma(_, value) {
496 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
497 | }
498 | function maybelabel(type) {
499 | if (type == ":") return cont(poplex, statement);
500 | return pass(maybeoperatorComma, expect(";"), poplex);
501 | }
502 | function property(type) {
503 | if (type == "variable") {cx.marked = "property"; return cont();}
504 | }
505 | function objprop(type, value) {
506 | if (type == "async") {
507 | cx.marked = "property";
508 | return cont(objprop);
509 | } else if (type == "variable" || cx.style == "keyword") {
510 | cx.marked = "property";
511 | if (value == "get" || value == "set") return cont(getterSetter);
512 | var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
513 | if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
514 | cx.state.fatArrowAt = cx.stream.pos + m[0].length
515 | return cont(afterprop);
516 | } else if (type == "number" || type == "string") {
517 | cx.marked = jsonldMode ? "property" : (cx.style + " property");
518 | return cont(afterprop);
519 | } else if (type == "jsonld-keyword") {
520 | return cont(afterprop);
521 | } else if (isTS && isModifier(value)) {
522 | cx.marked = "keyword"
523 | return cont(objprop)
524 | } else if (type == "[") {
525 | return cont(expression, maybetype, expect("]"), afterprop);
526 | } else if (type == "spread") {
527 | return cont(expressionNoComma, afterprop);
528 | } else if (value == "*") {
529 | cx.marked = "keyword";
530 | return cont(objprop);
531 | } else if (type == ":") {
532 | return pass(afterprop)
533 | }
534 | }
535 | function getterSetter(type) {
536 | if (type != "variable") return pass(afterprop);
537 | cx.marked = "property";
538 | return cont(functiondef);
539 | }
540 | function afterprop(type) {
541 | if (type == ":") return cont(expressionNoComma);
542 | if (type == "(") return pass(functiondef);
543 | }
544 | function commasep(what, end, sep) {
545 | function proceed(type, value) {
546 | if (sep ? sep.indexOf(type) > -1 : type == ",") {
547 | var lex = cx.state.lexical;
548 | if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
549 | return cont(function(type, value) {
550 | if (type == end || value == end) return pass()
551 | return pass(what)
552 | }, proceed);
553 | }
554 | if (type == end || value == end) return cont();
555 | return cont(expect(end));
556 | }
557 | return function(type, value) {
558 | if (type == end || value == end) return cont();
559 | return pass(what, proceed);
560 | };
561 | }
562 | function contCommasep(what, end, info) {
563 | for (var i = 3; i < arguments.length; i++)
564 | cx.cc.push(arguments[i]);
565 | return cont(pushlex(end, info), commasep(what, end), poplex);
566 | }
567 | function block(type) {
568 | if (type == "}") return cont();
569 | return pass(statement, block);
570 | }
571 | function maybetype(type, value) {
572 | if (isTS) {
573 | if (type == ":") return cont(typeexpr);
574 | if (value == "?") return cont(maybetype);
575 | }
576 | }
577 | function mayberettype(type) {
578 | if (isTS && type == ":") {
579 | if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
580 | else return cont(typeexpr)
581 | }
582 | }
583 | function isKW(_, value) {
584 | if (value == "is") {
585 | cx.marked = "keyword"
586 | return cont()
587 | }
588 | }
589 | function typeexpr(type, value) {
590 | if (value == "keyof" || value == "typeof") {
591 | cx.marked = "keyword"
592 | return cont(value == "keyof" ? typeexpr : expressionNoComma)
593 | }
594 | if (type == "variable" || value == "void") {
595 | cx.marked = "type"
596 | return cont(afterType)
597 | }
598 | if (type == "string" || type == "number" || type == "atom") return cont(afterType);
599 | if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
600 | if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType)
601 | if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType)
602 | if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
603 | }
604 | function maybeReturnType(type) {
605 | if (type == "=>") return cont(typeexpr)
606 | }
607 | function typeprop(type, value) {
608 | if (type == "variable" || cx.style == "keyword") {
609 | cx.marked = "property"
610 | return cont(typeprop)
611 | } else if (value == "?") {
612 | return cont(typeprop)
613 | } else if (type == ":") {
614 | return cont(typeexpr)
615 | } else if (type == "[") {
616 | return cont(expression, maybetype, expect("]"), typeprop)
617 | }
618 | }
619 | function typearg(type, value) {
620 | if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
621 | if (type == ":") return cont(typeexpr)
622 | return pass(typeexpr)
623 | }
624 | function afterType(type, value) {
625 | if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
626 | if (value == "|" || type == "." || value == "&") return cont(typeexpr)
627 | if (type == "[") return cont(expect("]"), afterType)
628 | if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
629 | }
630 | function maybeTypeArgs(_, value) {
631 | if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
632 | }
633 | function typeparam() {
634 | return pass(typeexpr, maybeTypeDefault)
635 | }
636 | function maybeTypeDefault(_, value) {
637 | if (value == "=") return cont(typeexpr)
638 | }
639 | function vardef(_, value) {
640 | if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
641 | return pass(pattern, maybetype, maybeAssign, vardefCont);
642 | }
643 | function pattern(type, value) {
644 | if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
645 | if (type == "variable") { register(value); return cont(); }
646 | if (type == "spread") return cont(pattern);
647 | if (type == "[") return contCommasep(pattern, "]");
648 | if (type == "{") return contCommasep(proppattern, "}");
649 | }
650 | function proppattern(type, value) {
651 | if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
652 | register(value);
653 | return cont(maybeAssign);
654 | }
655 | if (type == "variable") cx.marked = "property";
656 | if (type == "spread") return cont(pattern);
657 | if (type == "}") return pass();
658 | return cont(expect(":"), pattern, maybeAssign);
659 | }
660 | function maybeAssign(_type, value) {
661 | if (value == "=") return cont(expressionNoComma);
662 | }
663 | function vardefCont(type) {
664 | if (type == ",") return cont(vardef);
665 | }
666 | function maybeelse(type, value) {
667 | if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
668 | }
669 | function forspec(type, value) {
670 | if (value == "await") return cont(forspec);
671 | if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
672 | }
673 | function forspec1(type) {
674 | if (type == "var") return cont(vardef, expect(";"), forspec2);
675 | if (type == ";") return cont(forspec2);
676 | if (type == "variable") return cont(formaybeinof);
677 | return pass(expression, expect(";"), forspec2);
678 | }
679 | function formaybeinof(_type, value) {
680 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
681 | return cont(maybeoperatorComma, forspec2);
682 | }
683 | function forspec2(type, value) {
684 | if (type == ";") return cont(forspec3);
685 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
686 | return pass(expression, expect(";"), forspec3);
687 | }
688 | function forspec3(type) {
689 | if (type != ")") cont(expression);
690 | }
691 | function functiondef(type, value) {
692 | if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
693 | if (type == "variable") {register(value); return cont(functiondef);}
694 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
695 | if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
696 | }
697 | function funarg(type, value) {
698 | if (value == "@") cont(expression, funarg)
699 | if (type == "spread") return cont(funarg);
700 | if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
701 | return pass(pattern, maybetype, maybeAssign);
702 | }
703 | function classExpression(type, value) {
704 | // Class expressions may have an optional name.
705 | if (type == "variable") return className(type, value);
706 | return classNameAfter(type, value);
707 | }
708 | function className(type, value) {
709 | if (type == "variable") {register(value); return cont(classNameAfter);}
710 | }
711 | function classNameAfter(type, value) {
712 | if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
713 | if (value == "extends" || value == "implements" || (isTS && type == ",")) {
714 | if (value == "implements") cx.marked = "keyword";
715 | return cont(isTS ? typeexpr : expression, classNameAfter);
716 | }
717 | if (type == "{") return cont(pushlex("}"), classBody, poplex);
718 | }
719 | function classBody(type, value) {
720 | if (type == "async" ||
721 | (type == "variable" &&
722 | (value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
723 | cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false))) {
724 | cx.marked = "keyword";
725 | return cont(classBody);
726 | }
727 | if (type == "variable" || cx.style == "keyword") {
728 | cx.marked = "property";
729 | return cont(isTS ? classfield : functiondef, classBody);
730 | }
731 | if (type == "[")
732 | return cont(expression, maybetype, expect("]"), isTS ? classfield : functiondef, classBody)
733 | if (value == "*") {
734 | cx.marked = "keyword";
735 | return cont(classBody);
736 | }
737 | if (type == ";") return cont(classBody);
738 | if (type == "}") return cont();
739 | if (value == "@") return cont(expression, classBody)
740 | }
741 | function classfield(type, value) {
742 | if (value == "?") return cont(classfield)
743 | if (type == ":") return cont(typeexpr, maybeAssign)
744 | if (value == "=") return cont(expressionNoComma)
745 | return pass(functiondef)
746 | }
747 | function afterExport(type, value) {
748 | if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
749 | if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
750 | if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
751 | return pass(statement);
752 | }
753 | function exportField(type, value) {
754 | if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
755 | if (type == "variable") return pass(expressionNoComma, exportField);
756 | }
757 | function afterImport(type) {
758 | if (type == "string") return cont();
759 | if (type == "(") return pass(expression);
760 | return pass(importSpec, maybeMoreImports, maybeFrom);
761 | }
762 | function importSpec(type, value) {
763 | if (type == "{") return contCommasep(importSpec, "}");
764 | if (type == "variable") register(value);
765 | if (value == "*") cx.marked = "keyword";
766 | return cont(maybeAs);
767 | }
768 | function maybeMoreImports(type) {
769 | if (type == ",") return cont(importSpec, maybeMoreImports)
770 | }
771 | function maybeAs(_type, value) {
772 | if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
773 | }
774 | function maybeFrom(_type, value) {
775 | if (value == "from") { cx.marked = "keyword"; return cont(expression); }
776 | }
777 | function arrayLiteral(type) {
778 | if (type == "]") return cont();
779 | return pass(commasep(expressionNoComma, "]"));
780 | }
781 | function enumdef() {
782 | return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
783 | }
784 | function enummember() {
785 | return pass(pattern, maybeAssign);
786 | }
787 |
788 | function isContinuedStatement(state, textAfter) {
789 | return state.lastType == "operator" || state.lastType == "," ||
790 | isOperatorChar.test(textAfter.charAt(0)) ||
791 | /[,.]/.test(textAfter.charAt(0));
792 | }
793 |
794 | function expressionAllowed(stream, state, backUp) {
795 | return state.tokenize == tokenBase &&
796 | /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
797 | (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
798 | }
799 |
800 | // Interface
801 |
802 | return {
803 | startState: function(basecolumn) {
804 | var state = {
805 | tokenize: tokenBase,
806 | lastType: "sof",
807 | cc: [],
808 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
809 | localVars: parserConfig.localVars,
810 | context: parserConfig.localVars && new Context(null, null, false),
811 | indented: basecolumn || 0
812 | };
813 | if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
814 | state.globalVars = parserConfig.globalVars;
815 | return state;
816 | },
817 |
818 | token: function(stream, state) {
819 | if (stream.sol()) {
820 | if (!state.lexical.hasOwnProperty("align"))
821 | state.lexical.align = false;
822 | state.indented = stream.indentation();
823 | findFatArrow(stream, state);
824 | }
825 | if (state.tokenize != tokenComment && stream.eatSpace()) return null;
826 | var style = state.tokenize(stream, state);
827 | if (type == "comment") return style;
828 | state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
829 | return parseJS(state, style, type, content, stream);
830 | },
831 |
832 | indent: function(state, textAfter) {
833 | if (state.tokenize == tokenComment) return CodeMirror.Pass;
834 | if (state.tokenize != tokenBase) return 0;
835 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
836 | // Kludge to prevent 'maybelse' from blocking lexical scope pops
837 | if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
838 | var c = state.cc[i];
839 | if (c == poplex) lexical = lexical.prev;
840 | else if (c != maybeelse) break;
841 | }
842 | while ((lexical.type == "stat" || lexical.type == "form") &&
843 | (firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
844 | (top == maybeoperatorComma || top == maybeoperatorNoComma) &&
845 | !/^[,\.=+\-*:?[\(]/.test(textAfter))))
846 | lexical = lexical.prev;
847 | if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
848 | lexical = lexical.prev;
849 | var type = lexical.type, closing = firstChar == type;
850 |
851 | if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
852 | else if (type == "form" && firstChar == "{") return lexical.indented;
853 | else if (type == "form") return lexical.indented + indentUnit;
854 | else if (type == "stat")
855 | return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
856 | else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
857 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
858 | else if (lexical.align) return lexical.column + (closing ? 0 : 1);
859 | else return lexical.indented + (closing ? 0 : indentUnit);
860 | },
861 |
862 | electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
863 | blockCommentStart: jsonMode ? null : "/*",
864 | blockCommentEnd: jsonMode ? null : "*/",
865 | blockCommentContinue: jsonMode ? null : " * ",
866 | lineComment: jsonMode ? null : "//",
867 | fold: "brace",
868 | closeBrackets: "()[]{}''\"\"``",
869 |
870 | helperType: jsonMode ? "json" : "javascript",
871 | jsonldMode: jsonldMode,
872 | jsonMode: jsonMode,
873 |
874 | expressionAllowed: expressionAllowed,
875 |
876 | skipExpression: function(state) {
877 | var top = state.cc[state.cc.length - 1]
878 | if (top == expression || top == expressionNoComma) state.cc.pop()
879 | }
880 | };
881 | });
882 |
883 | CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
884 |
885 | CodeMirror.defineMIME("text/javascript", "javascript");
886 | CodeMirror.defineMIME("text/ecmascript", "javascript");
887 | CodeMirror.defineMIME("application/javascript", "javascript");
888 | CodeMirror.defineMIME("application/x-javascript", "javascript");
889 | CodeMirror.defineMIME("application/ecmascript", "javascript");
890 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
891 | CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
892 | CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
893 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
894 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
895 |
896 | });
897 |
--------------------------------------------------------------------------------
/public/vendor/react.production.min.js:
--------------------------------------------------------------------------------
1 | /** @license React v16.9.0
2 | * react.production.min.js
3 | *
4 | * Copyright (c) Facebook, Inc. and its affiliates.
5 | *
6 | * This source code is licensed under the MIT license found in the
7 | * LICENSE file in the root directory of this source tree.
8 | */
9 | 'use strict';(function(t,q){"object"===typeof exports&&"undefined"!==typeof module?module.exports=q():"function"===typeof define&&define.amd?define(q):t.React=q()})(this,function(){function t(a){for(var b=a.message,c="https://reactjs.org/docs/error-decoder.html?invariant="+b,d=1;dH.length&&H.push(a)}function R(a,b,c,d){var g=typeof a;if("undefined"===g||"boolean"===g)a=null;var k=!1;if(null===a)k=!0;else switch(g){case "string":case "number":k=!0;break;case "object":switch(a.$$typeof){case y:case Da:k=!0}}if(k)return c(d,a,""===b?"."+S(a,0):b),1;k=0;b=""===b?".":b+":";if(Array.isArray(a))for(var e=0;ea;a++)b["_"+String.fromCharCode(a)]=a;if("0123456789"!==Object.getOwnPropertyNames(b).map(function(a){return b[a]}).join(""))return!1;var c={};"abcdefghijklmnopqrst".split("").forEach(function(a){c[a]=a});return"abcdefghijklmnopqrst"!==Object.keys(Object.assign({},c)).join("")?
20 | !1:!0}catch(d){return!1}}()?Object.assign:function(a,b){if(null===a||void 0===a)throw new TypeError("Object.assign cannot be called with null or undefined");var c=Object(a);for(var d,g=1;g=N};h=function(){};Y=function(a){0>a||125p&&(p=8.33));x=c}F=a;N=a+p;ya.postMessage(null)}};w=function(a){E=a;M||(M=!0,ba(function(a){Aa(a)}))};C=function(a,b){da=aa(function(){a(n())},b)};I=function(){xa(da);da=-1}}var e=null,f=null,z=null,m=3,J=!1,u=!1,B=!1,Ra=0;G={ReactCurrentDispatcher:qa,
26 | ReactCurrentOwner:P,IsSomeRendererActing:{current:!1},assign:L};L(G,{Scheduler:{unstable_ImmediatePriority:1,unstable_UserBlockingPriority:2,unstable_NormalPriority:3,unstable_IdlePriority:5,unstable_LowPriority:4,unstable_runWithPriority:function(a,b){switch(a){case 1:case 2:case 3:case 4:case 5:break;default:a=3}var c=m;m=a;try{return b()}finally{m=c}},unstable_next:function(a){switch(m){case 1:case 2:case 3:var b=3;break;default:b=m}var c=m;m=b;try{return a()}finally{m=c}},unstable_scheduleCallback:function(a,
27 | b,c){var d=n();if("object"===typeof c&&null!==c){var g=c.delay;g="number"===typeof g&&0d){c=g;if(null===f)f=a.next=a.previous=a;else{b=null;var k=f;do{if(c t.list([t.symbol(t.DEF), id, init]);
5 |
6 | const FN = (next, id, params, body, opts = { isImplicitDo: true }) => {
7 | const bodies = next(body, opts);
8 |
9 | const larr = [t.symbol(t.FN)];
10 | if (id !== null) {
11 | larr.push(next(id));
12 | }
13 | larr.push(t.vector(params.map(next)));
14 |
15 | const l = t.list(larr);
16 |
17 | if (Array.isArray(bodies)) {
18 | l.children.push(...bodies);
19 | } else {
20 | l.children.push(bodies);
21 | }
22 | return l;
23 | };
24 |
25 | const DEFN = (next, id, params, body) => {
26 | const bodies = next(body, { isImplicitDo: true });
27 |
28 | const l = t.list([t.symbol(t.DEFN), next(id), t.vector(params.map(next))]);
29 |
30 | if (Array.isArray(bodies)) {
31 | l.children.push(...bodies);
32 | } else {
33 | l.children.push(bodies);
34 | }
35 | return l;
36 | };
37 |
38 | const FN_CALL = (next, fn, args = []) => t.list([fn, ...args.map(next)]);
39 |
40 | const METHOD_CALL = (next, method, object, args) =>
41 | t.list([next(method, { isCall: true }), object, ...args.map(next)]);
42 |
43 | const THIS_AS = (name, bodies) =>
44 | t.list([t.symbol("this-as"), t.symbol(name), ...bodies]);
45 |
46 | const PROP_GET = (next, prop, object) =>
47 | t.list([
48 | next(prop, { isDotGetter: true }),
49 | next(object, { checkGlobal: true })
50 | ]);
51 |
52 | const NESTED_PROPS_GET = (next, target, props) =>
53 | t.list([
54 | t.symbol(".."),
55 | next(target, { checkGlobal: true }),
56 | ...props.map(n => next(n, { isGetter: true }))
57 | ]);
58 |
59 | const DO = bodies => t.list([t.symbol("do"), ...bodies]);
60 |
61 | const IF = (next, test, consequent, alternate) => {
62 | const l = t.list([t.symbol(t.IF), next(test)]);
63 | if (consequent.body === undefined || consequent.body.length > 1) {
64 | l.children.push(next(consequent));
65 | } else {
66 | l.children.push(...next(consequent, { isImplicitDo: true }));
67 | }
68 | if (alternate.body === undefined || alternate.body.length > 1) {
69 | l.children.push(next(alternate));
70 | } else {
71 | l.children.push(...next(alternate, { isImplicitDo: true }));
72 | }
73 | return l;
74 | };
75 |
76 | const WHEN = (next, test, consequent) => {
77 | const ret = t.list([t.symbol(t.WHEN), next(test)]);
78 | const conseq = next(consequent, { isImplicitDo: true });
79 | if (Array.isArray(conseq)) {
80 | ret.children.push(...conseq);
81 | } else {
82 | ret.children.push(conseq);
83 | }
84 | return ret;
85 | };
86 |
87 | const COND = (next, ast) => {
88 | const entries = utils.getCondEntries(ast).map(n => {
89 | if (n === ":else") {
90 | return t.keyword("else");
91 | }
92 | if (n === "nil") {
93 | return t.symbol(t.NIL);
94 | }
95 | if (n.body && n.body.length === 1) {
96 | const r = next(n, { isImplicitDo: true });
97 | return r[0];
98 | }
99 | return next(n);
100 | });
101 | return t.list([t.symbol(t.COND), ...entries]);
102 | };
103 |
104 | const CASE = (next, discriminant, cases) =>
105 | t.list([t.symbol(t.CASE), next(discriminant), ...utils.flatMap(next, cases)]);
106 |
107 | const HICCUP_ELEMENT = (next, tag, attrs, children) =>
108 | t.vector([
109 | next(tag),
110 | t.HashMap(attrs ? attrs.map(next) : null),
111 | ...children.map(next)
112 | ]);
113 |
114 | module.exports = {
115 | DEF,
116 | FN,
117 | DEFN,
118 | FN_CALL,
119 | METHOD_CALL,
120 | THIS_AS,
121 | PROP_GET,
122 | NESTED_PROPS_GET,
123 | DO,
124 | IF,
125 | WHEN,
126 | COND,
127 | CASE,
128 | HICCUP_ELEMENT
129 | };
130 |
--------------------------------------------------------------------------------
/src/ast-transforms.js:
--------------------------------------------------------------------------------
1 | const bt = require("babel-types");
2 | const invariant = require("invariant");
3 | const t = require("./cljs-types");
4 | const utils = require("./utils");
5 |
6 | const jsTypes = require("./ast-types/javascript");
7 | const jsxTypes = require("./ast-types/jsx");
8 |
9 | const { globalObj } = utils;
10 |
11 | const {
12 | DEF,
13 | FN,
14 | DEFN,
15 | FN_CALL,
16 | METHOD_CALL,
17 | THIS_AS,
18 | PROP_GET,
19 | NESTED_PROPS_GET,
20 | DO,
21 | IF,
22 | WHEN,
23 | COND,
24 | CASE,
25 | HICCUP_ELEMENT
26 | } = require("./ast-builders");
27 |
28 | const File = (next, ast, opts) => next(ast.program);
29 | const Program = (next, ast, opts) => t.program(ast.body.map(next));
30 | const ExpressionStatement = (next, ast, opts) => next(ast.expression);
31 |
32 | const BinaryExpression = (next, ast, opts) => {
33 | const { operator, left, right } = ast;
34 |
35 | return t.list([
36 | t.symbol(utils.normalizeOperator(operator)),
37 | next(left),
38 | next(right)
39 | ]);
40 | };
41 |
42 | const DeleteStatement = (next, ast, opts) => {
43 | const { argument } = ast;
44 |
45 | invariant(
46 | bt.isMemberExpression(argument),
47 | `Can't transform "delete" for non MemberExpression node`
48 | );
49 |
50 | const prop = next(argument.property);
51 |
52 | invariant(
53 | prop.value !== undefined || prop.name !== undefined,
54 | `Couldn't infer "delete" key. Should be a symbol or a number`
55 | );
56 |
57 | const property =
58 | prop.type === "StringLiteral"
59 | ? prop
60 | : prop.type === "NumericLiteral"
61 | ? prop
62 | : t.StringLiteral(prop.name);
63 |
64 | return t.list([t.symbol("js-delete"), next(argument.object), property]);
65 | };
66 |
67 | const UnaryExpression = (next, ast, opts) => {
68 | const { operator, argument } = ast;
69 | if (operator === "delete") {
70 | return DeleteStatement(next, ast, opts);
71 | }
72 | return t.list([t.symbol(utils.normalizeOperator(operator)), next(argument)]);
73 | };
74 |
75 | const Identifier = (next, ast, opts) => {
76 | if (opts.isGetter) {
77 | return t.symbol(`-${ast.name}`);
78 | }
79 | if (opts.isDotGetter) {
80 | return t.symbol(`.-${ast.name}`);
81 | }
82 | if (opts.isCall) {
83 | return t.symbol(`.${ast.name}`);
84 | }
85 | if (opts.checkGlobal && globalObj.hasOwnProperty(ast.name)) {
86 | return t.symbol(`js/${ast.name}`);
87 | }
88 | return t.symbol(ast.name);
89 | };
90 |
91 | const NumericLiteral = (next, ast, opts) => t.NumericLiteral(ast.extra.raw);
92 |
93 | const VariableDeclaration = (next, ast, opts) => next(ast.declarations[0]);
94 |
95 | const VariableDeclarator = (next, ast, opts) => {
96 | const { id, init } = ast;
97 |
98 | if (init === null) {
99 | return DEF(next(id), t.symbol(t.NIL));
100 | }
101 |
102 | if (bt.isArrowFunctionExpression(init)) {
103 | const { body, params } = init;
104 | return DEFN(next, id, params, body);
105 | }
106 |
107 | return DEF(next(id), next(init, { isVar: true }));
108 | };
109 |
110 | const FunctionDeclaration = (next, ast, opts) => {
111 | const { id, params, body } = ast;
112 | return DEFN(next, id, params, body);
113 | };
114 |
115 | const FunctionExpression = (next, ast, opts) => {
116 | const { id, params, body } = ast;
117 |
118 | if (id === null || opts.isVar) {
119 | return FN(next, id, params, body);
120 | } else {
121 | return DEFN(next, id, params, body);
122 | }
123 | };
124 |
125 | const ArrowFunctionExpression = (next, ast, opts) => {
126 | const { params, body } = ast;
127 | return FN(next, null, params, body, { isImplicitDo: !ast.expression });
128 | };
129 |
130 | const ReturnStatement = (next, ast, opts) => next(ast.argument);
131 |
132 | const CallExpression = (next, ast, opts) => {
133 | const { callee } = ast;
134 |
135 | const memberChain = utils.maybeThreadMemberSyntax(next, ast).reverse();
136 | const isSpreadCall = ast.arguments.some(arg => bt.isSpreadElement(arg));
137 | const spreadArgs = isSpreadCall
138 | ? ArrayExpression(next, { elements: ast.arguments }, opts)
139 | : undefined;
140 |
141 | if (memberChain.length > 2) {
142 | return t.list([t.symbol("->"), ...memberChain]);
143 | }
144 |
145 | if (bt.isMemberExpression(callee)) {
146 | if (callee.object.name && globalObj.hasOwnProperty(callee.object.name)) {
147 | const fn = t.symbol(`js/${callee.object.name}`);
148 | if (isSpreadCall) {
149 | return t.list([
150 | t.symbol(".apply"),
151 | t.list([next(callee.property, { isDotGetter: true }), fn]),
152 | fn,
153 | spreadArgs
154 | ]);
155 | }
156 | return METHOD_CALL(next, callee.property, fn, ast.arguments);
157 | } else {
158 | const fn = next(callee, { isCallExpression: true });
159 | if (isSpreadCall) {
160 | const object = fn.children[1];
161 | return t.list([
162 | t.symbol(".apply"),
163 | t.list([next(callee.property, { isDotGetter: true }), object]),
164 | object,
165 | spreadArgs
166 | ]);
167 | }
168 | return t.list([...fn.children, ...ast.arguments.map(next)]);
169 | }
170 | }
171 | if (globalObj.hasOwnProperty(callee.name)) {
172 | const fn = t.symbol(`js/${callee.name}`);
173 | if (isSpreadCall) {
174 | return t.list([t.symbol(".apply"), fn, t.symbol(t.NIL), spreadArgs]);
175 | }
176 | return FN_CALL(next, fn, ast.arguments);
177 | }
178 | if (isSpreadCall) {
179 | return t.list([
180 | t.symbol(".apply"),
181 | next(callee),
182 | t.symbol(t.NIL),
183 | spreadArgs
184 | ]);
185 | }
186 |
187 | return FN_CALL(next, next(callee), ast.arguments);
188 | };
189 |
190 | const MemberExpression = (next, ast, opts) => {
191 | const { object, property } = ast;
192 |
193 | if (opts.isCallExpression) {
194 | if (bt.isThisExpression(object)) {
195 | return THIS_AS("this", [
196 | METHOD_CALL(next, property, t.symbol("this"), [])
197 | ]);
198 | }
199 | if (ast.computed) {
200 | return FN_CALL(
201 | next,
202 | FN_CALL(next, t.symbol("aget"), [object, property]),
203 | []
204 | );
205 | }
206 | return METHOD_CALL(next, property, next(object), []);
207 | }
208 |
209 | if (bt.isThisExpression(object)) {
210 | return THIS_AS("this", [METHOD_CALL(next, property, t.symbol("this"), [])]);
211 | }
212 |
213 | if (ast.computed) {
214 | return FN_CALL(next, t.symbol("aget"), [object, property]);
215 | }
216 |
217 | const [target, ...props] = utils.getDotProps(ast);
218 |
219 | if (props.length === 1) {
220 | return PROP_GET(next, props[0], target);
221 | }
222 |
223 | return NESTED_PROPS_GET(next, target, props);
224 | };
225 |
226 | const StringLiteral = (next, ast, opts) => t.StringLiteral(ast.value);
227 |
228 | const ArrayExpression = (next, ast, opts) => {
229 | const { elements } = ast;
230 |
231 | return elements.reduce((ret, el) => {
232 | if (bt.isSpreadElement(el)) {
233 | return t.list([t.symbol(".concat"), ret, next(el)]);
234 | } else {
235 | ret.children.push(next(el));
236 | return ret;
237 | }
238 | }, t.ArrayExpression([]));
239 | };
240 |
241 | const ObjectExpression = (next, ast, opts) => {
242 | const { properties } = ast;
243 | return properties.reduce((ret, el) => {
244 | if (bt.isSpreadProperty(el)) {
245 | return t.list([t.symbol("js/Object.assign"), ret, next(el)]);
246 | } else {
247 | const lastChild = ret.children[ret.children.length - 1];
248 | if (lastChild && lastChild.type !== "ObjectProperty") {
249 | ret.children.push(t.ObjectExpression([next(el)]));
250 | } else {
251 | ret.children.push(next(el));
252 | }
253 | return ret;
254 | }
255 | }, t.ObjectExpression([]));
256 | };
257 |
258 | const ObjectProperty = (next, ast, opts) =>
259 | t.ObjectProperty([next(ast.key), next(ast.value)]);
260 |
261 | const ThisExpression = (next, ast, opts) => THIS_AS("this", []);
262 |
263 | const AssignmentExpression = (next, ast, opts) => {
264 | if (bt.isMemberExpression(ast.left) && ast.left.computed) {
265 | return t.list([
266 | t.symbol("aset"),
267 | next(ast.left.object),
268 | next(ast.left.property),
269 | next(ast.right)
270 | ]);
271 | }
272 |
273 | const expr = t.list([t.symbol("set!"), next(ast.left), next(ast.right)]);
274 |
275 | if (
276 | bt.isMemberExpression(ast.left) &&
277 | utils.isNestedThisExpression(ast.left)
278 | ) {
279 | utils.alterNestedThisExpression("that", ast.left);
280 | return THIS_AS("that", [expr]);
281 | }
282 | return expr;
283 | };
284 |
285 | const NewExpression = (next, ast, opts) => t.list([
286 | t.symbol("new"),
287 | next(ast.callee, { isCallExpression: !bt.isMemberExpression(ast.callee), checkGlobal: true }),
288 | ...ast.arguments.map(next)
289 | ]);
290 |
291 | const ObjectMethod = (next, ast, opts) =>
292 | t.ObjectProperty([next(ast.key), FN(next, null, ast.params, ast.body)]);
293 |
294 | const EmptyStatement = (next, ast, opts) => t.EmptyStatement();
295 |
296 | const BlockStatement = (next, ast, opts) => {
297 | if (bt.isVariableDeclaration(ast.body[0])) {
298 | const [decls, rest] = utils.takeWhile(
299 | n => bt.isVariableDeclaration(n),
300 | ast.body
301 | );
302 | const entries = utils.flatMap(d => {
303 | const { id, init } = d.declarations[0];
304 | if (init === null) {
305 | return [next(id), t.symbol(t.NIL)];
306 | }
307 | return [next(id), next(init)];
308 | }, decls);
309 | const ret = t.list([t.symbol(t.LET), t.vector(entries)]);
310 | if (rest) {
311 | ret.children.push(...rest.map(next));
312 | }
313 | return ret;
314 | }
315 | if (opts.isImplicitDo) {
316 | return ast.body.map(next);
317 | }
318 |
319 | return DO(ast.body.map(next));
320 | };
321 |
322 | const IfStatement = (next, ast, opts) => {
323 | const { test, consequent, alternate } = ast;
324 |
325 | if (bt.isIfStatement(alternate)) {
326 | return COND(next, ast);
327 | }
328 | if (alternate !== null) {
329 | return IF(next, test, consequent, alternate);
330 | }
331 | return WHEN(next, test, consequent);
332 | };
333 |
334 | const SwitchStatement = (next, ast, opts) => {
335 | const { discriminant, cases } = ast;
336 | return CASE(next, discriminant, cases);
337 | };
338 |
339 | const SwitchCase = (next, ast, opts) => {
340 | const { test, consequent } = ast;
341 |
342 | const csqf = consequent.filter(n => !bt.isBreakStatement(n));
343 | const csq = csqf.map(next);
344 |
345 | if (bt.isVariableDeclaration(consequent[0])) {
346 | const [decls, rest] = utils.takeWhile(
347 | n => bt.isVariableDeclaration(n),
348 | csqf
349 | );
350 | const entries = utils.flatMap(d => {
351 | const { id, init } = d.declarations[0];
352 | return [next(id), next(init)];
353 | }, decls);
354 |
355 | return [
356 | next(test),
357 | t.list([t.symbol(t.LET), t.vector(entries), ...rest.map(next)])
358 | ];
359 | }
360 |
361 | if (test === null) {
362 | return csq;
363 | }
364 | return [next(test), csq.length > 1 ? DO(csq) : csq[0]];
365 | };
366 |
367 | const BreakStatement = (next, ast, opts) => t.BreakStatement();
368 |
369 | const ImportDeclaration = (next, ast, opts) => {
370 | const { source, specifiers } = ast;
371 |
372 | const sxs = specifiers.map(s => {
373 | if (bt.isImportSpecifier(s)) {
374 | return [next(s.imported, { isDotGetter: true }), next(s.local)];
375 | }
376 | if (bt.isImportDefaultSpecifier(s)) {
377 | return [t.symbol(".-default"), next(s.local)];
378 | }
379 | if (bt.isImportNamespaceSpecifier(s)) {
380 | return ["*", next(s.local)];
381 | }
382 | });
383 |
384 | const imported = sxs[0][0];
385 | const local = sxs[0][1];
386 |
387 | if (imported === "*") {
388 | return DEF(local, FN_CALL(next, t.symbol("js/require"), [source]));
389 | }
390 |
391 | return DEF(
392 | local,
393 | t.list([imported, FN_CALL(next, t.symbol("js/require"), [source])])
394 | );
395 | };
396 |
397 | const ExportDefaultDeclaration = (next, ast, opts) => {
398 | const { declaration } = ast;
399 | return t.list([
400 | t.symbol("set!"),
401 | t.list([t.symbol(".-default"), t.symbol("js/exports")]),
402 | next(declaration)
403 | ]);
404 | };
405 |
406 | const ExportNamedDeclaration = (next, ast, opts) => {
407 | const declaration = next(ast.declaration);
408 | const id = declaration.children[1];
409 | const exporter = t.list([
410 | t.symbol("set!"),
411 | t.list([t.symbol(`.-${id.name}`), t.symbol("js/exports")]),
412 | id
413 | ]);
414 | return DO([declaration, exporter]);
415 | };
416 |
417 | const ConditionalExpression = (next, ast, opts) => {
418 | const { test, consequent, alternate } = ast;
419 | return IF(next, test, consequent, alternate);
420 | };
421 |
422 | const LogicalExpression = (next, ast, opts) => {
423 | const { operator, left, right } = ast;
424 | return FN_CALL(next, t.symbol(utils.normalizeOperator(operator)), [
425 | left,
426 | right
427 | ]);
428 | };
429 |
430 | const NullLiteral = (next, ast, opts) => t.symbol(t.NIL);
431 |
432 | const BooleanLiteral = (next, ast, opts) => t.BooleanLiteral(ast.value);
433 |
434 | const RegExpLiteral = (next, ast, opts) => t.RegExpLiteral(ast);
435 |
436 | const TryStatement = (next, ast, opts) => {
437 | const { block, handler, finalizer } = ast;
438 | const body = next(block, { isImplicitDo: true });
439 | const expr = t.list([t.symbol(t.TRY)]);
440 |
441 | if (Array.isArray(body)) {
442 | expr.children.push(...body);
443 | } else {
444 | expr.children.push(body);
445 | }
446 |
447 | expr.children.push(t.list([t.symbol(t.CATCH), ...next(handler)]));
448 |
449 | if (finalizer) {
450 | const finalBody = next(finalizer, { isImplicitDo: true });
451 | if (Array.isArray(finalBody)) {
452 | expr.children.push(t.list([t.symbol(t.FINALLY), ...finalBody]));
453 | } else {
454 | expr.children.push(t.list([t.symbol(t.FINALLY), finalBody]));
455 | }
456 | }
457 | return expr;
458 | };
459 |
460 | const CatchClause = (next, ast, opts) => {
461 | const { param, body } = ast;
462 |
463 | const catchBody = next(body, { isImplicitDo: true });
464 |
465 | if (Array.isArray(catchBody)) {
466 | return [t.symbol("js/Object"), next(param), ...catchBody];
467 | } else {
468 | return [t.symbol("js/Object"), next(param), catchBody];
469 | }
470 | };
471 |
472 | const ThrowStatement = (next, ast, opts) =>
473 | t.list([t.symbol(t.THROW), next(ast.argument)]);
474 |
475 | const TemplateLiteral = (next, ast, opts) => {
476 | const { expressions, quasis } = ast;
477 | const args = quasis.reduce((ret, q, idx) => {
478 | const s = t.StringLiteral(q.value.raw);
479 | if (q === quasis[quasis.length - 1]) {
480 | return ret.concat(s);
481 | } else {
482 | return ret.concat([s, next(expressions[idx])]);
483 | }
484 | }, []);
485 | return t.list([t.symbol("str"), ...args]);
486 | };
487 |
488 | const DebuggerStatement = (next, ast, opts) =>
489 | FN_CALL(next, t.symbol("js-debugger"));
490 |
491 | const SpreadElement = (next, ast, opts) => next(ast.argument);
492 | const SpreadProperty = (next, ast, opts) => next(ast.argument);
493 |
494 | const ArrayPattern = (next, ast, opts) => {
495 | const { elements } = ast;
496 | return t.vector(elements.map(el => next(el)));
497 | };
498 |
499 | /* ========= JSX ========= */
500 | const JSXExpressionContainer = (next, ast, opts) => next(ast.expression);
501 |
502 | const JSXElement = (next, ast, opts) => {
503 | const attrs = ast.openingElement.attributes;
504 | return HICCUP_ELEMENT(next, ast.openingElement, attrs, ast.children);
505 | };
506 |
507 | const JSXAttribute = (next, ast, opts) =>
508 | t.MapEntry(next(ast.name), next(ast.value));
509 |
510 | const JSXOpeningElement = (next, ast, opts) =>
511 | next(ast.name, {
512 | isJSXElement: utils.isComponentElement(ast.name.name)
513 | });
514 |
515 | const JSXIdentifier = (next, ast, opts) =>
516 | opts.isJSXElement ? t.symbol(ast.name) : t.keyword(ast.name);
517 |
518 | const JSXText = (next, ast, opts) =>
519 | ast.value.trim() !== "" ? t.StringLiteral(ast.value) : t.EmptyStatement();
520 |
521 | const ForOfStatement = (next, ast, opts) => {
522 | const { left, right, body } = ast;
523 |
524 | const bindingLeft = (() => {
525 | if (bt.isVariableDeclaration(left)) {
526 | return next(left.declarations[0].id)
527 | } else {
528 | return next(left)
529 | }
530 | })();
531 |
532 | return t.list([
533 | t.symbol("doseq"),
534 | t.vector([bindingLeft, next(right)]),
535 | next(body)
536 | ]);
537 | }
538 |
539 | const transforms = {
540 | File,
541 | Program,
542 | ExpressionStatement,
543 | BinaryExpression,
544 | UnaryExpression,
545 | Identifier,
546 | NumericLiteral,
547 | VariableDeclaration,
548 | VariableDeclarator,
549 | FunctionDeclaration,
550 | FunctionExpression,
551 | ArrowFunctionExpression,
552 | ReturnStatement,
553 | CallExpression,
554 | StringLiteral,
555 | MemberExpression,
556 | ArrayExpression,
557 | ObjectExpression,
558 | ObjectProperty,
559 | ThisExpression,
560 | AssignmentExpression,
561 | NewExpression,
562 | ObjectMethod,
563 | EmptyStatement,
564 | BlockStatement,
565 | IfStatement,
566 | SwitchStatement,
567 | SwitchCase,
568 | BreakStatement,
569 | ImportDeclaration,
570 | ExportDefaultDeclaration,
571 | ExportNamedDeclaration,
572 | ConditionalExpression,
573 | LogicalExpression,
574 | NullLiteral,
575 | BooleanLiteral,
576 | RegExpLiteral,
577 | TryStatement,
578 | CatchClause,
579 | ThrowStatement,
580 | TemplateLiteral,
581 | DebuggerStatement,
582 | SpreadElement,
583 | SpreadProperty,
584 | ArrayPattern,
585 | ForOfStatement,
586 |
587 | JSXExpressionContainer,
588 | JSXElement,
589 | JSXAttribute,
590 | JSXOpeningElement,
591 | JSXIdentifier,
592 | JSXText
593 | };
594 |
595 | if (false) {
596 | const missingJSTypes = jsTypes.filter(
597 | t => Object.keys(transforms).includes(t) === false
598 | );
599 | const missingJSXTypes = jsxTypes.filter(
600 | t => Object.keys(transforms).includes(t) === false
601 | );
602 |
603 | console.warn("Missing JS types", missingJSTypes);
604 | console.warn("Missing JSX types", missingJSXTypes);
605 | }
606 |
607 | module.exports = transforms;
608 |
--------------------------------------------------------------------------------
/src/ast-types/javascript.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | "ArrayExpression",
3 | "AssignmentExpression",
4 | "BinaryExpression",
5 | "Directive",
6 | "DirectiveLiteral",
7 | "BlockStatement",
8 | "BreakStatement",
9 | "CallExpression",
10 | "CatchClause",
11 | "ConditionalExpression",
12 | "ContinueStatement",
13 | "DebuggerStatement",
14 | "DoWhileStatement",
15 | "EmptyStatement",
16 | "ExpressionStatement",
17 | "File",
18 | "ForInStatement",
19 | "ForStatement",
20 | "FunctionDeclaration",
21 | "FunctionExpression",
22 | "Identifier",
23 | "IfStatement",
24 | "LabeledStatement",
25 | "StringLiteral",
26 | "NumericLiteral",
27 | "NullLiteral",
28 | "BooleanLiteral",
29 | "RegExpLiteral",
30 | "LogicalExpression",
31 | "MemberExpression",
32 | "NewExpression",
33 | "Program",
34 | "ObjectExpression",
35 | "ObjectMethod",
36 | "ObjectProperty",
37 | "RestElement",
38 | "ReturnStatement",
39 | "SequenceExpression",
40 | "SwitchCase",
41 | "SwitchStatement",
42 | "ThisExpression",
43 | "ThrowStatement",
44 | "TryStatement",
45 | "UnaryExpression",
46 | "UpdateExpression",
47 | "VariableDeclaration",
48 | "VariableDeclarator",
49 | "WhileStatement",
50 | "WithStatement",
51 | "AssignmentPattern",
52 | "ArrayPattern",
53 | "ArrowFunctionExpression",
54 | "ClassBody",
55 | "ClassDeclaration",
56 | "ClassExpression",
57 | "ExportAllDeclaration",
58 | "ExportDefaultDeclaration",
59 | "ExportNamedDeclaration",
60 | "ExportSpecifier",
61 | "ForOfStatement",
62 | "ImportDeclaration",
63 | "ImportDefaultSpecifier",
64 | "ImportNamespaceSpecifier",
65 | "ImportSpecifier",
66 | "MetaProperty",
67 | "ClassMethod",
68 | "ObjectPattern",
69 | "SpreadElement",
70 | "Super",
71 | "TaggedTemplateExpression",
72 | "TemplateElement",
73 | "TemplateLiteral",
74 | "YieldExpression",
75 | "ClassImplements",
76 | "ClassProperty",
77 | "DeclareClass",
78 | "DeclareFunction",
79 | "DeclareInterface",
80 | "DeclareModule",
81 | "DeclareModuleExports",
82 | "DeclareVariable",
83 | "DeclareExportDeclaration",
84 | "InterfaceExtends",
85 | "InterfaceDeclaration",
86 | "Noop",
87 | "ParenthesizedExpression",
88 | "AwaitExpression",
89 | "ForAwaitStatement",
90 | "BindExpression",
91 | "Import",
92 | "Decorator",
93 | "DoExpression",
94 | "ExportDefaultSpecifier",
95 | "ExportNamespaceSpecifier",
96 | "RestProperty",
97 | "SpreadProperty",
98 | "NumberLiteral",
99 | "RegexLiteral"
100 | ];
101 |
--------------------------------------------------------------------------------
/src/ast-types/jsx.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | "JSXAttribute",
3 | "JSXClosingElement",
4 | "JSXElement",
5 | "JSXEmptyExpression",
6 | "JSXExpressionContainer",
7 | "JSXSpreadChild",
8 | "JSXIdentifier",
9 | "JSXMemberExpression",
10 | "JSXNamespacedName",
11 | "JSXOpeningElement",
12 | "JSXSpreadAttribute",
13 | "JSXText"
14 | ];
15 |
--------------------------------------------------------------------------------
/src/cljs-gen.js:
--------------------------------------------------------------------------------
1 | const codegen = require("./code-generators");
2 |
3 | function generate(node) {
4 | if (codegen.hasOwnProperty(node.type)) {
5 | return codegen[node.type](generate, node);
6 | }
7 | console.info(node);
8 | throw new Error(`${node.type} is not implemented`);
9 | }
10 |
11 | module.exports = generate;
12 |
--------------------------------------------------------------------------------
/src/cljs-types.js:
--------------------------------------------------------------------------------
1 | const program = children => ({
2 | type: "program",
3 | children
4 | });
5 |
6 | const comment = value => ({
7 | type: "comment",
8 | value
9 | });
10 |
11 | const symbol = name => ({
12 | type: "symbol",
13 | name
14 | });
15 |
16 | const list = children => ({
17 | type: "list",
18 | children
19 | });
20 |
21 | const vector = children => ({
22 | type: "vector",
23 | children
24 | });
25 |
26 | const keyword = value => ({
27 | type: "keyword",
28 | value
29 | });
30 |
31 | const tagged = (tag, expr) => ({
32 | type: "tagged",
33 | tag,
34 | expr
35 | });
36 |
37 | // ==========================
38 |
39 | const NumericLiteral = value => ({
40 | type: "NumericLiteral",
41 | value
42 | });
43 |
44 | const StringLiteral = value => ({
45 | type: "StringLiteral",
46 | value
47 | });
48 |
49 | const BooleanLiteral = value => ({
50 | type: "BooleanLiteral",
51 | value
52 | });
53 |
54 | const ArrayExpression = children => ({
55 | type: "ArrayExpression",
56 | children
57 | });
58 |
59 | const ObjectExpression = children => ({
60 | type: "ObjectExpression",
61 | children
62 | });
63 |
64 | const ObjectProperty = children => ({
65 | type: "ObjectProperty",
66 | children
67 | });
68 |
69 | const EmptyStatement = () => ({
70 | type: "EmptyStatement"
71 | });
72 |
73 | const BreakStatement = () => ({
74 | type: "BreakStatement"
75 | });
76 |
77 | const RegExpLiteral = ({ pattern, flags }) => ({
78 | type: "RegExpLiteral",
79 | pattern,
80 | flags
81 | });
82 |
83 | // ==========================
84 |
85 | const ForOfStatement = () => ({
86 | type: "ForOfStatement"
87 | });
88 |
89 | // ==========================
90 |
91 | const HashMap = children => ({
92 | type: "HashMap",
93 | children
94 | });
95 |
96 | const MapEntry = (key, value) => ({
97 | type: "MapEntry",
98 | children: [key, value]
99 | });
100 |
101 | // ============================
102 |
103 | const DEF = "def";
104 | const DEFN = "defn";
105 | const FN = "fn";
106 | const LET = "let";
107 | const IF = "if";
108 | const WHEN = "when";
109 | const COND = "cond";
110 | const CASE = "case";
111 | const NIL = "nil";
112 | const TRY = "try";
113 | const CATCH = "catch";
114 | const FINALLY = "finally";
115 | const THROW = "throw";
116 | const DO = "throw";
117 |
118 | module.exports = {
119 | NumericLiteral,
120 | StringLiteral,
121 | BooleanLiteral,
122 | ArrayExpression,
123 | ObjectExpression,
124 | ObjectProperty,
125 | EmptyStatement,
126 | BreakStatement,
127 | RegExpLiteral,
128 |
129 | ForOfStatement,
130 |
131 | program,
132 | comment,
133 | symbol,
134 | list,
135 | vector,
136 | tagged,
137 | keyword,
138 |
139 | HashMap,
140 | MapEntry,
141 |
142 | DEF,
143 | DEFN,
144 | FN,
145 | LET,
146 | IF,
147 | WHEN,
148 | COND,
149 | CASE,
150 | NIL,
151 | TRY,
152 | CATCH,
153 | FINALLY,
154 | THROW,
155 | DO
156 | };
157 |
--------------------------------------------------------------------------------
/src/code-generators.js:
--------------------------------------------------------------------------------
1 | const REGEX_FLAGS = new Set(["i", "m", "u"]);
2 |
3 | const regexFlags = s => {
4 | const flags = Array.from(s)
5 | .filter(f => REGEX_FLAGS.has(f))
6 | .join("");
7 | return flags === "" ? "" : `(?${flags})`;
8 | };
9 |
10 | // ===================
11 |
12 | const program = (next, node) => node.children.map(next).join("");
13 |
14 | const symbol = (next, node) => node.name;
15 |
16 | const list = (next, node) => `(${node.children.map(next).join(" ")})\n\n`;
17 |
18 | const vector = (next, node) => `[${node.children.map(next).join(" ")}]`;
19 |
20 | const keyword = (next, node) => `:${node.value}`;
21 |
22 | const tagged = (next, node) => `${node.tag} ${generate(node.expr)}`;
23 |
24 | // =======================================
25 |
26 | const HashMap = (next, node) => `{${node.children.map(next).join(" ")}}`;
27 |
28 | const MapEntry = (next, node) => {
29 | const [key, value] = node.children;
30 | return `${next(key)} ${next(value)}`;
31 | };
32 |
33 | // ==========================================
34 |
35 | const NumericLiteral = (next, node) => node.value;
36 |
37 | const StringLiteral = (next, node) => JSON.stringify(node.value);
38 |
39 | const BooleanLiteral = (next, node) => node.value;
40 |
41 | const EmptyStatement = (next, node) => undefined;
42 |
43 | const BreakStatement = (next, node) => undefined;
44 |
45 | const ObjectProperty = (next, node) => {
46 | const [key, value] = node.children;
47 |
48 | const nextKey =
49 | key.type === "StringLiteral"
50 | ? JSON.parse(next(key))
51 | : next(key);
52 |
53 | return `:${nextKey} ${next(value)}`;
54 | };
55 |
56 | const ObjectExpression = (next, node) =>
57 | `#js {${node.children.map(next).join(" ")}}`;
58 |
59 | const ArrayExpression = (next, node) =>
60 | `#js [${node.children.map(next).join(" ")}]`;
61 |
62 | const RegExpLiteral = (next, node) =>
63 | `#"${regexFlags(node.flags)}${node.pattern}"`;
64 |
65 | module.exports = {
66 | program,
67 | symbol,
68 | list,
69 | vector,
70 | keyword,
71 | tagged,
72 |
73 | HashMap,
74 | MapEntry,
75 |
76 | NumericLiteral,
77 | StringLiteral,
78 | BooleanLiteral,
79 | EmptyStatement,
80 | BreakStatement,
81 | ObjectProperty,
82 | ObjectExpression,
83 | ArrayExpression,
84 | RegExpLiteral
85 | };
86 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const { parse } = require("babylon");
2 | const zprint = require("zprint-clj");
3 |
4 | const generate = require("./cljs-gen");
5 | const transformAST = require("./js2cljs");
6 | const addSyntaxSugar = require("./syntax-builder");
7 |
8 | const toLispAST = code =>
9 | transformAST(
10 | parse(code, { sourceType: "module", plugins: ["jsx", "objectRestSpread"] })
11 | );
12 |
13 | const transform = code =>
14 | zprint(generate(toLispAST(code)), "sample", {
15 | isHangEnabled: false
16 | });
17 |
18 | module.exports = {
19 | toLispAST,
20 | transform,
21 | addSyntaxSugar
22 | };
23 |
--------------------------------------------------------------------------------
/src/js2cljs.js:
--------------------------------------------------------------------------------
1 | const astt = require("./ast-transforms");
2 |
3 | function tr(ast, opts = {}) {
4 | if (astt.hasOwnProperty(ast.type)) {
5 | return astt[ast.type](tr, ast, opts);
6 | }
7 | console.info(ast);
8 | throw new Error(`${ast.type} is not implemented`);
9 | }
10 |
11 | module.exports = tr;
12 |
--------------------------------------------------------------------------------
/src/syntax-builder.js:
--------------------------------------------------------------------------------
1 | function walk(node) {
2 | if (Array.isArray(node.children)) {
3 | node.children.forEach(ch => walk(ch));
4 | }
5 | }
6 |
7 | module.exports = walk;
8 |
--------------------------------------------------------------------------------
/src/ui.js:
--------------------------------------------------------------------------------
1 | const js2cljs = require("./index");
2 | const pako = require("pako");
3 |
4 | window.html = j2c.core.compileHiccup;
5 |
6 | const overlay = document.querySelector(".popup-overlay");
7 | const popup = document.querySelector(".popup");
8 |
9 | const openPopup = () => {
10 | popup.style.display = "block";
11 | overlay.style.display = "block";
12 | };
13 |
14 | const closePopup = () => {
15 | popup.remove();
16 | overlay.remove();
17 | };
18 |
19 | if (localStorage.getItem("seen-popup?") === "1") {
20 | closePopup();
21 | } else {
22 | openPopup();
23 | localStorage.setItem("seen-popup?", "1");
24 | overlay.addEventListener("click", closePopup);
25 | window["close-btn"].addEventListener("click", closePopup);
26 | if (popup.clientWidth >= document.body.clientWidth) {
27 | popup.style.width = `${document.body.clientWidth - 96}px`;
28 | }
29 | }
30 |
31 | function router({ urls, fn }) {
32 | const handle = v => {
33 | const r = v.replace("#", "");
34 | if (urls.includes(r)) {
35 | return fn(r);
36 | }
37 | };
38 | window.addEventListener("hashchange", e => handle(window.location.hash));
39 | return handle;
40 | }
41 |
42 | // =================
43 |
44 | const examples = {
45 | primitives: "01.primitives.js",
46 | variables: "02.variables.js",
47 | functions: "03.functions.js",
48 | conditionals: "04.conditionals.js",
49 | operators: "05.operators.js",
50 | array: "06.array.js",
51 | object: "07.object.js",
52 | "try..catch": "08.try-catch.js",
53 | threading: "09.threading.js",
54 | basic: "basic.js",
55 | react: "react.js"
56 | };
57 |
58 | const loadExample = id => fetch(`examples/${examples[id]}`).then(r => r.text());
59 |
60 | const jsEditor = new CodeMirror(window.jsCode, {
61 | lineNumbers: true,
62 | mode: "javascript"
63 | });
64 |
65 | const cljsEditor = new CodeMirror(window.cljsCode, {
66 | lineNumbers: true,
67 | readOnly: true,
68 | mode: "clojure"
69 | });
70 |
71 | const stdoutEditor = new CodeMirror(window.stdout, { readOnly: true });
72 | const cljsCompiledCodeEditor = new CodeMirror(window.cljsCompiledCode, {
73 | readOnly: true
74 | });
75 |
76 | const debounce = (t, fn) => {
77 | let id;
78 | return (...args) => {
79 | if (id !== undefined) {
80 | clearTimeout(id);
81 | }
82 | id = setTimeout(() => {
83 | fn(...args);
84 | }, t);
85 | };
86 | };
87 |
88 | console.log = (...args) => {
89 | const v = stdoutEditor.getValue();
90 | stdoutEditor.setValue(v + "\n" + args.join(" "));
91 | };
92 | console.error = (...args) => {
93 | const v = stdoutEditor.getValue();
94 | stdoutEditor.setValue(v + "\n" + args.join(" "));
95 | };
96 |
97 | const handleJSChange = () => {
98 | stdoutEditor.setValue("");
99 |
100 | try {
101 | const code = js2cljs.transform(jsEditor.getValue());
102 | cljsEditor.setValue(code);
103 | j2c.core.evalExpr(code, (err, code) => {
104 | if (err) {
105 | console.error(err);
106 | } else {
107 | cljsCompiledCodeEditor.setValue(code);
108 | updateShareLink();
109 | window.cljs.user = {};
110 | try {
111 | eval(code);
112 | } catch (err) {
113 | console.log(err);
114 | }
115 | }
116 | });
117 | } catch (err) {
118 | console.error(err);
119 | console.error(`Couldn't compile JavaScript code into ClojureScript :(`);
120 | }
121 | };
122 |
123 | const handleJSChangeD = debounce(1000, handleJSChange);
124 |
125 | jsEditor.on("change", handleJSChangeD);
126 |
127 | const loadExampleAndDisplay = id =>
128 | loadExample(id)
129 | .then(code => {
130 | jsEditor.setValue(code);
131 | })
132 | .catch(() => {
133 | alert(`Couldn't load example "${val}"`);
134 | });
135 |
136 | const r = router({
137 | urls: Object.keys(examples).concat([""]),
138 | fn: loadExampleAndDisplay
139 | });
140 |
141 | const h = (tag, attrs, ...children) => {
142 | const el = document.createElement(tag);
143 | Object.assign(el, attrs);
144 | el.append(...children);
145 | return el;
146 | };
147 |
148 | const options = Object.keys(examples).map(id => h("option", { value: id }, id));
149 | const select = h("select", {}, ...options);
150 |
151 | document.querySelector(".selector").append(select);
152 |
153 | const shareLink = (window.location.hash.match(/#share-link=([0-9,]+)/) || [])[1];
154 | if (shareLink) {
155 | jsEditor.setValue(decodeLinkedExample(shareLink));
156 | } else {
157 | r(window.location.hash || "basic");
158 | select.value = window.location.hash.replace("#", "") || "basic";
159 | }
160 |
161 | select.addEventListener("change", e => {
162 | const val = e.target.value;
163 | window.location.hash = val;
164 | });
165 |
166 | const tabToView = {
167 | "btn-cljs": document.querySelector("#view-cljs"),
168 | "btn-ccljs": document.querySelector("#view-ccljs"),
169 | "btn-console": document.querySelector("#view-console"),
170 | "btn-dom": document.querySelector("#view-dom")
171 | };
172 |
173 | const tabs = document.querySelectorAll(".tabs .btn");
174 | Array.from(tabs).forEach(btn => {
175 | btn.addEventListener("click", () => {
176 | document.querySelector(".tabs .btn.active").classList.remove("active");
177 | btn.classList.add("active");
178 | tabToView[btn.id].style.display = "flex";
179 |
180 | if (btn.id === "btn-ccljs") {
181 | cljsCompiledCodeEditor.setValue(cljsCompiledCodeEditor.getValue());
182 | }
183 | if (btn.id === "btn-console") {
184 | stdoutEditor.setValue(stdoutEditor.getValue());
185 | }
186 |
187 | Object.entries(tabToView).forEach(([id, view]) => {
188 | if (id !== btn.id) {
189 | view.style.display = "none";
190 | }
191 | });
192 | });
193 | });
194 |
195 | function decodeLinkedExample(s) {
196 | return pako.inflate(new Uint8Array(s.split(",")), { to: 'string' });
197 | }
198 |
199 | function updateShareLink() {
200 | const compressed = pako.deflate(jsEditor.getValue());
201 | window.location.hash = `#share-link=${compressed.join()}`;
202 | }
203 |
204 | function shareCurrentExample() {
205 | const compressed = pako.deflate(jsEditor.getValue());
206 | const hash = `#share-link=${compressed.join()}`;
207 | const shareLink = `https://roman01la.github.io/javascript-to-clojurescript/${hash}`;
208 | navigator.clipboard.writeText(shareLink)
209 | .then(() => alert("Link copied!"))
210 | .catch(() => alert("Couldn't copy the link, please copy it from here\n" + shareLink));
211 | window.location.hash = hash;
212 | }
213 |
214 | document.getElementById("btn-share")
215 | .addEventListener("click", shareCurrentExample)
216 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | const bt = require("babel-types");
2 | const t = require("./cljs-types");
3 |
4 | const globalObj = (typeof window !== "undefined" ? window : global);
5 |
6 | const isComponentElement = n => /^[A-Z]/.test(n);
7 |
8 | const flatMap = (fn, coll) =>
9 | coll.map(fn).reduce((ret, e) => ret.concat(e), []);
10 |
11 | function takeWhile(pred, [x, ...xs], ret = []) {
12 | if (pred(x)) {
13 | return takeWhile(pred, xs, ret.concat(x));
14 | }
15 | if (x === undefined) {
16 | return [ret];
17 | } else {
18 | return [ret, [x, ...xs]];
19 | }
20 | }
21 |
22 | function getCondEntries(node, ret = []) {
23 | const { test, consequent, alternate } = node;
24 |
25 | if (bt.isIfStatement(alternate)) {
26 | return getCondEntries(alternate, ret.concat([test, consequent]));
27 | }
28 | return ret.concat([
29 | test,
30 | consequent,
31 | ":else",
32 | alternate === null ? t.NIL : alternate
33 | ]);
34 | }
35 |
36 | function getDotProps(node, ret = []) {
37 | if (bt.isMemberExpression(node.object)) {
38 | return getDotProps(node.object, [node.property, ...ret]);
39 | }
40 | return [node.object, node.property, ...ret];
41 | }
42 |
43 | function normalizeOperator(op) {
44 | if (op === "==") {
45 | return "=";
46 | }
47 | if (op === "===") {
48 | return "=";
49 | }
50 | if (op === "!=") {
51 | return "not=";
52 | }
53 | if (op === "!==") {
54 | return "not=";
55 | }
56 | if (op === "||") {
57 | return "or";
58 | }
59 | if (op === "&&") {
60 | return "and";
61 | }
62 | if (op === "!") {
63 | return "not";
64 | }
65 | return op;
66 | }
67 |
68 | function maybeThreadMemberSyntax(next, node) {
69 | if (bt.isCallExpression(node)) {
70 | if (bt.isCallExpression(node.callee.object)) {
71 | return [
72 | t.list([
73 | next(node.callee.property, { isCall: true }),
74 | ...node.arguments.map(next)
75 | ]),
76 | ...maybeThreadMemberSyntax(next, node.callee.object)
77 | ];
78 | }
79 |
80 | let f;
81 |
82 | if (
83 | bt.isIdentifier(node.callee) &&
84 | globalObj.hasOwnProperty(node.callee.name)
85 | ) {
86 | f = t.symbol(`js/${node.callee.name}`);
87 | } else {
88 | f = next(node.callee);
89 | }
90 |
91 | return [t.list([f, ...node.arguments.map(next)])];
92 | }
93 | }
94 |
95 | function isNestedThisExpression(node) {
96 | if (bt.isThisExpression(node.object)) {
97 | return node;
98 | }
99 | if (node.object.hasOwnProperty("object")) {
100 | return isNestedThisExpression(node.object);
101 | }
102 | return false;
103 | }
104 |
105 | function alterNestedThisExpression(name, node) {
106 | const thisNode = isNestedThisExpression(node);
107 | if (thisNode) {
108 | thisNode.object = bt.identifier(name);
109 | }
110 | }
111 |
112 | module.exports = {
113 | isComponentElement,
114 | flatMap,
115 | takeWhile,
116 | getCondEntries,
117 | getDotProps,
118 | normalizeOperator,
119 | maybeThreadMemberSyntax,
120 | isNestedThisExpression,
121 | alterNestedThisExpression,
122 | globalObj
123 | };
124 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | const { transform } = require("./src/index");
2 | const astTransforms = require("./src/ast-transforms");
3 |
4 | const errors = [];
5 | let tests = 0;
6 | const astTypes = new Set();
7 |
8 | function test(astType,jsInput, cljsExpected) {
9 | const cljsOut = transform(jsInput);
10 | if (cljsOut !== (cljsExpected.endsWith(")") ? cljsExpected + "\n" : cljsExpected)) {
11 | errors.push({ expected: cljsExpected, actual: cljsOut })
12 | }
13 | tests++;
14 | astTypes.add(astType)
15 | }
16 |
17 | test("BinaryExpression", "2 / 1", "(/ 2 1)");
18 | test("BinaryExpression", "2 / 1 / 9 / 6", "(/ (/ (/ 2 1) 9) 6)"); // TODO: Flatten
19 |
20 | test("DeleteStatement", "delete obj.x", `(js-delete obj "x")`);
21 |
22 | test("UnaryExpression", "+x", `(+ x)`);
23 |
24 | test("Identifier", "x", `x`);
25 | test("Identifier", "x.y", `(.-y x)`);
26 | test("Identifier", "x.y.z", `(.. x -y -z)`);
27 | test("Identifier", "x()", `(x)`);
28 | test("Identifier", "setTimeout", `js/setTimeout`); // FIXME
29 | test("Identifier", "setTimeout()", `(js/setTimeout)`);
30 |
31 | test("NumericLiteral", "123", `123`);
32 | test("NumericLiteral", "1.23", `1.23`);
33 | test("NumericLiteral", ".23", `.23`);
34 | test("NumericLiteral", "-345", `(- 345)`);
35 |
36 | test("VariableDeclaration", "var x = 1", `(def x 1)`);
37 | test("VariableDeclaration", "let x = 1", `(def x 1)`);
38 | test("VariableDeclaration", "let x", `(def x nil)`); // Not sure if correct
39 | test("VariableDeclaration", "const x = 1", `(def x 1)`);
40 | test("VariableDeclaration", "const x = x => x", `(defn x [x] x)`);
41 |
42 | test("VariableDeclarator", "const x = x => x", `(defn x [x] x)`);
43 |
44 | test("FunctionDeclaration", "function f(a, b) { return a }", `(defn f [a b] a)`);
45 |
46 | test("FunctionExpression", "(function(a, b) { return a })", `(fn [a b] a)`);
47 |
48 | test("ArrowFunctionExpression", "((a, b) => a)", `(fn [a b] a)`);
49 |
50 | test("ReturnStatement", "function f(a, b) { return a }", `(defn f [a b] a)`);
51 |
52 | test("CallExpression", "x.y.z()", `(.z (.-y x))`);
53 | test("CallExpression", "global.setTimeout()", `(.setTimeout js/global)`);
54 | test("CallExpression", "[1, ...[2, 3], 4]", `(.concat #js [1] #js [2 3] 4)`);
55 | test("CallExpression", "[1, ...x, ...y.z]", `(.concat (.concat #js [1] x) (.-z y))`);
56 |
57 | test("MemberExpression", "x.y", `(.-y x)`);
58 | test("MemberExpression", "x.y()", `(.y x)`);
59 | test("MemberExpression", `x["y"]`, `(aget x "y")`);
60 | test("MemberExpression", `x["y"]()`, `((aget x "y"))`);
61 | test("MemberExpression", `this.x`, `(this-as this (.-x this))`); // FIXME
62 | test("MemberExpression", `this.x()`, `(this-as this (.x this))`);
63 |
64 | test("StringLiteral", `"xasd"`, `"xasd"`); // FIXME
65 |
66 | const missingTests = Object.keys(astTransforms).filter(fname => !astTypes.has(fname));
67 | console.log("Missing tests for following AST types");
68 | console.log(missingTests);
69 |
70 | if (errors.length > 0) {
71 | errors.forEach(({actual, expected}) => {
72 | console.log("Failed test");
73 | console.log(actual)
74 | console.log(expected)
75 | })
76 | process.exit(1)
77 | } else {
78 | console.log(`${tests} tests passed`);
79 | }
--------------------------------------------------------------------------------