├── .gitignore ├── README.md ├── makefile ├── project.clj ├── src └── clojurejs │ ├── boot.cljs │ ├── js.clj │ └── util.clj └── test └── clojurejs ├── test_boot.clj ├── test_js.clj └── util ├── test_rhino.clj └── test_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | *.jar 2 | pom.xml 3 | /classes 4 | /lib 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ClojureJS 2 | 3 | ClojureJS is a naive implementation of a Clojure subset language to Javascript translator. ClojureJS is an attempt to implement the predictable semantics in the generated Javascript. Some of its features are: 4 | 5 | * Consistent scoping in ``let`` and ``loop/recur`` forms 6 | * Macros with ``defmacro`` 7 | * Implicit ``return`` from all forms 8 | * ``loop/recur`` translates to Javascript ``for`` loops 9 | * Translates Clojure vectors, strings, keywords, symbols and maps to Javascript equivalents 10 | 11 | ClojureJS is available under the Eclipse Public License - v 1.0. 12 | 13 | For more information see the ClojureJS [wiki](https://github.com/kriyative/clojurejs/wiki). 14 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | default: jar 2 | 3 | test jar:: 4 | lein $@ 5 | 6 | push: clean 7 | lein jar 8 | ln -s clojurejs-*.jar clojurejs.jar 9 | lein push 10 | 11 | clean: 12 | rm -f *.jar 13 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject org.clojars.kriyative/clojurejs "1.2.18" 2 | :description "A naive Clojure to Javascript translator" 3 | :url "http://github.com/kriyative/clojurejs" 4 | :dependencies [[org.clojure/clojure "1.2.0"] 5 | [org.clojure/clojure-contrib "1.2.0"]] 6 | :dev-dependencies [[swank-clojure "1.2.1"] 7 | [lein-clojars "0.5.0"] 8 | [lein-difftest "1.3.1"] 9 | [rhino/js "1.7R2"]] 10 | :hooks [leiningen.hooks.difftest] 11 | :test-path "test") 12 | -------------------------------------------------------------------------------- /src/clojurejs/boot.cljs: -------------------------------------------------------------------------------- 1 | ;;; -*- Mode: Clojure -*- 2 | ;;; vi: set ft=clojure : 3 | 4 | (defmacro apply [fun & args] `(.apply ~fun ~fun ~@args)) 5 | (defn true? [expr] (=== true expr)) 6 | (defn false? [expr] (=== false expr)) 7 | (defn undefined? [expr] (=== undefined expr)) 8 | (defn nil? [expr] (=== nil expr)) 9 | (defmacro count [x] `(inline ~(str (clojurejs.js/emit-str x) ".length"))) 10 | (defmacro not [expr] `(! ~expr)) 11 | (defn empty? [s] (or (undefined? s) (nil? s) (=== 0 (count s)))) 12 | (defn not-empty? [s] (not (empty? s))) 13 | (defmacro contains? [m k] 14 | `(inline ~(str (clojurejs.js/emit-str k) " in " (clojurejs.js/emit-str m)))) 15 | (defmacro not= [expr1 expr2] `(!= ~expr1 ~expr2)) 16 | (defmacro when [pred & body] `(if ~pred (do ~@body))) 17 | (defmacro when-not [pred & body] `(if (not ~pred) (do ~@body))) 18 | (defmacro unless [pred & body] `(if (not ~pred) (do ~@body))) 19 | (defmacro cond [& [pred consequent & alternates]] 20 | (if (coll? alternates) 21 | (if (= (first alternates) :else) 22 | `(if ~pred ~consequent ~(second alternates)) 23 | `(if ~pred ~consequent (cond ~@alternates))) 24 | `(if ~pred ~consequent))) 25 | (defn first [x] (get x 0)) 26 | (defn second [x] (get x 1)) 27 | (defn third [x] (get x 2)) 28 | (defn last [x] (get x (- (count x) 1))) 29 | (defmacro isa? [a t] 30 | `(inline ~(str "(" (clojurejs.js/emit-str a) " instanceof " t ")"))) 31 | (defn array? [a] (isa? a "Array")) 32 | (defn string? [s] (=== "string" (typeof s))) 33 | (defn number? [n] (=== "number" (typeof n))) 34 | (defn boolean? [b] (=== "boolean" (typeof b))) 35 | (defn fn? [f] (== "function" (typeof f))) 36 | (defmacro join [sep seq] `(.join ~seq ~sep)) 37 | (defn str [& args] (.join args "")) 38 | (defn inc [arg] (+ 1 arg)) 39 | (defn dec [arg] (- arg 1)) 40 | (defmacro inc! [arg] `(set! ~arg (+ 1 ~arg))) 41 | (defmacro dec! [arg] `(set! ~arg (- ~arg 1))) 42 | 43 | (defmacro delete [arg] `(inline ~(str "delete " (clojurejs.js/emit-str arg)))) 44 | 45 | (defmacro lvar [& bindings] 46 | `(inline 47 | ~(str "var " 48 | (clojure.string/join "," 49 | (map (fn [[vname vval]] 50 | (str vname " = " (clojurejs.js/emit-str vval))) 51 | (partition 2 bindings)))))) 52 | 53 | (defmacro doseq [[var seq] & body] 54 | (let [seqsym (gensym)] 55 | `(do 56 | (lvar ~seqsym ~seq) 57 | (loop [i# 0] 58 | (when (< i# (count ~seqsym)) 59 | (let [~var (get ~seqsym i#)] 60 | ~@body) 61 | (recur (+ i# 1))))))) 62 | 63 | (defmacro dotimes [[var n] & body] 64 | (let [nsym (gensym)] 65 | `(do 66 | (lvar ~nsym ~n) 67 | (loop [~var 0] 68 | (when (< ~var ~nsym) 69 | ~@body 70 | (recur (+ ~var 1))))))) 71 | 72 | (defn reduce [f val coll] 73 | (loop [i 0 74 | r val] 75 | (if (< i (count coll)) 76 | (recur (+ i 1) (f r (get coll i))) 77 | r))) 78 | 79 | (def *gensym* 999) 80 | (defn gensym [] 81 | (inc! *gensym*) 82 | (str "G__" *gensym*)) 83 | 84 | (defn subvec [a s e] 85 | (let [e (or e (count a)) 86 | r (new Array)] 87 | (loop [i (or s 0)] 88 | (if (< i e) 89 | (do 90 | (.push r (get a i)) 91 | (recur (+ i 1))) 92 | r)))) 93 | 94 | (defn map? [m] 95 | (and m 96 | (not (or (contains? #{:string :number :boolean} (typeof m)) 97 | (array? m))))) 98 | 99 | (defn map [fun arr] 100 | (loop [r [] 101 | i 0] 102 | (if (< i (count arr)) 103 | (do 104 | (.push r (fun (get arr i))) 105 | (recur r (+ i 1))) 106 | r))) 107 | 108 | (defn remove [pred seq] 109 | (loop [r [] 110 | i 0] 111 | (if (< i (count seq)) 112 | (do 113 | (when-not (pred (get seq i)) 114 | (.push r (get seq i))) 115 | (recur r (+ 1 i))) 116 | r))) 117 | 118 | (defn filter [pred arr] 119 | (loop [r [] 120 | i 0] 121 | (if (< i (count arr)) 122 | (do 123 | (if (pred (get arr i)) (.push r (get arr i))) 124 | (recur r (+ i 1))) 125 | r))) 126 | 127 | (defn merge 128 | "Merge the contents of map `m2' into map `m1' and return a new map." 129 | [m1 m2] 130 | (or (and m2 131 | (let [m {}] 132 | (dokeys [k m1] (if (.hasOwnProperty m1 k) (set! (get m k) (get m1 k)))) 133 | (dokeys [k m2] (if (.hasOwnProperty m2 k) (set! (get m k) (get m2 k)))) 134 | m)) 135 | m1)) 136 | 137 | (defn select-keys [m ks] 138 | (let [m1 {}] 139 | (doseq [k ks] 140 | (if (.hasOwnProperty m k) 141 | (set! (get m1 k) (get m k)))) 142 | m1)) 143 | 144 | (defn keys [m] 145 | (let [v []] 146 | (dokeys [k m] 147 | (if (.hasOwnProperty m k) 148 | (.push v k))) 149 | v)) 150 | 151 | (defn vals [m] 152 | (let [v []] 153 | (dokeys [k m] 154 | (if (.hasOwnProperty m k) 155 | (.push v (get m k)))) 156 | v)) 157 | 158 | (defn html-set-attrs [el attrs] 159 | (dokeys [k attrs] (.setAttribute el k (get attrs k)))) 160 | 161 | (defn html [spec] 162 | (cond 163 | (undefined? spec) (.createTextNode document "") 164 | (string? spec) (.createTextNode document spec) 165 | (array? spec) (let [el (.createElement document (first spec)) 166 | kindex 1] 167 | (when (map? (second spec)) 168 | (html-set-attrs el (second spec)) 169 | (set! kindex (+ 1 kindex))) 170 | (loop [i kindex] 171 | (when (< i (count spec)) 172 | (.appendChild el (html (get spec i))) 173 | (recur (+ i 1)))) 174 | el) 175 | :else spec)) 176 | 177 | (defn html-str 178 | "Generate a string representation of the specified HTML spec." 179 | [spec] 180 | (let [map-str (fn [m] 181 | (let [s []] 182 | (dokeys [k m] (.push s (+ k "='" (get m k) "'"))) 183 | (join " " s)))] 184 | (if (array? spec) 185 | (join "" 186 | [(+ "<" (first spec) 187 | (if (map? (second spec)) (+ " " (map-str (second spec))) "") 188 | ">") 189 | (let [s [] 190 | kindex (if (map? (second spec)) 2 1)] 191 | (loop [i kindex] 192 | (when (< i (count spec)) 193 | (.push s (html-str (get spec i))) 194 | (recur (+ i 1)))) 195 | (join "" s)) 196 | (+ "")]) 197 | spec))) 198 | -------------------------------------------------------------------------------- /src/clojurejs/js.clj: -------------------------------------------------------------------------------- 1 | ;; js.clj -- a naive Clojure (subset) to javascript translator 2 | 3 | (ns clojurejs.js 4 | (:require [clojure.string :as str]) 5 | (:use [clojure.java.io :only [reader]] 6 | [clojurejs.util :only [assert-args]])) 7 | 8 | (defn- sexp-reader [source] 9 | "Wrap `source' in a reader suitable to pass to `read'." 10 | (new java.io.PushbackReader (reader source))) 11 | 12 | (defn- unzip [s] 13 | (let [parts (partition 2 s)] 14 | [(into (empty s) (map first parts)) 15 | (into (empty s) (map second parts))])) 16 | 17 | ;; (= (unzip [:foo 1 :bar 2 :baz 3]) [[:foo :bar :baz] [1 2 3]]) 18 | 19 | (defn- re? [expr] (= (class expr) java.util.regex.Pattern)) 20 | 21 | (def *inline-if* false) 22 | (def *quoted* false) 23 | 24 | (def *print-pretty* false) 25 | (defmacro with-pretty-print [& body] 26 | `(binding [*print-pretty* true] 27 | ~@body)) 28 | 29 | (def *indent* 0) 30 | (defmacro with-indent [[& increment] & body] 31 | `(binding [*indent* (+ *indent* (or ~increment 4))] 32 | ~@body)) 33 | 34 | (def *in-block-exp* false) 35 | (defmacro with-block [& body] 36 | `(binding [*in-block-exp* true] 37 | ~@body)) 38 | 39 | (defn- newline-indent [] 40 | (if *print-pretty* 41 | (do 42 | (newline) 43 | (print (apply str (repeat *indent* " ")))) 44 | (print " "))) 45 | 46 | (defmacro with-parens [[& [left right]] & body] 47 | `(do 48 | (print (or ~left "(")) 49 | ~@body 50 | (print (or ~right ")")))) 51 | 52 | (defn- jskey [x] 53 | (let [x (if (and (coll? x) (seq x)) (first x) x)] 54 | (if (symbol? x) (name x) x))) 55 | 56 | (defn- dotsymbol? [s] 57 | (and (symbol? s) (.startsWith (name s) "."))) 58 | 59 | (declare emit-str) 60 | 61 | (defn- sym->property [s] 62 | "Transforms symbol or keyword into property access form." 63 | (binding [*quoted* true] 64 | (emit-str 65 | (if (dotsymbol? s) 66 | (symbol (subs (name s) 1)) 67 | s)))) 68 | 69 | (defmulti emit "Emit a javascript expression." {:private true} jskey) 70 | 71 | (defn- emit-delimited [delimiter args & [emitter]] 72 | (when-not (empty? args) 73 | ((or emitter emit) (first args)) 74 | (doseq [arg (rest args)] 75 | (print delimiter) 76 | ((or emitter emit) arg)))) 77 | 78 | (defn- emit-map [expr] 79 | (with-parens ["{" "}"] 80 | (binding [*inline-if* true] 81 | (emit-delimited "," 82 | (seq expr) 83 | (fn [[key val]] 84 | (emit key) 85 | (print " : ") 86 | (emit val)))))) 87 | 88 | (defn- emit-set [expr] 89 | (with-parens ["{" "}"] 90 | (binding [*inline-if* true] 91 | (emit-delimited "," 92 | (seq expr) 93 | (fn [key] 94 | (emit key) 95 | (print " : true")))))) 96 | 97 | (defn- emit-vector [expr] 98 | (with-parens ["[" "]"] 99 | (binding [*inline-if* true] 100 | (emit-delimited "," (seq expr))))) 101 | 102 | (defn- emit-re [expr] 103 | (print (str "/" (apply str (replace {\/ "\\/"} (str expr))) "/"))) 104 | 105 | (defn- emit-symbol [expr] 106 | (if *quoted* (print "'")) 107 | (print 108 | (if *quoted* 109 | (name expr) 110 | (apply str (replace {\- "_" \* "__" \? "p" \! "f" \= "_eq"} (name expr))))) 111 | (if *quoted* (print "'"))) 112 | 113 | (defn- emit-keyword [expr] 114 | (binding [*quoted* true] 115 | (emit-symbol expr))) 116 | 117 | (defn- unary-operator? [op] 118 | (and (symbol? op) (contains? #{"++" "--" "!"} (name op)))) 119 | 120 | (defn- emit-unary-operator [op arg] 121 | (print (name op)) 122 | (emit arg)) 123 | 124 | (defn- infix-operator? [op] 125 | (and (symbol? op) 126 | (contains? #{"and" "or" "+" "-" "/" "*" "%" 127 | ">" ">=" "<" "<=" "==" "===" "!=" "!==" 128 | "instanceof"} 129 | (name op)))) 130 | 131 | (defn- emit-infix-operator [op & args] 132 | (let [lisp->js {"and" "&&" 133 | "or" "||"} 134 | js-op (get lisp->js (name op) (name op))] 135 | (with-parens [] 136 | (emit-delimited (str " " js-op " ") args)))) 137 | 138 | (defn- emit-function-call [fun & args] 139 | (emit fun) 140 | (with-parens [] 141 | (with-indent [] (emit-delimited ", " args)))) 142 | 143 | (defn- emit-method-call [recvr selector & args] 144 | (emit recvr) 145 | (emit selector) 146 | (with-parens [] 147 | (with-indent [] (emit-delimited ", " args)))) 148 | 149 | (def *return-expr* false) 150 | (defmacro with-return-expr [[& [new-val]] & body] 151 | `(binding [*return-expr* (if *return-expr* 152 | (do 153 | (print "return ") 154 | false) 155 | (or ~new-val false))] 156 | ~@body)) 157 | 158 | (def *in-fn-toplevel* true) 159 | (defn- emit-function-form [form] 160 | (binding [*inline-if* true 161 | *in-fn-toplevel* false] 162 | (let [[fun & args] form 163 | method? (fn [f] (and (symbol? f) (= \. (first (name f))))) 164 | invoke-method (fn [[sel recvr & args]] 165 | (apply emit-method-call recvr sel args)) 166 | new-object? (fn [f] (and (symbol? f) (= \. (last (name f))))) 167 | invoke-fun (fn [fun & args] 168 | (with-parens [] (emit fun)) 169 | (with-parens [] (emit-delimited "," args)))] 170 | (cond 171 | (unary-operator? fun) (apply emit-unary-operator form) 172 | (infix-operator? fun) (apply emit-infix-operator form) 173 | (keyword? fun) (let [[map & default] args] (emit `(get ~map ~fun ~@default))) 174 | (method? fun) (invoke-method form) 175 | (new-object? fun) (emit `(new ~(symbol (apply str (drop-last (str fun)))) ~@args)) 176 | (coll? fun) (apply invoke-fun form) 177 | true (apply emit-function-call form))))) 178 | 179 | (defn emit-statement [expr] 180 | (binding [*inline-if* false] 181 | (if (and (coll? expr) (= 'defmacro (first expr))) ; cracks are showing 182 | (emit expr) 183 | (do 184 | (newline-indent) 185 | (emit expr) 186 | (print ";"))))) 187 | 188 | (defn emit-statements [exprs] 189 | (doseq [expr exprs] 190 | (emit-statement expr))) 191 | 192 | (defn emit-statements-with-return [exprs] 193 | (binding [*return-expr* false] 194 | (doseq [expr (butlast exprs)] 195 | (emit-statement expr))) 196 | (emit-statement (last exprs))) 197 | 198 | (defmethod emit "def" [[_ name value]] 199 | (print "var ") 200 | (emit-symbol name) 201 | (print " = ") 202 | (binding [*inline-if* true] 203 | (emit value))) 204 | 205 | (def *macros* (ref {})) 206 | (defn- macro? [n] (and (symbol? n) (contains? @*macros* (name n)))) 207 | (defn- get-macro [n] (and (symbol? n) (get @*macros* (name n)))) 208 | (defn- undef-macro [n] 209 | (when (macro? n) 210 | (when *print-pretty* (println "// undefining macro" n)) 211 | (dosync (alter *macros* dissoc (name n))))) 212 | 213 | (defmethod emit "defmacro" [[_ mname args & body]] 214 | (dosync 215 | (alter *macros* 216 | conj 217 | {(name mname) (eval `(clojure.core/fn ~args ~@body))})) 218 | nil) 219 | 220 | (defn- emit-macro-expansion [form] 221 | (let [[mac-name & args] form 222 | mac (get-macro mac-name) 223 | macex (apply mac args)] 224 | (emit macex))) 225 | 226 | (defn- emit-docstring [docstring] 227 | (when *print-pretty* 228 | (let [lines (str/split-lines docstring)] 229 | (newline-indent) 230 | (print (str "/* " (first lines))) 231 | (doseq [line (rest lines)] 232 | (newline-indent) 233 | (print (str " " line))) 234 | (print " */")))) 235 | 236 | (defn- ignorable-arg? [n] 237 | (and (symbol? n) (.startsWith (name n) "_"))) 238 | 239 | (def *temp-sym-count* nil) 240 | (defn tempsym [] 241 | (dosync 242 | (ref-set *temp-sym-count* (+ 1 @*temp-sym-count*)) 243 | (symbol (str "_temp_" @*temp-sym-count*)))) 244 | 245 | (defn- emit-simple-binding [vname val] 246 | (emit (if (ignorable-arg? vname) (tempsym) vname)) 247 | (print " = ") 248 | (binding [*inline-if* true] 249 | (emit val))) 250 | 251 | (declare emit-var-bindings 252 | emit-destructured-seq-binding 253 | emit-destructured-map-binding) 254 | 255 | (defn- destructuring-form? [form] 256 | (or (map? form) (vector? form))) 257 | 258 | (defn- binding-form? [form] 259 | (or (symbol? form) (destructuring-form? form))) 260 | 261 | (defn- binding-special? [form] 262 | (contains? #{'& :as} form)) 263 | 264 | (defn- emit-binding [vname val] 265 | (binding [*inline-if* true] 266 | (let [emitter (cond 267 | (vector? vname) emit-destructured-seq-binding 268 | (map? vname) emit-destructured-map-binding 269 | :else emit-simple-binding)] 270 | (emitter vname val)))) 271 | 272 | (defn- emit-destructured-seq-binding [vvec val] 273 | (let [temp (tempsym)] 274 | (print (str temp " = ")) 275 | (emit val) 276 | (loop [vseq vvec, i 0, seen-rest? false] 277 | (when (seq vseq) 278 | (let [vname (first vseq) 279 | vval (second vseq)] 280 | (print ", ") 281 | (condp = vname 282 | '& (cond 283 | seen-rest? 284 | (throw (Exception. "Unsupported binding form, only :as can follow &")) 285 | (not (symbol? vval)) 286 | (throw (Exception. "Unsupported binding form, & must be followed by exactly one symbol")) 287 | :else 288 | (do (emit-binding vval `(.slice ~temp ~i)) 289 | (recur (nnext vseq) (inc i) true))) 290 | :as (cond 291 | (not= (count (nnext vseq)) 0) 292 | (throw (Exception. "Unsupported binding form, nothing must follow after :as ")) 293 | (not (symbol? vval)) 294 | (throw (Exception. "Unsupported binding form, :as must be followed by a symbol")) 295 | :else 296 | (emit-binding vval temp)) 297 | (do (emit-binding vname `(get ~temp ~i)) 298 | (recur (next vseq) (inc i) seen-rest?)))))))) 299 | 300 | (defn- emit-destructured-map-binding [vmap val] 301 | (let [temp (tempsym) 302 | defaults (get vmap :or) 303 | keysmap (reduce #(assoc %1 %2 (keyword %2)) 304 | {} 305 | (mapcat vmap [:keys :strs :syms])) 306 | vmap (merge (dissoc vmap :or :keys :strs :syms) keysmap)] 307 | (print (str temp " = ")) 308 | (emit val) 309 | (doseq [[vname vkey] vmap] 310 | (print ", ") 311 | (cond 312 | (not (and (binding-form? vname) 313 | (or (some #(% vkey) #{keyword? number? binding-form?})))) 314 | (throw (Exception. "Unsupported binding form, binding symbols must be followed by keywords or numbers")) 315 | 316 | :else 317 | (if-let [[_ default] (find defaults vname)] 318 | (emit-binding vname `(get ~temp ~vkey ~default)) 319 | (emit-binding vname `(get ~temp ~vkey))))))) 320 | 321 | (defn- emit-var-bindings [bindings] 322 | (binding [*return-expr* false] 323 | (emit-delimited 324 | ", " 325 | (partition 2 bindings) 326 | (fn [[vname val]] 327 | (emit-binding vname val))))) 328 | 329 | (defn- emit-function [fdecl] 330 | (let [docstring (if (string? (first fdecl)) 331 | (first fdecl) 332 | nil) 333 | fdecl (if (string? (first fdecl)) 334 | (rest fdecl) 335 | fdecl) 336 | args (first fdecl) 337 | dargs? (or (some destructuring-form? args) 338 | (some binding-special? args) 339 | (some ignorable-arg? args)) 340 | body (rest fdecl)] 341 | (assert-args fn 342 | (vector? args) "a vector for its bindings") 343 | (if dargs? 344 | (do 345 | (print "function () {") 346 | (with-indent [] 347 | (newline-indent) 348 | (print "var ") 349 | (emit-binding args '(Array.prototype.slice.call arguments)) 350 | (print ";"))) 351 | (do 352 | (print "function (") 353 | (binding [*return-expr* false] (emit-delimited ", " args)) 354 | (print ") {"))) 355 | (with-indent [] 356 | (when docstring 357 | (emit-docstring docstring)) 358 | (binding [*return-expr* true] 359 | (emit-statements-with-return body))) 360 | (newline-indent) 361 | (print "}"))) 362 | 363 | (defmethod emit "fn" [[_ & fdecl]] 364 | (with-return-expr [] 365 | (with-block (emit-function fdecl)))) 366 | 367 | (defmethod emit "defn" [[_ name & fdecl]] 368 | (assert-args defn (symbol? name) "a symbol as its name") 369 | (undef-macro name) 370 | (emit-symbol name) 371 | (print " = ") 372 | (with-block 373 | (emit-function fdecl))) 374 | 375 | (defmethod emit "if" [[_ test consequent & [alternate]]] 376 | (let [emit-inline-if (fn [] 377 | (with-return-expr [] 378 | (with-parens [] 379 | (emit test) 380 | (print " ? ") 381 | (emit consequent) 382 | (print " : ") 383 | (emit alternate)))) 384 | emit-block-if (fn [] 385 | (print "if (") 386 | (binding [*return-expr* false 387 | *inline-if* true] 388 | (emit test)) 389 | (print ") {") 390 | (with-block 391 | (with-indent [] 392 | (emit-statement consequent))) 393 | (newline-indent) 394 | (print "}") 395 | ;; alternate might be `0`, which js equates as `nil` 396 | (when-not (nil? alternate) 397 | (print " else {") 398 | (with-block 399 | (with-indent [] 400 | (emit-statement alternate))) 401 | (newline-indent) 402 | (print "}")))] 403 | (if (and *inline-if* consequent) 404 | (emit-inline-if) 405 | (emit-block-if)))) 406 | 407 | (defmethod emit "do" [[_ & exprs]] 408 | (if *inline-if* 409 | (do 410 | (print "(function(){") 411 | (binding [*return-expr* true] 412 | (emit-statements-with-return exprs)) 413 | (print "})()")) 414 | (emit-statements-with-return exprs))) 415 | 416 | (defmethod emit "let" [[_ bindings & exprs]] 417 | (let [emit-var-decls (fn [] 418 | (print "var ") 419 | (binding [*return-expr* false] 420 | (with-block (emit-var-bindings bindings)) 421 | (print ";")) 422 | (emit-statements-with-return exprs))] 423 | (if (or (not *in-fn-toplevel*) *inline-if*) 424 | (with-return-expr [] 425 | (print "(function () {") 426 | (with-indent [] 427 | (newline-indent) 428 | (binding [*return-expr* true] 429 | (emit-var-decls))) 430 | (newline-indent) 431 | (print " }).call(this)")) 432 | (binding [*in-fn-toplevel* false] 433 | (emit-var-decls))))) 434 | 435 | (defmethod emit "new" [[_ class & args]] 436 | (with-return-expr [] 437 | (binding [*inline-if* true] 438 | (print "new ") 439 | (emit class) 440 | (with-parens [] (emit-delimited "," args))))) 441 | 442 | (defmethod emit "return" [[_ value]] 443 | (print "return ") 444 | (emit value)) 445 | 446 | (defmethod emit 'nil [_] 447 | (with-return-expr [] 448 | (print "null"))) 449 | 450 | (defmethod emit "get" [args] 451 | (let [[_ map key default] args 452 | default? (> (count args) 3) 453 | emit-get 454 | (fn [] 455 | (emit map) 456 | (if (dotsymbol? key) 457 | (emit key) 458 | (do 459 | (print "[") 460 | (emit key) 461 | (print "]"))))] 462 | (with-return-expr [] 463 | (if default? 464 | ;; FIXME Should be able to re-use code for 465 | ;; inline if and contains? macro here. 466 | ;; FIXME Also, `map` will be evaluated twice (once in 467 | ;; the `in` test, and once in output of `emit-get` 468 | (with-parens [] 469 | (print (sym->property key)) 470 | (print " in ") 471 | (emit map) 472 | (print " ? ") 473 | (emit-get) 474 | (print " : ") 475 | (emit default)) 476 | (emit-get))))) 477 | 478 | (defmethod emit "set!" [[_ & apairs]] 479 | (binding [*return-expr* false 480 | *in-fn-toplevel* false 481 | *inline-if* true] 482 | (let [apairs (partition 2 apairs)] 483 | (emit-delimited " = " (first apairs)) 484 | (doseq [apair (rest apairs)] 485 | (print ";") 486 | (newline-indent) 487 | (emit-delimited " = " apair))))) 488 | 489 | (defmethod emit "try" [[_ expr & clauses]] 490 | (print "try {") 491 | (with-indent [] 492 | (with-block 493 | (emit-statement expr))) 494 | (newline-indent) 495 | (print "}") 496 | (doseq [[clause & body] clauses] 497 | (case clause 498 | 'catch (let [[evar expr] body] 499 | (with-block 500 | (print " catch (") 501 | (emit-symbol evar) 502 | (print ") {") 503 | (with-indent [] (emit-statement expr)) 504 | (newline-indent) 505 | (print "}"))) 506 | 'finally (with-block 507 | (print " finally {") 508 | (with-indent [] (doseq [expr body] (emit-statement expr))) 509 | (newline-indent) 510 | (print "}"))))) 511 | 512 | (def *loop-vars* nil) 513 | (defmethod emit "loop" [[_ bindings & body]] 514 | (let [emit-for-block (fn [] 515 | (print "for (var ") 516 | (binding [*return-expr* false 517 | *in-block-exp* false] 518 | (emit-var-bindings bindings)) 519 | (print "; true;) {") 520 | (with-indent [] 521 | (binding [*loop-vars* (first (unzip bindings))] 522 | (emit-statements-with-return body)) 523 | (newline-indent) 524 | (print "break;")) 525 | (newline-indent) 526 | (print "}"))] 527 | (if (or (not *in-fn-toplevel*) *inline-if*) 528 | (with-return-expr [] 529 | (print "(function () {") 530 | (binding [*return-expr* true] 531 | (with-indent [] 532 | (newline-indent) 533 | (emit-for-block)) 534 | (newline-indent)) 535 | (print "}).call(this)")) 536 | (binding [*in-fn-toplevel* false] 537 | (emit-for-block))))) 538 | 539 | (defmethod emit "recur" [[_ & args]] 540 | (binding [*return-expr* false] 541 | (let [tmp (tempsym)] 542 | (print "var" (emit-str tmp) "= ") 543 | (emit-vector args) 544 | (println ";") 545 | (emit-statements (map (fn [lvar i] `(set! ~lvar (get ~tmp ~i))) 546 | *loop-vars* 547 | (range (count *loop-vars*)))))) 548 | (newline-indent) 549 | (print "continue")) 550 | 551 | (defmethod emit "dokeys" [[_ [lvar hash] & body]] 552 | (binding [*return-expr* false] 553 | (print "for (var ") 554 | (emit lvar) 555 | (print " in ") 556 | (emit hash) 557 | (print ") {") 558 | (with-indent [] 559 | (emit-statements body)) 560 | (newline-indent) 561 | (print "}"))) 562 | 563 | (defmethod emit "inline" [[_ js]] 564 | (with-return-expr [] 565 | (print js))) 566 | 567 | (defmethod emit "quote" [[_ expr]] 568 | (binding [*quoted* true] 569 | (emit expr))) 570 | 571 | (defmethod emit "throw" [[_ expr]] 572 | (binding [*return-expr* false] 573 | (print "throw ") 574 | (emit expr))) 575 | 576 | (defmethod emit :default [expr] 577 | (if (and (coll? expr) (not *quoted*) (macro? (first expr))) 578 | (emit-macro-expansion expr) 579 | (with-return-expr [] 580 | (cond 581 | (map? expr) (emit-map expr) 582 | (set? expr) (emit-set expr) 583 | (vector? expr) (emit-vector expr) 584 | (re? expr) (emit-re expr) 585 | (keyword? expr) (emit-keyword expr) 586 | (string? expr) (pr expr) 587 | (symbol? expr) (emit-symbol expr) 588 | (char? expr) (print (format "'%c'" expr)) 589 | (and *quoted* (coll? expr)) (emit-vector expr) 590 | (coll? expr) (emit-function-form expr) 591 | true (print expr))))) 592 | 593 | (defn emit-str [expr] 594 | (binding [*return-expr* false 595 | *inline-if* true] 596 | (with-out-str (emit expr)))) 597 | 598 | (defn js-emit [expr] (emit expr)) 599 | 600 | (defmacro js [& exprs] 601 | "Translate the Clojure subset `exprs' to a string of javascript 602 | code." 603 | (let [exprs# `(quote ~exprs)] 604 | `(binding [*temp-sym-count* (ref 999)] 605 | (with-out-str 606 | (if (< 1 (count ~exprs#)) 607 | (emit-statements ~exprs#) 608 | (js-emit (first ~exprs#))))))) 609 | 610 | (defmacro js-let [bindings & exprs] 611 | "Bind Clojure environment values to named vars of a cljs block, and 612 | translate the Clojure subset `exprs' to a string of javascript code." 613 | (let [form# 'fn 614 | [formals# actuals#] (unzip bindings)] 615 | `(with-out-str 616 | (emit-statement (list '(~form# ~(vec formals#) ~@exprs) ~@actuals#))))) 617 | 618 | ;; (print (js ((fn [a] (return (+ a 1))) 1))) 619 | 620 | (defmacro script [& forms] 621 | "Similar to the (js ...) form, but wraps the javascript in a 622 | [:script ] element, which can be passed to hiccup.core/html." 623 | `[:script {:type "text/javascript"} 624 | (js ~@forms)]) 625 | 626 | (defmacro script-let [bindings & forms] 627 | "Similar to the (js-let ...) form, but wraps the javascript in a 628 | [:script ] element, which can be passed to hiccup.core/html." 629 | `[:script {:type "text/javascript"} 630 | (js-let ~bindings ~@forms)]) 631 | 632 | (defmacro jq [& forms] 633 | "Similar to the (js ...) form, but wraps the javascript in a 634 | [:script ] element which is invoked on a jQuery document.ready 635 | event." 636 | (let [fnform# 'fn] 637 | `[:script {:type "text/javascript"} 638 | (js (.ready ($ document) (~fnform# [] ~@forms)))])) 639 | 640 | (defmacro jq-let [bindings & forms] 641 | "Similar to the (js-let ...) form, but wraps the javascript in a 642 | [:script ] element which is invoked on a jQuery document.ready 643 | event." 644 | (let [fnform# 'fn 645 | [formals# actuals#] (unzip bindings)] 646 | `[:script {:type "text/javascript"} 647 | "$(document).ready(function () {" 648 | (js-let ~bindings ~@forms) 649 | "});"])) 650 | 651 | (def *last-sexpr* nil) 652 | 653 | (defn tojs [& scripts] 654 | "Load and translate the list of cljs scripts into javascript, and 655 | return as a string. Useful for translating an entire cljs script file." 656 | (binding [*temp-sym-count* (ref 999) 657 | *last-sexpr* (ref nil)] 658 | (with-out-str 659 | (doseq [f scripts] 660 | (try 661 | (with-open [in (sexp-reader f)] 662 | (loop [expr (read in false :eof)] 663 | (when (not= expr :eof) 664 | (if-let [s (emit-statement expr)] 665 | (print s) 666 | (dosync 667 | (ref-set *last-sexpr* expr))) 668 | (recur (read in false :eof))))) 669 | (catch Throwable e 670 | (throw (new Throwable 671 | (str 672 | "Error translating script " f 673 | " last s-expr " @*last-sexpr*) 674 | e)))))))) 675 | -------------------------------------------------------------------------------- /src/clojurejs/util.clj: -------------------------------------------------------------------------------- 1 | 2 | (ns clojurejs.util) 3 | 4 | ; copied from clojure.core because it's private there 5 | (defmacro assert-args [fnname & pairs] 6 | `(do (when-not ~(first pairs) 7 | (throw (IllegalArgumentException. 8 | ~(str fnname " requires " (second pairs))))) 9 | ~(let [more (nnext pairs)] 10 | (when more 11 | (list* `assert-args fnname more))))) 12 | -------------------------------------------------------------------------------- /test/clojurejs/test_boot.clj: -------------------------------------------------------------------------------- 1 | ;;; tests_boot.clj -- unit tests for clojurejs standard library 2 | 3 | ;; Ram Krishnan, http://cynojure.posterous.com/ 4 | 5 | ;; Copyright (c) Ram Krishnan, 2011. All rights reserved. The use and 6 | ;; 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 | 13 | (ns clojurejs.test-boot 14 | (:use [clojure.test :only [deftest is]] 15 | clojurejs.js 16 | clojurejs.util.test-rhino 17 | clojure.contrib.mock)) 18 | 19 | (def *boot-js* (tojs "src/clojurejs/boot.cljs")) 20 | 21 | (deftest variables 22 | (is (= (js 23 | (lvar x 0) 24 | (set! x (+ x 1))) 25 | " var x = 0; x = (x + 1);"))) 26 | 27 | (deftest datastructures 28 | (is (= (js (contains? {:a 1} :a)) 29 | "'a' in {'a' : 1}"))) 30 | 31 | (deftest types 32 | (is (= true 33 | (js-eval* {:preload *boot-js*} 34 | (array? [1 2 3])))) 35 | 36 | (is (= (js (isa? "foobar" "String")) 37 | "(\"foobar\" instanceof String)"))) 38 | 39 | (deftest doseq-test 40 | (is (= 120 41 | (js-eval 42 | (defn test [] 43 | (let [prod 1] 44 | (doseq [i [1 2 3 4 5]] 45 | (set! prod (* prod i))) 46 | prod)) 47 | (test)))) 48 | 49 | (is (= [120 120] 50 | (js-eval 51 | (defn test [] 52 | (let [p1 1 53 | p2 1] 54 | (doseq [[x y] [[1 1] [2 2] [3 3] [4 4] [5 5]]] 55 | (set! p1 (* p1 x) 56 | p2 (* p2 y))) 57 | [p1 p2])) 58 | (test))))) 59 | 60 | (deftest dotimes-test 61 | (is (= 100 62 | (js-eval 63 | (defn test [] 64 | (let [sum 0] 65 | (dotimes [i 5] 66 | (dotimes [j 5] 67 | (set! sum (+ sum (* i j))))) 68 | sum)) 69 | (test))))) 70 | 71 | (deftest reduce-test 72 | (is (= 120 73 | (js-eval* {:preload *boot-js*} 74 | (defn test [] 75 | (reduce (fn [r x] (* r x)) 76 | 1 77 | [1 2 3 4 5])) 78 | (test))))) 79 | 80 | (deftest core-fns 81 | (is (= (js (defn has-foo? [] (contains? {:foo 1 :bar 2} :foo))) 82 | "has_foop = function () { return 'foo' in {'foo' : 1,'bar' : 2}; }")) 83 | 84 | (is (= true (js-eval* {:preload *boot-js*} (contains? {:foo 1 :bar 2} :foo)))) 85 | 86 | (is (= {:foo 1 :baz 3} (js-eval* {:preload *boot-js*} (select-keys {:foo 1 :bar 2 :baz 3} [:foo :baz])))) 87 | 88 | (is (= [1 2 3] (js-eval* {:preload *boot-js*} (vals {:foo 1 :bar 2 :baz 3})))) 89 | 90 | (is (= ["foo" "bar" "baz"] (js-eval* {:preload *boot-js*} (keys {:foo 1 :bar 2 :baz 3})))) 91 | 92 | (is (= [2 4 6] (js-eval* {:preload *boot-js*} (filter (fn [x] (=== (% x 2) 0)) [1 2 3 4 5 6])))) 93 | 94 | (is (= true (js-eval* {:preload *boot-js*} (map? {:a 1 :b 2}))))) 95 | -------------------------------------------------------------------------------- /test/clojurejs/test_js.clj: -------------------------------------------------------------------------------- 1 | ;;; tests_js.clj -- unit tests for clojurejs language core 2 | 3 | ;; Ram Krishnan, http://cynojure.posterous.com/ 4 | 5 | ;; Copyright (c) Ram Krishnan, 2011. All rights reserved. The use and 6 | ;; 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 | 13 | (ns clojurejs.test-js 14 | (:use [clojure.test :only [deftest is]] 15 | clojurejs.js 16 | clojurejs.util.test-rhino 17 | clojure.contrib.mock)) 18 | 19 | (tojs "src/clojurejs/boot.cljs") 20 | 21 | (deftest literals 22 | (is (= (js *print-pretty*) "__print_pretty__")) 23 | (is (= (js number?) "numberp")) 24 | (is (= (js foo-bar-baz) "foo_bar_baz")) 25 | (is (= (js inc!) "incf")) 26 | (is (= (js {:foo 1 :bar 2 :baz 3}) "{'foo' : 1,'bar' : 2,'baz' : 3}")) 27 | (is (= (js #{:foo :bar :baz}) "{'foo' : true,'bar' : true,'baz' : true}")) 28 | (is (= (js [:foo :bar :baz]) "['foo','bar','baz']")) 29 | (is (= (js #"^([a-z]*)([0-9]*)") "/^([a-z]*)([0-9]*)/")) 30 | (is (= (js \newline) "'\n'")) 31 | (is (= (js \a) "'a'"))) 32 | 33 | (deftest functions 34 | (is (= (js (+ 1 2 3)) "(1 + 2 + 3)")) 35 | (is (= (js (+ "foo" "bar" "baz")) "(\"foo\" + \"bar\" + \"baz\")")) 36 | (is (= (js (:test {:test 1 :foo 2 :bar 3})) 37 | "{'test' : 1,'foo' : 2,'bar' : 3}['test']")) 38 | (is (= (js (let [m {:test 1 :foo 2 :bar 3}] (:baz m 4))) 39 | "var m = {'test' : 1,'foo' : 2,'bar' : 3}; ('baz' in m ? m['baz'] : 4);")) 40 | (is (= (js (append '(:foo bar baz) '(quux))) 41 | "append(['foo','bar','baz'], ['quux'])")) 42 | 43 | (is (= (js (fn [a b] (+ a b))) 44 | "function (a, b) { return (a + b); }")) 45 | 46 | (is (= (with-pretty-print (js (fn "Some func does stuff" [x] (+ x 1)))) 47 | "function (x) {\n /* Some func does stuff */\n return (x + 1);\n}")) 48 | 49 | (is (= (with-pretty-print (js (fn "Some func\ndoes stuff" [x] (+ x 1)))) 50 | "function (x) {\n /* Some func\n does stuff */\n return (x + 1);\n}")) 51 | 52 | (is (= (js (defn foo [a b] (+ a b))) 53 | "foo = function (a, b) { return (a + b); }")) 54 | 55 | (is (= (js (defn foo [c] (.methodOf c))) 56 | "foo = function (c) { return c.methodOf(); }")) 57 | 58 | (is (= (js 59 | (defn test [] 60 | (let [a 1] (log (* a a))) 61 | (let [a 2] (log (* a a))))) 62 | "test = function () { var a = 1; log((a * a));; var a = 2; return log((a * a));; }")) 63 | (is (= (js 64 | (defn test [] 65 | (let [a 1] (log (* a a))) 66 | (do (log "test") (+ 1 1)))) 67 | "test = function () { var a = 1; log((a * a));; log(\"test\"); return (1 + 1);; }"))) 68 | 69 | (deftest property-access 70 | (is (= (js (get map :key)) 71 | "map['key']")) 72 | (is (= (js (:key map)) 73 | "map['key']")) 74 | (is (= (js (get map .key)) 75 | "map.key"))) 76 | 77 | (deftest property-access-default 78 | (is (= (js (get map :key default)) 79 | "('key' in map ? map['key'] : default)")) 80 | 81 | (is (= (js (get map .key default)) 82 | "('key' in map ? map.key : default)"))) 83 | 84 | (deftest destructuring 85 | (is (= (js 86 | (defn test [] 87 | (let [a 1 88 | b (+ a 1) 89 | c (+ b 1)] 90 | (+ a b c)))) 91 | "test = function () { var a = 1, b = (a + 1), c = (b + 1); return (a + b + c);; }")) 92 | 93 | ;; & rest 94 | (is (= (js 95 | (defn test [] 96 | (let [[a b & r] [1 2 3 4]] 97 | [(+ a b) r]))) 98 | "test = function () { var _temp_1000 = [1,2,3,4], a = _temp_1000[0], b = _temp_1000[1], r = _temp_1000.slice(2); return [(a + b),r];; }")) 99 | 100 | (is (= (js 101 | (defn test [[a b & r]] 102 | [(+ a b) r])) 103 | "test = function () { var _temp_1000 = Array.prototype.slice.call(arguments), _temp_1001 = _temp_1000[0], a = _temp_1001[0], b = _temp_1001[1], r = _temp_1001.slice(2); return [(a + b),r]; }")) 104 | 105 | (is (= (js 106 | (defn test [a b & r] 107 | [(+ a b) r])) 108 | "test = function () { var _temp_1000 = Array.prototype.slice.call(arguments), a = _temp_1000[0], b = _temp_1000[1], r = _temp_1000.slice(2); return [(a + b),r]; }")) 109 | 110 | ;; :as 111 | (is (= (js 112 | (fn [a [b] [c d & e :as f] :as g] nil)) 113 | "function () { var _temp_1000 = Array.prototype.slice.call(arguments), a = _temp_1000[0], _temp_1001 = _temp_1000[1], b = _temp_1001[0], _temp_1002 = _temp_1000[2], c = _temp_1002[0], d = _temp_1002[1], e = _temp_1002.slice(2), f = _temp_1002, g = _temp_1000; return null; }")) 114 | 115 | ;; map destructuring 116 | (is (= (js 117 | (fn [x {y :y, fred :fred}] fred)) 118 | "function () { var _temp_1000 = Array.prototype.slice.call(arguments), x = _temp_1000[0], _temp_1001 = _temp_1000[1], y = _temp_1001['y'], fred = _temp_1001['fred']; return fred; }")) 119 | 120 | (is (= (js 121 | (fn [[{x :x, {z :z} :y}]] z)) 122 | "function () { var _temp_1000 = Array.prototype.slice.call(arguments), _temp_1001 = _temp_1000[0], _temp_1002 = _temp_1001[0], x = _temp_1002['x'], _temp_1003 = _temp_1002['y'], z = _temp_1003['z']; return z; }")) 123 | 124 | ;; numbers as keys (this actually works) 125 | (is (= (js 126 | (fn [{a 1, b 2, :or {a 3}}])) 127 | "function () { var _temp_1000 = Array.prototype.slice.call(arguments), _temp_1001 = _temp_1000[0], a = (1 in _temp_1001 ? _temp_1001[1] : 3), b = _temp_1001[2]; return null; }")) 128 | 129 | ;; :keys, :strs 130 | (is (= (js 131 | (fn [x {y :y, z :z :keys [a b]}] z)) 132 | "function () { var _temp_1000 = Array.prototype.slice.call(arguments), x = _temp_1000[0], _temp_1001 = _temp_1000[1], a = _temp_1001['a'], b = _temp_1001['b'], y = _temp_1001['y'], z = _temp_1001['z']; return z; }")) 133 | 134 | (is (= (js 135 | (fn [x {y :y, z :z :strs [a b]}] z)) 136 | "function () { var _temp_1000 = Array.prototype.slice.call(arguments), x = _temp_1000[0], _temp_1001 = _temp_1000[1], a = _temp_1001['a'], b = _temp_1001['b'], y = _temp_1001['y'], z = _temp_1001['z']; return z; }")) 137 | ; defaults 138 | (is (= (js 139 | (fn [x {y :y, z :z :or {y 1, z "foo"}}] z)) 140 | "function () { var _temp_1000 = Array.prototype.slice.call(arguments), x = _temp_1000[0], _temp_1001 = _temp_1000[1], y = ('y' in _temp_1001 ? _temp_1001['y'] : 1), z = ('z' in _temp_1001 ? _temp_1001['z'] : \"foo\"); return z; }")) 141 | 142 | (is (= (js 143 | (fn [x {y :y, z :z :keys [a b] :or {a 1, y :bleh}}] z)) 144 | "function () { var _temp_1000 = Array.prototype.slice.call(arguments), x = _temp_1000[0], _temp_1001 = _temp_1000[1], a = ('a' in _temp_1001 ? _temp_1001['a'] : 1), b = _temp_1001['b'], y = ('y' in _temp_1001 ? _temp_1001['y'] : 'bleh'), z = _temp_1001['z']; return z; }")) 145 | 146 | ;; unsupported for now 147 | (is (thrown-with-msg? Exception #"& must be followed by" 148 | (js 149 | (fn [x y & {z :z}] z)))) 150 | ) 151 | 152 | (deftest loops 153 | (is (= (js 154 | (defn join [arr delim] 155 | (loop [str (get arr 0) 156 | i 1] 157 | (if (< i (get arr .length)) 158 | (recur (+ str delim (get arr i)) 159 | (+ i 1)) 160 | str)))) 161 | "join = function (arr, delim) { for (var str = arr[0], i = 1; true;) { if ((i < arr.length)) { var _temp_1000 = [(str + delim + arr[i]),(i + 1)];\n str = _temp_1000[0]; i = _temp_1000[1]; continue; } else { return str; }; break; }; }"))) 162 | 163 | (deftest inline-if 164 | (is (= (js 165 | (defn test [a] 166 | ((if (> a 0) minus plus) a 1))) 167 | "test = function (a) { return (((a > 0) ? minus : plus))(a,1); }")) 168 | 169 | ;; implicit `null` alternate 170 | (is (= (js (defn test [a] (console.log (if (> a 0) a)))) 171 | "test = function (a) { return console.log(((a > 0) ? a : null)); }"))) 172 | 173 | (deftest inline-primitives 174 | (is (= (js (defn isa? [i c] (inline "i instanceof c"))) 175 | "isap = function (i, c) { return i instanceof c; }"))) 176 | 177 | (deftest try-catch-finally 178 | (is (= (js 179 | (defn test [] 180 | (try 181 | (/ 5 0) 182 | (catch ex 183 | (console.log ex)) 184 | (finally 185 | 0)))) 186 | "test = function () { try { return (5 / 0); } catch (ex) { return console.log(ex); } finally { return 0; }; }")) 187 | 188 | (is (= (js (defn test [a] (if (< a 0) (throw (new Error "Negative numbers not accepted"))))) 189 | "test = function (a) { if ((a < 0)) { throw new Error(\"Negative numbers not accepted\"); }; }"))) 190 | 191 | (deftest combo 192 | (is (= (js 193 | (defn test [a] (if (! (or (boolean? a) (string? a))) (first a)))) 194 | "test = function (a) { if (!(booleanp(a) || stringp(a))) { return first(a); }; }")) 195 | 196 | (is (= (js 197 | (defn test [a] 198 | (cond 199 | (symbol? a) "yes" 200 | (number? a) "no" 201 | :else "don't know"))) 202 | "test = function (a) { if (symbolp(a)) { return \"yes\"; } else { if (numberp(a)) { return \"no\"; } else { return \"don't know\"; }; }; }")) 203 | 204 | (is (= (js 205 | (defn test [a] 206 | (cond 207 | (symbol? a) "yes" 208 | (number? a) "no"))) 209 | "test = function (a) { if (symbolp(a)) { return \"yes\"; } else { if (numberp(a)) { return \"no\"; }; }; }"))) 210 | 211 | (declare foo) 212 | 213 | (deftest do-expression-test 214 | (js-import [foo] 215 | (expect [foo (->> (times once) (returns 0))] 216 | (is (= 123 (js-eval (do (def x (do (foo) 123)) x))))) 217 | (expect [foo (->> (times once) (returns 0))] 218 | (is (= 123 (js-eval (do (def x -1) (set! x (do (foo) 123)) x))))))) 219 | 220 | (deftest if-expression-test 221 | (js-import [foo] 222 | (expect [foo (times 2)] 223 | (is (= 1 (js-eval 224 | (do (if (do (foo) true) 225 | (do (foo) 1) 226 | (do (foo) 2))))))) 227 | (expect [foo (times 2)] 228 | (is (= 1 (js-eval 229 | (if (do (foo) true) 230 | (do (foo) 1) 231 | (do (foo) 2)))))))) 232 | 233 | (deftest loop-expression-test 234 | (js-import [foo] 235 | (expect [foo (times 2)] 236 | (is (= -1 (js-eval 237 | (def x (loop [i 1] 238 | (foo) 239 | (if (>= i 0) (recur (- i 2)) i))) 240 | x)))) 241 | (expect [foo (times 6)] 242 | (is (= -1 (js-eval 243 | (loop [i (do (foo) 9)] 244 | (if (> i 0) 245 | (recur (do (foo) (- i 2))) 246 | i)))))) 247 | (expect [foo (times 6)] 248 | (is (= -1 (js-eval 249 | ((fn [] ; create and call anonymous fn 250 | (loop [i (do (foo) 9)] 251 | (if (> i 0) 252 | (recur (do (foo) (- i 2))) 253 | i)))))))))) 254 | 255 | (deftest let-expression-test 256 | (js-import [foo] 257 | (expect [foo (times 2)] 258 | (is (= 123 (js-eval (def x (let [y 123] (foo) y)) x))) 259 | (is (= 123 (js-eval (do (def x (let [y 123] (foo) y)) x))))))) 260 | 261 | (deftest new-form-test 262 | (js-import [foo] 263 | (expect [foo (times 5)] 264 | (is (= 123 (js-eval (new (do (foo) Number) (do (foo) 123))))) 265 | (is (= 123 (js-eval (do (foo) (new (do (foo) Number) (do (foo) 123)))))) 266 | 267 | (is (= (js (Number. 10)) "new Number(10)"))))) 268 | -------------------------------------------------------------------------------- /test/clojurejs/util/test_rhino.clj: -------------------------------------------------------------------------------- 1 | (ns clojurejs.util.test-rhino 2 | "Utility functions to help testing clojurejs." 3 | (:use [clojurejs.js :only [js]]) 4 | (:import (org.mozilla.javascript Context ScriptableObject NativeArray 5 | NativeObject NativeJavaObject Scriptable))) 6 | 7 | (def ^{:private true} *scope* nil) 8 | (def ^{:private true} *context* nil) 9 | 10 | (declare wrap-value unwrap-value) 11 | 12 | (defn call-in-new-js-context 13 | "Calls body-fn function in Rhino context prepared to execute tests." 14 | [body-fn] 15 | (try 16 | (let [ctx (Context/enter) 17 | scope (.initStandardObjects ctx)] 18 | (.setJavaPrimitiveWrap (.getWrapFactory ctx) false) 19 | (ScriptableObject/putProperty scope "_clj_wrap" wrap-value) 20 | (ScriptableObject/putProperty scope "_clj_unwrap" unwrap-value) 21 | (.evaluateString ctx scope 22 | (str "function _clj_importfn(n,v){" 23 | "this[n]=function(){" 24 | "var args=[];" 25 | "for(var i=0;i" 1 nil) 32 | (binding [*scope* scope 33 | *context* ctx] 34 | (body-fn))) 35 | (finally 36 | (when (Context/getCurrentContext) 37 | (Context/exit))))) 38 | 39 | (defn call-in-new-js-scope 40 | "Any changes to JavaScript scope done by body-fn will be rolled back upon 41 | return from this function." 42 | [body-fn] 43 | (if (nil? *context*) 44 | (call-in-new-js-context #(call-in-new-js-scope body-fn)) 45 | (let [clean-scope (.newObject *context* *scope*)] 46 | (.setPrototype clean-scope *scope*) 47 | (.setParentScope clean-scope nil) 48 | (binding [*scope* clean-scope] 49 | (body-fn))))) 50 | 51 | (defn- js-object-to-clj-map [obj seen] 52 | (into {} (for [id (.getIds obj)] 53 | (let [key (if (string? id) 54 | (keyword id) 55 | (unwrap-value id)) 56 | raw-val (ScriptableObject/getProperty obj id) 57 | val (unwrap-value raw-val seen)] 58 | [key val])))) 59 | 60 | (defn- js-array-to-clj-vector [obj seen] 61 | (into [] (map #(unwrap-value 62 | (ScriptableObject/getProperty obj %) 63 | seen) (.getIds obj)))) 64 | 65 | (defn- unwrap-value 66 | ([obj] (unwrap-value obj nil)) 67 | ([obj seen] 68 | (let [new-seen (conj seen obj)] 69 | (when-not (nil? (some #(identical? obj %) seen)) 70 | (throw 71 | (RuntimeException. "Circular reference in unwrapped JS objects."))) 72 | (cond 73 | (instance? NativeJavaObject obj) (.unwrap obj) 74 | (instance? NativeObject obj) (js-object-to-clj-map obj new-seen) 75 | (instance? NativeArray obj) (js-array-to-clj-vector obj new-seen) 76 | (instance? Scriptable obj) 77 | (let [class (.getClassName obj)] 78 | (case class 79 | "String" (str obj) 80 | "RegExp" (if-let [regexp (second (re-find "^/(.*)/$" (str obj)))] 81 | (re-pattern regexp) 82 | obj) 83 | "Number" (Double/valueOf (str obj)) 84 | obj)) 85 | :else obj)))) 86 | 87 | (defn- wrap-value [obj] 88 | (cond 89 | (sequential? obj) (.newArray *context* *scope* (to-array obj)) 90 | (map? obj) (let [jsobj (.newObject *context* *scope*)] 91 | (doseq [[k v] obj] 92 | (let [wrapped-key (if (keyword? k) (name k) (wrap-value k))] 93 | (.put jsobj wrapped-key jsobj (wrap-value v)))) 94 | jsobj) 95 | (or (and (instance? Long obj) 96 | (<= (Math/abs obj) 0x20000000000000)) 97 | (instance? Integer obj) 98 | (instance? Short obj) 99 | (instance? Byte obj) 100 | (instance? Float obj)) (double obj) 101 | :else (Context/javaToJS obj *scope*))) 102 | 103 | (defn- call-js-fn [name & args] 104 | (let [js-fn (ScriptableObject/getProperty *scope* name)] 105 | (.call js-fn *context* *scope* *scope* (object-array args)))) 106 | 107 | (defn js-import* 108 | "Import clojure function to JavaScript root object under given name." 109 | [name fn-var] 110 | (when (.isMacro fn-var) 111 | (throw (RuntimeException. "Can't import macros."))) 112 | (call-js-fn "_clj_importfn" name fn-var)) 113 | 114 | (defn simplify-import-decl [ns decl] 115 | (let [reslv (fn [s] 116 | (let [s (cond (string? s) (symbol s) 117 | (symbol? s) s)] 118 | (ns-resolve ns s)))] 119 | (cond 120 | (string? decl) [decl (reslv decl)] 121 | (symbol? decl) [(name decl) (reslv decl)] 122 | (sequential? decl) (let [rn (first decl) 123 | name (cond (string? rn) rn 124 | (symbol? rn) (name rn)) 125 | var (reslv (second decl))] 126 | [name var])))) 127 | 128 | (defmacro js-import 129 | "imports => [ import-spec* ] 130 | import-spec => symbol | [new-name symbol-or-var] 131 | 132 | Import spec can be just symbol, then the name of imported function will be 133 | the same as name of symbol. Or it can be list or vector of two elements: 134 | first is name of imported function in JavaScript, second is symbol or var 135 | representing a function to import." 136 | [imports & body] 137 | (let [imports (map #(simplify-import-decl *ns* %) imports)] 138 | `(call-in-new-js-scope 139 | (fn [] 140 | ~@(map (fn [x] `(js-import* ~@x)) imports) 141 | ~@body)))) 142 | 143 | (defn do-js-eval 144 | "Evaluate JavaScript code." 145 | ([code] (do-js-eval {} code)) 146 | ([opts code] 147 | (call-in-new-js-scope 148 | (fn [] 149 | (try 150 | (let [ctx-name ""] 151 | (if (:preload opts) 152 | (.evaluateString *context* *scope* (:preload opts) ctx-name 1 nil)) 153 | (unwrap-value 154 | (.evaluateString *context* *scope* code ctx-name 1 nil))) 155 | (catch Exception e 156 | (println "JavaScript Code:") 157 | (println code) 158 | (throw (RuntimeException. "Exception evaluating JavaScript" e)))))))) 159 | 160 | (defmacro js-eval 161 | "Compiles expressions in body to JavaScript and evaluate the result." 162 | [& body] 163 | `(do-js-eval (js ~@body))) 164 | 165 | (defmacro js-eval* 166 | "Compiles expressions in body to JavaScript and evaluate the result, 167 | with additional opts which may contain a :preload arg, specifying 168 | code to execute prior to evaluating `body`." 169 | [opts & body] 170 | `(do-js-eval ~opts (js ~@body))) 171 | -------------------------------------------------------------------------------- /test/clojurejs/util/test_test.clj: -------------------------------------------------------------------------------- 1 | (ns clojurejs.util.test-test 2 | "Unit tests for utility functions used in tests" 3 | (:use [clojure.test :only [deftest is]] 4 | clojurejs.util.test-rhino 5 | clojure.contrib.mock)) 6 | 7 | (deftest eval-test 8 | (is (= 1 (js-eval 1))) 9 | (is (= "BOO-YAH" (js-eval "BOO-YAH"))) 10 | (is (= [1 "two"] (js-eval [1 "two"]))) 11 | (is (= {1 "two" :three 4} (js-eval ((fn [] {1 "two" :three 4})))))) 12 | 13 | (defn- foo [] "imported") 14 | 15 | (deftest import-test 16 | (js-import [foo] 17 | (is (= "imported" (js-eval (foo)))))) 18 | 19 | (defn- scope-test-helper [] 20 | (js-eval 21 | (set! x (- x 1)) 22 | (if (> x 0) 23 | (helper)))) 24 | 25 | (deftest scope-test 26 | (js-import [[helper scope-test-helper] foo] 27 | (expect [scope-test-helper (->> (times 2) 28 | (calls scope-test-helper))] 29 | (is (= 2 (js-eval 30 | (def x 2) 31 | (helper) 32 | x)))))) 33 | --------------------------------------------------------------------------------