├── .travis.yml ├── .gitignore ├── makefile ├── project.clj ├── test └── chlorine │ ├── reader_test.clj │ └── js_test.clj ├── README.md └── src └── chlorine ├── page.clj ├── reader.clj └── js.clj /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | jdk: 4 | - openjdk7 5 | - openjdk6 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.lein-failures 3 | *.jar 4 | pom.xml 5 | /classes 6 | /lib 7 | /.lein-repl-history 8 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | default: jar 2 | 3 | test jar:: 4 | lein $@ 5 | 6 | push: clean 7 | lein jar 8 | ln -s chlorine-*.jar chlorine.jar 9 | lein push 10 | 11 | clean: 12 | rm -f *.jar 13 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject chlorine/js "1.6.4-SNAPSHOT" 2 | :description "A naive Clojure to Javascript translator" 3 | :url "http://github.com/chlorinejs/chlorine" 4 | :dependencies [[org.clojure/clojure "1.5.1"] 5 | [pathetic "0.4.0" :exclusions [org.clojure/clojure]] 6 | [chlorine-utils "1.2.0"] 7 | [hiccup "1.0.4"] 8 | [slingshot "0.10.3"] 9 | [org.clojure/tools.reader "0.8.3"]]) 10 | -------------------------------------------------------------------------------- /test/chlorine/reader_test.clj: -------------------------------------------------------------------------------- 1 | (ns chlorine.reader-test 2 | (:use [chlorine.reader] 3 | [clojure.test])) 4 | 5 | (deftest member-form?-tests 6 | (is (= (member-form? '.foo) 7 | true)) 8 | (is (= (member-form? '.-bar) 9 | true))) 10 | 11 | (deftest reserved-symbol?-test 12 | (is (reserved-symbol? ["a" "b" #"c"] "a")) 13 | (is (reserved-symbol? ["a" "b" #"c"] "c")) 14 | (is (not (reserved-symbol? ["a" "b" #"c"] "e"))) 15 | (is (not (reserved-symbol? ["a" "b" #"c"] "f")))) 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chlorine 2 | 3 | [![Build Status](https://api.travis-ci.org/chlorinejs/chlorine.png)](https://travis-ci.org/chlorinejs/chlorine) 4 | 5 | Chlorine (formerly Clojurejs) is a naive implementation of a Clojure subset language to Javascript translator. Chlorine is an attempt to implement the predictable semantics in the generated Javascript. Some of its features are: 6 | 7 | * Consistent scoping in ``let`` and ``loop/recur`` forms 8 | * Macros with ``defmacro`` 9 | * Implicit ``return`` from all forms 10 | * ``loop/recur`` translates to Javascript ``for`` loops 11 | * ``case`` translates to Javascript ``switch/case`` 12 | * Translates Clojure vectors, strings, keywords, symbols and maps to Javascript equivalents 13 | * File-based dependencies through ``load-file`` and ``load-file-macros`` 14 | * Clojure destructuring forms in functions and `let` 15 | 16 | Chlorine is available under the Eclipse Public License - v 1.0. 17 | 18 | For more information see the chlorine [wiki](https://github.com/chlorinejs/chlorine/wiki). 19 | -------------------------------------------------------------------------------- /src/chlorine/page.clj: -------------------------------------------------------------------------------- 1 | (ns chlorine.page 2 | (:use [chlorine.js :only [js js-let]] 3 | [chlorine.util :only [unzip]])) 4 | 5 | (defmacro script 6 | "Similar to the (js ...) form, but wraps the javascript in a 7 | [:script ] element, which can be passed to hiccup.core/html." 8 | [& forms] 9 | `[:script {:type "text/javascript"} 10 | (js ~@forms)]) 11 | 12 | (defmacro script-let 13 | "Similar to the (js-let ...) form, but wraps the javascript in a 14 | [:script ] element, which can be passed to hiccup.core/html." 15 | [bindings & forms] 16 | `[:script {:type "text/javascript"} 17 | (js-let ~bindings ~@forms)]) 18 | 19 | (defmacro jq 20 | "Similar to the (js ...) form, but wraps the javascript in a 21 | [:script ] element which is invoked on a jQuery document.ready 22 | event." 23 | [& forms] 24 | (let [fnform# 'fn] 25 | `[:script {:type "text/javascript"} 26 | (js (.ready ($ document) (~fnform# [] ~@forms)))])) 27 | 28 | (defmacro jq-let 29 | "Similar to the (js-let ...) form, but wraps the javascript in a 30 | [:script ] element which is invoked on a jQuery document.ready 31 | event." 32 | [bindings & forms] 33 | (let [fnform# 'fn 34 | [formals# actuals#] (unzip bindings)] 35 | `[:script {:type "text/javascript"} 36 | "$(document).ready(function () {" 37 | (js-let ~bindings ~@forms) 38 | "});"])) -------------------------------------------------------------------------------- /src/chlorine/reader.clj: -------------------------------------------------------------------------------- 1 | (ns chlorine.reader 2 | (:use [clojure.java.io :only [reader]] 3 | [chlorine.util :only [re?]])) 4 | 5 | (defn sexp-reader 6 | "Wrap `source' in a reader suitable to pass to `read'." 7 | [source] 8 | (new java.io.PushbackReader (reader source))) 9 | 10 | (defn ignorable-arg? [n] 11 | (and (symbol? n) (.startsWith (name n) "_"))) 12 | 13 | (defn unary-operator? [op] 14 | (and (symbol? op) (contains? #{"++" "--" "!"} (name op)))) 15 | 16 | (defn infix-operator? [op] 17 | (and (symbol? op) 18 | (contains? #{"and" "or" 19 | "bit-and" "bit-or" "bit-xor" "bit-not" 20 | "bit-shift-left" "bit-shift-right" 21 | "bit-shift-right-zero-fill" 22 | "rem" 23 | "+*" "-*" "**" "js-divide" 24 | ">" ">=" "<" "<=" 25 | "==" 26 | "===" 27 | "!=" 28 | "!==" 29 | "in" 30 | "instance?" "instanceof"} 31 | (name op)))) 32 | 33 | (defn destructuring-form? [form] 34 | (or (map? form) (vector? form))) 35 | 36 | (defn binding-form? [form] 37 | (or (symbol? form) (destructuring-form? form))) 38 | 39 | (defn binding-special? [form] 40 | (contains? #{'& :as} form)) 41 | 42 | (defn reserved-symbol? 43 | "Checks if a string is specified in reserved symbol list." 44 | [v s] 45 | (let [strings (filter string? v) 46 | regexps (filter re? v)] 47 | (or (contains? (set strings) s) 48 | (some #(re-matches % s) regexps)))) 49 | 50 | (defn member-form? 51 | "Checks if a form is a property access or method call 52 | (a symbol starting with '.')" 53 | [form-name] 54 | (and (symbol? form-name) (= \. (first (name form-name))))) 55 | 56 | (defn new-object? 57 | "Checks if a symbol is a new object call (a symbol ending with '.')" 58 | [f] 59 | (and (symbol? f) (= \. (last (name f))))) 60 | 61 | (defn will-output-nothing? [expr] 62 | (and (coll? expr) (#{'defmacro 'load-js 'load-file 'load-file-macros} 63 | (first expr)))) 64 | 65 | (defn require-no-trailing? [expr] 66 | (and (coll? expr) (#{'do 'let 'let*} (first expr)))) 67 | -------------------------------------------------------------------------------- /test/chlorine/js_test.clj: -------------------------------------------------------------------------------- 1 | (ns chlorine.js-test 2 | (:use [chlorine.js] 3 | [clojure.test] 4 | [slingshot.slingshot] 5 | [chlorine.util])) 6 | 7 | (dosync (ref-set *macros* {})) 8 | 9 | (deftest ->camelCase-test 10 | (is (= (->camelCase "foo-bar-boo") 11 | "fooBarBoo"))) 12 | 13 | (deftest symbol-alias-tests 14 | (is (= (binding [*aliases* (ref '{foo fool})] 15 | (with-out-str 16 | (emit-symbol 'foo))) 17 | "fool"))) 18 | 19 | (deftest track-emitted-symbols-tests 20 | (is (= (binding [*core-symbols* '#{foo bar boo} 21 | *core-symbols-in-use* (ref '#{bazz})] 22 | (with-out-str 23 | (emit-symbol 'foo) 24 | (emit-symbol 'bar) 25 | (emit-symbol 'no-thing)) 26 | @*core-symbols-in-use*) 27 | '#{bazz foo bar}))) 28 | 29 | (deftest no-alias-emiting-object-members-tests 30 | (is (= (binding [*aliases* (ref '{foo fool})] 31 | (js (. bar foo))) 32 | "bar.foo()")) 33 | (is (= (binding [*aliases* (ref '{foo fool})] 34 | (js (. bar (foo 1 2)))) 35 | "bar.foo(1, 2)")) 36 | (is (= (binding [*aliases* (ref '{foo fool})] 37 | (js (foo :bar))) 38 | "fool('bar')")) 39 | (is (= (binding [*aliases* (ref '{foo fool})] 40 | (js (get* foo boo))) 41 | "fool[boo]"))) 42 | 43 | (deftest alias-tests 44 | (is (= (binding [*aliases* (ref '{foo fool})] 45 | (js (alias boo bar)) 46 | @*aliases*) 47 | '{foo fool 48 | boo bar}))) 49 | 50 | (deftest detect-form-test 51 | (is (= (detect-form '(foo 1 :x)) 52 | "foo")) 53 | (is (= (detect-form 123) 54 | :default)) 55 | (is (= (detect-form "foo") 56 | :default))) 57 | 58 | (deftest sym->property-test 59 | (is (= (sym->property :a) 60 | "'a'")) 61 | (is (= (sym->property 'b) 62 | "'b'")) 63 | (is (= (sym->property '.c) 64 | "'c'"))) 65 | 66 | (deftest emit-delimted-test 67 | (is (= (with-out-str (emit-delimited "," [1 2 3])) 68 | "1,2,3")) 69 | (is (= (with-out-str 70 | (emit-delimited ";" [1 2 3] 71 | (fn [number] (print (inc number))))) 72 | "2;3;4"))) 73 | 74 | (deftest emit-map-test 75 | (is (= (with-out-str (emit-map {:a 1 :b 2})) 76 | "{a : 1,b : 2}")) 77 | (is (= (try+ 78 | (js 79 | (with-out-str (emit-map {:a 1 "b" {'c 2}}))) 80 | (catch [:known-error true] e 81 | (:msg e))) 82 | (str "Error emitting this map `{(quote c) 2}`:\n" 83 | "Invalid map key: `(quote c)`.\n" 84 | "Valid keys are elements which can be converted " 85 | "to strings.")))) 86 | 87 | (deftest literals 88 | (is (= (js use-camel-case) "useCamelCase")) 89 | (is (= (js predicate?) "predicate_p")) 90 | (is (= (js *ear-muffs*) "__earMuffs__")) 91 | (is (= (js special*) "special__")) 92 | (is (= (js with-quote') "withQuote_q")) 93 | (is (= (js has-side-effect!) "hasSideEffect_s")) 94 | (is (= (js a->b) "aToB")) 95 | (is (= (js -) "_")) 96 | (is (= (js /) "_divide")) 97 | (is (= (js (js-divide 1 2)) "(1 / 2)")) 98 | (is (= (js :foo?) "'foo_p'")) 99 | 100 | (is (= (js {:foo 1 :bar 2 :baz 3}) "{foo : 1,bar : 2,baz : 3}")) 101 | (is (= (js #{:foo :bar :baz}) "hashSet('foo', 'bar', 'baz')")) 102 | (is (= (js [:foo :bar :baz]) "['foo','bar','baz']")) 103 | (is (= (js \newline) "'\n'")) 104 | (is (= (js ".") "\".\"")) 105 | (is (= (js "defmacro") "\"defmacro\"")) 106 | (is (= (js ".abc") "\".abc\"")) 107 | (is (= (js \a) "'a'"))) 108 | 109 | (deftest functions 110 | (is (= (js (+* 1 2 3)) "(1 + 2 + 3)")) 111 | (is (= (js (+* "foo" "bar" "baz")) "(\"foo\" + \"bar\" + \"baz\")")) 112 | (is (= (js (:test {:test 1 :foo 2 :bar 3})) 113 | "get({test : 1,foo : 2,bar : 3}, 'test')")) 114 | (is (= (js (append '(:foo bar baz) '(quux))) 115 | "append(['foo','bar','baz'], ['quux'])")) 116 | 117 | (is (= (js (fn* [a b] (+* a b))) 118 | "function (a, b) { return (a + b); }")) 119 | 120 | (is (= (js (fn* foo [a b] (+* a b))) 121 | "function foo (a, b) { return (a + b); }")) 122 | 123 | (is (= (with-pretty-print (js (fn* "Some func does stuff" [x] (+* x 1)))) 124 | (str "function (x) {\n" 125 | " /* Some func does stuff */\n" 126 | " return (x + 1);\n}"))) 127 | 128 | (is (= (with-pretty-print (js (fn* "Some func\ndoes stuff" [x] (+* x 1)))) 129 | (str "function (x) {\n" 130 | " /* Some func\n" 131 | " does stuff */\n" 132 | " return (x + 1);\n}"))) 133 | 134 | (is (= (js (fn* foo [a b] (+* a b))) 135 | "function foo (a, b) { return (a + b); }")) 136 | 137 | (is (= (js (fn* foo [c] (.methodOf c))) 138 | "function foo (c) { return c.methodOf(); }")) 139 | 140 | (is (= (js 141 | (fn* test [] 142 | (let [a 1] (log (** a a))) 143 | (let [a 2] (log (** a a))))) 144 | (str "function test () {" 145 | " (function () {" 146 | " var a = 1;" 147 | " return log((a * a));" 148 | " })();" 149 | " return (function () {" 150 | " var a = 2;" 151 | " return log((a * a));" 152 | " })() }" 153 | )))) 154 | 155 | (deftest function-with-let-inside-tests 156 | (is (= (js 157 | (fn* test [] 158 | (let [a 1] (log (** a a))) 159 | (do (log "test") (+* 1 1)))) 160 | (str "function test () {" 161 | " (function () {" 162 | " var a = 1;" 163 | " return log((a * a));" 164 | " })();" 165 | " log(\"test\");" 166 | " return (1 + 1); }" 167 | )))) 168 | 169 | (deftest normalize-dot-form-test 170 | (is (= (normalize-dot-form '.) 171 | '.)) 172 | (is (= (normalize-dot-form '.f) 173 | 'f)) 174 | (is (= (normalize-dot-form '.foo) 175 | 'foo)) 176 | (is (= (normalize-dot-form 'Foo.) 177 | 'Foo)) 178 | (is (= (normalize-dot-form 'F.) 179 | 'F))) 180 | 181 | (deftest property-access 182 | (is (= (js (get* map :some-key)) 183 | "map.someKey")) 184 | (is (= (js (:an-other-key map)) 185 | "get(map, 'anOtherKey')"))) 186 | 187 | (deftest dot-form-test 188 | (is (= (js (. foo -bar)) 189 | "foo.bar")) 190 | (is (= (js (.-bar foo)) 191 | "foo.bar")) 192 | (is (= (js (. foo bar)) 193 | "foo.bar()")) 194 | (is (= (js (. foo bar :bazz 0)) 195 | "foo.bar('bazz', 0)")) 196 | (is (= (js (.bar foo)) 197 | "foo.bar()")) 198 | (is (= (js (.bar (new foo))) 199 | "new foo().bar()")) 200 | (is (= (js (. (. a (b 1)) (c 2))) 201 | "a.b(1).c(2)"))) 202 | 203 | (deftest destructuring 204 | (is (= (js 205 | (fn* test [] 206 | (let [a 1 207 | b (+* a 1) 208 | c (+* b 1)] 209 | (+* a b c)))) 210 | (str "function test () {" 211 | " var a = 1, b = (a + 1), c = (b + 1);" 212 | " return (a + b + c); }"))) 213 | 214 | ;; & rest 215 | (is (= (js 216 | (fn* test [] 217 | (let [[a b & r] [1 2 3 4]] 218 | [(+* a b) r]))) 219 | (str "function test () {" 220 | " var _temp_1000 = [1,2,3,4]," 221 | " a = _temp_1000[0]," 222 | " b = _temp_1000[1]," 223 | " r = _temp_1000.slice(2);" 224 | " return [(a + b),r]; }"))) 225 | 226 | (is (= (js 227 | (fn* test [[a b & r]] 228 | [(+* a b) r])) 229 | (str "function test () {" 230 | " var _temp_1000 = Array.prototype.slice.call(arguments)," 231 | " _temp_1001 = _temp_1000[0]," 232 | " a = _temp_1001[0]," 233 | " b = _temp_1001[1]," 234 | " r = _temp_1001.slice(2);" 235 | " return [(a + b),r]; }"))) 236 | 237 | (is (= (js 238 | (fn* test [a b & r] 239 | [(+* a b) r])) 240 | (str "function test () {" 241 | " var _temp_1000 = Array.prototype.slice.call(arguments)," 242 | " a = _temp_1000[0]," 243 | " b = _temp_1000[1]," 244 | " r = _temp_1000.slice(2);" 245 | " return [(a + b),r]; }"))) 246 | 247 | ;; :as 248 | (is (= (js 249 | (fn* [a [b] [c d & e :as f] :as g] nil)) 250 | (str "function () {" 251 | " var _temp_1000 = Array.prototype.slice.call(arguments)," 252 | " a = _temp_1000[0]," 253 | " _temp_1001 = _temp_1000[1]," 254 | " b = _temp_1001[0]," 255 | " _temp_1002 = _temp_1000[2]," 256 | " c = _temp_1002[0]," 257 | " d = _temp_1002[1]," 258 | " e = _temp_1002.slice(2)," 259 | " f = _temp_1002," 260 | " g = _temp_1000; }") 261 | )) 262 | 263 | ;; map destructuring 264 | (is (= (js 265 | (fn* [x {y :y, fred :fred}] fred)) 266 | (str "function () {" 267 | " var _temp_1000 = Array.prototype.slice.call(arguments)," 268 | " x = _temp_1000[0]," 269 | " _temp_1001 = _temp_1000[1]," 270 | " y = get(_temp_1001, 'y')," 271 | " fred = get(_temp_1001, 'fred');" 272 | " return fred; }"))) 273 | 274 | (is (= (js 275 | (fn* [[{x :x, {z :z} :y}]] z)) 276 | (str "function () {" 277 | " var _temp_1000 = Array.prototype.slice.call(arguments)," 278 | " _temp_1001 = _temp_1000[0]," 279 | " _temp_1002 = _temp_1001[0]," 280 | " x = get(_temp_1002, 'x')," 281 | " _temp_1003 = get(_temp_1002, 'y')," 282 | " z = get(_temp_1003, 'z');" 283 | " return z; }"))) 284 | 285 | ;; numbers as keys (this actually works) 286 | (is (= (js 287 | (fn* [{a 1, b 2, :or {a 3}}])) 288 | (str "function () {" 289 | " var _temp_1000 = Array.prototype.slice.call(arguments)," 290 | " _temp_1001 = _temp_1000[0]," 291 | " a = get(_temp_1001, 1, 3)," 292 | " b = get(_temp_1001, 2); }") 293 | )) 294 | 295 | ;; :keys, :strs 296 | (is (= (js 297 | (fn* [x {y :y, z :z :keys [a b]}] z)) 298 | (str "function () {" 299 | " var _temp_1000 = Array.prototype.slice.call(arguments)," 300 | " x = _temp_1000[0]," 301 | " _temp_1001 = _temp_1000[1]," 302 | " a = get(_temp_1001, 'a')," 303 | " b = get(_temp_1001, 'b')," 304 | " y = get(_temp_1001, 'y')," 305 | " z = get(_temp_1001, 'z');" 306 | " return z; }"))) 307 | 308 | (is (= (js 309 | (fn* [x {y :y, z :z :strs [a b]}] z)) 310 | (str "function () {" 311 | " var _temp_1000 = Array.prototype.slice.call(arguments)," 312 | " x = _temp_1000[0]," 313 | " _temp_1001 = _temp_1000[1]," 314 | " a = get(_temp_1001, 'a')," 315 | " b = get(_temp_1001, 'b')," 316 | " y = get(_temp_1001, 'y')," 317 | " z = get(_temp_1001, 'z');" 318 | " return z; }"))) 319 | 320 | (is (= (js 321 | (fn* [x {y :y, z :z :or {y 1, z "foo"}}] z)) 322 | (str "function () {" 323 | " var _temp_1000 = Array.prototype.slice.call(arguments)," 324 | " x = _temp_1000[0]," 325 | " _temp_1001 = _temp_1000[1]," 326 | " y = get(_temp_1001, 'y', 1)," 327 | " z = get(_temp_1001, 'z', \"foo\");" 328 | " return z; }"))) 329 | 330 | (is (= (js 331 | (fn* [x {y :y, z :z :keys [a b] :or {a 1, y :bleh}}] z)) 332 | (str "function () {" 333 | " var _temp_1000 = Array.prototype.slice.call(arguments)," 334 | " x = _temp_1000[0]," 335 | " _temp_1001 = _temp_1000[1]," 336 | " a = get(_temp_1001, 'a', 1)," 337 | " b = get(_temp_1001, 'b')," 338 | " y = get(_temp_1001, 'y', 'bleh')," 339 | " z = get(_temp_1001, 'z');" 340 | " return z; }"))) 341 | 342 | (is (= (js 343 | (fn* [{x :x y :y :as all}] 344 | [x y all])) 345 | (str "function () {" 346 | " var _temp_1000 = Array.prototype.slice.call(arguments)," 347 | " all = _temp_1000[0]," 348 | " x = get(all, 'x')," 349 | " y = get(all, 'y');" 350 | " return [x,y,all]; }"))) 351 | ;; unsupported for now 352 | (is (= (try+ 353 | (js 354 | (fn* [x y & {z :z}] z)) 355 | (catch [:known-error true] e 356 | (:msg e))) 357 | (str "Unsupported binding form `[x y & {z :z}]`:\n" 358 | "`&` must be followed by exactly one symbol")))) 359 | 360 | (deftest loops 361 | (is (= (js 362 | (fn* join [arr delim] 363 | (loop [str (get* arr 0) 364 | i 1] 365 | (if (< i (count arr)) 366 | (recur (+* str delim (get* arr i)) 367 | (+* i 1)) 368 | str)))) 369 | (str "function join (arr, delim) {" 370 | " for (var str = arr[0], i = 1; true;) {" 371 | " if ((i < count(arr))) {" 372 | " var _temp_1000 = [(str + delim + arr[i]),(i + 1)];\n" 373 | " str = _temp_1000[0];" 374 | " i = _temp_1000[1];" 375 | " continue; }" 376 | " else {" 377 | " return str; };" 378 | " break; }; }")))) 379 | 380 | (deftest inline-if 381 | (is (= (js 382 | (fn* test [a] 383 | ((if (> a 0) minus plus) a 1))) 384 | "function test (a) { return (((a > 0) ? minus : plus))(a,1); }")) 385 | 386 | (is (= (js (fn* test [a] (console.log (if (> a 0) a)))) 387 | (str "function test (a) {" 388 | " return console.log(" 389 | "((a > 0) ? a : void(0))); }") 390 | ))) 391 | 392 | (deftest if-tests 393 | (is (= (js (if a b c)) 394 | "if (a) { b; } else { c; }")) 395 | (is (= (js (if :a b c)) 396 | "if ('a') { b; } else { c; }")) 397 | (is (= (js (def x (if x :a :b))) 398 | "var x = (x ? 'a' : 'b')")) 399 | (is (= (js (def x (if true :a :b))) 400 | "var x = (true ? 'a' : 'b')")) 401 | (is (= (js (fn* [] (if a b c))) 402 | "function () { if (a) { return b; } else { return c; }; }")) 403 | (is (= (js (fn* [] (if :true b c))) 404 | "function () { if ('true') { return b; } else { return c; }; }")) 405 | ) 406 | 407 | (deftest inline-primitives 408 | (is (= (js (fn* foo [i c] (inline "i instanceof c"))) 409 | "function foo (i, c) { return i instanceof c; }"))) 410 | 411 | (deftest case-tests 412 | (is (= (with-pretty-print (js (case answer 42 (bingo) 24 (tiny)))) 413 | (str "switch (answer) {\n" 414 | " case 42:\n" 415 | " bingo();\n" 416 | " break;\n" 417 | " case 24:\n" 418 | " tiny();\n" 419 | " break;\n" 420 | "}"))) 421 | (is (= (js (case answer 42 (bingo) 24 (tiny))) 422 | (str "switch (answer) {" 423 | " case 42: bingo(); break; case 24: tiny(); break; }"))) 424 | (is (= (js (fn* foo [answer] (case answer 42 (bingo) 24 (tiny)))) 425 | (str "function foo (answer) {" 426 | " switch (answer) {" 427 | " case 42: return bingo(); " 428 | " case 24: return tiny(); }; }"))) 429 | (is (= (with-pretty-print (js (def foo (case answer 42 (bingo) 24 (tiny))))) 430 | (str "var foo = (function(){\n" 431 | " switch (answer) {\n" 432 | " case 42:\n" 433 | " return bingo();\n" 434 | " \n" 435 | " case 24:\n" 436 | " return tiny();\n" 437 | " \n" 438 | " }})()"))) 439 | (is (= (js (case answer (+* 10 20) (bingo))) 440 | "switch (answer) { case (10 + 20): bingo(); break; }")) 441 | (is (= (js (case answer "text" (foo) (+* 10 20) (bingo))) 442 | (str "switch (answer) {" 443 | " case \"text\": foo(); break;" 444 | " case (10 + 20): bingo(); break; }"))) 445 | (is (= (js (case answer 1 :one 2 :two "anything else")) 446 | (str "switch (answer) {" 447 | " case 1: 'one'; break;" 448 | " case 2: 'two'; break;" 449 | " default: \"anything else\"; }")))) 450 | 451 | (deftest do-test 452 | (is (= (with-pretty-print 453 | (binding [*inline-if* true] 454 | (js (do 1 2 3)))) 455 | (str "(function(){\n" 456 | " \n" 457 | " 1;\n" 458 | " 2;\n" 459 | " return 3;})()"))) 460 | (is (= (binding [*inline-if* false] 461 | (js (do 1 2 3))) 462 | " 1; 2; 3;"))) 463 | 464 | (deftest try-catch-finally 465 | (is (= (js 466 | (fn* test [] 467 | (try 468 | (js-divide 5 0) 469 | (catch ex 470 | (console.log ex)) 471 | (finally 472 | 0)))) 473 | (str "function test () {" 474 | " try { return (5 / 0); } catch (ex) {" 475 | " return console.log(ex); }" 476 | " finally {" 477 | " return 0; }; }"))) 478 | 479 | (is (= (js 480 | (fn* test [a] 481 | (if (< a 0) (throw (new Error "Negative numbers not accepted"))))) 482 | (str "function test (a) {" 483 | " if ((a < 0)) {" 484 | " throw new Error(\"Negative numbers not accepted\"); }; }")))) 485 | 486 | (deftest let-tests 487 | (is (= (js (def x (let [y 3] y))) 488 | "var x = (function () { var y = 3; return y; })()")) 489 | (is (= (js (fn* [] (let [x 1 y 2] (plus x y)))) 490 | "function () { var x = 1, y = 2; return plus(x, y); }")) 491 | (is (= (js (fn* [] (let [x 1 y 2] (plus x y)) 3)) 492 | (str "function () {" 493 | " (function () {" 494 | " var x = 1, y = 2; return plus(x, y); })();" 495 | " return 3; }"))) 496 | (is (= (js (let [m {:test 1 :foo 2 :bar 3}] (list m 4))) 497 | (str "(function () { var m = {test : 1,foo : 2,bar : 3};" 498 | " return list(m, 4); })();")))) 499 | 500 | (deftest js-let-test 501 | (is (= (js-let [a 2 b 3] (+* a b)) 502 | " (function (a, b) { return (a + b); })(2,3);"))) 503 | 504 | (deftest let-js-test 505 | (is (= (let-js [foo 1] 506 | `(def x ~foo)) 507 | "var x = 1"))) 508 | 509 | (deftest new-and-delete-tests 510 | (is (= (js (Foo. :bar)) 511 | "new Foo('bar')")) 512 | (is (= (js (new bar boo buzz)) 513 | "new bar(boo,buzz)")) 514 | (is (= (js (delete foo)) 515 | "delete foo"))) 516 | 517 | (borrow-macros '..) 518 | (deftest expand-macro-test 519 | (is (= (expand-macro-1 'foo) 520 | 'foo)) 521 | (is (= (expand-macro-1 '(.. foo bar)) 522 | '(. foo bar))) 523 | (is (= (expand-macro '(.. foo (bar) (buzz))) 524 | '(. (. foo (bar)) (buzz))))) 525 | 526 | (deftest macroexpand-1-test 527 | (is (= (js (macroexpand-1 (. foo (bar)))) 528 | "\"(. foo (bar))\"")) 529 | (is (= (js (macroexpand-1 (.. foo (bar) (buzz)))) 530 | "\"(.. (. foo (bar)) (buzz))\"")) 531 | (is (= (js (macroexpand (.. foo (bar) (buzz)))) 532 | "\"(. (. foo (bar)) (buzz))\"")) 533 | ;; ensures namespaces are removed 534 | (is (= (js (macroexpand (some-namespace/str foo (bar) (buzz)))) 535 | "\"(str foo (bar) (buzz))\""))) 536 | 537 | (deftest dofor-test 538 | (is (= (js 539 | (dofor [(let* i 0 540 | j 1) 541 | (< i 5) 542 | (set! i (+* i 1))] 543 | 1)) 544 | "for ( var i = 0, j = 1; (i < 5); i = (i + 1);) { 1; }")) 545 | (is (= (js 546 | (dofor [(def i 0) 547 | (< i 5) 548 | (set! i (+* i 1))] 549 | 1)) 550 | "for ( var i = 0; (i < 5); i = (i + 1);) { 1; }")) 551 | (is (= (js 552 | (dofor [[i 0 j 1] 553 | (< i 5) 554 | (set! i (+* i 1))] 555 | 1)) 556 | "for ( var i = 0, j = 1; (i < 5); i = (i + 1);) { 1; }"))) 557 | 558 | (deftest regexp-test 559 | (is (= (js #"foo") 560 | "/foo/")) 561 | (is (= (js #"(?i)foo") 562 | "/foo/i")) 563 | (is (= (js #"^([a-z]*)([0-9]*)") 564 | "/^([a-z]*)([0-9]*)/"))) 565 | 566 | (deftest do-while-test 567 | (is (= (js 568 | (do-while (< x 10) 569 | (set! x (+* x 1)))) 570 | "do { x = (x + 1); } while ((x < 10))" 571 | )) 572 | (is (= (js 573 | (do-while (and (< x 10) (> x 5)) 574 | (set! x (+* x 1)))) 575 | "do { x = (x + 1); } while (((x < 10) && (x > 5)))" 576 | ))) 577 | 578 | (deftest require*-test 579 | (is (= (js 580 | (require* "foo.js")) 581 | "require(\"foo.js\")"))) 582 | -------------------------------------------------------------------------------- /src/chlorine/js.clj: -------------------------------------------------------------------------------- 1 | (ns chlorine.js 2 | (:require [clojure.string :as str] 3 | [hiccup.core] 4 | [clojure.walk]) 5 | (:use [chlorine.reader] 6 | [slingshot.slingshot] 7 | [pathetic.core :only [normalize url-normalize]] 8 | [chlorine.util 9 | :only [url? resource-path? to-resource unzip assert-args 10 | *cwd* *paths* get-dir find-in-paths 11 | re? replace-map]])) 12 | 13 | (defn ->camelCase [^String method-name] 14 | (str/replace method-name #"-(\w)" 15 | #(str/upper-case (second %1)))) 16 | 17 | (def ^:dynamic *print-pretty* false) 18 | 19 | (def ^:dynamic *object-member* false) 20 | 21 | (defmacro with-pretty-print [& body] 22 | `(binding [*print-pretty* true] 23 | ~@body)) 24 | 25 | (def ^:dynamic *indent* 0) 26 | 27 | (defmacro with-indent [[& increment] & body] 28 | `(binding [*indent* (+ *indent* (or ~increment 4))] 29 | ~@body)) 30 | 31 | (def ^:dynamic *in-block-exp?* false) 32 | 33 | (defmacro with-block [& body] 34 | `(binding [*in-block-exp?* true] 35 | ~@body)) 36 | 37 | (defmacro with-bracket-block [& body] 38 | `(with-parens ["{" "}"] 39 | (with-block 40 | (with-indent [] ~@body)) 41 | (newline-indent))) 42 | 43 | (defn newline-indent [] 44 | (if *print-pretty* 45 | (do 46 | (newline) 47 | (print (apply str (repeat *indent* " ")))) 48 | (print " "))) 49 | 50 | (defmacro with-parens [[& [left right]] & body] 51 | `(do 52 | (print (or ~left "(")) 53 | ~@body 54 | (print (or ~right ")")))) 55 | 56 | (def ^:dynamic *inline-if* false) 57 | (def ^:dynamic *quoted* false) 58 | 59 | (def ^:dynamic *in-fn-toplevel* true) 60 | (def ^:dynamic *unique-return-expr* false) 61 | 62 | ;; Chlorinejs transforms Clojure code/data to Javascript equivalents. 63 | ;; 64 | ;; Normal data such as strings, numbers, keywords, symbols, vectors, (quoted) 65 | ;; lists are transformed by associated emitters of their type. 66 | ;; 67 | ;; Functions, macros and special forms (including javascript native ones) 68 | ;; share the same looks: they are unquoted lists whose first element is the 69 | ;; form name and require one more step: looking up by the names to detect 70 | ;; their types. 71 | 72 | (defn detect-form 73 | "Detects macro/function/special form names from expressions 74 | for further processing. Used as dispatch function for chlorine.js/emit, the 75 | most hardworking multi-method in chlorine library." 76 | [expr] 77 | (let [expr (if (and (coll? expr) (seq expr)) (first expr) expr)] 78 | (if (symbol? expr) (name expr) :default))) 79 | 80 | (defn normalize-dot-form 81 | "Normalizes dot forms or new-object forms by removing \".\" from their 82 | beginnings or endings." 83 | [form] 84 | (cond (and (.startsWith (name form) ".") 85 | (< 1 (count (name form)))) 86 | (symbol (subs (name form) 1)) 87 | 88 | (and (.endsWith (name form) ".") 89 | (< 1 (count (name form)))) 90 | (symbol (apply str (drop-last (str form)))) 91 | :default 92 | form)) 93 | 94 | (declare emit-str) 95 | (declare emit-symbol) 96 | (declare tojs') 97 | 98 | (defn sym->property 99 | "Transforms symbol or keyword into object's property access form." 100 | [s] 101 | (binding [*quoted* true] 102 | (emit-str 103 | (if (member-form? s) 104 | (symbol (subs (name s) 1)) 105 | s)))) 106 | 107 | (defmulti emit 108 | "Receives forms, emits javascript expressions." 109 | detect-form) 110 | 111 | (defn emit-delimited 112 | "Emit sequences with delimiters. Useful to emit javascript arrays, 113 | function arguments etc." 114 | [delimiter args & [emitter]] 115 | (when-not (empty? args) 116 | ((or emitter emit) (first args)) 117 | (doseq [arg (rest args)] 118 | (print delimiter) 119 | ((or emitter emit) arg)))) 120 | 121 | ;; several functions to emit Clojure data of 122 | ;; map, set, vector, regexp, symbol and keyword types 123 | 124 | (defn emit-map 125 | "Clojure maps are emitted to javascript key/value objects. 126 | Keys can only be strings. Keywords and quoted symbols don't really make 127 | sense in Chlorinejs and that's why they are emitted to plain strings." 128 | [expr] 129 | (with-parens ["{" "}"] 130 | (binding [*inline-if* true] 131 | (emit-delimited 132 | "," 133 | (seq expr) 134 | (fn [[key val]] 135 | (cond 136 | (keyword? key) 137 | (emit-symbol key) 138 | 139 | (or (string? key) 140 | (number? key)) 141 | (emit key) 142 | 143 | :default 144 | (throw+ {:known-error true 145 | :msg 146 | (str "Error emitting this map `" 147 | expr "`:\n" 148 | "Invalid map key: `" key "`.\n" 149 | "Valid keys are elements which can be" 150 | " converted to strings.") 151 | :causes [expr key]})) 152 | (print " : ") 153 | (emit val)))))) 154 | 155 | (defn emit-set 156 | "Clojure sets are emitted to javascript key/value objects whose 157 | values are all `true`. These 'sets' objects as javascript nature will have 158 | distinct elements (the keys) which can be checked by `contains?` (javascript's 159 | `in`). Please remember, all set elements are coerced to strings by javascript. 160 | That means, both `(contains? 5 {:a 1 \"5\" 2})` and 161 | `(contains? \"5\" {:b 3 5 4} will return true." 162 | [expr] 163 | (emit `(hash-set ~@(seq expr)))) 164 | 165 | (defn emit-vector 166 | "Clojure vectors and quoted lists are emitted as javascript arrays." 167 | [expr] 168 | (with-parens ["[" "]"] 169 | (binding [*inline-if* true] 170 | (emit-delimited "," (seq expr))))) 171 | 172 | (defn emit-re [expr] 173 | (let [[_ flags pattern] (re-find #"^(?:\(\?([idmsux]*)\))?(.*)" (str expr))] 174 | (print (str \/ (.replaceAll (re-matcher #"/" pattern) "\\\\/") \/ flags)))) 175 | 176 | ;; Symbols are Chlorine's amazing pieces. We have a wide range of valid 177 | ;; characters for Chlorine just like Clojure. You can use Lisp-style naming 178 | ;; conventions such as "?" endings for predicate functions. 179 | 180 | ;; You can tell ChlorineJS to emit a symbol as if it's an other one by 181 | ;; using aliases 182 | (def ^:dynamic *aliases* 183 | (ref '{;; `int` and `boolean` are reserved symbols in js. 184 | ;; They're also function names in Clojure and Chlorine core 185 | ;; library. 186 | int int* 187 | boolean boolean* 188 | 189 | ;; Chlorine uses a Clojure-like syntax of `(require ...)` 190 | ;; to load nodejs/browserify. It's implemented as macro which 191 | ;; expands to the lower level `require*`. `require*` in turn 192 | ;; emitted as javascript `require()` 193 | require* require 194 | })) 195 | 196 | ;; Because javascript doesn't allow such characters, the function 197 | ;; `chlorine.util/replace-map` will be used to replace all Clojure-only 198 | ;; characters to javascript-friendly ones. 199 | 200 | ;; The mapping used to do the replacements 201 | (def ^:dynamic *symbol-map* 202 | (array-map 203 | "?" "_p" 204 | "*" "__" 205 | "'" "_q" 206 | "!" "_s" 207 | "+" "_plus" 208 | "/" "_divide" 209 | "=" "_eq" 210 | "->" "-to-" 211 | "<-" "-from-" 212 | ">" "-gt-" 213 | "<" "-lt-" 214 | ">=" "-ge-" 215 | "=<" "-le-" 216 | "#" "_h" 217 | "%" "$P100$" 218 | "&" "-and-" 219 | )) 220 | 221 | (defn hyphen->underscore [s] 222 | (str/escape s {\- \_})) 223 | ;; You can also specify "reserved symbols", which are NOT affected by 224 | ;; `replace-map`. 225 | ;; For example: [;;#"^\$.*" #"^\.\$.*"] 226 | 227 | (def ^:dynamic *reserved-symbols* []) 228 | 229 | (def ^:dynamic *core-symbols* #{}) 230 | (def ^:dynamic *core-symbols-in-use* (ref #{})) 231 | 232 | (defn emit-symbol 233 | "Emits Clojure symbols to javascript ones. If the symbol is quoted, emits its 234 | name as a string. Does some replacements with characters not supported by 235 | javascript if the symbol isn't marked as reserved ones." 236 | [expr] 237 | (let [sym-name (name expr)] 238 | (print 239 | (let [output-string 240 | (if (or (reserved-symbol? *reserved-symbols* sym-name) 241 | *object-member*) 242 | sym-name 243 | (-> (or (get @*aliases* (symbol sym-name)) 244 | sym-name) 245 | (replace-map *symbol-map*) 246 | ->camelCase 247 | hyphen->underscore)) 248 | output-sym (symbol output-string)] 249 | (if (and (contains? *core-symbols* output-sym) 250 | (not (contains? @*core-symbols-in-use* output-sym))) 251 | (dosync (alter *core-symbols-in-use* 252 | conj output-sym))) 253 | (if *quoted* 254 | (format "'%s'" output-string) 255 | output-string))))) 256 | 257 | (defn emit-keyword 258 | "Emits Clojure keywords. Uses emit-symbol as backend." 259 | [expr] 260 | (binding [*quoted* true] 261 | (emit-symbol expr))) 262 | 263 | ;; Some Chlorine forms are converted directly to javascript native 264 | ;; operators: unary and infix ones. 265 | 266 | ;; Unary operators in Chlorine: "!" 267 | ;; Infix operators are consist of 268 | ;; - `instance?` special form 269 | ;; - and/or macros 270 | ;; - some low-level math operators such as plus (+*), minus (-*), multiple (**) 271 | ;; and remainder (rem) 272 | ;; - low-level comparator === which can't work on vectors and maps 273 | ;; - binary operators 274 | ;; 275 | ;; Please use high-level functions from Chlorine's core library instead of 276 | ;; low-level ones" 277 | 278 | (defn emit-unary-operator 279 | [op arg] 280 | (print (name op)) 281 | (emit arg)) 282 | 283 | (defn emit-infix-operator 284 | [op & args] 285 | (let [clj->js {"instance?" "instanceof" 286 | "and" "&&" 287 | "or" "||" 288 | "=*" "===" 289 | "+*" "+" 290 | "-*" "-" 291 | "**" "*" 292 | "js-divide" "/" 293 | "rem" "%" 294 | "bit-and" "&" 295 | "bit-or" "|" 296 | "bit-xor" "^" 297 | "bit-not" "~" 298 | "bit-shift-left" "<<" 299 | "bit-shift-right" ">>" 300 | "bit-shift-right-zero-fill" ">>>"} 301 | js-op (get clj->js (name op) (name op))] 302 | (binding [*unique-return-expr* false 303 | *in-fn-toplevel* false] 304 | (with-parens [] 305 | (emit-delimited (str " " js-op " ") args))))) 306 | 307 | (defn property->member 308 | "Removes `-` prefix in a property name to bring it a member look." 309 | [property] 310 | (symbol (subs (name property) 1))) 311 | 312 | (defn emit-function-call 313 | "Emits a function call by simply emitting the function name and its arguments 314 | in parentheses." 315 | [fun & args] 316 | (emit fun) 317 | (with-parens [] 318 | (with-indent [] (emit-delimited ", " args)))) 319 | 320 | (defn emit-invoke-function 321 | "Like emit-function-call, but wraps the function in parentheses. Used to 322 | emit function calls where function is not a symbol but an other form instead." 323 | [fun & args] 324 | (with-parens [] (emit fun)) 325 | (with-parens [] (emit-delimited "," args))) 326 | 327 | ;; All Clojure forms return something (even nil). Javascript is imperative 328 | ;; and its forms may or may not return values. Javascript function bodies 329 | ;; require a manual `return` keyword. 330 | ;; 331 | ;; That's why we create this dynamic var with initial value `false`, 332 | ;; change its value to `true` where necessary and "consume" `true` values 333 | ;; (print "return" and set the var back to `false`) 334 | (def ^:dynamic *return-expr* false) 335 | 336 | (defmacro with-return-expr 337 | "Consumes *return-expr* `true` states or sets it to a new value." 338 | [[& [new-val]] & body] 339 | `(binding [*return-expr* (if *return-expr* 340 | (do 341 | (print "return ") 342 | false) 343 | (or ~new-val false))] 344 | ~@body)) 345 | 346 | (defn emit-function-form 347 | "Emits function forms such as: unary and infix operator calls, 348 | applying keyword on a map, method calls, creating new object calls, 349 | and normal function calls." 350 | [form] 351 | (binding [*inline-if* true 352 | *unique-return-expr* false 353 | *in-fn-toplevel* false] 354 | (let [[fun & args] form] 355 | (cond 356 | ;; those are not normal function calls 357 | (unary-operator? fun) (apply emit-unary-operator form) 358 | 359 | (infix-operator? fun) (apply emit-infix-operator form) 360 | 361 | (keyword? fun) 362 | (let [[map & default] args] 363 | (emit `(get ~map ~fun ~@default))) 364 | 365 | (member-form? fun) 366 | (let [[object & margs] args] 367 | (emit `(. ~object ~(normalize-dot-form fun) ~@margs))) 368 | 369 | (new-object? fun) 370 | (emit 371 | `(new ~(normalize-dot-form fun) 372 | ~@args)) 373 | 374 | ;; Normal function calls: 375 | ;; - Ensures caller are in parentheses by using `emit-invoke-function` 376 | ;; instead of `emit-function-call` in case the caller is not simply 377 | ;; a symbol. 378 | (coll? fun) (apply emit-invoke-function form) 379 | 380 | true (apply emit-function-call form))))) 381 | 382 | (defn emit-statement 383 | "Emits an expression with trailing `;` and `newline-indent` if necessary." 384 | [expr] 385 | (try+ 386 | (binding [*inline-if* false] 387 | (if (will-output-nothing? expr) 388 | (emit expr) 389 | (do 390 | (newline-indent) 391 | (emit expr) 392 | (when-not (require-no-trailing? expr) 393 | (print ";"))))) 394 | (catch map? e 395 | (throw+ (merge e 396 | {:causes (conj (or (:causes e) []) 397 | expr)}))) 398 | (catch Throwable e 399 | (throw+ {:known-error false 400 | :msg (.getMessage e) 401 | :causes [expr] 402 | :trace e})))) 403 | 404 | (defn emit-statements [exprs] 405 | (doseq [expr exprs] 406 | (emit-statement expr))) 407 | 408 | (defn emit-statements-with-return 409 | "Emits statements with the manual `return` added in front of the 410 | last expression. If the last expression in `nil`, ignores it." 411 | [exprs] 412 | (binding [*return-expr* false] 413 | (doseq [expr (butlast exprs)] 414 | (emit-statement expr))) 415 | (when (not= 'nil (last exprs)) 416 | (emit-statement (last exprs)))) 417 | 418 | ;; Lispers love macros. In fact, making a macro means writing a function 419 | ;; that returns some code. Chlorine macros are nearly Clojure ones: 420 | ;; you can write the function (by `defmacro`) using all Clojure expressions, 421 | ;; even ones from external Clojure libraries (if you've already loaded them). 422 | ;; The only difference is that the generated code is treated as Chlorine one. 423 | 424 | ;; When defined, new macros are added to a ref holding a map. The map keys 425 | ;; are macro names while the values are the macro functions (the one that 426 | ;; generates code). 427 | (def ^:dynamic *macros* (ref {})) 428 | 429 | (defn macro? 430 | "Checks if a macro with that name is defined." 431 | [n] (and (symbol? n) (contains? @*macros* (name n)))) 432 | 433 | (defn get-macro 434 | "Gets the macro function by its name in order to generate code." 435 | [n] (and (symbol? n) (get @*macros* (name n)))) 436 | 437 | (defn undef-macro 438 | "Removes a macro from known macro list." 439 | [n] 440 | (when (macro? n) 441 | (when *print-pretty* (println "// undefining macro" n)) 442 | (dosync (alter *macros* dissoc (name n))))) 443 | 444 | (defmethod emit "defmacro" [[_ mname & mdeclrs]] 445 | (try+ 446 | (let [mdeclrs (if (string? (first mdeclrs)) 447 | (rest mdeclrs) 448 | mdeclrs)] 449 | (when *print-pretty* (println "// defining macro" mname)) 450 | (dosync 451 | (alter *macros* 452 | assoc 453 | (name mname) 454 | (eval `(clojure.core/fn ~@mdeclrs))))) 455 | (catch Throwable e 456 | (throw+ {:known-error true 457 | :msg (str "Error defining macro `" mname "`:\n" 458 | (.getMessage e)) 459 | :causes [`(~_ ~mname ~@mdeclrs)] 460 | :trace e}))) 461 | ;; returns `nil` because output are consumed by `with-out-str` 462 | nil) 463 | 464 | (defn borrow-macros 465 | "Many Clojure macros work the same in Chlorine. Use this function to reuse 466 | them instead of rewriting." 467 | [& syms] 468 | (doseq [sym syms] 469 | (dosync 470 | (alter *macros* conj 471 | {(name sym) 472 | (try+ 473 | (fn [& args#] 474 | (apply (resolve sym) (concat [nil nil] args#))) 475 | (catch Throwable e 476 | (throw+ {:known-error true 477 | :msg (str "Error borrowing macro `" sym "`:\n" 478 | (.getMessage e)) 479 | :causes [`(borrow-macros ~sym)] 480 | :trace e})))})))) 481 | 482 | (defn expand-macro-1 483 | "Gets and executes macro function, returns the Chlorine code." 484 | [form] 485 | (if (seq? form) 486 | (let [[mac-name & args] form] 487 | (if-let [mac (get-macro mac-name)] 488 | (try+ 489 | (apply mac args) 490 | (catch Throwable e 491 | (throw+ {:known-error true 492 | :msg (str "Error expanding macro `" form "`:\n" 493 | (.getMessage e)) 494 | :causes [form] 495 | :trace e}))) 496 | form)) 497 | form)) 498 | 499 | (defn expand-macro 500 | "Repeatedly calls expand-macro-1 on form until it no longer 501 | represents a macro form, then returns it. Note neither 502 | expand-macro-1 nor expand-macro expand macros in subforms." 503 | [form] 504 | (let [ex (expand-macro-1 form)] 505 | (if (identical? ex form) 506 | form 507 | (expand-macro ex)))) 508 | 509 | (defn emit-macro-expansion 510 | "Gets and executes macro function, emits the result as Chlorine code." 511 | [form] 512 | (emit (expand-macro-1 form))) 513 | 514 | (defn emit-docstring 515 | "Prints docstrings as javascript comments." 516 | [docstring] 517 | (when *print-pretty* 518 | (let [lines (str/split-lines docstring)] 519 | (newline-indent) 520 | (print (str "/* " (first lines))) 521 | (doseq [line (rest lines)] 522 | (newline-indent) 523 | (print (str " " line))) 524 | (print " */")))) 525 | 526 | ;; ChlorineJS produces a lot of temporary javascript symbols. To ensure 527 | ;; all these symbols are unique, we use this counter 528 | (def ^:dynamic *temp-sym-count* nil) 529 | 530 | (defn tempsym 531 | "Generates an unique temporary symbol." 532 | [] 533 | (dosync 534 | (ref-set *temp-sym-count* (+ 1 @*temp-sym-count*)) 535 | (symbol (str "_temp_" @*temp-sym-count*)))) 536 | 537 | ;; Chlorine supports the following Clojure binding forms: 538 | ;; - Basic binding with just a single symbol 539 | ;; - Destructuring binding with sequences or maps 540 | 541 | (defn- emit-simple-binding [vname val] 542 | (emit (if (ignorable-arg? vname) (tempsym) vname)) 543 | (print " = ") 544 | (binding [*inline-if* true] 545 | (emit val))) 546 | 547 | (declare emit-var-bindings 548 | emit-destructured-seq-binding 549 | emit-destructured-map-binding) 550 | 551 | (defn- emit-binding [vname val] 552 | (binding [*inline-if* true] 553 | (let [emitter (cond 554 | (vector? vname) emit-destructured-seq-binding 555 | (map? vname) emit-destructured-map-binding 556 | :else emit-simple-binding)] 557 | (emitter vname val)))) 558 | 559 | ;; Note on choice of get/get* in destructuring: 560 | ;; - destructuring seq use `get*` for faster array access 561 | ;; - destructuring map use `get` function which works correctly 562 | ;; on maps and supports default value when not found. 563 | (defn- emit-destructured-seq-binding [vvec val] 564 | (let [temp (tempsym)] 565 | (print (str temp " = ")) 566 | (emit val) 567 | (loop [vseq vvec, i 0, seen-rest? false] 568 | (when (seq vseq) 569 | (let [vname (first vseq) 570 | vval (second vseq)] 571 | (print ", ") 572 | (condp = vname 573 | '& (cond 574 | seen-rest? 575 | (throw+ 576 | {:known-error true 577 | :msg (str "Unsupported binding form `" vvec "`:\n" 578 | "only `:as` can follow `&`") 579 | :causes [vvec]}) 580 | (not (symbol? vval)) 581 | (throw+ 582 | {:known-error true 583 | :msg 584 | (str "Unsupported binding form `" vvec "`:\n" 585 | "`&` must be followed by exactly one symbol") 586 | :causes [vvec]}) 587 | :else 588 | (do (emit-binding vval `(.slice ~temp ~i)) 589 | (recur (nnext vseq) (inc i) true))) 590 | :as (cond 591 | (not= (count (nnext vseq)) 0) 592 | (throw+ 593 | {:known-error true 594 | :msg (str "Unsupported binding form `" vvec "`:\n" 595 | "nothing may follow after `:as `") 596 | :causes [vvec]}) 597 | (not (symbol? vval)) 598 | (throw+ 599 | {:known-error true 600 | :msg (str "Unsupported binding form, `" vvec "`:\n" 601 | "`:as` must be followed by a symbol") 602 | :causes [vvec]}) 603 | :else 604 | (emit-binding vval temp)) 605 | (do (emit-binding vname `(get* ~temp ~i)) 606 | (recur (next vseq) (inc i) seen-rest?)))))))) 607 | 608 | (defn- emit-destructured-map-binding [vmap val] 609 | (let [temp (or (:as vmap) (tempsym)) 610 | defaults (get vmap :or) 611 | keysmap (reduce #(assoc %1 %2 (keyword %2)) 612 | {} 613 | (mapcat vmap [:keys :strs :syms])) 614 | vmap (merge (dissoc vmap :as :or :keys :strs :syms) keysmap)] 615 | (print (str temp " = ")) 616 | (emit val) 617 | (doseq [[vname vkey] vmap] 618 | (print ", ") 619 | (cond 620 | (not (and (binding-form? vname) 621 | (or (some #(% vkey) #{keyword? number? binding-form?})))) 622 | (throw+ 623 | {:known-error true 624 | :msg (str "Unsupported binding form `" vmap "`:\n" 625 | "binding symbols must be followed by keywords or numbers") 626 | :causes [vmap]}) 627 | 628 | :else 629 | (if-let [[_ default] (find defaults vname)] 630 | (emit-binding vname `(get ~temp ~vkey ~default)) 631 | (emit-binding vname `(get ~temp ~vkey))))))) 632 | 633 | (defn- emit-var-bindings [bindings] 634 | (binding [*return-expr* false] 635 | (emit-delimited 636 | ", " 637 | (partition 2 bindings) 638 | (fn [[vname val]] 639 | (emit-binding vname val))))) 640 | 641 | (defn- emit-function [fdecl] 642 | (let [[fname fdecl] (if (symbol? (first fdecl)) 643 | [(first fdecl) (rest fdecl)] 644 | [nil fdecl]) 645 | docstring (if (string? (first fdecl)) 646 | (first fdecl) 647 | nil) 648 | fdecl (if (string? (first fdecl)) 649 | (rest fdecl) 650 | fdecl) 651 | args (first fdecl) 652 | dargs? (or (some destructuring-form? args) 653 | (some binding-special? args) 654 | (some ignorable-arg? args)) 655 | body (rest fdecl)] 656 | (assert-args fn 657 | (vector? args) "a vector for its bindings") 658 | (if dargs? 659 | (do 660 | (print "function ") 661 | (if fname (do (emit-symbol fname) (print " "))) 662 | (print "() {") 663 | (with-indent [] 664 | (newline-indent) 665 | (print "var ") 666 | (emit-binding args '(Array.prototype.slice.call arguments)) 667 | (print ";"))) 668 | (do 669 | (print "function ") 670 | (if fname (do (emit-symbol fname) (print " "))) 671 | (print "(") 672 | (binding [*return-expr* false] (emit-delimited ", " args)) 673 | (print ") {"))) 674 | (with-indent [] 675 | (when docstring 676 | (emit-docstring docstring)) 677 | (binding [*return-expr* true 678 | *unique-return-expr* (when (= 1 (count body)) true) 679 | *in-fn-toplevel* false] 680 | (emit-statements-with-return body) 681 | )) 682 | (newline-indent) 683 | (print "}"))) 684 | 685 | ;; We define local vars with `def` 686 | (defmethod emit "def" [[_ name value]] 687 | (print "var ") 688 | (emit-symbol name) 689 | (print " = ") 690 | (binding [*inline-if* true] 691 | (emit value))) 692 | 693 | (defmethod emit "alias" [[_ sym other]] 694 | (when *print-pretty* (println "// alias" sym "as" other)) 695 | (dosync 696 | (alter *aliases* assoc sym other))) 697 | 698 | ;; Macro expansions are useful in REPL. 699 | ;; macroexpand-1 and macroexpand work the like in Clojure except: 700 | ;; - they're special forms, not functions and receive unquoted Chlorine forms 701 | ;; instead of quoted ones like in Clojure. 702 | ;; - they print out code as strings because javascript is not a Lisp. 703 | ;; - namespaces don't make sense in ChlorineJS so they're automatically removed. 704 | 705 | (defn remove-namespaces 706 | "Removes all namespaces in forms using clojure.walk/postwalk." 707 | [forms] 708 | (clojure.walk/postwalk 709 | (fn [x] (if (symbol? x) (symbol (name x)) x)) 710 | forms)) 711 | 712 | (defmethod emit "macroexpand-1" [[_ form]] 713 | (emit (pr-str (remove-namespaces (expand-macro-1 form))))) 714 | 715 | (defmethod emit "macroexpand" [[_ form]] 716 | (emit (pr-str (remove-namespaces (expand-macro form))))) 717 | 718 | ;; Low-level function form. Please use `fn` and `defn` macros instead 719 | (defmethod emit "fn*" [[_ & fdecl]] 720 | (with-return-expr [] 721 | (with-block (emit-function fdecl)))) 722 | 723 | ;; Javascript's `if` expressions don't return values directly [1]. That's 724 | ;; opposite to Clojure/Chlorine where returning something is a must 725 | ;; (functional programming means efficiency!) 726 | ;; Just keep writing `(if exprs)` as usual, and ChlorineJS will determine 727 | ;; whether an `if` expression should return a value *directly* or not. 728 | ;; If 'yes', outputs the "inline" syntax as following: 729 | ;; `{{test}} ? {{consequent}}: {{alternate}}` 730 | ;; 731 | ;; [1]: Javascript's `if` with `return` in it is for the upper 732 | ;; function but itself 733 | 734 | (defn emit-inline-if 735 | [test consequent alternate] 736 | (with-return-expr [] 737 | (with-parens [] 738 | (emit test) 739 | (print " ? ") 740 | (emit consequent) 741 | (print " : ") 742 | (emit alternate)))) 743 | ;; If 'no', traditional javascript `if` will be used instead. 744 | (defn emit-block-if [test consequent alternate] 745 | (print "if (") 746 | (binding [*return-expr* false 747 | *inline-if* true] 748 | (emit test)) 749 | (print ") ") 750 | (with-bracket-block 751 | (with-indent [] 752 | (emit-statement consequent))) 753 | ;; alternate might be `0`, which js equates as `nil` 754 | (when-not (or (nil? alternate) 755 | (= '(clojure.core/cond) 756 | alternate)) 757 | (print " else ") 758 | (with-bracket-block 759 | (with-indent [] 760 | (emit-statement alternate))))) 761 | 762 | (defmethod emit "if" [[_ test consequent & [alternate]]] 763 | ;; emit consequent directly without printing checks 764 | ;; used to optimize `cond` macro output 765 | (if (and *inline-if* consequent) 766 | (emit-inline-if test consequent alternate) 767 | (emit-block-if test consequent alternate))) 768 | 769 | ;; Clojure/ChlorineJS `(case ...)`syntax will output 770 | ;; javascript `switch ... case` equivalent. 771 | 772 | (defn emit-case [e clauses] 773 | (binding [*unique-return-expr* false 774 | *in-fn-toplevel* false] 775 | (let [pairs (partition 2 clauses)] 776 | (print "switch (") 777 | (binding [*return-expr* false] 778 | (emit e)) 779 | (print ") {") 780 | (doseq [[k v] pairs] 781 | (with-indent [] 782 | (newline-indent) 783 | (print "case " ) 784 | (binding [*return-expr* false] 785 | (emit k)) 786 | (print ":") 787 | (with-block 788 | (with-indent [] 789 | (emit-statement v) 790 | (newline-indent) 791 | (when-not *return-expr* 792 | (print "break;"))))))) 793 | 794 | (when (odd? (count clauses)) 795 | (with-indent [] 796 | (newline-indent) 797 | (print "default:") 798 | (with-block 799 | (with-indent [] 800 | (emit-statement (last clauses))))))) 801 | (newline-indent) 802 | (print "}")) 803 | 804 | (defmethod emit "case" [[_ e & clauses]] 805 | (if *inline-if* 806 | (do 807 | (print "(function(){") 808 | (binding [*return-expr* true] 809 | (with-indent [] 810 | (newline-indent) 811 | (emit-case e clauses))) 812 | (print "})()")) 813 | (emit-case e clauses))) 814 | 815 | (defmethod emit "do" [[_ & exprs]] 816 | (if *inline-if* 817 | (do 818 | (print "(function(){") 819 | (binding [*return-expr* true] 820 | (with-indent [] 821 | (newline-indent) 822 | (emit-statements-with-return exprs))) 823 | (print "})()")) 824 | (emit-statements-with-return exprs))) 825 | 826 | ;; `let` is a Clojure fundamental form that provides lexical bindings 827 | ;; of data structures to symbols. 828 | ;; The binding is available only within the lexical context of the let. 829 | ;; 830 | ;; Chlorine implements the same behavior of `let` by wrapping the body 831 | ;; inside a function in most cases. 832 | 833 | (defmethod emit "let" [[_ bindings & exprs]] 834 | (let [emit-var-decls (fn [] 835 | (print "var ") 836 | (binding [*return-expr* false] 837 | (with-block (emit-var-bindings bindings)) 838 | (print ";")) 839 | (emit-statements-with-return exprs)) 840 | emit-let-fun (fn [] 841 | (print "(function () {") 842 | (with-indent [] 843 | (newline-indent) 844 | (binding [*return-expr* true] 845 | (emit-var-decls))) 846 | (newline-indent) 847 | (print " })()"))] 848 | (cond 849 | *inline-if* 850 | (with-return-expr [] 851 | (emit-let-fun)) 852 | *unique-return-expr* ;; *in-fn-toplevel* 853 | (binding [*unique-return-expr* false] 854 | (emit-var-decls)) 855 | 856 | *return-expr* 857 | (with-return-expr [] 858 | (emit-let-fun)) 859 | 860 | :default 861 | (do (emit-let-fun) 862 | (print ";"))))) 863 | 864 | ;; "Leaky" versions of `let` that don't wrap anything inside a function. 865 | (defmethod emit "let!" [[_ & bindings]] 866 | (binding [*return-expr* false] 867 | (with-block (emit-var-bindings bindings)) 868 | (print ";"))) 869 | 870 | (defmethod emit "let*" [[_ & bindings]] 871 | (print "var ") 872 | (binding [*return-expr* false] 873 | (with-block (emit-var-bindings bindings)) 874 | (print ";"))) 875 | 876 | (defmethod emit "new" [[_ class & args]] 877 | (with-return-expr [] 878 | (binding [*inline-if* true] 879 | (print "new ") 880 | (emit class) 881 | (with-parens [] (emit-delimited "," args))))) 882 | 883 | (defmethod emit "delete" [[_ item]] 884 | (with-return-expr [] 885 | (binding [*inline-if* true] 886 | (print "delete ") 887 | (emit item)))) 888 | 889 | (defmethod emit "return" [[_ value]] 890 | (print "return ") 891 | (emit value)) 892 | 893 | ;; Low-level form to directly access object properties/array indexes. 894 | ;; Use `get` (in core library) which support default value when not found 895 | ;; instead 896 | (defmethod emit "get*" [[_ map key]] 897 | (with-return-expr [] 898 | (emit map) 899 | (if (keyword? key) 900 | (do (print ".") 901 | (binding [*quoted* false] 902 | (emit-symbol key))) 903 | (do (print "[") 904 | (emit key) 905 | (print "]"))))) 906 | 907 | (defmethod emit "." [[_ object key & args]] 908 | (with-return-expr [] 909 | (emit object) 910 | (print ".") 911 | (cond 912 | (symbol? key) 913 | (if (.startsWith (name key) "-") 914 | (binding [*object-member* true] 915 | (emit (property->member key))) 916 | (do (binding [*object-member* true] 917 | (emit key)) 918 | (with-parens [] 919 | (with-indent [] (emit-delimited ", " args))))) 920 | (coll? key) 921 | (do (binding [*object-member* true] 922 | (emit (first key))) 923 | (with-parens [] 924 | (with-indent [] (emit-delimited ", " (rest key)))))))) 925 | 926 | (defmethod emit "set!" [[_ & apairs]] 927 | (binding [*return-expr* false 928 | *in-fn-toplevel* false 929 | *unique-return-expr* false 930 | *inline-if* true] 931 | (let [apairs (partition 2 apairs)] 932 | (emit-delimited " = " (first apairs)) 933 | (doseq [apair (rest apairs)] 934 | (print ";") 935 | (newline-indent) 936 | (emit-delimited " = " apair))))) 937 | 938 | (defmethod emit "try" [[_ expr & clauses]] 939 | (print "try ") 940 | (with-bracket-block 941 | (emit-statement expr)) 942 | (doseq [[clause & body] clauses] 943 | (case clause 944 | catch (let [[evar expr] body] 945 | (with-block 946 | (print " catch (") 947 | (emit-symbol evar) 948 | (print ") ") 949 | (with-bracket-block 950 | (emit-statement expr)))) 951 | finally (with-block 952 | (print " finally ") 953 | (with-bracket-block 954 | (doseq [expr body] (emit-statement expr))))))) 955 | 956 | (def ^:dynamic *loop-vars* nil) 957 | 958 | (defmethod emit "loop" [[_ bindings & body]] 959 | (let [emit-for-block (fn [] 960 | (print "for (var ") 961 | (binding [*return-expr* false 962 | *in-block-exp?* false] 963 | (emit-var-bindings bindings)) 964 | (print "; true;) {") 965 | (with-indent [] 966 | (binding [*loop-vars* (first (unzip bindings))] 967 | (emit-statements-with-return body)) 968 | (newline-indent) 969 | (print "break;")) 970 | (newline-indent) 971 | (print "}"))] 972 | (if (or *in-fn-toplevel* *unique-return-expr*) 973 | (binding [*unique-return-expr* false 974 | *in-fn-toplevel* false] 975 | (emit-for-block)) 976 | (with-return-expr [] 977 | (print "(function () {") 978 | (binding [*return-expr* true] 979 | (with-indent [] 980 | (newline-indent) 981 | (emit-for-block)) 982 | (newline-indent)) 983 | (print "}).call(this)"))))) 984 | 985 | (defmethod emit "recur" [[_ & args]] 986 | (binding [*return-expr* false] 987 | (let [tmp (tempsym)] 988 | (print "var" (emit-str tmp) "= ") 989 | (emit-vector args) 990 | (println ";") 991 | (emit-statements (map (fn [lvar i] `(set! ~lvar (get* ~tmp ~i))) 992 | *loop-vars* 993 | (range (count *loop-vars*)))))) 994 | (newline-indent) 995 | (print "continue")) 996 | 997 | (defmethod emit "dokeys" [[_ [lvar hash] & body]] 998 | (binding [*return-expr* false] 999 | (print "for (var ") 1000 | (emit lvar) 1001 | (print " in ") 1002 | (emit hash) 1003 | (print ") ") 1004 | (with-bracket-block 1005 | (emit-statements body)))) 1006 | 1007 | (defmethod emit "while" [[_ test & body]] 1008 | (binding [*return-expr* false] 1009 | (print "while (") 1010 | (emit test) 1011 | (print ") ") 1012 | (with-bracket-block 1013 | (emit-statements body)))) 1014 | 1015 | (defmethod emit "do-while" [[_ test & body]] 1016 | (binding [*return-expr* false] 1017 | (print "do ") 1018 | (with-bracket-block 1019 | (emit-statements body)) 1020 | (print " while (") 1021 | (emit test) 1022 | (print ")"))) 1023 | 1024 | (defmethod emit "dofor" [[_ [init-bindings test update] & body]] 1025 | (let [init (if (vector? init-bindings) 1026 | `(let* ~@init-bindings) 1027 | init-bindings)] 1028 | (binding [*return-expr* false] 1029 | (print "for (") 1030 | (emit-statements [init test update]) 1031 | (print ") ") 1032 | (with-bracket-block 1033 | (emit-statements body))))) 1034 | 1035 | (defmethod emit "inline" [[_ js]] 1036 | (with-return-expr [] 1037 | (print js))) 1038 | 1039 | (defmethod emit "quote" [[_ expr]] 1040 | (binding [*quoted* true] 1041 | (emit expr))) 1042 | 1043 | (defmethod emit "throw" [[_ expr]] 1044 | (binding [*return-expr* false] 1045 | (print "throw ") 1046 | (emit expr))) 1047 | 1048 | (defmethod emit :default [expr] 1049 | (if (and (coll? expr) (not *quoted*) (macro? (first expr))) 1050 | (emit-macro-expansion expr) 1051 | (with-return-expr [] 1052 | (cond 1053 | (map? expr) (emit-map expr) 1054 | (set? expr) (emit-set expr) 1055 | (vector? expr) (emit-vector expr) 1056 | (re? expr) (emit-re expr) 1057 | (keyword? expr) (emit-keyword expr) 1058 | (string? expr) (pr expr) 1059 | (symbol? expr) (emit-symbol expr) 1060 | (char? expr) (print (format "'%c'" expr)) 1061 | (and *quoted* (coll? expr)) (emit-vector expr) 1062 | (coll? expr) (emit-function-form expr) 1063 | (nil? expr) (print "void(0)") 1064 | true (print expr))))) 1065 | 1066 | (defn emit-str [expr] 1067 | (binding [*return-expr* false 1068 | *inline-if* true] 1069 | (with-out-str (emit expr)))) 1070 | 1071 | (defn js-emit [expr] (emit expr)) 1072 | 1073 | (defmacro js 1074 | "Translate the Clojure subset `exprs' to a string of javascript 1075 | code." 1076 | [& exprs] 1077 | (let [exprs# `(quote ~exprs)] 1078 | `(binding [*temp-sym-count* (ref 999)] 1079 | (with-out-str 1080 | (if (< 1 (count ~exprs#)) 1081 | (emit-statements ~exprs#) 1082 | (js-emit (first ~exprs#))))))) 1083 | 1084 | (defmacro js-let 1085 | "Bind Clojure environment values to named vars of a cljs block, and 1086 | translate the Clojure subset `exprs' to a string of javascript code." 1087 | [bindings & exprs] 1088 | (let [form# 'fn* 1089 | [formals# actuals#] (unzip bindings)] 1090 | `(with-out-str 1091 | (emit-statement (list '(~form# ~(vec formals#) ~@exprs) ~@actuals#))))) 1092 | 1093 | (defmacro let-js 1094 | "Bind Clojure environment values to named vars of a quoted cljs block, and 1095 | translate the Clojure subset `exprs' to a string of javascript code." 1096 | [bindings quoted-expr] 1097 | (let [body# `(let ~bindings ~quoted-expr)] 1098 | `(with-out-str 1099 | (js-emit ~body#)))) 1100 | 1101 | (declare raw-script) 1102 | 1103 | ;; Chlorine doesn't support an official way to modularize code like Clojure 1104 | ;; with namespaces. Instead, Chlorine provides a basic syntax to load code 1105 | ;; from other files into the current file as if they are one. This can be 1106 | ;; done with `load-file` 1107 | 1108 | (defmethod emit "load-file" [[_ & files]] 1109 | ;(print (str (apply tojs' files))) 1110 | (doseq [file files] 1111 | (when *print-pretty* (println "// <-- Starts loading file: " file)) 1112 | (if-let [content (tojs' file)] 1113 | (print (str content))) 1114 | (when *print-pretty* (println "// Ends loading file: " file " -->")))) 1115 | 1116 | ;; Sometimes you only want to load macros from an outside file and print out 1117 | ;; nothing. Use `load-file-macros` then 1118 | (defmethod emit "load-file-macros" [[_ & files]] 1119 | (doseq [file files] 1120 | (when *print-pretty* (println "// Loads macros from file: " file)) 1121 | (tojs' file))) 1122 | 1123 | ;; Inlines raw javascript from files instead of Chlorine ones. 1124 | (defmethod emit "load-js" [[_ & files]] 1125 | (doseq [file files] 1126 | (when *print-pretty* (println "// <-- Starts Javascipt file: " file)) 1127 | (if-let [content (raw-script file)] 1128 | (print (str content))) 1129 | (when *print-pretty* (println "// Ends Javascript file: " file " -->")))) 1130 | 1131 | (defn raw-script [& scripts] 1132 | (with-out-str 1133 | (doseq [script scripts 1134 | :let [file (find-in-paths script) 1135 | dir (get-dir file)]] 1136 | (binding [*cwd* dir] 1137 | (if (nil? file) (throw+ {:known-error true 1138 | :msg 1139 | "File not found `" script "`" 1140 | :causes [script]})) 1141 | (let [f (if (resource-path? file) 1142 | (to-resource file) 1143 | file)] 1144 | (print (slurp f))))))) 1145 | 1146 | (defn tojs' 1147 | "The low-level, stateful way to compile Chlorine source files. This function 1148 | varies depending on states such as macros, temporary symbol count etc." 1149 | [& scripts] 1150 | (with-out-str 1151 | (doseq [script scripts 1152 | :let [file (find-in-paths script) 1153 | dir (get-dir file)]] 1154 | (binding [*cwd* dir] 1155 | (try+ 1156 | (if (nil? file) (throw+ {:known-error true 1157 | :msg 1158 | "File not found `" script "`" 1159 | :causes [script]})) 1160 | (let [f (if (resource-path? file) 1161 | (to-resource file) 1162 | file)] 1163 | (with-open [in (sexp-reader f)] 1164 | (loop [expr (read in false :eof)] 1165 | (when (not= expr :eof) 1166 | (when-let [s (emit-statement expr)] 1167 | (print s)) 1168 | (recur (read in false :eof)))))) 1169 | (catch map? e 1170 | (throw+ (merge e 1171 | {:causes (conj (or (:causes e) []) 1172 | file)}))) 1173 | (catch RuntimeException e 1174 | (if (= (.getMessage e) "EOF while reading") 1175 | (throw+ {:known-error true 1176 | :msg (str "EOF while reading file " 1177 | file "\n" 1178 | "Maybe you've got mismatched parentheses," 1179 | " brackets or braces.") 1180 | :causes [file] 1181 | :trace e}) 1182 | (throw+ {:known-error false 1183 | :msg (.getMessage e) 1184 | :causes [file] 1185 | :trace e}))) 1186 | ))))) 1187 | 1188 | (defn tojs 1189 | "The top-level, stateless way to compile Chlorine source files. 1190 | Loads and compiles a list of cl2 scripts into javascript, and 1191 | returns them in a string. This function starts its own temporary symbol count 1192 | and macro memory." 1193 | [& scripts] 1194 | (binding [*temp-sym-count* (ref 999) 1195 | *macros* (ref {})] 1196 | (apply tojs' scripts))) 1197 | --------------------------------------------------------------------------------