clementine 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. data/.gitignore +7 -0
  2. data/Gemfile +4 -0
  3. data/README.md +52 -0
  4. data/Rakefile +1 -0
  5. data/clementine.gemspec +23 -0
  6. data/lib/clementine.rb +27 -0
  7. data/lib/clementine/clementine_rails.rb +8 -0
  8. data/lib/clementine/clojurescript_engine.rb +49 -0
  9. data/lib/clementine/clojurescript_engine_mri.rb +65 -0
  10. data/lib/clementine/clojurescript_template.rb +21 -0
  11. data/lib/clementine/options.rb +9 -0
  12. data/lib/clementine/version.rb +3 -0
  13. data/test/clojurescript_engine_test.rb +46 -0
  14. data/test/options_test.rb +22 -0
  15. data/vendor/assets/bin/cljsc.clj +21 -0
  16. data/vendor/assets/lib/clojure.jar +0 -0
  17. data/vendor/assets/lib/compiler.jar +0 -0
  18. data/vendor/assets/lib/goog.jar +0 -0
  19. data/vendor/assets/lib/js.jar +0 -0
  20. data/vendor/assets/src/clj/cljs/closure.clj +823 -0
  21. data/vendor/assets/src/clj/cljs/compiler.clj +1341 -0
  22. data/vendor/assets/src/clj/cljs/core.clj +702 -0
  23. data/vendor/assets/src/clj/cljs/repl.clj +162 -0
  24. data/vendor/assets/src/clj/cljs/repl/browser.clj +341 -0
  25. data/vendor/assets/src/clj/cljs/repl/rhino.clj +170 -0
  26. data/vendor/assets/src/cljs/cljs/core.cljs +3330 -0
  27. data/vendor/assets/src/cljs/cljs/nodejs.cljs +11 -0
  28. data/vendor/assets/src/cljs/cljs/nodejs_externs.js +2 -0
  29. data/vendor/assets/src/cljs/cljs/nodejscli.cljs +9 -0
  30. data/vendor/assets/src/cljs/cljs/reader.cljs +360 -0
  31. data/vendor/assets/src/cljs/clojure/browser/dom.cljs +106 -0
  32. data/vendor/assets/src/cljs/clojure/browser/event.cljs +100 -0
  33. data/vendor/assets/src/cljs/clojure/browser/net.cljs +182 -0
  34. data/vendor/assets/src/cljs/clojure/browser/repl.cljs +109 -0
  35. data/vendor/assets/src/cljs/clojure/set.cljs +162 -0
  36. data/vendor/assets/src/cljs/clojure/string.cljs +160 -0
  37. data/vendor/assets/src/cljs/clojure/walk.cljs +94 -0
  38. data/vendor/assets/src/cljs/clojure/zip.cljs +291 -0
  39. metadata +103 -0
@@ -0,0 +1,1341 @@
1
+ ; Copyright (c) Rich Hickey. All rights reserved.
2
+ ; The use and distribution terms for this software are covered by the
3
+ ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4
+ ; which can be found in the file epl-v10.html at the root of this distribution.
5
+ ; By using this software in any fashion, you are agreeing to be bound by
6
+ ; the terms of this license.
7
+ ; You must not remove this notice, or any other, from this software.
8
+
9
+ (set! *warn-on-reflection* true)
10
+
11
+ (ns cljs.compiler
12
+ (:refer-clojure :exclude [munge macroexpand-1])
13
+ (:require [clojure.java.io :as io]
14
+ [clojure.string :as string]))
15
+
16
+ (declare resolve-var)
17
+ (require 'cljs.core)
18
+
19
+ (def js-reserved
20
+ #{"abstract" "boolean" "break" "byte" "case"
21
+ "catch" "char" "class" "const" "continue"
22
+ "debugger" "default" "delete" "do" "double"
23
+ "else" "enum" "export" "extends" "final"
24
+ "finally" "float" "for" "function" "goto" "if"
25
+ "implements" "import" "in" "instanceof" "int"
26
+ "interface" "let" "long" "native" "new"
27
+ "package" "private" "protected" "public"
28
+ "return" "short" "static" "super" "switch"
29
+ "synchronized" "this" "throw" "throws"
30
+ "transient" "try" "typeof" "var" "void"
31
+ "volatile" "while" "with" "yield" "methods"})
32
+
33
+ (defonce namespaces (atom '{cljs.core {:name cljs.core}
34
+ cljs.user {:name cljs.user}}))
35
+
36
+ (def ^:dynamic *cljs-ns* 'cljs.user)
37
+ (def ^:dynamic *cljs-warn-on-undeclared* false)
38
+
39
+ (defn munge [s]
40
+ (let [ss (str s)
41
+ ms (if (.contains ss "]")
42
+ (let [idx (inc (.lastIndexOf ss "]"))]
43
+ (str (subs ss 0 idx)
44
+ (clojure.lang.Compiler/munge (subs ss idx))))
45
+ (clojure.lang.Compiler/munge ss))
46
+ ms (if (js-reserved ms) (str ms "$") ms)]
47
+ (if (symbol? s)
48
+ (symbol ms)
49
+ ms)))
50
+
51
+ (defn confirm-var-exists [env prefix suffix]
52
+ (when *cljs-warn-on-undeclared*
53
+ (let [crnt-ns (-> env :ns :name)]
54
+ (when (= prefix crnt-ns)
55
+ (when-not (-> @namespaces crnt-ns :defs suffix)
56
+ (binding [*out* *err*]
57
+ (println
58
+ (str "WARNING: Use of undeclared Var " prefix "/" suffix
59
+ (when (:line env)
60
+ (str " at line " (:line env)))))))))))
61
+
62
+ (defn resolve-ns-alias [env name]
63
+ (let [sym (symbol name)]
64
+ (get (:requires (:ns env)) sym sym)))
65
+
66
+ (defn core-name?
67
+ "Is sym visible from core in the current compilation namespace?"
68
+ [env sym]
69
+ (and (get (:defs (@namespaces 'cljs.core)) sym)
70
+ (not (contains? (-> env :ns :excludes) sym))))
71
+
72
+ (defn js-var [sym]
73
+ (let [parts (string/split (name sym) #"\.")
74
+ first (first parts)
75
+ step (fn [part] (str "['" part "']"))]
76
+ (apply str first (map step (rest parts)))))
77
+
78
+ (defn resolve-existing-var [env sym]
79
+ (if (= (namespace sym) "js")
80
+ {:name (js-var sym)}
81
+ (let [s (str sym)
82
+ lb (-> env :locals sym)
83
+ nm
84
+ (cond
85
+ lb (:name lb)
86
+
87
+ (namespace sym)
88
+ (let [ns (namespace sym)
89
+ ns (if (= "clojure.core" ns) "cljs.core" ns)
90
+ full-ns (resolve-ns-alias env ns)]
91
+ (confirm-var-exists env full-ns (symbol (name sym)))
92
+ (symbol (str full-ns "." (munge (name sym)))))
93
+
94
+ (.contains s ".")
95
+ (munge (let [idx (.indexOf s ".")
96
+ prefix (symbol (subs s 0 idx))
97
+ suffix (subs s idx)
98
+ lb (-> env :locals prefix)]
99
+ (if lb
100
+ (symbol (str (:name lb) suffix))
101
+ (do
102
+ (confirm-var-exists env prefix (symbol suffix))
103
+ sym))))
104
+
105
+ (get-in @namespaces [(-> env :ns :name) :uses sym])
106
+ (symbol (str (get-in @namespaces [(-> env :ns :name) :uses sym]) "." (munge (name sym))))
107
+
108
+ :else
109
+ (let [full-ns (if (core-name? env sym)
110
+ 'cljs.core
111
+ (-> env :ns :name))]
112
+ (confirm-var-exists env full-ns sym)
113
+ (munge (symbol (str full-ns "." (munge (name sym)))))))]
114
+ {:name nm})))
115
+
116
+ (defn resolve-var [env sym]
117
+ (if (= (namespace sym) "js")
118
+ {:name (js-var sym)}
119
+ (let [s (str sym)
120
+ lb (-> env :locals sym)
121
+ nm
122
+ (cond
123
+ lb (:name lb)
124
+
125
+ (namespace sym)
126
+ (let [ns (namespace sym)
127
+ ns (if (= "clojure.core" ns) "cljs.core" ns)]
128
+ (symbol (str (resolve-ns-alias env ns) "." (munge (name sym)))))
129
+
130
+ (.contains s ".")
131
+ (munge (let [idx (.indexOf s ".")
132
+ prefix (symbol (subs s 0 idx))
133
+ suffix (subs s idx)
134
+ lb (-> env :locals prefix)]
135
+ (if lb
136
+ (symbol (str (:name lb) suffix))
137
+ sym)))
138
+
139
+ :else
140
+ (munge (symbol (str
141
+ (if (core-name? env sym)
142
+ 'cljs.core
143
+ (-> env :ns :name))
144
+ "." (munge (name sym))))))]
145
+ {:name nm})))
146
+
147
+ (defn- comma-sep [xs]
148
+ (apply str (interpose "," xs)))
149
+
150
+ (defmulti emit-constant class)
151
+ (defmethod emit-constant nil [x] (print "null"))
152
+ (defmethod emit-constant Long [x] (print x))
153
+ (defmethod emit-constant Integer [x] (print x)) ; reader puts Integers in metadata
154
+ (defmethod emit-constant Double [x] (print x))
155
+ (defmethod emit-constant String [x] (pr x))
156
+ (defmethod emit-constant Boolean [x] (print (if x "true" "false")))
157
+ (defmethod emit-constant Character [x] (pr (str x)))
158
+
159
+ (defmethod emit-constant java.util.regex.Pattern [x]
160
+ (let [[_ flags pattern] (re-find #"^(?:\(\?([idmsux]*)\))?(.*)" (str x))]
161
+ (print (str \/ (.replaceAll (re-matcher #"/" pattern) "\\\\/") \/ flags))))
162
+
163
+ (defmethod emit-constant clojure.lang.Keyword [x]
164
+ (pr (str \uFDD0 \'
165
+ (if (namespace x)
166
+ (str (namespace x) "/") "")
167
+ (name x))))
168
+
169
+ (defmethod emit-constant clojure.lang.Symbol [x]
170
+ (pr (str \uFDD1 \'
171
+ (if (namespace x)
172
+ (str (namespace x) "/") "")
173
+ (name x))))
174
+
175
+ (defn- emit-meta-constant [x string]
176
+ (if (meta x)
177
+ (do
178
+ (print (str "cljs.core.with_meta(" string ","))
179
+ (emit-constant (meta x))
180
+ (print ")"))
181
+ (print string)))
182
+
183
+ (defmethod emit-constant clojure.lang.PersistentList$EmptyList [x]
184
+ (emit-meta-constant x "cljs.core.List.EMPTY"))
185
+
186
+ (defmethod emit-constant clojure.lang.PersistentList [x]
187
+ (emit-meta-constant x
188
+ (str "cljs.core.list("
189
+ (comma-sep (map #(with-out-str (emit-constant %)) x))
190
+ ")")))
191
+
192
+ (defmethod emit-constant clojure.lang.Cons [x]
193
+ (emit-meta-constant x
194
+ (str "cljs.core.list("
195
+ (comma-sep (map #(with-out-str (emit-constant %)) x))
196
+ ")")))
197
+
198
+ (defmethod emit-constant clojure.lang.IPersistentVector [x]
199
+ (emit-meta-constant x
200
+ (str "(new cljs.core.Vector(null, ["
201
+ (comma-sep (map #(with-out-str (emit-constant %)) x))
202
+ "]))")))
203
+
204
+ (defmethod emit-constant clojure.lang.IPersistentMap [x]
205
+ (emit-meta-constant x
206
+ (str "cljs.core.hash_map("
207
+ (comma-sep (map #(with-out-str (emit-constant %))
208
+ (apply concat x)))
209
+ ")")))
210
+
211
+ (defmethod emit-constant clojure.lang.PersistentHashSet [x]
212
+ (emit-meta-constant x
213
+ (str "cljs.core.set(["
214
+ (comma-sep (map #(with-out-str (emit-constant %)) x))
215
+ "])")))
216
+
217
+ (defmulti emit :op)
218
+
219
+ (defn ^String emits [expr]
220
+ (with-out-str (emit expr)))
221
+
222
+ (defn emit-block
223
+ [context statements ret]
224
+ (if statements
225
+ (let [body (str (apply str (map emits statements)) (emits ret))]
226
+ (print body))
227
+ (emit ret)))
228
+
229
+ (defmacro emit-wrap [env & body]
230
+ `(let [env# ~env]
231
+ (when (= :return (:context env#)) (print "return "))
232
+ ~@body
233
+ (when-not (= :expr (:context env#)) (print ";\n"))))
234
+
235
+ (defmethod emit :var
236
+ [{:keys [info env] :as arg}]
237
+ (emit-wrap env (print (munge (:name info)))))
238
+
239
+ (defmethod emit :meta
240
+ [{:keys [expr meta env]}]
241
+ (emit-wrap env
242
+ (print (str "cljs.core.with_meta(" (emits expr) "," (emits meta) ")"))))
243
+
244
+ (defmethod emit :map
245
+ [{:keys [children env simple-keys? keys vals]}]
246
+ (emit-wrap env
247
+ (if simple-keys?
248
+ (print (str "cljs.core.ObjMap.fromObject(["
249
+ (comma-sep (map emits keys)) ; keys
250
+ "],{"
251
+ (comma-sep (map (fn [k v] (str (emits k) ":" (emits v)))
252
+ keys vals)) ; js obj
253
+ "})"))
254
+ (print (str "cljs.core.HashMap.fromArrays(["
255
+ (comma-sep (map emits keys))
256
+ "],["
257
+ (comma-sep (map emits vals))
258
+ "])")))))
259
+
260
+ (defmethod emit :vector
261
+ [{:keys [children env]}]
262
+ (emit-wrap env
263
+ (print (str "cljs.core.Vector.fromArray(["
264
+ (comma-sep (map emits children)) "])"))))
265
+
266
+ (defmethod emit :set
267
+ [{:keys [children env]}]
268
+ (emit-wrap env
269
+ (print (str "cljs.core.set(["
270
+ (comma-sep (map emits children)) "])"))))
271
+
272
+ (defmethod emit :constant
273
+ [{:keys [form env]}]
274
+ (when-not (= :statement (:context env))
275
+ (emit-wrap env (emit-constant form))))
276
+
277
+ (defmethod emit :if
278
+ [{:keys [test then else env]}]
279
+ (let [context (:context env)]
280
+ (if (= :expr context)
281
+ (print (str "(cljs.core.truth_(" (emits test) ")?" (emits then) ":" (emits else) ")"))
282
+ (print (str "if(cljs.core.truth_(" (emits test) "))\n{" (emits then) "} else\n{" (emits else) "}\n")))))
283
+
284
+ (defmethod emit :throw
285
+ [{:keys [throw env]}]
286
+ (if (= :expr (:context env))
287
+ (print (str "(function(){throw " (emits throw) "})()"))
288
+ (print (str "throw " (emits throw) ";\n"))))
289
+
290
+ (defn emit-comment
291
+ "Emit a nicely formatted comment string."
292
+ [doc jsdoc]
293
+ (let [docs (when doc [doc])
294
+ docs (if jsdoc (concat docs jsdoc) docs)
295
+ docs (remove nil? docs)]
296
+ (letfn [(print-comment-lines [e] (doseq [next-line (string/split-lines e)]
297
+ (println "*" (string/trim next-line))))]
298
+ (when (seq docs)
299
+ (println "/**")
300
+ (doseq [e docs]
301
+ (when e
302
+ (print-comment-lines e)))
303
+ (println "*/")))))
304
+
305
+ (defmethod emit :def
306
+ [{:keys [name init env doc export]}]
307
+ (when init
308
+ (emit-comment doc (:jsdoc init))
309
+ (print name)
310
+ (print (str " = " (emits init)))
311
+ (when-not (= :expr (:context env)) (print ";\n"))
312
+ (when export
313
+ (println (str "goog.exportSymbol('" export "', " name ");")))))
314
+
315
+ (defn emit-apply-to
316
+ [{:keys [name params env]}]
317
+ (let [arglist (gensym "arglist__")
318
+ delegate-name (str name "__delegate")]
319
+ (println (str "(function (" arglist "){"))
320
+ (doseq [[i param] (map-indexed vector (butlast params))]
321
+ (print (str "var " param " = cljs.core.first("))
322
+ (dotimes [_ i] (print "cljs.core.next("))
323
+ (print (str arglist ")"))
324
+ (dotimes [_ i] (print ")"))
325
+ (println ";"))
326
+ (if (< 1 (count params))
327
+ (do
328
+ (print (str "var " (last params) " = cljs.core.rest("))
329
+ (dotimes [_ (- (count params) 2)] (print "cljs.core.next("))
330
+ (print arglist)
331
+ (dotimes [_ (- (count params) 2)] (print ")"))
332
+ (println ");")
333
+ (println (str "return " delegate-name ".call(" (string/join ", " (cons "this" params)) ");")))
334
+ (do
335
+ (print (str "var " (last params) " = "))
336
+ (print "cljs.core.seq(" arglist ");")
337
+ (println ";")
338
+ (println (str "return " delegate-name ".call(" (string/join ", " (cons "this" params)) ");"))))
339
+ (print "})")))
340
+
341
+ (defn emit-fn-method
342
+ [{:keys [gthis name variadic params statements ret env recurs max-fixed-arity]}]
343
+ (emit-wrap env
344
+ (print (str "(function " name "(" (comma-sep params) "){\n"))
345
+ (when gthis
346
+ (println (str "var " gthis " = this;")))
347
+ (when recurs (print "while(true){\n"))
348
+ (emit-block :return statements ret)
349
+ (when recurs (print "break;\n}\n"))
350
+ (print "})")))
351
+
352
+ (defn emit-variadic-fn-method
353
+ [{:keys [gthis name variadic params statements ret env recurs max-fixed-arity] :as f}]
354
+ (emit-wrap env
355
+ (let [name (or name (gensym))
356
+ delegate-name (str name "__delegate")]
357
+ (println "(function() { ")
358
+ (println (str "var " delegate-name " = function (" (comma-sep params) "){"))
359
+ (when recurs (print "while(true){\n"))
360
+ (emit-block :return statements ret)
361
+ (when recurs (print "break;\n}\n"))
362
+ (println "};")
363
+
364
+ (print (str "var " name " = function (" (comma-sep
365
+ (if variadic
366
+ (concat (butlast params) ['var_args])
367
+ params)) "){\n"))
368
+ (when gthis
369
+ (println (str "var " gthis " = this;")))
370
+ (when variadic
371
+ (println (str "var " (last params) " = null;"))
372
+ (println (str "if (goog.isDef(var_args)) {"))
373
+ (println (str " " (last params) " = cljs.core.array_seq(Array.prototype.slice.call(arguments, " (dec (count params)) "),0);"))
374
+ (println (str "} ")))
375
+ (println (str "return " delegate-name ".call(" (string/join ", " (cons "this" params)) ");"))
376
+ (println "};")
377
+
378
+ (println (str name ".cljs$lang$maxFixedArity = " max-fixed-arity ";"))
379
+ (println (str name ".cljs$lang$applyTo = "
380
+ (with-out-str
381
+ (emit-apply-to (assoc f :name name)))
382
+ ";"))
383
+ (println (str "return " name ";"))
384
+ (println "})()"))))
385
+
386
+ (defmethod emit :fn
387
+ [{:keys [name env methods max-fixed-arity variadic recur-frames]}]
388
+ ;;fn statements get erased, serve no purpose and can pollute scope if named
389
+ (when-not (= :statement (:context env))
390
+ (let [loop-locals (seq (mapcat :names (filter #(and % @(:flag %)) recur-frames)))]
391
+ (when loop-locals
392
+ (when (= :return (:context env))
393
+ (print "return "))
394
+ (println (str "((function (" (comma-sep loop-locals) "){"))
395
+ (when-not (= :return (:context env))
396
+ (print "return ")))
397
+ (if (= 1 (count methods))
398
+ (if variadic
399
+ (emit-variadic-fn-method (assoc (first methods) :name name))
400
+ (emit-fn-method (assoc (first methods) :name name)))
401
+ (let [name (or name (gensym))
402
+ maxparams (apply max-key count (map :params methods))
403
+ mmap (zipmap (repeatedly #(gensym (str name "__"))) methods)
404
+ ms (sort-by #(-> % second :params count) (seq mmap))]
405
+ (when (= :return (:context env))
406
+ (print "return "))
407
+ (println "(function() {")
408
+ (println (str "var " name " = null;"))
409
+ (doseq [[n meth] ms]
410
+ (println (str "var " n " = " (with-out-str (if (:variadic meth)
411
+ (emit-variadic-fn-method meth)
412
+ (emit-fn-method meth))) ";")))
413
+ (println (str name " = function(" (comma-sep (if variadic
414
+ (concat (butlast maxparams) ['var_args])
415
+ maxparams)) "){"))
416
+ (when variadic
417
+ (println (str "var " (last maxparams) " = var_args;")))
418
+ (println "switch(arguments.length){")
419
+ (doseq [[n meth] ms]
420
+ (if (:variadic meth)
421
+ (do (println "default:")
422
+ (println (str "return " n ".apply(this,arguments);")))
423
+ (let [pcnt (count (:params meth))]
424
+ (println "case " pcnt ":")
425
+ (println (str "return " n ".call(this" (if (zero? pcnt) nil
426
+ (str "," (comma-sep (take pcnt maxparams)))) ");")))))
427
+ (println "}")
428
+ (println "throw('Invalid arity: ' + arguments.length);")
429
+ (println "};")
430
+ (when variadic
431
+ (println (str name ".cljs$lang$maxFixedArity = " max-fixed-arity ";"))
432
+ (println (str name ".cljs$lang$applyTo = " (some #(let [[n m] %] (when (:variadic m) n)) ms) ".cljs$lang$applyTo;")))
433
+ (println (str "return " name ";"))
434
+ (println "})()")))
435
+ (when loop-locals
436
+ (println (str ";})(" (comma-sep loop-locals) "))"))))))
437
+
438
+ (defmethod emit :do
439
+ [{:keys [statements ret env]}]
440
+ (let [context (:context env)]
441
+ (when (and statements (= :expr context)) (print "(function (){"))
442
+ ;(when statements (print "{\n"))
443
+ (emit-block context statements ret)
444
+ ;(when statements (print "}"))
445
+ (when (and statements (= :expr context)) (print "})()"))))
446
+
447
+ (defmethod emit :try*
448
+ [{:keys [env try catch name finally]}]
449
+ (let [context (:context env)
450
+ subcontext (if (= :expr context) :return context)]
451
+ (if (or name finally)
452
+ (do
453
+ (when (= :expr context) (print "(function (){"))
454
+ (print "try{")
455
+ (let [{:keys [statements ret]} try]
456
+ (emit-block subcontext statements ret))
457
+ (print "}")
458
+ (when name
459
+ (print (str "catch (" name "){"))
460
+ (when catch
461
+ (let [{:keys [statements ret]} catch]
462
+ (emit-block subcontext statements ret)))
463
+ (print "}"))
464
+ (when finally
465
+ (let [{:keys [statements ret]} finally]
466
+ (assert (not= :constant (:op ret)) "finally block cannot contain constant")
467
+ (print "finally {")
468
+ (emit-block subcontext statements ret)
469
+ (print "}")))
470
+ (when (= :expr context) (print "})()")))
471
+ (let [{:keys [statements ret]} try]
472
+ (when (and statements (= :expr context)) (print "(function (){"))
473
+ (emit-block subcontext statements ret)
474
+ (when (and statements (= :expr context)) (print "})()"))))))
475
+
476
+ (defmethod emit :let
477
+ [{:keys [bindings statements ret env loop]}]
478
+ (let [context (:context env)
479
+ bs (map (fn [{:keys [name init]}]
480
+ (str "var " name " = " (emits init) ";\n"))
481
+ bindings)]
482
+ (when (= :expr context) (print "(function (){"))
483
+ (print (str (apply str bs) "\n"))
484
+ (when loop (print "while(true){\n"))
485
+ (emit-block (if (= :expr context) :return context) statements ret)
486
+ (when loop (print "break;\n}\n"))
487
+ ;(print "}")
488
+ (when (= :expr context) (print "})()"))))
489
+
490
+ (defmethod emit :recur
491
+ [{:keys [frame exprs env]}]
492
+ (let [temps (vec (take (count exprs) (repeatedly gensym)))
493
+ names (:names frame)]
494
+ (print "{\n")
495
+ (dotimes [i (count exprs)]
496
+ (print (str "var " (temps i) " = " (emits (exprs i)) ";\n")))
497
+ (dotimes [i (count exprs)]
498
+ (print (str (names i) " = " (temps i) ";\n")))
499
+ (print "continue;\n")
500
+ (print "}\n")))
501
+
502
+ (defmethod emit :invoke
503
+ [{:keys [f args env]}]
504
+ (emit-wrap env
505
+ (print (str (emits f) ".call("
506
+ (comma-sep (cons "null" (map emits args)))
507
+ ")"))))
508
+
509
+ (defmethod emit :new
510
+ [{:keys [ctor args env]}]
511
+ (emit-wrap env
512
+ (print (str "(new " (emits ctor) "("
513
+ (comma-sep (map emits args))
514
+ "))"))))
515
+
516
+ (defmethod emit :set!
517
+ [{:keys [target val env]}]
518
+ (emit-wrap env (print (str (emits target) " = "(emits val)))))
519
+
520
+ (defmethod emit :ns
521
+ [{:keys [name requires uses requires-macros env]}]
522
+ (println (str "goog.provide('" (munge name) "');"))
523
+ (when-not (= name 'cljs.core)
524
+ (println (str "goog.require('cljs.core');")))
525
+ (doseq [lib (into (vals requires) (vals uses))]
526
+ (println (str "goog.require('" (munge lib) "');"))))
527
+
528
+ (defmethod emit :deftype*
529
+ [{:keys [t fields]}]
530
+ (let [fields (map munge fields)]
531
+ (println "\n/**\n* @constructor\n*/")
532
+ (println (str t " = (function (" (comma-sep (map str fields)) "){"))
533
+ (doseq [fld fields]
534
+ (println (str "this." fld " = " fld ";")))
535
+ (println "})")))
536
+
537
+ (defmethod emit :defrecord*
538
+ [{:keys [t fields]}]
539
+ (let [fields (map munge fields)]
540
+ (println "\n/**\n* @constructor")
541
+ (doseq [fld fields]
542
+ (println (str "* @param {*} " fld)))
543
+ (println "* @param {*=} __meta \n* @param {*=} __extmap\n*/")
544
+ (println (str t " = (function (" (comma-sep (map str fields)) ", __meta, __extmap){"))
545
+ (doseq [fld fields]
546
+ (println (str "this." fld " = " fld ";")))
547
+ (println (str "if(arguments.length>" (count fields) "){"))
548
+ ;; (println (str "this.__meta = arguments[" (count fields) "];"))
549
+ ;; (println (str "this.__extmap = arguments[" (inc (count fields)) "];"))
550
+ (println (str "this.__meta = __meta;"))
551
+ (println (str "this.__extmap = __extmap;"))
552
+ (println "} else {")
553
+ (print (str "this.__meta="))
554
+ (emit-constant nil)
555
+ (println ";")
556
+ (print (str "this.__extmap="))
557
+ (emit-constant nil)
558
+ (println ";")
559
+ (println "}")
560
+ (println "})")))
561
+
562
+ (defmethod emit :dot
563
+ [{:keys [target field method args env]}]
564
+ (emit-wrap env
565
+ (if field
566
+ (print (str (emits target) "." field))
567
+ (print (str (emits target) "." method "("
568
+ (comma-sep (map emits args))
569
+ ")")))))
570
+
571
+ (defmethod emit :js
572
+ [{:keys [env code segs args]}]
573
+ (emit-wrap env
574
+ (if code
575
+ (print code)
576
+ (print (apply str (interleave (concat segs (repeat nil))
577
+ (concat (map emits args) [nil])))))))
578
+
579
+ (declare analyze analyze-symbol analyze-seq)
580
+
581
+ (def specials '#{if def fn* do let* loop* throw try* recur new set! ns deftype* defrecord* . js* & quote})
582
+
583
+ (def ^:dynamic *recur-frames* nil)
584
+
585
+ (defmacro disallowing-recur [& body]
586
+ `(binding [*recur-frames* (cons nil *recur-frames*)] ~@body))
587
+
588
+ (defn analyze-block
589
+ "returns {:statements .. :ret .. :children ..}"
590
+ [env exprs]
591
+ (let [statements (disallowing-recur
592
+ (seq (map #(analyze (assoc env :context :statement) %) (butlast exprs))))
593
+ ret (if (<= (count exprs) 1)
594
+ (analyze env (first exprs))
595
+ (analyze (assoc env :context (if (= :statement (:context env)) :statement :return)) (last exprs)))]
596
+ {:statements statements :ret ret :children (vec (cons ret statements))}))
597
+
598
+ (defmulti parse (fn [op & rest] op))
599
+
600
+ (defmethod parse 'if
601
+ [op env [_ test then else :as form] name]
602
+ (let [test-expr (disallowing-recur (analyze (assoc env :context :expr) test))
603
+ then-expr (analyze env then)
604
+ else-expr (analyze env else)]
605
+ {:env env :op :if :form form
606
+ :test test-expr :then then-expr :else else-expr
607
+ :children [test-expr then-expr else-expr]}))
608
+
609
+ (defmethod parse 'throw
610
+ [op env [_ throw :as form] name]
611
+ (let [throw-expr (disallowing-recur (analyze (assoc env :context :expr) throw))]
612
+ {:env env :op :throw :form form
613
+ :throw throw-expr
614
+ :children [throw-expr]}))
615
+
616
+ (defmethod parse 'try*
617
+ [op env [_ & body :as form] name]
618
+ (let [body (vec body)
619
+ catchenv (update-in env [:context] #(if (= :expr %) :return %))
620
+ tail (peek body)
621
+ fblock (when (and (seq? tail) (= 'finally (first tail)))
622
+ (rest tail))
623
+ finally (when fblock
624
+ (analyze-block
625
+ (assoc env :context :statement)
626
+ fblock))
627
+ body (if finally (pop body) body)
628
+ tail (peek body)
629
+ cblock (when (and (seq? tail)
630
+ (= 'catch (first tail)))
631
+ (rest tail))
632
+ name (first cblock)
633
+ locals (:locals catchenv)
634
+ mname (when name (munge name))
635
+ locals (if name
636
+ (assoc locals name {:name mname})
637
+ locals)
638
+ catch (when cblock
639
+ (analyze-block (assoc catchenv :locals locals) (rest cblock)))
640
+ body (if name (pop body) body)
641
+ try (when body
642
+ (analyze-block (if (or name finally) catchenv env) body))]
643
+ (when name (assert (not (namespace name)) "Can't qualify symbol in catch"))
644
+ {:env env :op :try* :form form
645
+ :try try
646
+ :finally finally
647
+ :name mname
648
+ :catch catch
649
+ :children [try {:name mname} catch finally]}))
650
+
651
+ (defmethod parse 'def
652
+ [op env form name]
653
+ (let [pfn (fn ([_ sym] {:sym sym})
654
+ ([_ sym init] {:sym sym :init init})
655
+ ([_ sym doc init] {:sym sym :doc doc :init init}))
656
+ args (apply pfn form)
657
+ sym (:sym args)]
658
+ (assert (not (namespace sym)) "Can't def ns-qualified name")
659
+ (let [name (munge (:name (resolve-var (dissoc env :locals) sym)))
660
+ init-expr (when (contains? args :init) (disallowing-recur
661
+ (analyze (assoc env :context :expr) (:init args) sym)))
662
+ export-as (when-let [export-val (-> sym meta :export)]
663
+ (if (= true export-val) name export-val))
664
+ doc (or (:doc args) (-> sym meta :doc))]
665
+ (swap! namespaces assoc-in [(-> env :ns :name) :defs sym] name)
666
+ (merge {:env env :op :def :form form
667
+ :name name :doc doc :init init-expr}
668
+ (when init-expr {:children [init-expr]})
669
+ (when export-as {:export export-as})))))
670
+
671
+ (defn- analyze-fn-method [env locals meth]
672
+ (let [params (first meth)
673
+ fields (-> params meta ::fields)
674
+ variadic (boolean (some '#{&} params))
675
+ params (remove '#{&} params)
676
+ fixed-arity (count (if variadic (butlast params) params))
677
+ body (next meth)
678
+ gthis (and fields (gensym "this__"))
679
+ locals (reduce (fn [m fld] (assoc m fld {:name (symbol (str gthis "." (munge fld)))})) locals fields)
680
+ locals (reduce (fn [m name] (assoc m name {:name (munge name)})) locals params)
681
+ recur-frame {:names (vec (map munge params)) :flag (atom nil)}
682
+ block (binding [*recur-frames* (cons recur-frame *recur-frames*)]
683
+ (analyze-block (assoc env :context :return :locals locals) body))]
684
+
685
+ (merge {:env env :variadic variadic :params (map munge params) :max-fixed-arity fixed-arity :gthis gthis :recurs @(:flag recur-frame)} block)))
686
+
687
+ (defmethod parse 'fn*
688
+ [op env [_ & args] name]
689
+ (let [[name meths] (if (symbol? (first args))
690
+ [(first args) (next args)]
691
+ [name (seq args)])
692
+ ;;turn (fn [] ...) into (fn ([]...))
693
+ meths (if (vector? (first meths)) (list meths) meths)
694
+ mname (when name (munge name))
695
+ locals (:locals env)
696
+ locals (if name (assoc locals name {:name mname}) locals)
697
+ menv (if (> (count meths) 1) (assoc env :context :expr) env)
698
+ methods (map #(analyze-fn-method menv locals %) meths)
699
+ max-fixed-arity (apply max (map :max-fixed-arity methods))
700
+ variadic (boolean (some :variadic methods))]
701
+ ;;(assert (= 1 (count methods)) "Arity overloading not yet supported")
702
+ ;;todo - validate unique arities, at most one variadic, variadic takes max required args
703
+ {:env env :op :fn :name mname :methods methods :variadic variadic :recur-frames *recur-frames*
704
+ :jsdoc [(when variadic "@param {...*} var_args")]
705
+ :max-fixed-arity max-fixed-arity}))
706
+
707
+ (defmethod parse 'do
708
+ [op env [_ & exprs] _]
709
+ (merge {:env env :op :do} (analyze-block env exprs)))
710
+
711
+ (defn analyze-let
712
+ [encl-env [_ bindings & exprs :as form] is-loop]
713
+ (assert (and (vector? bindings) (even? (count bindings))) "bindings must be vector of even number of elements")
714
+ (let [context (:context encl-env)
715
+ [bes env]
716
+ (disallowing-recur
717
+ (loop [bes []
718
+ env (assoc encl-env :context :expr)
719
+ bindings (seq (partition 2 bindings))]
720
+ (if-let [[name init] (first bindings)]
721
+ (do
722
+ (assert (not (or (namespace name) (.contains (str name) "."))) (str "Invalid local name: " name))
723
+ (let [init-expr (analyze env init)
724
+ be {:name (gensym (str (munge name) "__")) :init init-expr}]
725
+ (recur (conj bes be)
726
+ (assoc-in env [:locals name] be)
727
+ (next bindings))))
728
+ [bes env])))
729
+ recur-frame (when is-loop {:names (vec (map :name bes)) :flag (atom nil)})
730
+ {:keys [statements ret children]}
731
+ (binding [*recur-frames* (if recur-frame (cons recur-frame *recur-frames*) *recur-frames*)]
732
+ (analyze-block (assoc env :context (if (= :expr context) :return context)) exprs))]
733
+ {:env encl-env :op :let :loop is-loop
734
+ :bindings bes :statements statements :ret ret :form form :children (into [children] (map :init bes))}))
735
+
736
+ (defmethod parse 'let*
737
+ [op encl-env form _]
738
+ (analyze-let encl-env form false))
739
+
740
+ (defmethod parse 'loop*
741
+ [op encl-env form _]
742
+ (analyze-let encl-env form true))
743
+
744
+ (defmethod parse 'recur
745
+ [op env [_ & exprs] _]
746
+ (let [context (:context env)
747
+ frame (first *recur-frames*)]
748
+ (assert frame "Can't recur here")
749
+ (assert (= (count exprs) (count (:names frame))) "recur argument count mismatch")
750
+ (reset! (:flag frame) true)
751
+ (assoc {:env env :op :recur}
752
+ :frame frame
753
+ :exprs (disallowing-recur (vec (map #(analyze (assoc env :context :expr) %) exprs))))))
754
+
755
+ (defmethod parse 'quote
756
+ [_ env [_ x] _]
757
+ {:op :constant :env env :form x})
758
+
759
+ (defmethod parse 'new
760
+ [_ env [_ ctor & args] _]
761
+ (disallowing-recur
762
+ (let [enve (assoc env :context :expr)
763
+ ctorexpr (analyze enve ctor)
764
+ argexprs (vec (map #(analyze enve %) args))]
765
+ {:env env :op :new :ctor ctorexpr :args argexprs :children (conj argexprs ctorexpr)})))
766
+
767
+ (defmethod parse 'set!
768
+ [_ env [_ target val] _]
769
+ (disallowing-recur
770
+ (let [enve (assoc env :context :expr)
771
+ targetexpr (if (symbol? target)
772
+ (do
773
+ (assert (nil? (-> env :locals target))
774
+ "Can't set! local var")
775
+ (analyze-symbol enve target))
776
+ (when (seq? target)
777
+ (let [targetexpr (analyze-seq enve target nil)]
778
+ (when (:field targetexpr)
779
+ targetexpr))))
780
+ valexpr (analyze enve val)]
781
+ (assert targetexpr "set! target must be a field or a symbol naming a var")
782
+ {:env env :op :set! :target targetexpr :val valexpr :children [targetexpr valexpr]})))
783
+
784
+ (defmethod parse 'ns
785
+ [_ env [_ name & args] _]
786
+ (let [excludes
787
+ (reduce (fn [s [k exclude xs]]
788
+ (if (= k :refer-clojure)
789
+ (do
790
+ (assert (= exclude :exclude) "Only [:refer-clojure :exclude [names]] form supported")
791
+ (into s xs))
792
+ s))
793
+ #{} args)
794
+ {uses :use requires :require uses-macros :use-macros requires-macros :require-macros :as params}
795
+ (reduce (fn [m [k & libs]]
796
+ (assert (#{:use :use-macros :require :require-macros} k)
797
+ "Only :refer-clojure, :require, :require-macros, :use and :use-macros libspecs supported")
798
+ (assoc m k (into {}
799
+ (mapcat (fn [[lib kw expr]]
800
+ (case k
801
+ (:require :require-macros)
802
+ (do (assert (and expr (= :as kw))
803
+ "Only (:require [lib.ns :as alias]*) form of :require / :require-macros is supported")
804
+ [[expr lib]])
805
+ (:use :use-macros)
806
+ (do (assert (and expr (= :only kw))
807
+ "Only (:use [lib.ns :only [names]]*) form of :use / :use-macros is supported")
808
+ (map vector expr (repeat lib)))))
809
+ libs))))
810
+ {} (remove (fn [[r]] (= r :refer-clojure)) args))]
811
+ (set! *cljs-ns* name)
812
+ (require 'cljs.core)
813
+ (doseq [nsym (concat (vals requires-macros) (vals uses-macros))]
814
+ (clojure.core/require nsym))
815
+ (swap! namespaces #(-> %
816
+ (assoc-in [name :name] name)
817
+ (assoc-in [name :excludes] excludes)
818
+ (assoc-in [name :uses] uses)
819
+ (assoc-in [name :requires] requires)
820
+ (assoc-in [name :uses-macros] uses-macros)
821
+ (assoc-in [name :requires-macros]
822
+ (into {} (map (fn [[alias nsym]]
823
+ [alias (find-ns nsym)])
824
+ requires-macros)))))
825
+ {:env env :op :ns :name name :uses uses :requires requires
826
+ :uses-macros uses-macros :requires-macros requires-macros :excludes excludes}))
827
+
828
+ (defmethod parse 'deftype*
829
+ [_ env [_ tsym fields] _]
830
+ (let [t (munge (:name (resolve-var (dissoc env :locals) tsym)))]
831
+ (swap! namespaces assoc-in [(-> env :ns :name) :defs tsym] t)
832
+ {:env env :op :deftype* :t t :fields fields}))
833
+
834
+ (defmethod parse 'defrecord*
835
+ [_ env [_ tsym fields] _]
836
+ (let [t (munge (:name (resolve-var (dissoc env :locals) tsym)))]
837
+ (swap! namespaces assoc-in [(-> env :ns :name) :defs tsym] t)
838
+ {:env env :op :defrecord* :t t :fields fields}))
839
+
840
+ (defmethod parse '.
841
+ [_ env [_ target & member+] _]
842
+ (disallowing-recur
843
+ (let [enve (assoc env :context :expr)
844
+ targetexpr (analyze enve target)
845
+ children [enve]]
846
+ (if (and (symbol? (first member+)) (nil? (next member+))) ;;(. target field)
847
+ {:env env :op :dot :target targetexpr :field (munge (first member+)) :children children}
848
+ (let [[method args]
849
+ (if (symbol? (first member+))
850
+ [(first member+) (next member+)]
851
+ [(ffirst member+) (nfirst member+)])
852
+ argexprs (map #(analyze enve %) args)]
853
+ {:env env :op :dot :target targetexpr :method (munge method) :args argexprs :children (into children argexprs)})))))
854
+
855
+ (defmethod parse 'js*
856
+ [op env [_ form & args] _]
857
+ (assert (string? form))
858
+ (if args
859
+ (disallowing-recur
860
+ (let [seg (fn seg [^String s]
861
+ (let [idx (.indexOf s "~{")]
862
+ (if (= -1 idx)
863
+ (list s)
864
+ (let [end (.indexOf s "}" idx)]
865
+ (cons (subs s 0 idx) (seg (subs s (inc end))))))))
866
+ enve (assoc env :context :expr)
867
+ argexprs (vec (map #(analyze enve %) args))]
868
+ {:env env :op :js :segs (seg form) :args argexprs :children argexprs}))
869
+ (let [interp (fn interp [^String s]
870
+ (let [idx (.indexOf s "~{")]
871
+ (if (= -1 idx)
872
+ (list s)
873
+ (let [end (.indexOf s "}" idx)
874
+ inner (:name (resolve-existing-var env (symbol (subs s (+ 2 idx) end))))]
875
+ (cons (subs s 0 idx) (cons inner (interp (subs s (inc end)))))))))]
876
+ {:env env :op :js :code (apply str (interp form))})))
877
+
878
+ (defn parse-invoke
879
+ [env [f & args]]
880
+ (disallowing-recur
881
+ (let [enve (assoc env :context :expr)
882
+ fexpr (analyze enve f)
883
+ argexprs (vec (map #(analyze enve %) args))]
884
+ {:env env :op :invoke :f fexpr :args argexprs :children (conj argexprs fexpr)})))
885
+
886
+ (defn analyze-symbol
887
+ "Finds the var associated with sym"
888
+ [env sym]
889
+ (let [ret {:env env :form sym}
890
+ lb (-> env :locals sym)]
891
+ (if lb
892
+ (assoc ret :op :var :info lb)
893
+ (assoc ret :op :var :info (resolve-existing-var env sym)))))
894
+
895
+ (defn get-expander [sym env]
896
+ (let [mvar
897
+ (when-not (-> env :locals sym) ;locals hide macros
898
+ (if-let [nstr (namespace sym)]
899
+ (when-let [ns (cond
900
+ (= "clojure.core" nstr) (find-ns 'cljs.core)
901
+ (.contains nstr ".") (find-ns (symbol nstr))
902
+ :else
903
+ (-> env :ns :requires-macros (get (symbol nstr))))]
904
+ (.findInternedVar ^clojure.lang.Namespace ns (symbol (name sym))))
905
+ (if-let [nsym (-> env :ns :uses-macros sym)]
906
+ (.findInternedVar ^clojure.lang.Namespace (find-ns nsym) sym)
907
+ (.findInternedVar ^clojure.lang.Namespace (find-ns 'cljs.core) sym))))]
908
+ (when (and mvar (.isMacro ^clojure.lang.Var mvar))
909
+ @mvar)))
910
+
911
+ (defn macroexpand-1 [env form]
912
+ (let [op (first form)]
913
+ (if (specials op)
914
+ form
915
+ (if-let [mac (and (symbol? op) (get-expander op env))]
916
+ (apply mac form env (rest form))
917
+ (if (symbol? op)
918
+ (let [opname (str op)]
919
+ (cond
920
+ (= (first opname) \.) (let [[target & args] (next form)]
921
+ (list* '. target (symbol (subs opname 1)) args))
922
+ (= (last opname) \.) (list* 'new (symbol (subs opname 0 (dec (count opname)))) (next form))
923
+ :else form))
924
+ form)))))
925
+
926
+ (defn analyze-seq
927
+ [env form name]
928
+ (let [env (assoc env :line (-> form meta :line))]
929
+ (let [op (first form)]
930
+ (assert (not (nil? op)) "Can't call nil")
931
+ (let [mform (macroexpand-1 env form)]
932
+ (if (identical? form mform)
933
+ (if (specials op)
934
+ (parse op env form name)
935
+ (parse-invoke env form))
936
+ (analyze env mform name))))))
937
+
938
+ (declare analyze-wrap-meta)
939
+
940
+ (defn analyze-map
941
+ [env form name]
942
+ (let [expr-env (assoc env :context :expr)
943
+ simple-keys? (every? #(or (string? %) (keyword? %))
944
+ (keys form))
945
+ ks (disallowing-recur (vec (map #(analyze expr-env % name) (keys form))))
946
+ vs (disallowing-recur (vec (map #(analyze expr-env % name) (vals form))))]
947
+ (analyze-wrap-meta {:op :map :env env :form form :children (vec (concat ks vs))
948
+ :keys ks :vals vs :simple-keys? simple-keys?}
949
+ name)))
950
+
951
+ (defn analyze-vector
952
+ [env form name]
953
+ (let [expr-env (assoc env :context :expr)
954
+ items (disallowing-recur (vec (map #(analyze expr-env % name) form)))]
955
+ (analyze-wrap-meta {:op :vector :env env :form form :children items} name)))
956
+
957
+ (defn analyze-set
958
+ [env form name]
959
+ (let [expr-env (assoc env :context :expr)
960
+ items (disallowing-recur (vec (map #(analyze expr-env % name) form)))]
961
+ (analyze-wrap-meta {:op :set :env env :form form :children items} name)))
962
+
963
+ (defn analyze-wrap-meta [expr name]
964
+ (let [form (:form expr)]
965
+ (if (meta form)
966
+ (let [env (:env expr) ; take on expr's context ourselves
967
+ expr (assoc-in expr [:env :context] :expr) ; change expr to :expr
968
+ meta-expr (analyze-map (:env expr) (meta form) name)]
969
+ {:op :meta :env env :form form :children [meta-expr expr]
970
+ :meta meta-expr :expr expr})
971
+ expr)))
972
+
973
+ (defn analyze
974
+ "Given an environment, a map containing {:locals (mapping of names to bindings), :context
975
+ (one of :statement, :expr, :return), :ns (a symbol naming the
976
+ compilation ns)}, and form, returns an expression object (a map
977
+ containing at least :form, :op and :env keys). If expr has any (immediately)
978
+ nested exprs, must have :children [exprs...] entry. This will
979
+ facilitate code walking without knowing the details of the op set."
980
+ ([env form] (analyze env form nil))
981
+ ([env form name]
982
+ (let [form (if (instance? clojure.lang.LazySeq form)
983
+ (or (seq form) ())
984
+ form)]
985
+ (cond
986
+ (symbol? form) (analyze-symbol env form)
987
+ (and (seq? form) (seq form)) (analyze-seq env form name)
988
+ (map? form) (analyze-map env form name)
989
+ (vector? form) (analyze-vector env form name)
990
+ (set? form) (analyze-set env form name)
991
+ :else {:op :constant :env env :form form}))))
992
+
993
+ (defn analyze-file
994
+ [f]
995
+ (binding [*cljs-ns* 'cljs.user]
996
+ (let [res (if (= \/ (first f)) f (io/resource f))]
997
+ (assert res (str "Can't find " f " in classpath"))
998
+ (with-open [r (io/reader res)]
999
+ (let [env {:ns (@namespaces *cljs-ns*) :context :statement :locals {}}
1000
+ pbr (clojure.lang.LineNumberingPushbackReader. r)
1001
+ eof (Object.)]
1002
+ (loop [r (read pbr false eof false)]
1003
+ (let [env (assoc env :ns (@namespaces *cljs-ns*))]
1004
+ (when-not (identical? eof r)
1005
+ (analyze env r)
1006
+ (recur (read pbr false eof false))))))))))
1007
+
1008
+ (defn forms-seq
1009
+ "Seq of forms in a Clojure or ClojureScript file."
1010
+ ([f]
1011
+ (forms-seq f (java.io.PushbackReader. (io/reader f))))
1012
+ ([f ^java.io.PushbackReader rdr]
1013
+ (if-let [form (read rdr nil nil)]
1014
+ (lazy-seq (cons form (forms-seq f rdr)))
1015
+ (.close rdr))))
1016
+
1017
+ (defn rename-to-js
1018
+ "Change the file extension from .cljs to .js. Takes a File or a
1019
+ String. Always returns a String."
1020
+ [file-str]
1021
+ (clojure.string/replace file-str #".cljs$" ".js"))
1022
+
1023
+ (defn mkdirs
1024
+ "Create all parent directories for the passed file."
1025
+ [^java.io.File f]
1026
+ (.mkdirs (.getParentFile (.getCanonicalFile f))))
1027
+
1028
+ (defmacro with-core-cljs
1029
+ "Ensure that core.cljs has been loaded."
1030
+ [& body]
1031
+ `(do (when-not (:defs (get @namespaces 'cljs.core))
1032
+ (analyze-file "cljs/core.cljs"))
1033
+ ~@body))
1034
+
1035
+ (defn compile-file* [src dest]
1036
+ (with-core-cljs
1037
+ (with-open [out ^java.io.Writer (io/make-writer dest {})]
1038
+ (binding [*out* out
1039
+ *cljs-ns* 'cljs.user]
1040
+ (loop [forms (forms-seq src)
1041
+ ns-name nil
1042
+ deps nil]
1043
+ (if (seq forms)
1044
+ (let [env {:ns (@namespaces *cljs-ns*) :context :statement :locals {}}
1045
+ ast (analyze env (first forms))]
1046
+ (do (emit ast)
1047
+ (if (= (:op ast) :ns)
1048
+ (recur (rest forms) (:name ast) (merge (:uses ast) (:requires ast)))
1049
+ (recur (rest forms) ns-name deps))))
1050
+ {:ns (or ns-name 'cljs.user)
1051
+ :provides [ns-name]
1052
+ :requires (if (= ns-name 'cljs.core) (set (vals deps)) (conj (set (vals deps)) 'cljs.core))
1053
+ :file dest}))))))
1054
+
1055
+ (defn requires-compilation?
1056
+ "Return true if the src file requires compilation."
1057
+ [^java.io.File src ^java.io.File dest]
1058
+ (or (not (.exists dest))
1059
+ (> (.lastModified src) (.lastModified dest))))
1060
+
1061
+ (defn compile-file
1062
+ "Compiles src to a file of the same name, but with a .js extension,
1063
+ in the src file's directory.
1064
+
1065
+ With dest argument, write file to provided location. If the dest
1066
+ argument is a file outside the source tree, missing parent
1067
+ directories will be created. The src file will only be compiled if
1068
+ the dest file has an older modification time.
1069
+
1070
+ Both src and dest may be either a String or a File.
1071
+
1072
+ Returns a map containing {:ns .. :provides .. :requires .. :file ..}.
1073
+ If the file was not compiled returns only {:file ...}"
1074
+ ([src]
1075
+ (let [dest (rename-to-js src)]
1076
+ (compile-file src dest)))
1077
+ ([src dest]
1078
+ (let [src-file (io/file src)
1079
+ dest-file (io/file dest)]
1080
+ (if (.exists src-file)
1081
+ (if (requires-compilation? src-file dest-file)
1082
+ (do (mkdirs dest-file)
1083
+ (compile-file* src-file dest-file))
1084
+ {:file dest-file})
1085
+ (throw (java.io.FileNotFoundException. (str "The file " src " does not exist.")))))))
1086
+
1087
+ (comment
1088
+ ;; flex compile-file
1089
+ (do
1090
+ (compile-file "/tmp/hello.cljs" "/tmp/something.js")
1091
+ (slurp "/tmp/hello.js")
1092
+
1093
+ (compile-file "/tmp/somescript.cljs")
1094
+ (slurp "/tmp/somescript.js")))
1095
+
1096
+ (defn path-seq
1097
+ [file-str]
1098
+ (->> java.io.File/separator
1099
+ java.util.regex.Pattern/quote
1100
+ re-pattern
1101
+ (string/split file-str)))
1102
+
1103
+ (defn to-path
1104
+ ([parts]
1105
+ (to-path parts java.io.File/separator))
1106
+ ([parts sep]
1107
+ (apply str (interpose sep parts))))
1108
+
1109
+ (defn to-target-file
1110
+ "Given the source root directory, the output target directory and
1111
+ file under the source root, produce the target file."
1112
+ [^java.io.File dir ^String target ^java.io.File file]
1113
+ (let [dir-path (path-seq (.getAbsolutePath dir))
1114
+ file-path (path-seq (.getAbsolutePath file))
1115
+ relative-path (drop (count dir-path) file-path)
1116
+ parents (butlast relative-path)
1117
+ parent-file (java.io.File. ^String (to-path (cons target parents)))]
1118
+ (java.io.File. parent-file ^String (rename-to-js (last relative-path)))))
1119
+
1120
+ (defn cljs-files-in
1121
+ "Return a sequence of all .cljs files in the given directory."
1122
+ [dir]
1123
+ (filter #(let [name (.getName ^java.io.File %)]
1124
+ (and (.endsWith name ".cljs")
1125
+ (not= \. (first name))))
1126
+ (file-seq dir)))
1127
+
1128
+ (defn compile-root
1129
+ "Looks recursively in src-dir for .cljs files and compiles them to
1130
+ .js files. If target-dir is provided, output will go into this
1131
+ directory mirroring the source directory structure. Returns a list
1132
+ of maps containing information about each file which was compiled
1133
+ in dependency order."
1134
+ ([src-dir]
1135
+ (compile-root src-dir "out"))
1136
+ ([src-dir target-dir]
1137
+ (let [src-dir-file (io/file src-dir)]
1138
+ (loop [cljs-files (cljs-files-in src-dir-file)
1139
+ output-files []]
1140
+ (if (seq cljs-files)
1141
+ (let [cljs-file (first cljs-files)
1142
+ output-file ^java.io.File (to-target-file src-dir-file target-dir cljs-file)
1143
+ ns-info (compile-file cljs-file output-file)]
1144
+ (recur (rest cljs-files) (conj output-files (assoc ns-info :file-name (.getPath output-file)))))
1145
+ output-files)))))
1146
+
1147
+ (comment
1148
+ ;; compile-root
1149
+ ;; If you have a standard project layout with all file in src
1150
+ (compile-root "src")
1151
+ ;; will produce a mirrored directory structure under "out" but all
1152
+ ;; files will be compiled to js.
1153
+ )
1154
+
1155
+ (comment
1156
+
1157
+ ;;the new way - use the REPL!!
1158
+ (require '[cljs.compiler :as comp])
1159
+ (def repl-env (comp/repl-env))
1160
+ (comp/repl repl-env)
1161
+ ;having problems?, try verbose mode
1162
+ (comp/repl repl-env :verbose true)
1163
+ ;don't forget to check for uses of undeclared vars
1164
+ (comp/repl repl-env :warn-on-undeclared true)
1165
+
1166
+ (test-stuff)
1167
+ (+ 1 2 3)
1168
+ ([ 1 2 3 4] 2)
1169
+ ({:a 1 :b 2} :a)
1170
+ ({1 1 2 2} 1)
1171
+ (#{1 2 3} 2)
1172
+ (:b {:a 1 :b 2})
1173
+ ('b '{:a 1 b 2})
1174
+
1175
+ (extend-type number ISeq (-seq [x] x))
1176
+ (seq 42)
1177
+ ;(aset cljs.core.ISeq "number" true)
1178
+ ;(aget cljs.core.ISeq "number")
1179
+ (satisfies? ISeq 42)
1180
+ (extend-type nil ISeq (-seq [x] x))
1181
+ (satisfies? ISeq nil)
1182
+ (seq nil)
1183
+
1184
+ (extend-type default ISeq (-seq [x] x))
1185
+ (satisfies? ISeq true)
1186
+ (seq true)
1187
+
1188
+ (test-stuff)
1189
+
1190
+ (array-seq [])
1191
+ (defn f [& etc] etc)
1192
+ (f)
1193
+
1194
+ (in-ns 'cljs.core)
1195
+ ;;hack on core
1196
+
1197
+
1198
+ (deftype Foo [a] IMeta (-meta [_] (fn [] a)))
1199
+ ((-meta (Foo. 42)))
1200
+
1201
+ ;;OLD way, don't you want to use the REPL?
1202
+ (in-ns 'cljs.compiler)
1203
+ (import '[javax.script ScriptEngineManager])
1204
+ (def jse (-> (ScriptEngineManager.) (.getEngineByName "JavaScript")))
1205
+ (.eval jse cljs.compiler/bootjs)
1206
+ (def envx {:ns (@namespaces 'cljs.user) :context :expr :locals '{ethel {:name ethel__123 :init nil}}})
1207
+ (analyze envx nil)
1208
+ (analyze envx 42)
1209
+ (analyze envx "foo")
1210
+ (analyze envx 'fred)
1211
+ (analyze envx 'fred.x)
1212
+ (analyze envx 'ethel)
1213
+ (analyze envx 'ethel.x)
1214
+ (analyze envx 'my.ns/fred)
1215
+ (analyze envx 'your.ns.fred)
1216
+ (analyze envx '(if test then else))
1217
+ (analyze envx '(if test then))
1218
+ (analyze envx '(and fred ethel))
1219
+ (analyze (assoc envx :context :statement) '(def test "fortytwo" 42))
1220
+ (analyze (assoc envx :context :expr) '(fn* ^{::fields [a b c]} [x y] a y x))
1221
+ (analyze (assoc envx :context :statement) '(let* [a 1 b 2] a))
1222
+ (analyze (assoc envx :context :statement) '(defprotocol P (bar [a]) (baz [b c])))
1223
+ (analyze (assoc envx :context :statement) '(. x y))
1224
+ (analyze envx '(fn foo [x] (let [x 42] (js* "~{x}['foobar']"))))
1225
+
1226
+ (analyze envx '(ns fred (:require [your.ns :as yn]) (:require-macros [clojure.core :as core])))
1227
+ (defmacro js [form]
1228
+ `(emit (analyze {:ns (@namespaces 'cljs.user) :context :statement :locals {}} '~form)))
1229
+
1230
+ (defn jseval [form]
1231
+ (let [js (emits (analyze {:ns (@namespaces 'cljs.user) :context :expr :locals {}}
1232
+ form))]
1233
+ ;;(prn js)
1234
+ (.eval jse (str "print(" js ")"))))
1235
+
1236
+ (defn jscapture [form]
1237
+ "just grabs the js, doesn't print it"
1238
+ (emits (analyze {:ns (@namespaces 'cljs.user) :context :expr :locals {}} form)))
1239
+
1240
+ ;; from closure.clj
1241
+ (optimize (jscapture '(defn foo [x y] (if true 46 (recur 1 x)))))
1242
+
1243
+ (js (if a b c))
1244
+ (js (def x 42))
1245
+ (js (defn foo [a b] a))
1246
+ (js (do 1 2 3))
1247
+ (js (let [a 1 b 2 a b] a))
1248
+
1249
+ (js (ns fred (:require [your.ns :as yn]) (:require-macros [cljs.core :as core])))
1250
+
1251
+ (js (def foo? (fn* ^{::fields [a? b c]} [x y] (if true a? (recur 1 x)))))
1252
+ (js (def foo (fn* ^{::fields [a b c]} [x y] (if true a (recur 1 x)))))
1253
+ (js (defn foo [x y] (if true x y)))
1254
+ (jseval '(defn foo [x y] (if true x y)))
1255
+ (js (defn foo [x y] (if true 46 (recur 1 x))))
1256
+ (jseval '(defn foo [x y] (if true 46 (recur 1 x))))
1257
+ (jseval '(foo 1 2))
1258
+ (js (and fred ethel))
1259
+ (jseval '(ns fred (:require [your.ns :as yn]) (:require-macros [cljs.core :as core])))
1260
+ (js (def x 42))
1261
+ (jseval '(def x 42))
1262
+ (jseval 'x)
1263
+ (jseval '(if 42 1 2))
1264
+ (jseval '(or 1 2))
1265
+ (jseval '(fn* [x y] (if true 46 (recur 1 x))))
1266
+ (.eval jse "print(test)")
1267
+ (.eval jse "print(cljs.user.Foo)")
1268
+ (.eval jse "print(cljs.user.Foo = function (){\n}\n)")
1269
+ (js (def fred 42))
1270
+ (js (deftype* Foo [a b-foo c]))
1271
+ (jseval '(deftype* Foo [a b-foo c]))
1272
+ (jseval '(. (new Foo 1 2 3) b-foo))
1273
+ (js (. (new Foo 1 2 3) b))
1274
+ (.eval jse "print(new cljs.user.Foo(1, 42, 3).b)")
1275
+ (.eval jse "(function (x, ys){return Array.prototype.slice.call(arguments, 1);})(1,2)[0]")
1276
+
1277
+ (macroexpand-1 '(cljs.core/deftype Foo [a b c] Fred (fred [x] a) (fred [x y] b) (ethel [x] c) Ethel (foo [] d)))
1278
+ (-> (macroexpand-1 '(cljs.core/deftype Foo [a b c] Fred (fred [x] a) (fred [x y] b) (ethel [x] c) Ethel (foo [] d)))
1279
+ last last last first meta)
1280
+
1281
+ (macroexpand-1 '(cljs.core/extend-type Foo Fred (fred ([x] a) ([x y] b)) (ethel ([x] c)) Ethel (foo ([] d))))
1282
+ (js (new foo.Bar 65))
1283
+ (js (defprotocol P (bar [a]) (baz [b c])))
1284
+ (js (. x y))
1285
+ (js (. "fred" (y)))
1286
+ (js (. x y 42 43))
1287
+ (js (.. a b c d))
1288
+ (js (. x (y 42 43)))
1289
+ (js (fn [x] x))
1290
+ (js (fn ([t] t) ([x y] y) ([ a b & zs] b)))
1291
+
1292
+ (js (. (fn foo ([t] t) ([x y] y) ([a b & zs] b)) call nil 1 2))
1293
+ (js (fn foo
1294
+ ([t] t)
1295
+ ([x y] y)
1296
+ ([ a b & zs] b)))
1297
+
1298
+ (js ((fn foo
1299
+ ([t] (foo t nil))
1300
+ ([x y] y)
1301
+ ([ a b & zs] b)) 1 2 3))
1302
+
1303
+
1304
+ (jseval '((fn foo ([t] t) ([x y] y) ([ a b & zs] zs)) 12 13 14 15))
1305
+
1306
+ (js (defn foo [this] this))
1307
+
1308
+ (js (defn foo [a b c & ys] ys))
1309
+ (js ((fn [x & ys] ys) 1 2 3 4))
1310
+ (jseval '((fn [x & ys] ys) 1 2 3 4))
1311
+ (js (cljs.core/deftype Foo [a b c] Fred (fred [x] a) (fred [x y] a) (ethel [x] c) Ethel (foo [] d)))
1312
+ (jseval '(cljs.core/deftype Foo [a b c] Fred (fred [x] a) (fred [x y] a) (ethel [x] c) Ethel (foo [] d)))
1313
+
1314
+ (js (do
1315
+ (defprotocol Proto (foo [this]))
1316
+ (deftype Type [a] Proto (foo [this] a))
1317
+ (foo (new Type 42))))
1318
+
1319
+ (jseval '(do
1320
+ (defprotocol P-roto (foo? [this]))
1321
+ (deftype T-ype [a] P-roto (foo? [this] a))
1322
+ (foo? (new T-ype 42))))
1323
+
1324
+ (js (def x (fn foo [x] (let [x 42] (js* "~{x}['foobar']")))))
1325
+ (js (let [a 1 b 2 a b] a))
1326
+
1327
+ (doseq [e '[nil true false 42 "fred" fred ethel my.ns/fred your.ns.fred
1328
+ (if test then "fooelse")
1329
+ (def x 45)
1330
+ (do x y y)
1331
+ (fn* [x y] x y x)
1332
+ (fn* [x y] (if true 46 (recur 1 x)))
1333
+ (let* [a 1 b 2 a a] a b)
1334
+ (do "do1")
1335
+ (loop* [x 1 y 2] (if true 42 (do (recur 43 44))))
1336
+ (my.foo 1 2 3)
1337
+ (let* [a 1 b 2 c 3] (set! y.s.d b) (new fred.Ethel a b c))
1338
+ (let [x (do 1 2 3)] x)
1339
+ ]]
1340
+ (->> e (analyze envx) emit)
1341
+ (newline)))