├── .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/assets/img/guidelines/download-assets-sm-1.svg)](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 |
handleSubmit(e, state.value)}> 17 | { 21 | setState({ ...state, value: e.target.value }); 22 | }} 23 | /> 24 | 25 |
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 |
17 |
18 | JavaScript to ClojureScript translator 19 | 20 | Examples: 21 | 22 | 23 |
24 |
25 | 26 | 27 | 28 | 29 |
30 | GitHub 33 |
34 | 35 |
36 |
37 |
38 |
JavaScript
39 |
40 |
41 |
42 |
ClojureScript
43 |
44 |
45 | 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 | } --------------------------------------------------------------------------------