|
1 | 1 | ;;; scriptjure -- a library for generating javascript from Clojure s-exprs
|
2 | 2 |
|
3 | 3 | ;; by Allen Rohner, http://arohner.blogspot.com
|
4 |
| -;; http://www.reasonr.com |
| 4 | +;; http://www.reasonr.com |
5 | 5 | ;; October 7, 2009
|
6 | 6 |
|
7 | 7 | ;; Copyright (c) Allen Rohner, 2009. All rights reserved. The use
|
|
12 | 12 | ;; agreeing to be bound by the terms of this license. You must not
|
13 | 13 | ;; remove this notice, or any other, from this software.
|
14 | 14 |
|
15 |
| -;; This library generates javascript from Clojure s-exprs. To use it, |
| 15 | +;; This library generates javascript from Clojure s-exprs. To use it, |
16 | 16 | ;; (js (fn foo [x] (var x (+ 3 5)) (return x)))
|
17 | 17 | ;; returns a string, "function foo (x) { var x = (3 + 5); return x; }"
|
18 | 18 | ;;
|
19 | 19 | ;; See the README and the tests for more information on what is supported.
|
20 |
| -;; |
| 20 | +;; |
21 | 21 | ;; The library is intended to generate javascript glue code in Clojure
|
22 | 22 | ;; webapps. One day it might become useful enough to write entirely
|
23 | 23 | ;; JS libraries in clojure, but it's not there yet.
|
|
26 | 26 |
|
27 | 27 | (ns #^{:author "Allen Rohner"
|
28 | 28 | :doc "A library for generating javascript from Clojure."}
|
29 |
| - cherry.compiler |
30 |
| - (:require [clojure.string :as str]) |
31 |
| - (:require [com.reasonr.string :as rstr]) |
32 |
| - (:use clojure.walk)) |
| 29 | + cherry.transpiler |
| 30 | + (:require [clojure.string :as str] |
| 31 | + [com.reasonr.string :as rstr] |
| 32 | + [edamame.core :as e]) |
| 33 | + (:use clojure.walk)) |
33 | 34 |
|
34 | 35 | (defn- throwf [& message]
|
35 | 36 | (throw (Exception. (apply format message))))
|
|
86 | 87 | 'return 'delete 'new 'do 'aget 'while 'doseq
|
87 | 88 | 'str 'inc! 'dec! 'dec 'inc 'defined? 'and 'or
|
88 | 89 | '? 'try 'break
|
89 |
| - 'await 'const 'defn 'let])) |
| 90 | + 'await 'const 'defn 'let 'ns])) |
90 | 91 |
|
91 | 92 | (def prefix-unary-operators (set ['!]))
|
92 | 93 |
|
|
168 | 169 | (return (emit-do more)))
|
169 | 170 | {:async? *async*}))
|
170 | 171 |
|
| 172 | +(defmethod emit-special 'ns [_ & _] |
| 173 | + ;; TODO |
| 174 | + ) |
| 175 | + |
171 | 176 | (defmethod emit-special 'funcall [type [name & args]]
|
172 | 177 | (str (if (and (list? name) (= 'fn (first name))) ; function literal call
|
173 | 178 | (str "(" (emit name) ")")
|
|
316 | 321 | finally-clause (filter #(= 'finally (first %))
|
317 | 322 | body)]
|
318 | 323 | (cond
|
319 |
| - (and (empty? catch-clause) |
320 |
| - (empty? finally-clause)) |
321 |
| - (throw (new Exception (str "Must supply a catch or finally clause (or both) in a try statement! " expression))) |
322 |
| - |
323 |
| - (> (count catch-clause) 1) |
324 |
| - (throw (new Exception (str "Multiple catch clauses in a try statement are not currently supported! " expression))) |
325 |
| - |
326 |
| - (> (count finally-clause) 1) |
327 |
| - (throw (new Exception (str "Cannot supply more than one finally clause in a try statement! " expression))) |
328 |
| - |
329 |
| - :true (str "try{\n" |
330 |
| - (emit-do try-body) |
331 |
| - "}\n" |
332 |
| - (if-let [[_ exception & catch-body] (first catch-clause)] |
333 |
| - (str "catch(" (emit exception) "){\n" |
334 |
| - (emit-do catch-body) |
335 |
| - "}\n")) |
336 |
| - (if-let [[_ & finally-body] (first finally-clause)] |
337 |
| - (str "finally{\n" |
338 |
| - (emit-do finally-body) |
339 |
| - "}\n")))))) |
| 324 | + (and (empty? catch-clause) |
| 325 | + (empty? finally-clause)) |
| 326 | + (throw (new Exception (str "Must supply a catch or finally clause (or both) in a try statement! " expression))) |
| 327 | + |
| 328 | + (> (count catch-clause) 1) |
| 329 | + (throw (new Exception (str "Multiple catch clauses in a try statement are not currently supported! " expression))) |
| 330 | + |
| 331 | + (> (count finally-clause) 1) |
| 332 | + (throw (new Exception (str "Cannot supply more than one finally clause in a try statement! " expression))) |
| 333 | + |
| 334 | + :true (str "try{\n" |
| 335 | + (emit-do try-body) |
| 336 | + "}\n" |
| 337 | + (if-let [[_ exception & catch-body] (first catch-clause)] |
| 338 | + (str "catch(" (emit exception) "){\n" |
| 339 | + (emit-do catch-body) |
| 340 | + "}\n")) |
| 341 | + (if-let [[_ & finally-body] (first finally-clause)] |
| 342 | + (str "finally{\n" |
| 343 | + (emit-do finally-body) |
| 344 | + "}\n")))))) |
340 | 345 |
|
341 | 346 | (defmethod emit-special 'break [type [break]]
|
342 | 347 | (statement "break"))
|
|
351 | 356 | (let [head (symbol (name (first expr))) ; remove any ns resolution
|
352 | 357 | expr (conj (rest expr) head)]
|
353 | 358 | (cond
|
354 |
| - (and (= (rstr/get (str head) 0) \.) |
355 |
| - (> (count (str head)) 1) |
| 359 | + (and (= (rstr/get (str head) 0) \.) |
| 360 | + (> (count (str head)) 1) |
356 | 361 |
|
357 |
| - (not (= (rstr/get (str head) 1) \.))) (emit-special 'dot-method expr) |
| 362 | + (not (= (rstr/get (str head) 1) \.))) (emit-special 'dot-method expr) |
358 | 363 | (custom-form? head) (emit-custom head expr)
|
359 |
| - (special-form? head) (emit-special head expr) |
360 |
| - (infix-operator? head) (emit-infix head expr) |
| 364 | + (special-form? head) (emit-special head expr) |
| 365 | + (infix-operator? head) (emit-infix head expr) |
361 | 366 | (prefix-unary? head) (emit-prefix-unary head expr)
|
362 | 367 | (suffix-unary? head) (emit-suffix-unary head expr)
|
363 |
| - :else (emit-special 'funcall expr))) |
| 368 | + :else (emit-special 'funcall expr))) |
364 | 369 | (if (list? expr)
|
365 | 370 | (emit-special 'funcall expr)
|
366 | 371 | (throw (new Exception (str "invalid form: " expr))))))
|
|
377 | 382 |
|
378 | 383 | (defn _js [forms]
|
379 | 384 | (with-var-declarations
|
380 |
| - (let [code (if (> (count forms) 1) |
381 |
| - (emit-do forms {:top-level? true}) |
382 |
| - (emit (first forms)))] |
383 |
| - ;;(println "js " forms " => " code) |
384 |
| - (str (emit-var-declarations) code)))) |
| 385 | + (let [code (if (> (count forms) 1) |
| 386 | + (emit-do forms {:top-level? true}) |
| 387 | + (emit (first forms)))] |
| 388 | + ;;(println "js " forms " => " code) |
| 389 | + (str (emit-var-declarations) code)))) |
385 | 390 |
|
386 | 391 | (defn- unquote?
|
387 | 392 | "Tests whether the form is (unquote ...)."
|
|
394 | 399 | (declare inner-walk outer-walk)
|
395 | 400 |
|
396 | 401 | (defn- inner-walk [form]
|
397 |
| - (cond |
398 |
| - (unquote? form) (handle-unquote form) |
399 |
| - :else (walk inner-walk outer-walk form))) |
| 402 | + (cond |
| 403 | + (unquote? form) (handle-unquote form) |
| 404 | + :else (walk inner-walk outer-walk form))) |
400 | 405 |
|
401 | 406 | (defn- outer-walk [form]
|
402 | 407 | (cond
|
|
426 | 431 | [form]
|
427 | 432 | `(js (clj ~form)))
|
428 | 433 |
|
429 |
| -(defmacro js |
| 434 | +(defmacro js |
430 | 435 | "takes one or more forms. Returns a string of the forms translated into javascript"
|
431 | 436 | [& forms]
|
432 | 437 | `(_js (quasiquote ~forms)))
|
|
442 | 447 | `(do
|
443 | 448 | (defn ~nme ~params
|
444 | 449 | (js*
|
445 |
| - ~@body)) |
| 450 | + ~@body)) |
446 | 451 | (add-custom-form '~nme ~nme)))
|
447 | 452 |
|
448 | 453 | (defn add-custom-form [form func]
|
|
459 | 464 | (let [v (apply func (next expr))]
|
460 | 465 | (emit v))))
|
461 | 466 |
|
| 467 | +(defn transpile-string [s] |
| 468 | + (let [rdr (e/reader s)] |
| 469 | + (loop [transpiled ""] |
| 470 | + (let [next-form (e/parse-next rdr)] |
| 471 | + (if (= ::e/eof next-form) |
| 472 | + transpiled |
| 473 | + (let [next-js (js (clj next-form))] |
| 474 | + (recur (str transpiled next-js (when-not (str/blank? next-js) |
| 475 | + "\n"))))))))) |
| 476 | + |
| 477 | +(defn transpile-file [{:keys [in-file out-file]}] |
| 478 | + (let [out-file (or out-file |
| 479 | + (str/replace in-file #".cljs$" ".mjs"))] |
| 480 | + (spit out-file (transpile-string (slurp in-file))) |
| 481 | + {:out-file out-file})) |
0 commit comments