/src/com/reasonr/scriptjure.clj

http://github.com/arohner/scriptjure · Clojure · 405 lines · 282 code · 96 blank · 27 comment · 32 complexity · fe5964552c05ea023826ee0872068ad2 MD5 · raw file

  1. ;;; scriptjure -- a library for generating javascript from Clojure s-exprs
  2. ;; by Allen Rohner, http://arohner.blogspot.com
  3. ;; http://www.reasonr.com
  4. ;; October 7, 2009
  5. ;; Copyright (c) Allen Rohner, 2009. All rights reserved. The use
  6. ;; and distribution terms for this software are covered by the Eclipse
  7. ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
  8. ;; which can be found in the file epl-v10.html at the root of this
  9. ;; distribution. By using this software in any fashion, you are
  10. ;; agreeing to be bound by the terms of this license. You must not
  11. ;; remove this notice, or any other, from this software.
  12. ;; This library generates javascript from Clojure s-exprs. To use it,
  13. ;; (js (fn foo [x] (var x (+ 3 5)) (return x)))
  14. ;; returns a string, "function foo (x) { var x = (3 + 5); return x; }"
  15. ;;
  16. ;; See the README and the tests for more information on what is supported.
  17. ;;
  18. ;; The library is intended to generate javascript glue code in Clojure
  19. ;; webapps. One day it might become useful enough to write entirely
  20. ;; JS libraries in clojure, but it's not there yet.
  21. ;;
  22. ;;
  23. (ns #^{:author "Allen Rohner"
  24. :doc "A library for generating javascript from Clojure."}
  25. com.reasonr.scriptjure
  26. (:require [clojure.string :as str])
  27. (:require [com.reasonr.string :as rstr])
  28. (:use clojure.walk))
  29. (defn- throwf [& message]
  30. (throw (Exception. (apply format message))))
  31. (defmulti emit (fn [ expr ] (type expr)))
  32. (defmulti emit-special (fn [ & args] (first args)))
  33. (def statement-separator ";\n")
  34. (defn statement [expr]
  35. (if (not (= statement-separator (rstr/tail (count statement-separator) expr)))
  36. (str expr statement-separator)
  37. expr))
  38. (defn comma-list [coll]
  39. (str "(" (str/join ", " coll) ")"))
  40. (defmethod emit nil [expr]
  41. "null")
  42. (defmethod emit java.lang.Integer [expr]
  43. (str expr))
  44. (defmethod emit clojure.lang.Ratio [expr]
  45. (str (float expr)))
  46. (defmethod emit java.lang.String [^String expr]
  47. (str \" (.replace expr "\"" "\\\"") \"))
  48. (defn valid-symbol? [sym]
  49. ;;; This is incomplete, it disallows unicode
  50. (boolean (re-matches #"[_$\p{Alpha}][.\w]*" (str sym))))
  51. (defmethod emit clojure.lang.Keyword [expr]
  52. (when-not (valid-symbol? (name expr))
  53. (#'throwf "%s is not a valid javascript symbol" expr))
  54. (str (name expr)))
  55. (defmethod emit clojure.lang.Symbol [expr]
  56. (when-not (valid-symbol? (str expr))
  57. (#' throwf "%s is not a valid javascript symbol" expr))
  58. (str expr))
  59. (defmethod emit java.util.regex.Pattern [expr]
  60. (str \/ expr \/))
  61. (defmethod emit :default [expr]
  62. (str expr))
  63. (def special-forms (set ['var '. '.. 'if 'funcall 'fn 'quote 'set! 'return 'delete 'new 'do 'aget 'while 'doseq 'str 'inc! 'dec! 'dec 'inc 'defined? 'and 'or '? 'try 'break]))
  64. (def prefix-unary-operators (set ['!]))
  65. (def suffix-unary-operators (set ['++ '--]))
  66. (def infix-operators (set ['+ '+= '- '-= '/ '* '% '== '=== '< '> '<= '>= '!=
  67. '<< '>> '<<< '>>> '!== '& '| '&& '|| '= 'not= 'instanceof]))
  68. (def chainable-infix-operators (set ['+ '- '* '/ '& '| '&& '||]))
  69. (defn special-form? [expr]
  70. (contains? special-forms expr))
  71. (defn infix-operator? [expr]
  72. (contains? infix-operators expr))
  73. (defn prefix-unary? [expr]
  74. (contains? prefix-unary-operators expr))
  75. (defn suffix-unary? [expr]
  76. (contains? suffix-unary-operators expr))
  77. (defn emit-prefix-unary [type [operator arg]]
  78. (str operator (emit arg)))
  79. (defn emit-suffix-unary [type [operator arg]]
  80. (str (emit arg) operator))
  81. (defn emit-infix [type [operator & args]]
  82. (when (and (not (chainable-infix-operators operator)) (> (count args) 2))
  83. (throw (Exception. (str "operator " operator " supports only 2 arguments"))))
  84. (let [substitutions {'= '=== '!= '!== 'not= '!==}]
  85. (str "(" (str/join (str " " (or (substitutions operator) operator) " ")
  86. (map emit args)) ")")))
  87. (def ^{:dynamic true} var-declarations nil)
  88. (defmacro with-var-declarations [& body]
  89. `(binding [var-declarations (atom [])]
  90. ~@body))
  91. (defmethod emit-special 'var [type [var & more]]
  92. (apply swap! var-declarations conj (filter identity (map (fn [name i] (when (odd? i) name)) more (iterate inc 1))))
  93. (apply str (interleave (map (fn [[name expr]]
  94. (str (when-not var-declarations "var ") (emit name) " = " (emit expr)))
  95. (partition 2 more))
  96. (repeat statement-separator))))
  97. (defmethod emit-special 'funcall [type [name & args]]
  98. (str (if (and (list? name) (= 'fn (first name))) ; function literal call
  99. (str "(" (emit name) ")")
  100. (emit name))
  101. (comma-list (map emit args))))
  102. (defmethod emit-special 'str [type [str & args]]
  103. (apply clojure.core/str (interpose " + " (map emit args))))
  104. (defn emit-method [obj method args]
  105. (str (emit obj) "." (emit method) (comma-list (map emit args))))
  106. (defmethod emit-special '. [type [period obj method & args]]
  107. (emit-method obj method args))
  108. (defmethod emit-special '.. [type [dotdot & args]]
  109. (apply str (interpose "." (map emit args))))
  110. (defmethod emit-special 'if [type [if test true-form & false-form]]
  111. (str "if (" (emit test) ") { \n"
  112. (emit true-form)
  113. "\n }"
  114. (when (first false-form)
  115. (str " else { \n"
  116. (emit (first false-form))
  117. " }"))))
  118. (defmethod emit-special 'dot-method [type [method obj & args]]
  119. (let [method (symbol (rstr/drop 1 (str method)))]
  120. (emit-method obj method args)))
  121. (defmethod emit-special 'return [type [return expr]]
  122. (statement (str "return " (emit expr))))
  123. (defmethod emit-special 'delete [type [return expr]]
  124. (str "delete " (emit expr)))
  125. (defmethod emit-special 'set! [type [set! var val & more]]
  126. (assert (or (nil? more) (even? (count more))))
  127. (str (emit var) " = " (emit val) statement-separator
  128. (if more (str (emit (cons 'set! more))))))
  129. (defmethod emit-special 'new [type [new class & args]]
  130. (str "new " (emit class) (comma-list (map emit args))))
  131. (defmethod emit-special 'aget [type [aget var & idxs]]
  132. (apply str
  133. (emit var)
  134. (interleave (repeat "[") (map emit idxs) (repeat "]"))))
  135. (defmethod emit-special 'inc! [type [inc var]]
  136. (str (emit var) "++"))
  137. (defmethod emit-special 'dec! [type [dec var]]
  138. (str (emit var) "--"))
  139. (defmethod emit-special 'dec [type [_ var]]
  140. (str "(" (emit var) " - " 1 ")"))
  141. (defmethod emit-special 'inc [type [_ var]]
  142. (str "(" (emit var) " + " 1 ")"))
  143. (defmethod emit-special 'defined? [type [_ var]]
  144. (str "typeof " (emit var) " !== \"undefined\" && " (emit var) " !== null"))
  145. (defmethod emit-special '? [type [_ test then else]]
  146. (str (emit test) " ? " (emit then) " : " (emit else)))
  147. (defmethod emit-special 'and [type [_ & more]]
  148. (apply str (interpose "&&" (map emit more))))
  149. (defmethod emit-special 'or [type [_ & more]]
  150. (apply str (interpose "||" (map emit more))))
  151. (defmethod emit-special 'quote [type [_ & more]]
  152. (apply str more))
  153. (defn emit-do [exprs]
  154. (str/join "" (map (comp statement emit) exprs)))
  155. (defmethod emit-special 'do [type [ do & exprs]]
  156. (emit-do exprs))
  157. (defmethod emit-special 'while [type [while test & body]]
  158. (str "while (" (emit test) ") { \n"
  159. (emit-do body)
  160. "\n }"))
  161. (defmethod emit-special 'doseq [type [doseq bindings & body]]
  162. (str "for (" (emit (first bindings)) " in " (emit (second bindings)) ") { \n"
  163. (if-let [more (nnext bindings)]
  164. (emit (list* 'doseq more body))
  165. (emit-do body))
  166. "\n }"))
  167. (defn emit-var-declarations []
  168. (when-not (empty? @var-declarations)
  169. (apply str "var "
  170. (str/join ", " (map emit @var-declarations))
  171. statement-separator)))
  172. (defn emit-function [name sig body]
  173. (assert (or (symbol? name) (nil? name)))
  174. (assert (vector? sig))
  175. (with-var-declarations
  176. (let [body (emit-do body)]
  177. (str "function " (comma-list sig) " {\n"
  178. (emit-var-declarations) body " }"))))
  179. (defmethod emit-special 'fn [type [fn & expr]]
  180. (let [name (when (symbol? (first expr)) (first expr))]
  181. (when name
  182. (swap! var-declarations conj name))
  183. (if name
  184. (let [signature (second expr)
  185. body (rest (rest expr))]
  186. (str name " = " (emit-function name signature body)))
  187. (let [signature (first expr)
  188. body (rest expr)]
  189. (str (emit-function nil signature body))))))
  190. (defmethod emit-special 'try [type [try & body :as expression]]
  191. (let [try-body (remove #(contains? #{'catch 'finally} (first %))
  192. body)
  193. catch-clause (filter #(= 'catch (first %))
  194. body)
  195. finally-clause (filter #(= 'finally (first %))
  196. body)]
  197. (cond
  198. (and (empty? catch-clause)
  199. (empty? finally-clause))
  200. (throw (new Exception (str "Must supply a catch or finally clause (or both) in a try statement! " expression)))
  201. (> (count catch-clause) 1)
  202. (throw (new Exception (str "Multiple catch clauses in a try statement are not currently supported! " expression)))
  203. (> (count finally-clause) 1)
  204. (throw (new Exception (str "Cannot supply more than one finally clause in a try statement! " expression)))
  205. :true (str "try{\n"
  206. (emit-do try-body)
  207. "}\n"
  208. (if-let [[_ exception & catch-body] (first catch-clause)]
  209. (str "catch(" (emit exception) "){\n"
  210. (emit-do catch-body)
  211. "}\n"))
  212. (if-let [[_ & finally-body] (first finally-clause)]
  213. (str "finally{\n"
  214. (emit-do finally-body)
  215. "}\n"))))))
  216. (defmethod emit-special 'break [type [break]]
  217. (statement "break"))
  218. (declare emit-custom custom-form?)
  219. (derive clojure.lang.Cons ::list)
  220. (derive clojure.lang.IPersistentList ::list)
  221. (defmethod emit ::list [expr]
  222. (if (symbol? (first expr))
  223. (let [head (symbol (name (first expr))) ; remove any ns resolution
  224. expr (conj (rest expr) head)]
  225. (cond
  226. (and (= (rstr/get (str head) 0) \.)
  227. (> (count (str head)) 1)
  228. (not (= (rstr/get (str head) 1) \.))) (emit-special 'dot-method expr)
  229. (custom-form? head) (emit-custom head expr)
  230. (special-form? head) (emit-special head expr)
  231. (infix-operator? head) (emit-infix head expr)
  232. (prefix-unary? head) (emit-prefix-unary head expr)
  233. (suffix-unary? head) (emit-suffix-unary head expr)
  234. :else (emit-special 'funcall expr)))
  235. (if (list? expr)
  236. (emit-special 'funcall expr)
  237. (throw (new Exception (str "invalid form: " expr))))))
  238. (defmethod emit clojure.lang.IPersistentVector [expr]
  239. (str "[" (str/join ", " (map emit expr)) "]"))
  240. (defmethod emit clojure.lang.LazySeq [expr]
  241. (emit (into [] expr)))
  242. (defmethod emit clojure.lang.IPersistentMap [expr]
  243. (letfn [(json-pair [pair] (str (emit (key pair)) ": " (emit (val pair))))]
  244. (str "{" (str/join ", " (map json-pair (seq expr))) "}")))
  245. (defn _js [forms]
  246. (with-var-declarations
  247. (let [code (if (> (count forms) 1)
  248. (emit-do forms)
  249. (emit (first forms)))]
  250. ;;(println "js " forms " => " code)
  251. (str (emit-var-declarations) code))))
  252. (defn- unquote?
  253. "Tests whether the form is (unquote ...)."
  254. [form]
  255. (and (seq? form) (symbol? (first form)) (= (symbol (name (first form))) 'clj)))
  256. (defn handle-unquote [form]
  257. (second form))
  258. (declare inner-walk outer-walk)
  259. (defn- inner-walk [form]
  260. (cond
  261. (unquote? form) (handle-unquote form)
  262. :else (walk inner-walk outer-walk form)))
  263. (defn- outer-walk [form]
  264. (cond
  265. (symbol? form) (list 'quote form)
  266. (seq? form) (list* 'list form)
  267. :else form))
  268. (defmacro quasiquote [form]
  269. (let [post-form (walk inner-walk outer-walk form)]
  270. post-form))
  271. (defmacro js*
  272. "returns a fragment of 'uncompiled' javascript. Compile to a string using js."
  273. [& forms]
  274. (if (= (count forms) 1)
  275. `(quasiquote ~(first forms))
  276. (let [do-form `(do ~@forms)]
  277. `(quasiquote ~do-form))))
  278. (defmacro cljs*
  279. "equivalent to (js* (clj form))"
  280. [form]
  281. `(js* (~'clj ~form)))
  282. (defmacro cljs
  283. "equivalent to (js (clj form))"
  284. [form]
  285. `(js (clj ~form)))
  286. (defmacro js
  287. "takes one or more forms. Returns a string of the forms translated into javascript"
  288. [& forms]
  289. `(_js (quasiquote ~forms)))
  290. ;;**********************************************************
  291. ;; Custom forms
  292. ;;**********************************************************
  293. (defonce custom-forms (atom {}))
  294. (defmacro defjsmacro [nme params & body]
  295. `(do
  296. (defn ~nme ~params
  297. (js*
  298. ~@body))
  299. (add-custom-form '~nme ~nme)))
  300. (defn add-custom-form [form func]
  301. (swap! custom-forms assoc form func))
  302. (defn get-custom [form]
  303. (get @custom-forms form))
  304. (defn custom-form? [expr]
  305. (get-custom expr))
  306. (defn emit-custom [head expr]
  307. (when-let [func (get-custom head)]
  308. (let [v (apply func (next expr))]
  309. (emit v))))