 (defn- wrap-in-double-quotes [x]
   (str \" x \"))
 
+(defmulti emit :op)
+
+(defn emits [& xs]
+  (doseq [x xs]
+    (cond
+      (nil? x) nil
+      (map? x) (emit x)
+      (seq? x) (apply emits x)
+      (fn? x)  (x)
+      :else (do
+              (let [s (print-str x)]
+                (when *position*
+                  (swap! *position* (fn [[line column]]
+                                      [line (+ column (count s))])))
+                (print s)))))
+  nil)
+
+(defn emitln [& xs]
+  (apply emits xs)
+  ;; Prints column-aligned line number comments; good test of *position*.
+  ;(when *position*
+  ;  (let [[line column] @*position*]
+  ;    (print (apply str (concat (repeat (- 120 column) \space) ["// " (inc line)])))))
+  (println)
+  (when *position*
+    (swap! *position* (fn [[line column]]
+                        [(inc line) 0])))
+  nil)
+
 (defmulti emit-constant class)
-(defmethod emit-constant nil [x] (print "null"))
-(defmethod emit-constant Long [x] (print x))
-(defmethod emit-constant Integer [x] (print x)) ; reader puts Integers in metadata
-(defmethod emit-constant Double [x] (print x))
+(defmethod emit-constant nil [x] (emits "null"))
+(defmethod emit-constant Long [x] (emits x))
+(defmethod emit-constant Integer [x] (emits x)) ; reader puts Integers in metadata
+(defmethod emit-constant Double [x] (emits x))
 (defmethod emit-constant String [x]
-  (print (wrap-in-double-quotes (escape-string x))))
-(defmethod emit-constant Boolean [x] (print (if x "true" "false")))
+  (emits (wrap-in-double-quotes (escape-string x))))
+(defmethod emit-constant Boolean [x] (emits (if x "true" "false")))
 (defmethod emit-constant Character [x]
-  (print (wrap-in-double-quotes (escape-char x))))
+  (emits (wrap-in-double-quotes (escape-char x))))
 
 (defmethod emit-constant java.util.regex.Pattern [x]
   (let [[_ flags pattern] (re-find #"^(?:\(\?([idmsux]*)\))?(.*)" (str x))]
-    (print (str \/ (.replaceAll (re-matcher #"/" pattern) "\\\\/") \/ flags))))
+    (emits \/ (.replaceAll (re-matcher #"/" pattern) "\\\\/") \/ flags)))
 
 (defmethod emit-constant clojure.lang.Keyword [x]
-           (print (str \" "\\uFDD0" \'
-                       (if (namespace x)
-                         (str (namespace x) "/") "")
-                       (name x)
-                       \")))
+           (emits \" "\\uFDD0" \'
+                  (if (namespace x)
+                    (str (namespace x) "/") "")
+                  (name x)
+                  \"))
 
 (defmethod emit-constant clojure.lang.Symbol [x]
-           (print (str \" "\\uFDD1" \'
-                    (if (namespace x)
-                      (str (namespace x) "/") "")
-                    (name x)
-                    \")))
-
-(defn- emit-meta-constant [x string]
+           (emits \" "\\uFDD1" \'
+                  (if (namespace x)
+                    (str (namespace x) "/") "")
+                  (name x)
+                  \"))
+
+(defn- emit-meta-constant [x & body]
   (if (meta x)
     (do
-      (print (str "cljs.core.with_meta(" string ","))
+      (emits "cljs.core.with_meta(" body ",")
       (emit-constant (meta x))
-      (print ")"))
-    (print string)))
+      (emits ")"))
+    (emits body)))
 
 (defmethod emit-constant clojure.lang.PersistentList$EmptyList [x]
   (emit-meta-constant x "cljs.core.List.EMPTY"))
 
 (defmethod emit-constant clojure.lang.PersistentList [x]
   (emit-meta-constant x
-    (str "cljs.core.list("
-         (comma-sep (map #(with-out-str (emit-constant %)) x))
-         ")")))
+    (concat ["cljs.core.list("]
+            (comma-sep (map #(fn [] (emit-constant %)) x))
+            [")"])))
 
 (defmethod emit-constant clojure.lang.Cons [x]
   (emit-meta-constant x
-    (str "cljs.core.list("
-         (comma-sep (map #(with-out-str (emit-constant %)) x))
-         ")")))
+    (concat ["cljs.core.list("]
+            (comma-sep (map #(fn [] (emit-constant %)) x))
+            [")"])))
 
 (defmethod emit-constant clojure.lang.IPersistentVector [x]
   (emit-meta-constant x
-    (str "cljs.core.vec(["
-         (comma-sep (map #(with-out-str (emit-constant %)) x))
-         "])")))
+    (concat ["cljs.core.vec(["]
+            (comma-sep (map #(fn [] (emit-constant %)) x))
+            ["])"])))
 
 (defmethod emit-constant clojure.lang.IPersistentMap [x]
   (emit-meta-constant x
-    (str "cljs.core.hash_map("
-         (comma-sep (map #(with-out-str (emit-constant %))
-                         (apply concat x)))
-         ")")))
+    (concat ["cljs.core.hash_map("]
+            (comma-sep (map #(fn [] (emit-constant %))
+                            (apply concat x)))
+            [")"])))
 
 (defmethod emit-constant clojure.lang.PersistentHashSet [x]
   (emit-meta-constant x
-    (str "cljs.core.set(["
-         (comma-sep (map #(with-out-str (emit-constant %)) x))
-         "])")))
-
-(defmulti emit :op)
-
-(defmethod emit :no-op
-  [m] (println "void 0;"))
-
-(defn ^String emits [expr]
-  (with-out-str (emit expr)))
+    (concat ["cljs.core.set(["]
+            (comma-sep (map #(fn [] (emit-constant %)) x))
+            ["])"])))
 
 (defn emit-block
   [context statements ret]
-  (if statements
-    (let [body (str (apply str (map emits statements)) (emits ret))]
-      (print body))
-    (emit ret)))
+  (when statements
+    (emits statements))
+  (emit ret))
 
 (defmacro emit-wrap [env & body]
   `(let [env# ~env]
-     (when (= :return (:context env#)) (print "return "))
+     (when (= :return (:context env#)) (emits "return "))
      ~@body
-     (when-not (= :expr (:context env#)) (print ";\n"))))
+     (when-not (= :expr (:context env#)) (emitln ";"))))
+
+(defmethod emit :no-op
+  [m] (emits "void 0;"))
 
 (defmethod emit :var
   [{:keys [info env] :as arg}]
-  (emit-wrap env (print (munge (:name info)))))
+  (emit-wrap env (emits (munge (:name info)))))
 
 (defmethod emit :meta
   [{:keys [expr meta env]}]
   (emit-wrap env
-    (print (str "cljs.core.with_meta(" (emits expr) "," (emits meta) ")"))))
+    (emits "cljs.core.with_meta(" expr "," meta ")")))
 
 (defmethod emit :map
   [{:keys [children env simple-keys? keys vals]}]
   (emit-wrap env
     (if simple-keys?
-      (print (str "cljs.core.ObjMap.fromObject(["
-                  (comma-sep (map emits keys)) ; keys
-                  "],{"
-                  (comma-sep (map (fn [k v] (str (emits k) ":" (emits v)))
-                                  keys vals)) ; js obj
-                  "})"))
-      (print (str "cljs.core.HashMap.fromArrays(["
-                  (comma-sep (map emits keys))
-                  "],["
-                  (comma-sep (map emits vals))
-                  "])")))))
+      (emits "cljs.core.ObjMap.fromObject(["
+             (comma-sep keys) ; keys
+             "],{"
+             (comma-sep (map (fn [k v] #(emits k ":" v))
+                             keys vals)) ; js obj
+             "})")
+      (emits "cljs.core.HashMap.fromArrays(["
+             (comma-sep keys)
+             "],["
+             (comma-sep vals)
+             "])"))))
 
 (defmethod emit :vector
   [{:keys [children env]}]
   (emit-wrap env
-    (print (str "cljs.core.PersistentVector.fromArray(["
-                (comma-sep (map emits children)) "])"))))
+    (emits "cljs.core.PersistentVector.fromArray(["
+           (comma-sep children) "])")))
 
 (defmethod emit :set
   [{:keys [children env]}]
   (emit-wrap env
-    (print (str "cljs.core.set(["
-                (comma-sep (map emits children)) "])"))))
+    (emits "cljs.core.set(["
+           (comma-sep children) "])")))
 
 (defmethod emit :constant
   [{:keys [form env]}]

                            (mapcat :names loop-lets)))]
      (when loop-locals
        (when (= :return (:context env))
            (print "return "))
        (println (str "((function (" (comma-sep loop-locals) "){"))
        (when-not (= :return (:context env))
            (print "return ")))
      (if (= 1 (count methods))
        (if variadic
          (emit-variadic-fn-method (assoc (first methods) :name name))

        docs (if jsdoc (concat docs jsdoc) docs)
        docs (remove nil? docs)]
    (letfn [(print-comment-lines [e] (doseq [next-line (string/split-lines e)]
                                       (emitln "* " (string/trim next-line))))]
      (when (seq docs)
        (emitln "/**")
        (doseq [e docs]
          (when e
            (print-comment-lines e)))
        (emitln "*/")))))

(defmethod emit :def
  [{:keys [name init env doc export]}]
  (if init
    (do
      (emit-comment doc (:jsdoc init))
      (emits name)
      (emits " = " init)
      (when-not (= :expr (:context env)) (emitln ";"))
      (when export
        (emitln "goog.exportSymbol('" export "', " name ");")))
    (println "void 0;")))

(defn emit-apply-to
  [{:keys [name params env]}]
  (let [arglist (gensym "arglist__")
        delegate-name (str name "__delegate")]
    (emitln "(function (" arglist "){")
    (doseq [[i param] (map-indexed vector (butlast params))]
      (emits "var " param " = cljs.core.first(")
      (dotimes [_ i] (emits "cljs.core.next("))
      (emits arglist ")")
      (dotimes [_ i] (emits ")"))
      (emitln ";"))
    (if (< 1 (count params))
      (do
        (emits "var " (last params) " = cljs.core.rest(")
        (dotimes [_ (- (count params) 2)] (emits "cljs.core.next("))
        (emits arglist)
        (dotimes [_ (- (count params) 2)] (emits ")"))
        (emitln ");")
        (emitln "return " delegate-name ".call(" (string/join ", " (cons "this" params)) ");"))
      (do
        (emits "var " (last params) " = ")
        (emits "cljs.core.seq(" arglist ");")
        (emitln ";")
        (emitln "return " delegate-name ".call(" (string/join ", " (cons "this" params)) ");")))
    (emits "})")))

(defn emit-fn-method
  [{:keys [gthis name variadic params statements ret env recurs max-fixed-arity]}]
  (emit-wrap env
             (emitln "(function " name "(" (comma-sep params) "){")
             (when gthis
               (emitln "var " gthis " = this;"))
             (when recurs (emitln "while(true){"))
             (emit-block :return statements ret)
             (when recurs
               (emitln "break;")
               (emitln "}"))
             (emits "})")))

(defn emit-variadic-fn-method
  [{:keys [gthis name variadic params statements ret env recurs max-fixed-arity] :as f}]
  (emit-wrap env
             (let [name (or name (gensym))
                   delegate-name (str name "__delegate")]
               (emitln "(function() { ")
               (emitln "var " delegate-name " = function (" (comma-sep params) "){")
               (when recurs (emitln "while(true){"))
               (emit-block :return statements ret)
               (when recurs
                 (emitln "break;")
                 (emitln "}"))
               (emitln "};")

               (emitln "var " name " = function (" (comma-sep
                                                     (if variadic
                                                       (concat (butlast params) ['var_args])
                                                       params)) "){")
               (when gthis
                 (emitln "var " gthis " = this;"))
               (when variadic
                 (emitln "var " (last params) " = null;")
                 (emitln "if (goog.isDef(var_args)) {")
                 (emitln "  " (last params) " = cljs.core.array_seq(Array.prototype.slice.call(arguments, " (dec (count params)) "),0);")
                 (emitln "} "))
               (emitln "return " delegate-name ".call(" (string/join ", " (cons "this" params)) ");")
               (emitln "};")

               (emitln name ".cljs$lang$maxFixedArity = " max-fixed-arity ";")
               (emits name ".cljs$lang$applyTo = ")
               (emit-apply-to (assoc f :name name))
               (emitln ";")
               (emitln "return " name ";")
               (emitln "})()"))))

(defmethod emit :fn
  [{:keys [name env methods max-fixed-arity variadic recur-frames loop-lets]}]
