aboutsummaryrefslogtreecommitdiff
path: root/clojurescript/src/clojure/contrib/clojurescript.clj
diff options
context:
space:
mode:
Diffstat (limited to 'clojurescript/src/clojure/contrib/clojurescript.clj')
-rw-r--r--clojurescript/src/clojure/contrib/clojurescript.clj357
1 files changed, 357 insertions, 0 deletions
diff --git a/clojurescript/src/clojure/contrib/clojurescript.clj b/clojurescript/src/clojure/contrib/clojurescript.clj
new file mode 100644
index 00000000..66e7eac3
--- /dev/null
+++ b/clojurescript/src/clojure/contrib/clojurescript.clj
@@ -0,0 +1,357 @@
+; Copyright (c) Chris Houser, Sep 2008-Jan 2009. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+; which can be found in the file epl-v10.html at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+
+; Reads Clojure code and emits equivalent JavaScript
+
+(ns clojure.contrib.clojurescript
+ (:import (clojure.lang Compiler Compiler$C Compiler$BodyExpr
+ Compiler$DefExpr Compiler$InstanceMethodExpr))
+ (:require [clojure.contrib.duck-streams :as ds]))
+
+(defn- vstr [v]
+ (let [sb (StringBuilder.)
+ lvl (fn lvl [v]
+ (doseq [i v]
+ (if (vector? i)
+ (lvl i)
+ (.append sb (str i)))))]
+ (lvl v)
+ (str sb)))
+
+(def *debug-fn-names* true)
+(def *debug-comments* true)
+(def *eval-defmacro* true)
+
+; used internally
+(def *has-recur*)
+(def *local-names* {})
+
+(defmulti #^{:private true} tojs (fn [e ctx] (class e)))
+
+(defn- fnmethod [fm maxm ctx]
+ (let [lm (into {} (for [[lb lb] (.locals fm)]
+ [lb (str (.name lb) "_" (.idx lb))]))
+ thisfn (first (filter #(= 0 (.idx %)) (keys lm)))
+ [body has-recur] (binding [*has-recur* false]
+ [(tojs (.body fm)
+ (merge-with merge ctx {:localmap lm}))
+ *has-recur*])
+ mparm (into {} (for [p (.reqParms maxm)] [(.idx p) p]))
+ inits (concat
+ (when has-recur ["_cnt" "_rtn"])
+ (vals (reduce dissoc lm
+ (conj (.reqParms fm) thisfn (.restParm fm))))
+ (when (:fnname ctx) [(str (lm thisfn) "=arguments.callee")])
+ (when (not= fm maxm)
+ (for [lb (.reqParms fm)
+ :when (not= (.name lb) (.name (mparm (.idx lb))))]
+ [(lm lb) "=arguments[" (dec (.idx lb)) "]"]))
+ (when-let [lb (.restParm fm)]
+ [(str (lm lb) "=clojure.JS.rest_args(this,arguments,"
+ (count (.reqParms fm)) ")")]))]
+ (.reqParms maxm)
+ (vstr [(when (seq inits)
+ [(apply vector "var " (interpose "," inits)) ";\n"])
+ (if has-recur
+ ["do{_cnt=0;_rtn="
+ body
+ "\n}while(_cnt);return _rtn;"]
+ ["return (" body ")"])])))
+
+(defmethod tojs clojure.lang.Compiler$FnExpr [e ctx]
+ (let [maxm (or (.variadicMethod e)
+ (-> (into (sorted-map)
+ (for [fm (.methods e)
+ :when (not= fm (.variadicMethod e))]
+ [(count (.reqParms fm)) fm]))
+ last val))
+ manym (< 1 (count (.methods e)))
+ newctx (assoc ctx :fnname (.thisName e))
+ [methods local-names] (binding [*local-names* *local-names*]
+ [(into {} (for [fm (.methods e)]
+ [fm (fnmethod fm maxm newctx)]))
+ *local-names*])]
+ (vstr [(when (.variadicMethod e)
+ ["clojure.JS.variadic(" (count (.reqParms maxm)) ","])
+ "(function"
+ (when *debug-fn-names*
+ [" __" (.replaceAll (.name e) "[\\W_]+" "_")])
+ "("
+ (vec (interpose "," (for [lb (.reqParms maxm)]
+ [(.name lb) "_" (.idx lb)])))
+ "){"
+ ;"\n//" (vec (interpose "," (vals local-names))) "\n"
+ (when manym
+ ["switch(arguments.length){"
+ (vec (for [[fm body] methods :when (not= fm maxm)]
+ ["\ncase " (count (.reqParms fm)) ":" body]))
+ "}"])
+ "\n"
+ (methods maxm) "})"
+ (when (.variadicMethod e)
+ ")")
+ ])))
+
+(defmethod tojs clojure.lang.Compiler$BodyExpr [e ctx]
+ (apply str (interpose ",\n" (map #(tojs % ctx) (.exprs e)))))
+
+(defmethod tojs clojure.lang.Compiler$LetExpr [e ctx]
+ (let [inits (vec (interpose ",\n" (for [bi (.bindingInits e)]
+ ["(" ((:localmap ctx) (.binding bi))
+ "=" (tojs (.init bi) ctx) ")"])))]
+ (if (.isLoop e)
+ (binding [*has-recur* false]
+ (vstr ["((function"
+ (when *debug-fn-names* " __loop")
+ "(){var _rtn,_cnt;"
+ inits ";"
+ "do{_cnt=0;\n_rtn=" (tojs (.body e) ctx)
+ "}while(_cnt);return _rtn;})())"]))
+ (vstr ["(" inits ",\n" (tojs (.body e) ctx) ")"]))))
+
+(defmethod tojs clojure.lang.Compiler$VectorExpr [e ctx]
+ (vstr ["clojure.JS.lit_vector(["
+ (vec (interpose "," (map #(tojs % ctx) (.args e))))
+ "])"]))
+
+(defn- const-str [c]
+ (cond
+ (or (instance? Character c)
+ (string? c)) (pr-str (str c))
+ (keyword? c) (str "clojure.core.keyword(\"" (namespace c) "\",\"" (name c) "\")")
+ (symbol? c) (str "clojure.core.symbol(\"" c "\")")
+ (class? c) (.getCanonicalName c)
+ (list? c) (vstr ["clojure.JS.lit_list(["
+ (vec (interpose "," (map const-str c)))
+ "])"])
+ (fn? c) (str \" c \")
+ (instance? java.util.regex.Pattern c) (str "(/"
+ (.replace (str c) "/" "\\/")
+ "/)")
+ :else (str "(" c ")")))
+
+(defmethod tojs clojure.lang.Compiler$ConstantExpr [e ctx]
+ (const-str (.v e)))
+
+(def js-reserved '#{import boolean short byte char class})
+
+(defn- var-munge [x]
+ (let [n (-> x str Compiler/munge (.replace "." "_DOT_"))]
+ (if (js-reserved (symbol n))
+ (str n "_")
+ n)))
+
+(defn- var-parts [e]
+ (let [{:keys [name ns]} ^(.var e)]
+ [(Compiler/munge (str (.getName ns))) (var-munge name)]))
+
+(defmethod tojs clojure.lang.Compiler$UnresolvedVarExpr [e ctx]
+ (vstr ["clojure.JS.resolveVar(\""
+ (var-munge (name (.symbol e))) "\","
+ (Compiler/munge (name (.name *ns*))) ")"]))
+
+(defmethod tojs clojure.lang.Compiler$VarExpr [e ctx]
+ (let [[vns vname] (var-parts e)]
+ (if (and (= vns "clojurescript.js") (#{"this"} vname))
+ vname
+ (str vns "." vname))))
+
+(defmethod tojs clojure.lang.Compiler$TheVarExpr [e ctx]
+ (let [[vns vname] (var-parts e)]
+ (str vns "._var_" vname)))
+
+(defmethod tojs clojure.lang.Compiler$AssignExpr [e ctx]
+ (let [target (.target e)]
+ (if (instance? clojure.lang.Compiler$InstanceFieldExpr target)
+ (vstr ["(" (tojs (.target target) ctx) "."
+ (var-munge (.fieldName target)) "=" (tojs (.val e) ctx) ")"])
+ (let [[vns vname] (var-parts target)]
+ (str vns "._var_" vname ".set(" (tojs (.val e) ctx) ")")))))
+
+(defmethod tojs clojure.lang.Compiler$DefExpr [e ctx]
+ (let [[vns vname] (var-parts e)]
+ (str "clojure.JS.def(" vns ",\"" vname "\"," (tojs (.init e) ctx) ")")))
+
+
+(defmethod tojs clojure.lang.Compiler$InvokeExpr [e ctx]
+ (vstr [(tojs (.fexpr e) ctx)
+ ".apply(null,["
+ (vec (interpose "," (map #(tojs % ctx) (.args e))))
+ "])"]))
+
+(defmethod tojs clojure.lang.Compiler$LocalBindingExpr [e ctx]
+ (let [local-name ((:localmap ctx) (.b e))]
+ (set! *local-names* (assoc *local-names* (.b e) local-name))
+ local-name))
+
+(defmethod tojs clojure.lang.Compiler$NilExpr [e ctx]
+ "null")
+
+(defmethod tojs clojure.lang.Compiler$EmptyExpr [e ctx]
+ (str (.getCanonicalName (class (.coll e))) ".EMPTY"))
+
+(defmethod tojs clojure.lang.Compiler$StringExpr [e ctx]
+ (const-str (.str e)))
+
+(defmethod tojs clojure.lang.Compiler$KeywordExpr [e ctx]
+ (const-str (.k e)))
+
+(defmethod tojs clojure.lang.Compiler$StaticFieldExpr [e ctx]
+ (str "clojure.JS.getOrRun(" (.getCanonicalName (.c e)) ",\""
+ (var-munge (.fieldName e)) "\")"))
+
+(defmethod tojs clojure.lang.Compiler$StaticMethodExpr [e ctx]
+ (vstr [(.getCanonicalName (.c e)) "." (.methodName e) "("
+ (vec (interpose "," (map #(tojs % ctx) (.args e))))
+ ")"]))
+
+(defmethod tojs clojure.lang.Compiler$NewExpr [e ctx]
+ (vstr ["(new " (.getCanonicalName (.c e)) "("
+ (vec (interpose "," (map #(tojs % ctx) (.args e))))
+ "))"]))
+
+(defmethod tojs clojure.lang.Compiler$InstanceMethodExpr [e ctx]
+ (vstr ["(" (tojs (.target e) ctx) ")." (var-munge (.methodName e))
+ "(" (vec (interpose "," (map #(tojs % ctx) (.args e)))) ")"]))
+
+(defmethod tojs clojure.lang.Compiler$InstanceFieldExpr [e ctx]
+ (vstr ["clojure.JS.getOrRun(" (tojs (.target e) ctx) ",\""
+ (var-munge (.fieldName e)) "\")"]))
+
+(defmethod tojs clojure.lang.Compiler$IfExpr [e ctx]
+ (str "((" (tojs (.testExpr e) ctx)
+ ")?(" (tojs (.thenExpr e) ctx)
+ "):(" (tojs (.elseExpr e) ctx) "))"))
+
+(defmethod tojs clojure.lang.Compiler$RecurExpr [e ctx]
+ (set! *has-recur* true)
+ (vstr ["(_cnt=1,_rtn=["
+ (vec (interpose "," (map #(tojs % ctx) (.args e))))
+ "]"
+ (vec (map #(str "," ((:localmap ctx) %1) "=_rtn[" %2 "]")
+ (.loopLocals e) (iterate inc 0)))
+ ")"]))
+
+(defmethod tojs clojure.lang.Compiler$MapExpr [e ctx]
+ (vstr ["clojure.core.hash_map("
+ (vec (interpose "," (map #(tojs % ctx) (.keyvals e))))
+ ")"]))
+
+(defmethod tojs clojure.lang.Compiler$SetExpr [e ctx]
+ (vstr ["clojure.core.hash_set("
+ (vec (interpose "," (map #(tojs % ctx) (.keys e))))
+ ")"]))
+
+(defmethod tojs clojure.lang.Compiler$BooleanExpr [e ctx]
+ (if (.val e) "true" "false"))
+
+(defmethod tojs clojure.lang.Compiler$ThrowExpr [e ctx]
+ (vstr ["(function"
+ (when *debug-fn-names* " __throw")
+ "(){throw " (tojs (.excExpr e) ctx) "})()"]))
+
+(defmethod tojs clojure.lang.Compiler$TryExpr [e ctx]
+ (vstr ["(function"
+ (when *debug-fn-names* " __try")
+ "(){try{var _rtn=("
+ (tojs (.tryExpr e) ctx)
+ ")}"
+ (when (seq (.catchExprs e))
+ (when (not= 1 (count (.catchExprs e)))
+ (throw (Exception. "tojs only supports one catch clause per try")))
+ (let [cc (first (.catchExprs e))]
+ ["\ncatch(" ((:localmap ctx) (.lb cc)) "){_rtn="
+ (tojs (.handler cc) ctx)
+ "}"]))
+ (when (.finallyExpr e)
+ ["\nfinally{"
+ (tojs (.finallyExpr e) ctx)
+ "}"])
+ "return _rtn})()"]))
+
+
+(defmulti toclj class)
+(defmethod toclj clojure.lang.Compiler$KeywordExpr [e] (.k e))
+(defmethod toclj clojure.lang.Compiler$StringExpr [e] (.str e))
+(defmethod toclj clojure.lang.Compiler$ConstantExpr [e] (.v e))
+
+
+(def skip-def '#{;-- implemented directly in clj.js
+ seq instance? assoc apply refer first rest import
+ hash-map count find keys vals get class contains?
+ print-method class? number? string? integer? nth
+ to-array cons keyword symbol load
+ ;-- not supported yet
+ make-array to-array-2d re-pattern re-matcher re-groups
+ re-seq re-matches re-find format
+ ;-- macros defined without using defmacro
+ let loop fn defn defmacro
+ ;-- will probably never be supported in clojurescript
+ eval resolve ns-resolve await await-for macroexpand
+ macroexpand-1 load-reader load-string special-symbol?
+ bigint bigdec floats doubles ints longs float-array
+ double-array int-array long-array aset-int
+ aset-long aset-boolean aset-float aset-double
+ aset-short aset-char aset-byte slurp seque
+ decimal? float? pmap primitives-classnames})
+
+(def skip-method #{"java.lang.Class"})
+
+(defn formtojs [f]
+ (when-not (and (coll? f) (= 'definline (first f)))
+ (binding [*allow-unresolved-vars* true
+ *private-compiler-loader* (clojure.lang.RT/makeClassLoader)]
+ (let [expr (Compiler/analyze Compiler$C/STATEMENT `((fn [] ~f)))
+ mainexpr (-> expr .fexpr .methods first .body .exprs first)
+ defmacro? (and (instance? Compiler$BodyExpr mainexpr)
+ (instance? Compiler$DefExpr (first (.exprs mainexpr)))
+ (instance? Compiler$InstanceMethodExpr (second (.exprs mainexpr)))
+ (= "setMacro" (.methodName (second (.exprs mainexpr)))))]
+ (if defmacro?
+ (when *eval-defmacro*
+ (eval f)
+ nil)
+ (when-not (or (and (instance? Compiler$DefExpr mainexpr)
+ (skip-def (:name ^(.var mainexpr))))
+ (and (instance? Compiler$InstanceMethodExpr mainexpr)
+ (or (= "setMacro" (.methodName mainexpr))
+ (and (= "addMethod" (.methodName mainexpr))
+ (skip-method (tojs (first (.args mainexpr))
+ nil)))))
+ (and (instance? Compiler$BodyExpr mainexpr)
+ (instance? Compiler$DefExpr (first (.exprs mainexpr)))
+ (instance? Compiler$InstanceMethodExpr (second (.exprs mainexpr)))
+ (= "setMacro" (.methodName (second (.exprs mainexpr))))))
+ (tojs expr {:localmap {}})))))))
+
+(defn filetojs [filename & optseq]
+ (let [reader (java.io.PushbackReader. (ds/reader filename))
+ opts (apply array-map optseq)]
+ (binding [*ns* (create-ns 'user)
+ *debug-fn-names* (:debug-fn-names opts true)
+ *debug-comments* (:debug-comments opts true)
+ *eval-defmacro* (:eval-defmacro opts true)]
+ (loop []
+ (let [f (read reader false reader false)]
+ (when-not (identical? f reader)
+ (if-let [js (formtojs f)]
+ (do
+ (when *debug-comments*
+ (println "\n//======")
+ (print "//")
+ (prn f)
+ (println "//---"))
+ (println (str js ";"))
+ (when (and (coll? f)
+ (or (= 'ns (first f))
+ (= 'in-ns (first f))))
+ (eval f)))
+ (when *debug-comments*
+ (print "// Skipping: ")
+ (prn f)))
+ (recur)))))))