Escape from JS .map madness.

Escape from JS .map madness.

(ns wisp.sequence
  (:require [wisp.runtime :refer [nil? vector? fn? number? string? dictionary?
                                  key-values str dec inc merge dictionary]]))

;; Implementation of list

(defn- List
  "List type"
  [head tail]
  (set! this.head head)
  (set! this.tail (or tail (list)))
  (set! this.length (inc (count this.tail)))
  this)

(set! List.prototype.length 0)
(set! List.type "wisp.list")
(set! List.prototype.type List.type)
(set! List.prototype.tail (Object.create List.prototype))
(set! List.prototype.to-string
      (fn []
        (loop [result ""
               list this]
          (if (empty? list)
            (str "(" (.substr result 1) ")")
            (recur
             (str result
                  " "
                  (if (vector? (first list))
                    (str "[" (.join (first list) " ") "]")
                    (if (nil? (first list))
                      "nil"
                      (if (string? (first list))
                        (.stringify JSON (first list))
                        (if (number? (first list))
                          (.stringify JSON (first list))
                          (first list))))))
             (rest list))))))

(defn- lazy-seq-value [lazy-seq]
  (if (not (.-realized lazy-seq))
    (and (set! (.-realized lazy-seq) true)
         (set! (.-x lazy-seq) (.x lazy-seq)))
    (.-x lazy-seq)))

(defn- LazySeq [realized x]
  (set! (.-realized this) (or realized false))
  (set! (.-x this) x)
  this)
(set! LazySeq.type "wisp.lazy.seq")
(set! LazySeq.prototype.type LazySeq.type)

(defn lazy-seq
  [realized body]
  (LazySeq. realized body))

(defn lazy-seq?
  [value]
  (and value (identical? LazySeq.type value.type)))

(defmacro lazy-seq
  "Takes a body of expressions that returns an ISeq or nil, and yields
  a Seqable object that will invoke the body only the first time seq
  is called, and will cache the result and return it on all subsequent
  seq calls. See also - realized?"
  {:added "1.0"}
  [& body]
  `(.call lazy-seq nil false (fn [] ~@body)))

(defn list?
  "Returns true if list"
  [value]
  (and value (identical? List.type value.type)))

(defn list
  "Creates list of the given items"
  []
  (if (identical? (.-length arguments) 0)
    (Object.create List.prototype)
    (.reduce-right (.call Array.prototype.slice arguments)
                   (fn [tail head] (cons head tail))
                   (list))))

(defn cons
  "Creates list with `head` as first item and `tail` as rest"
  [head tail]
  (new List head tail))

(defn- reverse-list
  [sequence]
  (loop [items []
           source sequence]
      (if (empty? source)
        (apply list items)
        (recur (.concat [(first source)] items)
               (rest source)))))

(defn ^boolean sequential?
  "Returns true if coll satisfies ISequential"
  [x] (or (list? x)
          (vector? x)
          (lazy-seq? x)
          (dictionary? x)
          (string? x)))

(defn reverse
  "Reverse order of items in the sequence"
  [sequence]
  (cond (list? sequence) (reverse-list sequence)
        (vector? sequence) (.reverse sequence)
        (nil? sequence) '()
        :else (reverse (seq sequence))))

(defn map
  "Returns a sequence consisting of the result of applying `f` to the
  first item, followed by applying f to the second items, until sequence is
  exhausted."
  [f sequence]
  (cond (vector? sequence) (.map sequence #(f %))
        (list? sequence) (map-list f sequence)
        (nil? sequence) '()
        :else (map f (seq sequence))))

(defn- map-list
  "Like map but optimized for lists"
  [f sequence]
  (loop [result '()
         items sequence]
    (if (empty? items)
      (reverse result)
      (recur (cons (f (first items)) result) (rest items)))))

(defn filter
  "Returns a sequence of the items in coll for which (f? item) returns true.
  f? must be free of side-effects."
  [f? sequence]
  (cond (vector? sequence) (.filter sequence f?)
        (list? sequence) (filter-list f? sequence)
        (nil? sequence) '()
        :else (filter f? (seq sequence))))

(defn- filter-list
  "Like filter but for lists"
  [f? sequence]
  (loop [result '()
         items sequence]
    (if (empty? items)
      (reverse result)
      (recur (if (f? (first items))
              (cons (first items) result)
              result)
              (rest items)))))

(defn reduce
  [f & params]
  (let [has-initial (>= (count params) 2)
        initial (if has-initial (first params))
        sequence (if has-initial (second params) (first params))]
    (cond (nil? sequence) initial
          (vector? sequence) (if has-initial
                              (.reduce sequence f initial)
                              (.reduce sequence f))
          (list? sequence) (if has-initial
                            (reduce-list f initial sequence)
                            (reduce-list f (first sequence) (rest sequence)))
          :else (reduce f initial (seq sequence)))))

(defn- reduce-list
  [f initial sequence]
  (loop [result initial
         items sequence]
    (if (empty? items)
      result
      (recur (f result (first items)) (rest items)))))

(defn count
  "Returns number of elements in list"
  [sequence]
  (if (nil? sequence)
    0
    (.-length (seq sequence))))

(defn empty?
  "Returns true if list is empty"
  [sequence]
  (identical? (count sequence) 0))

(defn first
  "Return first item in a list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (.-head sequence)
        (or (vector? sequence) (string? sequence)) (get sequence 0)
        (lazy-seq? sequence) (first (lazy-seq-value sequence))
        :else (first (seq sequence))))

(defn second
  "Returns second item of the list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (first (rest sequence))
        (or (vector? sequence) (string? sequence)) (get sequence 1)
        (lazy-seq? sequence) (second (lazy-seq-value sequence))
        :else (first (rest (seq sequence)))))

(defn third
  "Returns third item of the list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (first (rest (rest sequence)))
        (or (vector? sequence) (string? sequence)) (get sequence 2)
        (lazy-seq? sequence) (third (lazy-seq-value sequence))
        :else (second (rest (seq sequence)))))

(defn rest
  "Returns list of all items except first one"
  [sequence]
  (cond (nil? sequence) '()
        (list? sequence) (.-tail sequence)
        (or (vector? sequence) (string? sequence)) (.slice sequence 1)
        (lazy-seq? sequence) (rest (lazy-seq-value sequence))
        :else (rest (seq sequence))))

(defn- last-of-list
  [list]
  (loop [item (first list)
         items (rest list)]
    (if (empty? items)
      item
      (recur (first items) (rest items)))))

(defn last
  "Return the last item in coll, in linear time"
  [sequence]
  (cond (or (vector? sequence)
            (string? sequence)) (get sequence (dec (count sequence)))
        (list? sequence) (last-of-list sequence)
        (nil? sequence) nil
        (lazy-seq? sequence) (last (lazy-seq-value sequence))
        :else (last (seq sequence))))

(defn butlast
  "Return a seq of all but the last item in coll, in linear time"
  [sequence]
  (let [items (cond (nil? sequence) nil
                    (string? sequence) (subs sequence 0 (dec (count sequence)))
                    (vector? sequence) (.slice sequence 0 (dec (count sequence)))
                    (list? sequence) (apply list (butlast (vec sequence)))
                    (lazy-seq? sequence) (butlast (lazy-seq-value sequence))
                    :else (butlast (seq sequence)))]
    (if (not (or (nil? items) (empty? items)))
        items)))

(defn take
  "Returns a sequence of the first `n` items, or all items if
  there are fewer than `n`."
  [n sequence]
  (cond (nil? sequence) '()
        (vector? sequence) (take-from-vector n sequence)
        (list? sequence) (take-from-list n sequence)
        (lazy-seq? sequence) (take n (lazy-seq-value sequence))
        :else (take n (seq sequence))))

(defn- take-vector-while
  [predicate vector]
  (loop [result []
         tail vector
         head (first vector)]
    (if (and (not (empty? tail))
             (predicate head))
      (recur (conj result head)
             (rest tail)
             (first tail))
      result)))

(defn- take-list-while
  [predicate items]
  (loop [result []
         tail items
         head (first items)]
    (if (and (not (empty? tail))
             (predicate? head))
      (recur (conj result head)
             (rest tail)
             (first tail))
      (apply list result))))

(defn take-while
  [predicate sequence]
  (cond (nil? sequence) '()
        (vector? sequence) (take-vector-while predicate sequence)
        (list? sequence) (take-vector-while predicate sequence)
        :else (take-while predicate
                          (lazy-seq-value sequence))))

(defn- take-from-vector
  "Like take but optimized for vectors"
  [n vector]
  (.slice vector 0 n))

(defn- take-from-list
  "Like take but for lists"
  [n sequence]
  (loop [taken '()
         items sequence
         n n]
    (if (or (identical? n 0) (empty? items))
      (reverse taken)
      (recur (cons (first items) taken)
             (rest items)
             (dec n)))))

(defn- drop-from-list [n sequence]
  (loop [left n
         items sequence]
    (if (or (< left 1) (empty? items))
      items
      (recur (dec left) (rest items)))))

(defn drop
  [n sequence]
  (if (<= n 0)
    sequence
    (cond (string? sequence) (.substr sequence n)
          (vector? sequence) (.slice sequence n)
          (list? sequence) (drop-from-list n sequence)
          (nil? sequence) '()
          (lazy-seq? sequence) (drop n (lazy-seq-value sequence))
          :else (drop n (seq sequence)))))

(defn- conj-list
  [sequence items]
  (reduce (fn [result item] (cons item result)) sequence items))

(defn conj
  [sequence & items]
  (cond (vector? sequence) (.concat sequence items)
        (string? sequence) (str sequence (apply str items))
        (nil? sequence) (apply list (reverse items))
        (or (list? sequence)
            (lazy-seq?)) (conj-list sequence items)
        (dictionary? sequence) (merge sequence (apply merge items))
        :else (throw (TypeError (str "Type can't be conjoined " sequence)))))

(defn assoc
  [source & key-values]
  ;(assert (even? (count key-values)) "Wrong number of arguments")
  ;(assert (and (not (seq? source))
  ;             (not (vector? source))
  ;             (object? source)) "Can only assoc on dictionaries")
  (conj source (apply dictionary key-values)))

(defn concat
  "Returns list representing the concatenation of the elements in the
  supplied lists."
  [& sequences]
  (reverse
    (reduce
      (fn [result sequence]
        (reduce
          (fn [result item] (cons item result))
          result
          (seq sequence)))
      '()
      sequences)))

(defn seq [sequence]
  (cond (nil? sequence) nil
        (or (vector? sequence) (list? sequence) (lazy-seq? sequence)) sequence
        (string? sequence) (.call Array.prototype.slice sequence)
        (dictionary? sequence) (key-values sequence)
        :default (throw (TypeError (str "Can not seq " sequence)))))

(defn seq? [sequence]
  (or (list? sequence)
      (lazy-seq? sequence)))

(defn- list->vector [source]
  (loop [result []
         list source]
    (if (empty? list)
      result
      (recur
        (do (.push result (first list)) result)
        (rest list)))))

(defn vec
  "Creates a new vector containing the contents of sequence"
  [sequence]
  (cond (nil? sequence) []
        (vector? sequence) sequence
        (list? sequence) (list->vector sequence)
        :else (vec (seq sequence))))

(defn sort
  "Returns a sorted sequence of the items in coll.
  If no comparator is supplied, uses compare."
  [f items]
  (let [has-comparator (fn? f)
        items (if (and (not has-comparator) (nil? items)) f items)
        compare (if has-comparator (fn [a b] (if (f a b) 0 1)))]
    (cond (nil? items) '()
          (vector? items) (.sort items compare)
          (list? items) (apply list (.sort (vec items) compare))
          (dictionary? items) (.sort (seq items) compare)
          :else (sort f (seq items)))))

(defn repeat
  "Returns a vector of given `n` length with of given `x`
  items. Not compatible with clojure as it's not a lazy
  and only finite repeats are supported"
  [n x]
  (loop [n n
         result []]
    (if (<= n 0)
      result
      (recur (dec n)
             (conj result x)))))

(defn every?
  [predicate sequence]
  (.every (vec sequence) #(predicate %)))

(defn some
  "Returns the first logical true value of (pred x) for any x in coll,
  else nil.  One common idiom is to use a set as pred, for example
  this will return :fred if :fred is in the sequence, otherwise nil:
  (some even? [1 3]) => false
  (some even? [1 2 3 4] => true"
  [predicate sequence]
  (loop [items sequence]
    (cond (empty? items) false
          (predicate (first items)) true
          :else (recur (rest items)))))

(defn partition
  ([n coll] (partition n n coll))
  ([n step coll] (partition n step [] coll))
  ([n step pad coll]
   (loop [result []
          items (seq coll)]
     (let [chunk (take n items)
           size (count chunk)]
       (cond (identical? size n) (recur (conj result chunk)
                                        (drop step items)
                                        (take n items))
             (identical? 0 size) result
             (> n (+ size (count pad))) result
             :else (conj result
                         (take n (vec (concat chunk
                                              pad)))))))))

(defn interleave
  ([ax bx]
   (loop [cx []
          ax ax
          bx bx]
     (if (or (empty? ax)
             (empty? bx))
       (seq cx)
       (recur (conj cx
                    (first ax)
                    (first bx))
              (rest ax)
              (rest bx)))))
  ([& sequences]
   (loop [result []
          sequences sequences]
     (if (some empty? sequences)
       result
       (recur (concat result (map first sequences))
              (map rest sequences))))))

(defn nth
  "Returns nth item of the sequence"
  [sequence index not-found]
  (cond (nil? sequence) not-found
        (list? sequence) (if (< index (count sequence))
                           (first (drop index sequence))
                           not-found)
        (or (vector? sequence)
            (string? sequence)) (if (< index (count sequence))
                                  (aget sequence index)
                                  not-found)
        (lazy-seq? sequence) (nth (lazy-seq-value sequence) index not-found)
        :else (throw (TypeError "Unsupported type"))))

(ns wisp.sequence
  (:require [wisp.runtime :refer [nil? vector? fn? number? string? dictionary?
                                  key-values str dec inc merge dictionary]]))

;; Implementation of list

(defn- List
  "List type"
  [head tail]
  (set! this.head head)
  (set! this.tail (or tail (list)))
  (set! this.length (inc (count this.tail)))
  this)

(set! List.prototype.length 0)
(set! List.type "wisp.list")
(set! List.prototype.type List.type)
(set! List.prototype.tail (Object.create List.prototype))
(set! List.prototype.to-string
      (fn []
        (loop [result ""
               list this]
          (if (empty? list)
            (str "(" (.substr result 1) ")")
            (recur
             (str result
                  " "
                  (if (vector? (first list))
                    (str "[" (.join (first list) " ") "]")
                    (if (nil? (first list))
                      "nil"
                      (if (string? (first list))
                        (.stringify JSON (first list))
                        (if (number? (first list))
                          (.stringify JSON (first list))
                          (first list))))))
             (rest list))))))

(defn- lazy-seq-value [lazy-seq]
  (if (not (.-realized lazy-seq))
    (and (set! (.-realized lazy-seq) true)
         (set! (.-x lazy-seq) (.x lazy-seq)))
    (.-x lazy-seq)))

(defn- LazySeq [realized x]
  (set! (.-realized this) (or realized false))
  (set! (.-x this) x)
  this)
(set! LazySeq.type "wisp.lazy.seq")
(set! LazySeq.prototype.type LazySeq.type)

(defn lazy-seq
  [realized body]
  (LazySeq. realized body))

(defn lazy-seq?
  [value]
  (and value (identical? LazySeq.type value.type)))

(defmacro lazy-seq
  "Takes a body of expressions that returns an ISeq or nil, and yields
  a Seqable object that will invoke the body only the first time seq
  is called, and will cache the result and return it on all subsequent
  seq calls. See also - realized?"
  {:added "1.0"}
  [& body]
  `(.call lazy-seq nil false (fn [] ~@body)))

(defn list?
  "Returns true if list"
  [value]
  (and value (identical? List.type value.type)))

(defn list
  "Creates list of the given items"
  []
  (if (identical? (.-length arguments) 0)
    (Object.create List.prototype)
    (.reduce-right (.call Array.prototype.slice arguments)
                   (fn [tail head] (cons head tail))
                   (list))))

(defn cons
  "Creates list with `head` as first item and `tail` as rest"
  [head tail]
  (new List head tail))

(defn- reverse-list
  [sequence]
  (loop [items []
           source sequence]
      (if (empty? source)
        (apply list items)
        (recur (.concat [(first source)] items)
               (rest source)))))

(defn ^boolean sequential?
  "Returns true if coll satisfies ISequential"
  [x] (or (list? x)
          (vector? x)
          (lazy-seq? x)
          (dictionary? x)
          (string? x)))

(defn reverse
  "Reverse order of items in the sequence"
  [sequence]
  (cond (list? sequence) (reverse-list sequence)
        (vector? sequence) (.reverse sequence)
        (nil? sequence) '()
        :else (reverse (seq sequence))))

(defn map
  "Returns a sequence consisting of the result of applying `f` to the
  first item, followed by applying f to the second items, until sequence is
  exhausted."
  [f sequence]
  (cond (vector? sequence) (.map sequence f)
        (list? sequence) (map-list f sequence)
        (nil? sequence) '()
        :else (map f (seq sequence))))

(defn- map-list
  "Like map but optimized for lists"
  [f sequence]
  (loop [result '()
         items sequence]
    (if (empty? items)
      (reverse result)
      (recur (cons (f (first items)) result) (rest items)))))

(defn filter
  "Returns a sequence of the items in coll for which (f? item) returns true.
  f? must be free of side-effects."
  [f? sequence]
  (cond (vector? sequence) (.filter sequence f?)
        (list? sequence) (filter-list f? sequence)
        (nil? sequence) '()
        :else (filter f? (seq sequence))))

(defn- filter-list
  "Like filter but for lists"
  [f? sequence]
  (loop [result '()
         items sequence]
    (if (empty? items)
      (reverse result)
      (recur (if (f? (first items))
              (cons (first items) result)
              result)
              (rest items)))))

(defn reduce
  [f & params]
  (let [has-initial (>= (count params) 2)
        initial (if has-initial (first params))
        sequence (if has-initial (second params) (first params))]
    (cond (nil? sequence) initial
          (vector? sequence) (if has-initial
                              (.reduce sequence f initial)
                              (.reduce sequence f))
          (list? sequence) (if has-initial
                            (reduce-list f initial sequence)
                            (reduce-list f (first sequence) (rest sequence)))
          :else (reduce f initial (seq sequence)))))

(defn- reduce-list
  [f initial sequence]
  (loop [result initial
         items sequence]
    (if (empty? items)
      result
      (recur (f result (first items)) (rest items)))))

(defn count
  "Returns number of elements in list"
  [sequence]
  (if (nil? sequence)
    0
    (.-length (seq sequence))))

(defn empty?
  "Returns true if list is empty"
  [sequence]
  (identical? (count sequence) 0))

(defn first
  "Return first item in a list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (.-head sequence)
        (or (vector? sequence) (string? sequence)) (get sequence 0)
        (lazy-seq? sequence) (first (lazy-seq-value sequence))
        :else (first (seq sequence))))

(defn second
  "Returns second item of the list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (first (rest sequence))
        (or (vector? sequence) (string? sequence)) (get sequence 1)
        (lazy-seq? sequence) (second (lazy-seq-value sequence))
        :else (first (rest (seq sequence)))))

(defn third
  "Returns third item of the list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (first (rest (rest sequence)))
        (or (vector? sequence) (string? sequence)) (get sequence 2)
        (lazy-seq? sequence) (third (lazy-seq-value sequence))
        :else (second (rest (seq sequence)))))

(defn rest
  "Returns list of all items except first one"
  [sequence]
  (cond (nil? sequence) '()
        (list? sequence) (.-tail sequence)
        (or (vector? sequence) (string? sequence)) (.slice sequence 1)
        (lazy-seq? sequence) (rest (lazy-seq-value sequence))
        :else (rest (seq sequence))))

(defn- last-of-list
  [list]
  (loop [item (first list)
         items (rest list)]
    (if (empty? items)
      item
      (recur (first items) (rest items)))))

(defn last
  "Return the last item in coll, in linear time"
  [sequence]
  (cond (or (vector? sequence)
            (string? sequence)) (get sequence (dec (count sequence)))
        (list? sequence) (last-of-list sequence)
        (nil? sequence) nil
        (lazy-seq? sequence) (last (lazy-seq-value sequence))
        :else (last (seq sequence))))

(defn butlast
  "Return a seq of all but the last item in coll, in linear time"
  [sequence]
  (let [items (cond (nil? sequence) nil
                    (string? sequence) (subs sequence 0 (dec (count sequence)))
                    (vector? sequence) (.slice sequence 0 (dec (count sequence)))
                    (list? sequence) (apply list (butlast (vec sequence)))
                    (lazy-seq? sequence) (butlast (lazy-seq-value sequence))
                    :else (butlast (seq sequence)))]
    (if (not (or (nil? items) (empty? items)))
        items)))

(defn take
  "Returns a sequence of the first `n` items, or all items if
  there are fewer than `n`."
  [n sequence]
  (cond (nil? sequence) '()
        (vector? sequence) (take-from-vector n sequence)
        (list? sequence) (take-from-list n sequence)
        (lazy-seq? sequence) (take n (lazy-seq-value sequence))
        :else (take n (seq sequence))))

(defn- take-vector-while
  [predicate vector]
  (loop [result []
         tail vector
         head (first vector)]
    (if (and (not (empty? tail))
             (predicate head))
      (recur (conj result head)
             (rest tail)
             (first tail))
      result)))

(defn- take-list-while
  [predicate items]
  (loop [result []
         tail items
         head (first items)]
    (if (and (not (empty? tail))
             (predicate? head))
      (recur (conj result head)
             (rest tail)
             (first tail))
      (apply list result))))

(defn take-while
  [predicate sequence]
  (cond (nil? sequence) '()
        (vector? sequence) (take-vector-while predicate sequence)
        (list? sequence) (take-vector-while predicate sequence)
        :else (take-while predicate
                          (lazy-seq-value sequence))))

(defn- take-from-vector
  "Like take but optimized for vectors"
  [n vector]
  (.slice vector 0 n))

(defn- take-from-list
  "Like take but for lists"
  [n sequence]
  (loop [taken '()
         items sequence
         n n]
    (if (or (identical? n 0) (empty? items))
      (reverse taken)
      (recur (cons (first items) taken)
             (rest items)
             (dec n)))))

(defn- drop-from-list [n sequence]
  (loop [left n
         items sequence]
    (if (or (< left 1) (empty? items))
      items
      (recur (dec left) (rest items)))))

(defn drop
  [n sequence]
  (if (<= n 0)
    sequence
    (cond (string? sequence) (.substr sequence n)
          (vector? sequence) (.slice sequence n)
          (list? sequence) (drop-from-list n sequence)
          (nil? sequence) '()
          (lazy-seq? sequence) (drop n (lazy-seq-value sequence))
          :else (drop n (seq sequence)))))

(defn- conj-list
  [sequence items]
  (reduce (fn [result item] (cons item result)) sequence items))

(defn conj
  [sequence & items]
  (cond (vector? sequence) (.concat sequence items)
        (string? sequence) (str sequence (apply str items))
        (nil? sequence) (apply list (reverse items))
        (or (list? sequence)
            (lazy-seq?)) (conj-list sequence items)
        (dictionary? sequence) (merge sequence (apply merge items))
        :else (throw (TypeError (str "Type can't be conjoined " sequence)))))

(defn assoc
  [source & key-values]
  ;(assert (even? (count key-values)) "Wrong number of arguments")
  ;(assert (and (not (seq? source))
  ;             (not (vector? source))
  ;             (object? source)) "Can only assoc on dictionaries")
  (conj source (apply dictionary key-values)))

(defn concat
  "Returns list representing the concatenation of the elements in the
  supplied lists."
  [& sequences]
  (reverse
    (reduce
      (fn [result sequence]
        (reduce
          (fn [result item] (cons item result))
          result
          (seq sequence)))
      '()
      sequences)))

(defn seq [sequence]
  (cond (nil? sequence) nil
        (or (vector? sequence) (list? sequence) (lazy-seq? sequence)) sequence
        (string? sequence) (.call Array.prototype.slice sequence)
        (dictionary? sequence) (key-values sequence)
        :default (throw (TypeError (str "Can not seq " sequence)))))

(defn seq? [sequence]
  (or (list? sequence)
      (lazy-seq? sequence)))

(defn- list->vector [source]
  (loop [result []
         list source]
    (if (empty? list)
      result
      (recur
        (do (.push result (first list)) result)
        (rest list)))))

(defn vec
  "Creates a new vector containing the contents of sequence"
  [sequence]
  (cond (nil? sequence) []
        (vector? sequence) sequence
        (list? sequence) (list->vector sequence)
        :else (vec (seq sequence))))

(defn sort
  "Returns a sorted sequence of the items in coll.
  If no comparator is supplied, uses compare."
  [f items]
  (let [has-comparator (fn? f)
        items (if (and (not has-comparator) (nil? items)) f items)
        compare (if has-comparator (fn [a b] (if (f a b) 0 1)))]
    (cond (nil? items) '()
          (vector? items) (.sort items compare)
          (list? items) (apply list (.sort (vec items) compare))
          (dictionary? items) (.sort (seq items) compare)
          :else (sort f (seq items)))))

(defn repeat
  "Returns a vector of given `n` length with of given `x`
  items. Not compatible with clojure as it's not a lazy
  and only finite repeats are supported"
  [n x]
  (loop [n n
         result []]
    (if (<= n 0)
      result
      (recur (dec n)
             (conj result x)))))

(defn every?
  [predicate sequence]
  (.every (vec sequence) #(predicate %)))

(defn some
  "Returns the first logical true value of (pred x) for any x in coll,
  else nil.  One common idiom is to use a set as pred, for example
  this will return :fred if :fred is in the sequence, otherwise nil:
  (some even? [1 3]) => false
  (some even? [1 2 3 4] => true"
  [predicate sequence]
  (loop [items sequence]
    (cond (empty? items) false
          (predicate (first items)) true
          :else (recur (rest items)))))

(defn partition
  ([n coll] (partition n n coll))
  ([n step coll] (partition n step [] coll))
  ([n step pad coll]
   (loop [result []
          items (seq coll)]
     (let [chunk (take n items)
           size (count chunk)]
       (cond (identical? size n) (recur (conj result chunk)
                                        (drop step items)
                                        (take n items))
             (identical? 0 size) result
             (> n (+ size (count pad))) result
             :else (conj result
                         (take n (vec (concat chunk
                                              pad)))))))))

(defn interleave
  ([ax bx]
   (loop [cx []
          ax ax
          bx bx]
     (if (or (empty? ax)
             (empty? bx))
       (seq cx)
       (recur (conj cx
                    (first ax)
                    (first bx))
              (rest ax)
              (rest bx)))))
  ([& sequences]
   (loop [result []
          sequences sequences]
     (if (some empty? sequences)
       result
       (recur (concat result (map first sequences))
              (map rest sequences))))))

(defn nth
  "Returns nth item of the sequence"
  [sequence index not-found]
  (cond (nil? sequence) not-found
        (list? sequence) (if (< index (count sequence))
                           (first (drop index sequence))
                           not-found)
        (or (vector? sequence)
            (string? sequence)) (if (< index (count sequence))
                                  (aget sequence index)
                                  not-found)
        (lazy-seq? sequence) (nth (lazy-seq-value sequence) index not-found)
        :else (throw (TypeError "Unsupported type"))))


Implement lazy sequences.

Implement lazy sequences.

(import [nil? vector? fn? number? string? dictionary?
         key-values str dec inc merge] "./runtime")

;; Implementation of list

(defn List
  "List type"
  [head tail]
  (set! this.head head)
  (set! this.tail (or tail (list)))
  (set! this.length (inc (count this.tail)))
  this)

(set! List.prototype.length 0)
(set! List.prototype.tail (Object.create List.prototype))
(set! List.prototype.to-string
      (fn []
        (loop [result ""
               list this]
          (if (empty? list)
            (str "(" (.substr result 1) ")")
            (recur
             (str result
                  " "
                  (if (vector? (first list))
                    (str "[" (.join (first list) " ") "]")
                    (if (nil? (first list))
                      "nil"
                      (if (string? (first list))
                        (.stringify JSON (first list))
                        (if (number? (first list))
                          (.stringify JSON (first list))
                          (first list))))))
             (rest list))))))

(defn lazy-seq-value [lazy-seq]
  (if (not (.-realized lazy-seq))
    (and (set! (.-realized lazy-seq) true)
         (set! (.-x lazy-seq) (.x lazy-seq)))
    (.-x lazy-seq)))

(defn LazySeq [realized x]
  (set! (.-realized this) (or realized false))
  (set! (.-x this) x)
  this)

(defn lazy-seq
  [realized body]
  (LazySeq. realized body))

(defn lazy-seq?
  [value]
  (instance? LazySeq value))

(defmacro lazy-seq
  "Takes a body of expressions that returns an ISeq or nil, and yields
  a Seqable object that will invoke the body only the first time seq
  is called, and will cache the result and return it on all subsequent
  seq calls. See also - realized?"
  {:added "1.0"}
  [& body]
  `(.call lazy-seq nil false (fn [] ~@body)))

(defn list?
  "Returns true if list"
  [value]
  (.prototype-of? List.prototype value))

(defn list
  "Creates list of the given items"
  []
  (if (= (.-length arguments) 0)
    (Object.create List.prototype)
    (.reduce-right (.call Array.prototype.slice arguments)
                   (fn [tail head] (cons head tail))
                   (list))))

(defn cons
  "Creates list with `head` as first item and `tail` as rest"
  [head tail]
  (new List head tail))

(defn reverse-list
  [sequence]
  (loop [items []
           source sequence]
      (if (empty? source)
        (apply list items)
        (recur (.concat [(first source)] items)
               (rest source)))))

(defn reverse
  "Reverse order of items in the sequence"
  [sequence]
  (cond (list? sequence) (reverse-list sequence)
        (vector? sequence) (.reverse sequence)
        (nil? sequence) '()
        :else (reverse (seq sequence))))

(defn map
  "Returns a sequence consisting of the result of applying `f` to the
  first item, followed by applying f to the second items, until sequence is
  exhausted."
  [f sequence]
  (cond (vector? sequence) (.map sequence f)
        (list? sequence) (map-list f sequence)
        (nil? sequence) '()
        :else (map f (seq sequence))))

(defn map-list
  "Like map but optimized for lists"
  [f sequence]
  (loop [result '()
         items sequence]
    (if (empty? items)
      (reverse result)
      (recur (cons (f (first items)) result) (rest items)))))

(defn filter
  "Returns a sequence of the items in coll for which (f? item) returns true.
  f? must be free of side-effects."
  [f? sequence]
  (cond (vector? sequence) (.filter sequence f?)
        (list? sequence) (filter-list f? sequence)
        (nil? sequence) '()
        :else (filter f? (seq sequence))))

(defn filter-list
  "Like filter but for lists"
  [f? sequence]
  (loop [result '()
         items sequence]
    (if (empty? items)
      (reverse result)
      (recur (if (f? (first items))
              (cons (first items) result)
              result)
              (rest items)))))

(defn reduce
  [f & params]
  (let [has-initial (>= (count params) 2)
        initial (if has-initial (first params))
        sequence (if has-initial (second params) (first params))]
    (cond (nil? sequence) initial
          (vector? sequence) (if has-initial
                              (.reduce sequence f initial)
                              (.reduce sequence f))
          (list? sequence) (if has-initial
                            (reduce-list f initial sequence)
                            (reduce-list f (first sequence) (rest sequence)))
          :else (reduce f initial (seq sequence)))))

(defn reduce-list
  [f initial sequence]
  (loop [result initial
         items sequence]
    (if (empty? items)
      result
      (recur (f result (first items)) (rest items)))))

(defn count
  "Returns number of elements in list"
  [sequence]
  (if (nil? sequence)
    0
    (.-length (seq sequence))))

(defn empty?
  "Returns true if list is empty"
  [sequence]
  (= (count sequence) 0))

(defn first
  "Return first item in a list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (.-head sequence)
        (or (vector? sequence) (string? sequence)) (get sequence 0)
        (lazy-seq? sequence) (first (lazy-seq-value sequence))
        :else (first (seq sequence))))

(defn second
  "Returns second item of the list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (first (rest sequence))
        (or (vector? sequence) (string? sequence)) (get sequence 1)
        (lazy-seq? sequence) (second (lazy-seq-value sequence))
        :else (first (rest (seq sequence)))))

(defn third
  "Returns third item of the list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (first (rest (rest sequence)))
        (or (vector? sequence) (string? sequence)) (get sequence 2)
        (lazy-seq? sequence) (third (lazy-seq-value sequence))
        :else (second (rest (seq sequence)))))

(defn rest
  "Returns list of all items except first one"
  [sequence]
  (cond (nil? sequence) '()
        (list? sequence) (.-tail sequence)
        (or (vector? sequence) (string? sequence)) (.slice sequence 1)
        (lazy-seq? sequence) (rest (lazy-seq-value sequence))
        :else (rest (seq sequence))))

(defn last-of-list
  [list]
  (loop [item (first list)
         items (rest list)]
    (if (empty? items)
      item
      (recur (first items) (rest items)))))

(defn last
  "Return the last item in coll, in linear time"
  [sequence]
  (cond (or (vector? sequence)
            (string? sequence)) (get sequence (dec (count sequence)))
        (list? sequence) (last-of-list sequence)
        (nil? sequence) nil
        (lazy-seq? sequence) (last (lazy-seq-value sequence))
        :else (last (seq sequence))))

(defn butlast
  "Return a seq of all but the last item in coll, in linear time"
  [sequence]
  (let [items (cond (nil? sequence) nil
                    (string? sequence) (subs sequence 0 (dec (count sequence)))
                    (vector? sequence) (.slice sequence 0 (dec (count sequence)))
                    (list? sequence) (apply list (butlast (vec sequence)))
                    (lazy-seq? sequence) (butlast (lazy-seq-value sequence))
                    :else (butlast (seq sequence)))]
    (if (not (or (nil? items) (empty? items)))
        items)))

(defn take
  "Returns a sequence of the first `n` items, or all items if
  there are fewer than `n`."
  [n sequence]
  (cond (nil? sequence) '()
        (vector? sequence) (take-from-vector n sequence)
        (list? sequence) (take-from-list n sequence)
        (lazy-seq? sequence) (take n (lazy-seq-value sequence))
        :else (take n (seq sequence))))

(defn take-from-vector
  "Like take but optimized for vectors"
  [n vector]
  (.slice vector 0 n))

(defn take-from-list
  "Like take but for lists"
  [n sequence]
  (loop [taken '()
         items sequence
         n n]
    (if (or (= n 0) (empty? items))
      (reverse taken)
      (recur (cons (first items) taken)
             (rest items)
             (dec n)))))

(defn drop-from-list [n sequence]
  (loop [left n
         items sequence]
    (if (or (< left 1) (empty? items))
      items
      (recur (dec left) (rest items)))))

(defn drop
  [n sequence]
  (if (<= n 0)
    sequence
    (cond (string? sequence) (.substr sequence n)
          (vector? sequence) (.slice sequence n)
          (list? sequence) (drop-from-list n sequence)
          (nil? sequence) '()
          (lazy-seq? sequence) (drop n (lazy-seq-value sequence))
          :else (drop n (seq sequence)))))

(defn conj-list
  [sequence items]
  (reduce (fn [result item] (cons item result)) sequence items))

(defn conj
  [sequence & items]
  (cond (vector? sequence) (.concat sequence items)
        (string? sequence) (str sequence (apply str items))
        (nil? sequence) (apply list (reverse items))
        (or (list? sequence)
            (lazy-seq?)) (conj-list sequence items)
        (dictionary? sequence) (merge sequence (apply merge items))
        :else (throw (TypeError (str "Type can't be conjoined " sequence)))))

(defn concat
  "Returns list representing the concatenation of the elements in the
  supplied lists."
  [& sequences]
  (reverse
    (reduce
      (fn [result sequence]
        (reduce
          (fn [result item] (cons item result))
          result
          (seq sequence)))
      '()
      sequences)))

(defn seq [sequence]
  (cond (nil? sequence) nil
        (or (vector? sequence) (list? sequence) (lazy-seq? sequence)) sequence
        (string? sequence) (.call Array.prototype.slice sequence)
        (dictionary? sequence) (key-values sequence)
        :default (throw (TypeError (str "Can not seq " sequence)))))

(defn list->vector [source]
  (loop [result []
         list source]
    (if (empty? list)
      result
      (recur
        (do (.push result (first list)) result)
        (rest list)))))

(defn vec
  "Creates a new vector containing the contents of sequence"
  [sequence]
  (cond (nil? sequence) []
        (vector? sequence) sequence
        (list? sequence) (list->vector sequence)
        :else (vec (seq sequence))))

(defn sort
  "Returns a sorted sequence of the items in coll.
  If no comparator is supplied, uses compare."
  [f items]
  (let [has-comparator (fn? f)
        items (if (and (not has-comparator) (nil? items)) f items)
        compare (if has-comparator (fn [a b] (if (f a b) 0 1)))]
    (cond (nil? items) '()
          (vector? items) (.sort items compare)
          (list? items) (apply list (.sort (vec items) compare))
          (dictionary? items) (.sort (seq items) compare)
          :else (sort f (seq items)))))

(export cons conj list list? seq vec
        lazy-seq
        empty? count
        first second third rest last butlast
        take drop
        concat reverse
        sort
        map filter reduce)

(import [nil? vector? fn? number? string? dictionary?
         key-values str dec inc merge] "./runtime")

(defn List
  "List type"
  [head tail]
  (set! this.head head)
  (set! this.tail (or tail (list)))
  (set! this.length (inc (count this.tail)))
  this)

(set! List.prototype.length 0)
(set! List.prototype.tail (Object.create List.prototype))
(set! List.prototype.to-string
      (fn []
        (loop [result ""
               list this]
          (if (empty? list)
            (str "(" (.substr result 1) ")")
            (recur
             (str result
                  " "
                  (if (vector? (first list))
                    (str "[" (.join (first list) " ") "]")
                    (if (nil? (first list))
                      "nil"
                      (if (string? (first list))
                        (.stringify JSON (first list))
                        (if (number? (first list))
                          (.stringify JSON (first list))
                          (first list))))))
             (rest list))))))

(defn list?
  "Returns true if list"
  [value]
  (.prototype-of? List.prototype value))

(defn list
  "Creates list of the given items"
  []
  (if (= (.-length arguments) 0)
    (Object.create List.prototype)
    (.reduce-right (.call Array.prototype.slice arguments)
                   (fn [tail head] (cons head tail))
                   (list))))

(defn cons
  "Creates list with `head` as first item and `tail` as rest"
  [head tail]
  (new List head tail))

(defn reverse-list
  [sequence]
  (loop [items []
           source sequence]
      (if (empty? source)
        (apply list items)
        (recur (.concat [(first source)] items)
               (rest source)))))

(defn reverse
  "Reverse order of items in the sequence"
  [sequence]
  (cond (list? sequence) (reverse-list sequence)
        (vector? sequence) (.reverse sequence)
        (nil? sequence) '()
        :else (reverse (seq sequence))))

(defn map
  "Returns a sequence consisting of the result of applying `f` to the
  first item, followed by applying f to the second items, until sequence is
  exhausted."
  [f sequence]
  (cond (vector? sequence) (.map sequence f)
        (list? sequence) (map-list f sequence)
        (nil? sequence) '()
        :else (map f (seq sequence))))

(defn map-list
  "Like map but optimized for lists"
  [f sequence]
  (loop [result '()
         items sequence]
    (if (empty? items)
      (reverse result)
      (recur (cons (f (first items)) result) (rest items)))))

(defn filter
  "Returns a sequence of the items in coll for which (f? item) returns true.
  f? must be free of side-effects."
  [f? sequence]
  (cond (vector? sequence) (.filter sequence f?)
        (list? sequence) (filter-list f? sequence)
        (nil? sequence) '()
        :else (filter f? (seq sequence))))

(defn filter-list
  "Like filter but for lists"
  [f? sequence]
  (loop [result '()
         items sequence]
    (if (empty? items)
      (reverse result)
      (recur (if (f? (first items))
              (cons (first items) result)
              result)
              (rest items)))))

(defn reduce
  [f & params]
  (let [has-initial (>= (count params) 2)
        initial (if has-initial (first params))
        sequence (if has-initial (second params) (first params))]
    (cond (nil? sequence) initial
          (vector? sequence) (if has-initial
                              (.reduce sequence f initial)
                              (.reduce sequence f))
          (list? sequence) (if has-initial
                            (reduce-list f initial sequence)
                            (reduce-list f (first sequence) (rest sequence)))
          :else (reduce f initial (seq sequence)))))

(defn reduce-list
  [f initial sequence]
  (loop [result initial
         items sequence]
    (if (empty? items)
      result
      (recur (f result (first items)) (rest items)))))

(defn count
  "Returns number of elements in list"
  [sequence]
  (if (nil? sequence)
    0
    (.-length (seq sequence))))

(defn empty?
  "Returns true if list is empty"
  [sequence]
  (= (count sequence) 0))

(defn first
  "Return first item in a list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (.-head sequence)
        (or (vector? sequence) (string? sequence)) (get sequence 0)
        :else (first (seq sequence))))

(defn second
  "Returns second item of the list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (first (rest sequence))
        (or (vector? sequence) (string? sequence)) (get sequence 1)
        :else (first (rest (seq sequence)))))

(defn third
  "Returns third item of the list"
  [sequence]
  (cond (nil? sequence) nil
        (list? sequence) (first (rest (rest sequence)))
        (or (vector? sequence) (string? sequence)) (get sequence 2)
        :else (second (rest (seq sequence)))))

(defn rest
  "Returns list of all items except first one"
  [sequence]
  (cond (nil? sequence) '()
        (list? sequence) (.-tail sequence)
        (or (vector? sequence) (string? sequence)) (.slice sequence 1)
        :else (rest (seq sequence))))

(defn last-of-list
  [list]
  (loop [item (first list)
         items (rest list)]
    (if (empty? items)
      item
      (recur (first items) (rest items)))))

(defn last
  "Return the last item in coll, in linear time"
  [sequence]
  (cond (or (vector? sequence)
            (string? sequence)) (get sequence (dec (count sequence)))
        (list? sequence) (last-of-list sequence)
        (nil? sequence) nil
        :else (last (seq sequence))))

(defn butlast
  "Return a seq of all but the last item in coll, in linear time"
  [sequence]
  (let [items (cond (nil? sequence) nil
                    (string? sequence) (subs sequence 0 (dec (count sequence)))
                    (vector? sequence) (.slice sequence 0 (dec (count sequence)))
                    (list? sequence) (apply list (butlast (vec sequence)))
                    :else (butlast (seq sequence)))]
    (if (not (or (nil? items) (empty? items)))
        items)))

(defn take
  "Returns a sequence of the first `n` items, or all items if
  there are fewer than `n`."
  [n sequence]
  (cond (nil? sequence) '()
        (vector? sequence) (take-from-vector n sequence)
        (list? sequence) (take-from-list n sequence)
        :else (take n (seq sequence))))

(defn take-from-vector
  "Like take but optimized for vectors"
  [n vector]
  (.slice vector 0 n))

(defn take-from-list
  "Like take but for lists"
  [n sequence]
  (loop [taken '()
         items sequence
         n n]
    (if (or (= n 0) (empty? items))
      (reverse taken)
      (recur (cons (first items) taken)
             (rest items)
             (dec n)))))

(defn drop-from-list [n sequence]
  (loop [left n
         items sequence]
    (if (or (< left 1) (empty? items))
      items
      (recur (dec left) (rest items)))))

(defn drop
  [n sequence]
  (if (<= n 0)
    sequence
    (cond (string? sequence) (.substr sequence n)
          (vector? sequence) (.slice sequence n)
          (list? sequence) (drop-from-list n sequence)
          (nil? sequence) '()
          :else (drop n (seq sequence)))))

(defn conj-list
  [sequence items]
  (reduce (fn [result item] (cons item result)) sequence items))

(defn conj
  [sequence & items]
  (cond (vector? sequence) (.concat sequence items)
        (string? sequence) (str sequence (apply str items))
        (nil? sequence) (apply list (reverse items))
        (list? sequence) (conj-list sequence items)
        (dictionary? sequence) (merge sequence (apply merge items))
        :else (throw (TypeError (str "Type can't be conjoined " sequence)))))

(defn concat
  "Returns list representing the concatenation of the elements in the
  supplied lists."
  [& sequences]
  (reverse
    (reduce
      (fn [result sequence]
        (reduce
          (fn [result item] (cons item result))
          result
          (seq sequence)))
      '()
      sequences)))

(defn seq [sequence]
  (cond (nil? sequence) nil
        (or (vector? sequence) (list? sequence)) sequence
        (string? sequence) (.call Array.prototype.slice sequence)
        (dictionary? sequence) (key-values sequence)
        :default (throw (TypeError (str "Can not seq " sequence)))))

(defn list->vector [source]
  (loop [result []
         list source]
    (if (empty? list)
      result
      (recur
        (do (.push result (first list)) result)
        (rest list)))))

(defn vec
  "Creates a new vector containing the contents of sequence"
  [sequence]
  (cond (nil? sequence) []
        (vector? sequence) sequence
        (list? sequence) (list->vector sequence)
        :else (vec (seq sequence))))

(defn sort
  "Returns a sorted sequence of the items in coll.
  If no comparator is supplied, uses compare."
  [f items]
  (let [has-comparator (fn? f)
        items (if (and (not has-comparator) (nil? items)) f items)
        compare (if has-comparator (fn [a b] (if (f a b) 0 1)))]
    (cond (nil? items) '()
          (vector? items) (.sort items compare)
          (list? items) (apply list (.sort (vec items) compare))
          (dictionary? items) (.sort (seq items) compare)
          :else (sort f (seq items)))))

(export cons conj list list? seq vec
        empty? count
        first second third rest last butlast
        take drop
        concat reverse
        sort
        map filter reduce)


Add tests for export forms.

Add tests for export forms.

(import [symbol] "../src/ast")
(import [list] "../src/sequence")
(import [str =] "../src/runtime")
(import [self-evaluating? compile macroexpand compile-program] "../src/compiler")
(import [read-from-string] "../src/reader")

(defn transpile [& forms] (compile-program forms))

(.log console "self evaluating forms")
(assert (self-evaluating? 1) "number is self evaluating")
(assert (self-evaluating? "string") "string is self evaluating")
(assert (self-evaluating? true) "true is boolean => self evaluating")
(assert (self-evaluating? false) "false is boolean => self evaluating")
(assert (self-evaluating?) "no args is nil => self evaluating")
(assert (self-evaluating? nil) "nil is self evaluating")
(assert (self-evaluating? :keyword) "keyword is self evaluating")
(assert (not (self-evaluating? ':keyword)) "quoted keyword not self evaluating")
(assert (not (self-evaluating? (list))) "list is not self evaluating")
(assert (not (self-evaluating? self-evaluating?)) "fn is not self evaluating")
(assert (not (self-evaluating? (symbol "symbol"))) "symbol is not self evaluating")

(.log console "compile primitive forms")

(assert (= (transpile '(def x)) "var x = void(0);\nexports.x = x")
        "def compiles properly")
(assert (= (transpile '(def y 1)) "var y = 1;\nexports.y = y")
        "def with two args compiled properly")
(assert (= (transpile ''(def x 1))
                      "list(symbol(void(0), \"def\"), symbol(void(0), \"x\"), 1)")
        "quotes preserve lists")

(.log console "private defs")

;; Note need to actually read otherwise metadata is lost after
;; compilation.
(assert (= (transpile (read-from-string "(def ^:private x)"))
           "var x = void(0)"))
(assert (= (transpile (read-from-string "(def ^:private y 1)"))
           "var y = 1"))

(.log console "compile invoke forms")
(assert (identical? (transpile '(foo)) "foo()")
        "function calls compile")
(assert (identical? (transpile '(foo bar)) "foo(bar)")
        "function calls with single arg compile")
(assert (identical? (transpile '(foo bar baz)) "foo(bar, baz)")
        "function calls with multi arg compile")
(assert (identical? (transpile '(foo ((bar baz) beep)))
                    "foo((bar(baz))(beep))")
        "nested function calls compile")

(.log console "compile functions")

(assert (identical? (transpile '(fn [x] x))
                    "function(x) {\n  return x;\n}")
        "function compiles")
(assert (identical? (transpile '(fn [x] (def y 1) (foo x y)))
                    "function(x) {\n  var y = 1;\n  return foo(x, y);\n}")
        "function with multiple statements compiles")
(assert (identical? (transpile '(fn identity [x] x))
                    "function identity(x) {\n  return x;\n}")
        "named function compiles")
(assert (identical? (transpile '(fn a "docs docs" [x] x))
                    "function a(x) {\n  return x;\n}")
        "fn docs are supported")
(assert (identical? (transpile '(fn "docs docs" [x] x))
                    "function(x) {\n  return x;\n}")
        "fn docs for anonymous functions are supported")

(assert (identical? (transpile '(fn foo? ^boolean [x] true))
                    "function isFoo(x) {\n  return true;\n}")
        "metadata is supported")

(assert (identical? (transpile '(fn [a & b] a))
"function(a) {
  var b = Array.prototype.slice.call(arguments, 1);
  return a;
}") "function with variadic arguments")

(assert (identical? (transpile '(fn [& a] a))
"function() {
  var a = Array.prototype.slice.call(arguments, 0);
  return a;
}") "function with all variadic arguments")

(assert (identical? (transpile '(fn
                                  ([] 0)
                                  ([x] x)))
"function(x) {
  switch (arguments.length) {
    case 0:
      return 0;
    case 1:
      return x;

    default:
      (function() { throw Error(\"Invalid arity\"); })()
  };
  return void(0);
}") "function with overloads")

(assert (identical? (transpile
'(fn sum
  "doc"
  {:version "1.0"}
  ([] 0)
  ([x] x)
  ([x y] (+ x y))
  ([x & rest] (reduce rest sum x))))

"function sum(x, y) {
  switch (arguments.length) {
    case 0:
      return 0;
    case 1:
      return x;
    case 2:
      return x + y;

    default:
      var rest = Array.prototype.slice.call(arguments, 1);
      return reduce(rest, sum, x);
  };
  return void(0);
}") "function with overloads docs & metadata")

(.log console "compile if special form")

(assert (identical? (transpile '(if foo (bar)))
                    "foo ?\n  bar() :\n  void(0)")
        "if compiles")

(assert (identical? (transpile '(if foo (bar) baz))
                    "foo ?\n  bar() :\n  baz")
        "if-else compiles")

(assert (identical? (transpile '(if monday? (.log console "monday")))
                    "isMonday ?\n  console.log(\"monday\") :\n  void(0)")
        "macros inside blocks expand properly")

(.log console "compile do special form")

(assert (identical? (transpile '(do (foo bar) bar))
                    "(function() {\n  foo(bar);\n  return bar;\n})()")
        "do compiles")
(assert (identical? (transpile '(do))
                    "(function() {\n  return void(0);\n})()")
        "empty do compiles")

(.log console "compile let special form")

(assert (identical? (transpile '(let [] x))
                    "(function() {\n  return x;\n})()")
        "let bindings compiles properly")
(assert (identical?
         (transpile '(let [x 1 y 2] x))
         "(function() {\n  var x = 1;\n  var y = 2;\n  return x;\n})()")
        "let with bindings compiles properly")

(.log console "compile throw special form")

(assert (identical? (transpile '(throw error))
                    "(function() { throw error; })()")
        "throw reference compiles")

(assert (identical? (transpile '(throw (Error message)))
                    "(function() { throw Error(message); })()")
        "throw expression compiles")

(assert (identical? (transpile '(throw "boom"))
                    "(function() { throw \"boom\"; })()")
        "throw string compile")

(.log console "compile set! special form")

(assert (identical? (transpile '(set! x 1))
                    "x = 1")
        "set! compiles")

(assert (identical? (transpile '(set! x (foo bar 2)))
                    "x = foo(bar, 2)")
        "set! with value expression compiles")

(assert (identical? (transpile '(set! x (.m o)))
                    "x = o.m()")
        "set! expands macros")

(.log console "compile vectors")

(assert (identical? (transpile '[a b]) "[a, b]")
        "vector compiles")

(assert (identical? (transpile '[a (b c)]) "[a, b(c)]")
        "vector of expressions compiles")

(assert (identical? (transpile '[]) "[]")
        "empty vector compiles")

(.log console "compiles try special form")

(assert (identical?
         (transpile '(try (m 1 0) (catch e e)))
         "(function() {\ntry {\n  return m(1, 0);\n} catch (e) {\n  return e;\n}})()")
        "try / catch compiles")

(assert (identical?
         (transpile '(try (m 1 0) (finally 0)))
         "(function() {\ntry {\n  return m(1, 0);\n} finally {\n  return 0;\n}})()")
        "try / finally compiles")

(assert (identical?
         (transpile '(try (m 1 0) (catch e e) (finally 0)))
         "(function() {\ntry {\n  return m(1, 0);\n} catch (e) {\n  return e;\n} finally {\n  return 0;\n}})()")
        "try / catch / finally compiles")

(.log console "compile property / method access / call special forms")

(assert (identical? (transpile '(.log console message))
                    "console.log(message)")
        "method call compiles correctly")
(assert (identical? (transpile '(.-location window))
                    "window.location")
        "property access compiles correctly")
(assert (identical? (transpile '(.-foo? bar))
                    "bar.isFoo")
        "property access compiles naming conventions")
(assert (identical? (transpile '(.-location (.open window url)))
                    "(window.open(url)).location")
        "compound property access and method call")
(assert (identical? (transpile '(.slice (.splice arr 0)))
                    "arr.splice(0).slice()")
        "(.slice (.splice arr 0)) => arr.splice(0).slice()")
(assert (identical? (transpile '(.a (.b "/")))
                    "\"/\".b().a()")
        "(.a (.b \"/\")) => \"/\".b().a()")

(.log console "compile sugar for keyword based access")

(assert (identical? (transpile '(:foo bar))
                    "(bar || 0)[\"foo\"]"))

(.log console "compile unquote-splicing forms")

(assert (identical? (transpile '`(1 ~@'(2 3)))
                    "concat(list(1), list(2, 3))")
        "list unquote-splicing compiles")
(assert (identical? (transpile '`())
                    "list()")
         "empty list unquotes to empty list")

(assert (identical? (transpile '`[1 ~@[2 3]])
                    "vec(concat([1], [2, 3]))")
        "vector unquote-splicing compiles")

(assert (identical? (transpile '`[])
                    "[]")
        "syntax-quoted empty vector compiles to empty vector")

(.log console "compile references")

(assert (identical? (transpile '(set! **macros** []))
                    "__macros__ = []")
        "**macros** => __macros__")
(assert (identical?
         (transpile '(fn vector->list [v] (make list v)))
         "function vectorToList(v) {\n  return make(list, v);\n}")
        "list->vector => listToVector")
(assert (identical? (transpile '(swap! foo bar))
                    "swap(foo, bar)")
        "set! => set")

;(assert (identical? (transpile '(let [raw% foo-bar] raw%))
;                     "swap(foo, bar)")
;          "set! => set")

(assert (identical? (transpile '(def under_dog))
                    "var under_dog = void(0);\nexports.under_dog = under_dog")
        "foo_bar => foo_bar")
(assert (identical? (transpile '(digit? 0))
                    "isDigit(0)")
        "number? => isNumber")

(assert (identical? (transpile '(create-server options))
                    "createServer(options)")
        "create-server => createServer")

(assert (identical? (transpile '(.create-server http options))
                    "http.createServer(options)")
        "http.create-server => http.createServer")

(.log console "compiles new special form")

(assert (identical? (transpile '(new Foo)) "new Foo()")
        "(new Foo) => new Foo()")
(assert (identical? (transpile '(Foo.)) "new Foo()")
        "(Foo.) => new Foo()")
(assert (identical? (transpile '(new Foo a b)) "new Foo(a, b)")
        "(new Foo a b) => new Foo(a, b)")
(assert (identical? (transpile '(Foo. a b)) "new Foo(a, b)")
        "(Foo. a b) => new Foo(a, b)")

(.log console "compiles native special forms: and or + * - / not")

(assert (identical? (transpile '(and a b)) "a && b")
        "(and a b) => a && b")
(assert (identical? (transpile '(and a b c)) "a && b && c")
        "(and a b c) => a && b && c")
(assert (identical? (transpile '(and a (or b c))) "a && (b || c)")
        "(and a (or b c)) => a && (b || c)")
(assert (identical?
        "(a > b) && (c > d) ?\n  x :\n  y"
        (transpile '(if (and (> a b) (> c d)) x y))))

(assert (identical?
         (transpile '(and a (or b (or c d)))) "a && (b || (c || d))")
        "(and a (or b (or c d))) => a && (b || (c || d))")
(assert (identical? (transpile '(not x)) "!(x)")
        "(not x) => !(x)")
(assert (identical? (transpile '(not (or x y))) "!(x || y)")
        "(not x) => !(x)")

(.log console "compiles = == >= <= < > special forms")

(assert (identical? (transpile '(= a b)) "isEqual(a, b)")
        "(= a b) => isEqual(a, b)")
(assert (identical? (transpile '(= a b c)) "isEqual(a, b, c)")
        "(= a b c) => isEqual(a, b, c)")
(assert (identical? (transpile '(< a b c)) "a < b && b < c")
        "(< a b c) => a < b && b < c")
(assert (identical? (transpile '(identical? a b c)) "a === b && b === c")
        "(identical? a b c) => a === b && b === c")
(assert (identical? (transpile '(>= (.index-of arr el) 0))
                    "arr.indexOf(el) >= 0")
        "(>= (.index-of arr el) 0) => arr.indexOf(el) >= 0")

(.log console "compiles = - + == >= <= / * as functions")

(assert (identical? (transpile '(apply and nums))
        "and.apply(and, nums)"))
(assert (identical? (transpile '(apply or nums))
        "or.apply(or, nums)"))
(assert (identical? (transpile '(apply = nums))
                    "isEqual.apply(isEqual, nums)"))
(assert (identical? (transpile '(apply == nums))
                    "isStrictEqual.apply(isStrictEqual, nums)"))
(assert (identical? (transpile '(apply > nums))
                    "greaterThan.apply(greaterThan, nums)"))
(assert (identical? (transpile '(apply < nums))
                    "lessThan.apply(lessThan, nums)"))
(assert (identical? (transpile '(apply <= nums))
                    "notGreaterThan.apply(notGreaterThan, nums)"))
(assert (identical? (transpile '(apply >= nums))
                    "notLessThan.apply(notLessThan, nums)"))
(assert (identical? (transpile '(apply * nums))
                    "multiply.apply(multiply, nums)"))
(assert (identical? (transpile '(apply / nums))
                    "divide.apply(divide, nums)"))
(assert (identical? (transpile '(apply + nums))
                    "sum.apply(sum, nums)"))
(assert (identical? (transpile '(apply - nums))
                    "subtract.apply(subtract, nums)"))

(.log console "compiles dictionaries to js objects")

(assert (identical? (transpile '{}) "{}")
        "empty hash compiles to empty object")
(assert (identical? (transpile '{ :foo 1 }) "{\n  \"foo\": 1\n}")
        "compile dictionaries to js objects")

(assert (identical?
         (transpile '{:foo 1 :bar (a b) :bz (fn [x] x) :bla { :sub 2 }})
"{
  \"foo\": 1,
  \"bar\": a(b),
  \"bz\": function(x) {
    return x;
  },
  \"bla\": {
    \"sub\": 2
  }
}") "compile nested dictionaries")

(.log console "compiles compound accessor")

(assert (identical? (transpile '(get a b)) "a[b]")
        "(get a b) => a[b]")
(assert (identical? (transpile '(aget arguments 1)) "arguments[1]")
        "(aget arguments 1) => arguments[1]")
(assert (identical? (transpile '(get (a b) (get c d)))
                    "(a(b))[c[d]]")
        "(get (a b) (get c d)) => a(b)[c[d]]")
(assert (identical? (transpile '(get (or t1 t2) p))
                    "(t1 || t2)[p]"))

(.log console "compiles instance?")

(assert (identical? (transpile '(instance? Object a))
                    "a instanceof Object")
        "(instance? Object a) => a instanceof Object")
(assert (identical? (transpile '(instance? (C D) (a b)))
                    "a(b) instanceof C(D)")
        "(instance? (C D) (a b)) => a(b) instanceof C(D)")

(.log console "compile loop")
(assert (identical? (transpile '(loop [x 7] (if (f x) x (recur (b x)))))
"(function loop(x) {
  var recur = loop;
  while (recur === loop) {
    recur = f(x) ?
    x :
    (x = b(x), loop);
  };
  return recur;
})(7)") "single binding loops compile")

(assert (identical? (transpile '(loop [] (if (m?) m (recur))))
"(function loop() {
  var recur = loop;
  while (recur === loop) {
    recur = isM() ?
    m :
    (loop);
  };
  return recur;
})()") "zero bindings loops compile")

(assert
 (identical?
  (transpile '(loop [x 3 y 5] (if (> x y) x (recur (+ x 1) (- y 1)))))
"(function loop(x, y) {
  var recur = loop;
  while (recur === loop) {
    recur = x > y ?
    x :
    (x = x + 1, y = y - 1, loop);
  };
  return recur;
})(3, 5)") "multi bindings loops compile")

(assert (= (transpile '(defn identity [x] x))
           (str "var identity = function identity(x) {\n  return x;\n};\n"
                "exports.identity = identity")))

(assert (= (transpile '(defn- identity [x] x))
           "var identity = function identity(x) {\n  return x;\n}")
        "private functions")

(import [symbol] "../src/ast")
(import [list] "../src/sequence")
(import [str =] "../src/runtime")
(import [self-evaluating? compile macroexpand] "../src/compiler")

(defn transpile [form] (compile (macroexpand form)))

(.log console "self evaluating forms")
(assert (self-evaluating? 1) "number is self evaluating")
(assert (self-evaluating? "string") "string is self evaluating")
(assert (self-evaluating? true) "true is boolean => self evaluating")
(assert (self-evaluating? false) "false is boolean => self evaluating")
(assert (self-evaluating?) "no args is nil => self evaluating")
(assert (self-evaluating? nil) "nil is self evaluating")
(assert (self-evaluating? :keyword) "keyword is self evaluating")
(assert (not (self-evaluating? ':keyword)) "quoted keyword not self evaluating")
(assert (not (self-evaluating? (list))) "list is not self evaluating")
(assert (not (self-evaluating? self-evaluating?)) "fn is not self evaluating")
(assert (not (self-evaluating? (symbol "symbol"))) "symbol is not self evaluating")

(.log console "compile primitive forms")

(assert (= (transpile '(def x)) "var x = void(0)")
        "def compiles properly")
(assert (= (transpile '(def y 1)) "var y = 1")
        "def with two args compiled properly")
(assert (= (transpile ''(def x 1))
                      "list(symbol(void(0), \"def\"), symbol(void(0), \"x\"), 1)")
        "quotes preserve lists")

(.log console "compile invoke forms")
(assert (identical? (transpile '(foo)) "foo()")
        "function calls compile")
(assert (identical? (transpile '(foo bar)) "foo(bar)")
        "function calls with single arg compile")
(assert (identical? (transpile '(foo bar baz)) "foo(bar, baz)")
        "function calls with multi arg compile")
(assert (identical? (transpile '(foo ((bar baz) beep)))
                    "foo((bar(baz))(beep))")
        "nested function calls compile")

(.log console "compile functions")

(assert (identical? (transpile '(fn [x] x))
                    "function(x) {\n  return x;\n}")
        "function compiles")
(assert (identical? (transpile '(fn [x] (def y 1) (foo x y)))
                    "function(x) {\n  var y = 1;\n  return foo(x, y);\n}")
        "function with multiple statements compiles")
(assert (identical? (transpile '(fn identity [x] x))
                    "function identity(x) {\n  return x;\n}")
        "named function compiles")
(assert (identical? (transpile '(fn a "docs docs" [x] x))
                    "function a(x) {\n  return x;\n}")
        "fn docs are supported")
(assert (identical? (transpile '(fn "docs docs" [x] x))
                    "function(x) {\n  return x;\n}")
        "fn docs for anonymous functions are supported")

(assert (identical? (transpile '(fn foo? ^boolean [x] true))
                    "function isFoo(x) {\n  return true;\n}")
        "metadata is supported")

(assert (identical? (transpile '(fn [a & b] a))
"function(a) {
  var b = Array.prototype.slice.call(arguments, 1);
  return a;
}") "function with variadic arguments")

(assert (identical? (transpile '(fn [& a] a))
"function() {
  var a = Array.prototype.slice.call(arguments, 0);
  return a;
}") "function with all variadic arguments")

(assert (identical? (transpile '(fn
                                  ([] 0)
                                  ([x] x)))
"function(x) {
  switch (arguments.length) {
    case 0:
      return 0;
    case 1:
      return x;

    default:
      (function() { throw Error(\"Invalid arity\"); })()
  };
  return void(0);
}") "function with overloads")

(assert (identical? (transpile
'(fn sum
  "doc"
  {:version "1.0"}
  ([] 0)
  ([x] x)
  ([x y] (+ x y))
  ([x & rest] (reduce rest sum x))))

"function sum(x, y) {
  switch (arguments.length) {
    case 0:
      return 0;
    case 1:
      return x;
    case 2:
      return x + y;

    default:
      var rest = Array.prototype.slice.call(arguments, 1);
      return reduce(rest, sum, x);
  };
  return void(0);
}") "function with overloads docs & metadata")

(.log console "compile if special form")

(assert (identical? (transpile '(if foo (bar)))
                    "foo ?\n  bar() :\n  void(0)")
        "if compiles")

(assert (identical? (transpile '(if foo (bar) baz))
                    "foo ?\n  bar() :\n  baz")
        "if-else compiles")

(assert (identical? (transpile '(if monday? (.log console "monday")))
                    "isMonday ?\n  console.log(\"monday\") :\n  void(0)")
        "macros inside blocks expand properly")

(.log console "compile do special form")

(assert (identical? (transpile '(do (foo bar) bar))
                    "(function() {\n  foo(bar);\n  return bar;\n})()")
        "do compiles")
(assert (identical? (transpile '(do))
                    "(function() {\n  return void(0);\n})()")
        "empty do compiles")

(.log console "compile let special form")

(assert (identical? (transpile '(let [] x))
                    "(function() {\n  return x;\n})()")
        "let bindings compiles properly")
(assert (identical?
         (transpile '(let [x 1 y 2] x))
         "(function() {\n  var x = 1;\n  var y = 2;\n  return x;\n})()")
        "let with bindings compiles properly")

(.log console "compile throw special form")

(assert (identical? (transpile '(throw error))
                    "(function() { throw error; })()")
        "throw reference compiles")

(assert (identical? (transpile '(throw (Error message)))
                    "(function() { throw Error(message); })()")
        "throw expression compiles")

(assert (identical? (transpile '(throw "boom"))
                    "(function() { throw \"boom\"; })()")
        "throw string compile")

(.log console "compile set! special form")

(assert (identical? (transpile '(set! x 1))
                    "x = 1")
        "set! compiles")

(assert (identical? (transpile '(set! x (foo bar 2)))
                    "x = foo(bar, 2)")
        "set! with value expression compiles")

(assert (identical? (transpile '(set! x (.m o)))
                    "x = o.m()")
        "set! expands macros")

(.log console "compile vectors")

(assert (identical? (transpile '[a b]) "[a, b]")
        "vector compiles")

(assert (identical? (transpile '[a (b c)]) "[a, b(c)]")
        "vector of expressions compiles")

(assert (identical? (transpile '[]) "[]")
        "empty vector compiles")

(.log console "compiles try special form")

(assert (identical?
         (transpile '(try (m 1 0) (catch e e)))
         "(function() {\ntry {\n  return m(1, 0);\n} catch (e) {\n  return e;\n}})()")
        "try / catch compiles")

(assert (identical?
         (transpile '(try (m 1 0) (finally 0)))
         "(function() {\ntry {\n  return m(1, 0);\n} finally {\n  return 0;\n}})()")
        "try / finally compiles")

(assert (identical?
         (transpile '(try (m 1 0) (catch e e) (finally 0)))
         "(function() {\ntry {\n  return m(1, 0);\n} catch (e) {\n  return e;\n} finally {\n  return 0;\n}})()")
        "try / catch / finally compiles")

(.log console "compile property / method access / call special forms")

(assert (identical? (transpile '(.log console message))
                    "console.log(message)")
        "method call compiles correctly")
(assert (identical? (transpile '(.-location window))
                    "window.location")
        "property access compiles correctly")
(assert (identical? (transpile '(.-foo? bar))
                    "bar.isFoo")
        "property access compiles naming conventions")
(assert (identical? (transpile '(.-location (.open window url)))
                    "(window.open(url)).location")
        "compound property access and method call")
(assert (identical? (transpile '(.slice (.splice arr 0)))
                    "arr.splice(0).slice()")
        "(.slice (.splice arr 0)) => arr.splice(0).slice()")
(assert (identical? (transpile '(.a (.b "/")))
                    "\"/\".b().a()")
        "(.a (.b \"/\")) => \"/\".b().a()")

(.log console "compile sugar for keyword based access")

(assert (identical? (transpile '(:foo bar))
                    "(bar || 0)[\"foo\"]"))

(.log console "compile unquote-splicing forms")

(assert (identical? (transpile '`(1 ~@'(2 3)))
                    "concat(list(1), list(2, 3))")
        "list unquote-splicing compiles")
(assert (identical? (transpile '`())
                    "list()")
         "empty list unquotes to empty list")

(assert (identical? (transpile '`[1 ~@[2 3]])
                    "vec(concat([1], [2, 3]))")
        "vector unquote-splicing compiles")

(assert (identical? (transpile '`[])
                    "[]")
        "syntax-quoted empty vector compiles to empty vector")

(.log console "compile references")

(assert (identical? (transpile '(set! **macros** []))
                    "__macros__ = []")
        "**macros** => __macros__")
(assert (identical?
         (transpile '(fn vector->list [v] (make list v)))
         "function vectorToList(v) {\n  return make(list, v);\n}")
        "list->vector => listToVector")
(assert (identical? (transpile '(swap! foo bar))
                    "swap(foo, bar)")
        "set! => set")

;(assert (identical? (transpile '(let [raw% foo-bar] raw%))
;                     "swap(foo, bar)")
;          "set! => set")

(assert (identical? (transpile '(def under_dog))
                    "var under_dog = void(0)")
        "foo_bar => foo_bar")
(assert (identical? (transpile '(digit? 0))
                    "isDigit(0)")
        "number? => isNumber")

(assert (identical? (transpile '(create-server options))
                    "createServer(options)")
        "create-server => createServer")

(assert (identical? (transpile '(.create-server http options))
                    "http.createServer(options)")
        "http.create-server => http.createServer")

(.log console "compiles new special form")

(assert (identical? (transpile '(new Foo)) "new Foo()")
        "(new Foo) => new Foo()")
(assert (identical? (transpile '(Foo.)) "new Foo()")
        "(Foo.) => new Foo()")
(assert (identical? (transpile '(new Foo a b)) "new Foo(a, b)")
        "(new Foo a b) => new Foo(a, b)")
(assert (identical? (transpile '(Foo. a b)) "new Foo(a, b)")
        "(Foo. a b) => new Foo(a, b)")

(.log console "compiles native special forms: and or + * - / not")

(assert (identical? (transpile '(and a b)) "a && b")
        "(and a b) => a && b")
(assert (identical? (transpile '(and a b c)) "a && b && c")
        "(and a b c) => a && b && c")
(assert (identical? (transpile '(and a (or b c))) "a && (b || c)")
        "(and a (or b c)) => a && (b || c)")
(assert (identical?
        "(a > b) && (c > d) ?\n  x :\n  y"
        (transpile '(if (and (> a b) (> c d)) x y))))

(assert (identical?
         (transpile '(and a (or b (or c d)))) "a && (b || (c || d))")
        "(and a (or b (or c d))) => a && (b || (c || d))")
(assert (identical? (transpile '(not x)) "!(x)")
        "(not x) => !(x)")
(assert (identical? (transpile '(not (or x y))) "!(x || y)")
        "(not x) => !(x)")

(.log console "compiles = == >= <= < > special forms")

(assert (identical? (transpile '(= a b)) "isEqual(a, b)")
        "(= a b) => isEqual(a, b)")
(assert (identical? (transpile '(= a b c)) "isEqual(a, b, c)")
        "(= a b c) => isEqual(a, b, c)")
(assert (identical? (transpile '(< a b c)) "a < b && b < c")
        "(< a b c) => a < b && b < c")
(assert (identical? (transpile '(identical? a b c)) "a === b && b === c")
        "(identical? a b c) => a === b && b === c")
(assert (identical? (transpile '(>= (.index-of arr el) 0))
                    "arr.indexOf(el) >= 0")
        "(>= (.index-of arr el) 0) => arr.indexOf(el) >= 0")

(.log console "compiles = - + == >= <= / * as functions")

(assert (identical? (transpile '(apply and nums))
        "and.apply(and, nums)"))
(assert (identical? (transpile '(apply or nums))
        "or.apply(or, nums)"))
(assert (identical? (transpile '(apply = nums))
                    "isEqual.apply(isEqual, nums)"))
(assert (identical? (transpile '(apply == nums))
                    "isStrictEqual.apply(isStrictEqual, nums)"))
(assert (identical? (transpile '(apply > nums))
                    "greaterThan.apply(greaterThan, nums)"))
(assert (identical? (transpile '(apply < nums))
                    "lessThan.apply(lessThan, nums)"))
(assert (identical? (transpile '(apply <= nums))
                    "notGreaterThan.apply(notGreaterThan, nums)"))
(assert (identical? (transpile '(apply >= nums))
                    "notLessThan.apply(notLessThan, nums)"))
(assert (identical? (transpile '(apply * nums))
                    "multiply.apply(multiply, nums)"))
(assert (identical? (transpile '(apply / nums))
                    "divide.apply(divide, nums)"))
(assert (identical? (transpile '(apply + nums))
                    "sum.apply(sum, nums)"))
(assert (identical? (transpile '(apply - nums))
                    "subtract.apply(subtract, nums)"))

(.log console "compiles dictionaries to js objects")

(assert (identical? (transpile '{}) "{}")
        "empty hash compiles to empty object")
(assert (identical? (transpile '{ :foo 1 }) "{\n  \"foo\": 1\n}")
        "compile dictionaries to js objects")

(assert (identical?
         (transpile '{:foo 1 :bar (a b) :bz (fn [x] x) :bla { :sub 2 }})
"{
  \"foo\": 1,
  \"bar\": a(b),
  \"bz\": function(x) {
    return x;
  },
  \"bla\": {
    \"sub\": 2
  }
}") "compile nested dictionaries")

(.log console "compiles compound accessor")

(assert (identical? (transpile '(get a b)) "a[b]")
        "(get a b) => a[b]")
(assert (identical? (transpile '(aget arguments 1)) "arguments[1]")
        "(aget arguments 1) => arguments[1]")
(assert (identical? (transpile '(get (a b) (get c d)))
                    "(a(b))[c[d]]")
        "(get (a b) (get c d)) => a(b)[c[d]]")
(assert (identical? (transpile '(get (or t1 t2) p))
                    "(t1 || t2)[p]"))

(.log console "compiles instance?")

(assert (identical? (transpile '(instance? Object a))
                    "a instanceof Object")
        "(instance? Object a) => a instanceof Object")
(assert (identical? (transpile '(instance? (C D) (a b)))
                    "a(b) instanceof C(D)")
        "(instance? (C D) (a b)) => a(b) instanceof C(D)")

(.log console "compile loop")
(assert (identical? (transpile '(loop [x 7] (if (f x) x (recur (b x)))))
"(function loop(x) {
  var recur = loop;
  while (recur === loop) {
    recur = f(x) ?
    x :
    (x = b(x), loop);
  };
  return recur;
})(7)") "single binding loops compile")

(assert (identical? (transpile '(loop [] (if (m?) m (recur))))
"(function loop() {
  var recur = loop;
  while (recur === loop) {
    recur = isM() ?
    m :
    (loop);
  };
  return recur;
})()") "zero bindings loops compile")

(assert
 (identical?
  (transpile '(loop [x 3 y 5] (if (> x y) x (recur (+ x 1) (- y 1)))))
"(function loop(x, y) {
  var recur = loop;
  while (recur === loop) {
    recur = x > y ?
    x :
    (x = x + 1, y = y - 1, loop);
  };
  return recur;
})(3, 5)") "multi bindings loops compile")


fix: return function type

fix: return function type

(ns hu.lib.type
  (:require
    [hu.lib.misc :refer [browser? *global]]))

(def ^:private obj->str
  (.-to-string (.-prototype Object)))

(defn ^:private ->str
  [x] ((.-call obj->str) x))

(defn ^boolean null?
  "Check if the given value is null
  Performs a strict type coercion comparison"
  [x] (? x null))

(defn ^boolean undef?
  "Check if the given value is null or undefined
  Performs a strict type coercion comparison"
  [x]
  (or
    (? (typeof x) "undefined")
    (null? x)))

(defn ^boolean bool?
  "Check if the given value is boolean type"
  [x]
  (or
    (? x true)
    (? x false)
    (? (.call to-string x) "[object Boolean]")))

(defn ^boolean number?
  "Check if the given value is number type"
  [x] (? (->str x) "[object Number]"))

(defn ^boolean finite?
  "Check if the given value is a finite number"
  [x]
  (and
    (.finite? *global x)
    (not (NaN? parse-float x))))

(defn ^number NaN?
  "Is it NaN (not a number)?
  More accurate than the native isNaN function"
  [x]
  (if (identical? x x) false true))

(defn ^boolean symbol?
  "Check if the given value is a symbol type"
  [x] (? (->str x) "[object Symbol]"))

(defn ^boolean string?
  "Check if the given value is a string type"
  [x] (? (->str x) "[object String]"))

(defn ^boolean date?
  "Check if the given value is a date type"
  [x] (? (->str x) "[object Date]"))

(defn ^boolean reg-exp?
  "Check if the given value is a regexp type"
  [x] (? (->str x) "[object RegExp]"))

(def ^boolean pattern? reg-exp?)

(defn ^boolean args?
  "Check if the given value is a arguments object type"
  [x] (? (->str x) "[object Arguments]"))

(def ^boolean arguments? args?)

(defn ^boolean function?
  "Check if the given value is a function type"
  [x] (? (typeof x) "function"))

(def ^boolean fn? function?)

(defn ^boolean object?
  "Check if the given value is a object type"
  [x] (? (->str x) "[object Object]"))

(def ^boolean array?
  "Check if the given value is an array type"
  (if (fn? (.-isArray Array))
    (.-isArray Array)
    (fn [x] (? (->str x) "[object Array]"))))

(defn ^boolean error?
  "Check if the given value is an Error object instance"
  [x] (? (->str x) "[object Error]"))

(defn ^boolean plain-object?
  "Checks if the given value is a native object type
  (it was createdd by the Object native constructor)"
  [x]
  (and
    (object? x)
    (object? (.get-prototype-of Object x))
    (null? (.get-prototype-of Object (.get-prototype-of Object x)))))

(defn ^boolean element?
  "Checks if the given value is a DOM element object instance"
  [x]
  (if browser?
    (>= (.index-of (->str x) :Element) 0)
    false))

(defn ^boolean mutable?
  "Checks if the given value is a mutable data type.
  Objects, arrays, date objects, arguments objects and
  functions are considered mutable data types"
  [x]
  (or
    (object? x)
    (array? x)
    (error? x)
    (args? x)
    (date? x)
    (function? x)))

(defn ^boolean empty?
  "Checks if the given value is empty.
  Arrays, strings, or arguments objects with
  a length of 0 and objects with no own enumerable
  properties are considered empty values"
  [x]
  (or
    (undef? x)
    (if (object? x)
      (if (? (.-length (.keys Object x)) 0) true false) false)
    (? (.-length x) 0)))

(defn ^boolean not-empty
  "Checks if the given value is not empty"
  [x]
  (not (empty? x)))

(def ^boolean not-empty? not-empty)

(defn ^boolean primitive?
  "Checks if the given value is a primitive value type.
  Strings, numbers, booleans, symbols and null are
  considered primitives values"
  [x]
  (or
    (null? x)
    (bool? x)
    (reg-exp? x)
    (string? x)
    (number? x)
    (symbol? x)))

(defn ^boolean iterable?
  "Checks if the given value can be iterated.
  Objects, arrays, and arguments objects are
  considered iterables data types"
  [x]
  (or
    (object? x)
    (array? x)
    (args? x)))

(def ^boolean can-iterate iterable?)

(ns hu.lib.type
  (:require
    [hu.lib.misc :refer [browser? *global]]))

(def ^:private obj->str
  (.-to-string (.-prototype Object)))

(defn ^:private ->str
  [x] ((.-call obj->str) x))

(defn ^boolean null?
  "Check if the given value is null
  Performs a strict type coercion comparison"
  [x] (? x null))

(defn ^boolean undef?
  "Check if the given value is null or undefined
  Performs a strict type coercion comparison"
  [x]
  (or
    (? (typeof x) "undefined")
    (null? x)))

(defn ^boolean bool?
  "Check if the given value is boolean type"
  [x]
  (or
    (? x true)
    (? x false)
    (? (.call to-string x) "[object Boolean]")))

(defn ^boolean number?
  "Check if the given value is number type"
  [x] (? (->str x) "[object Number]"))

(defn ^boolean finite?
  "Check if the given value is a finite number"
  [x]
  (and
    (.finite? *global x)
    (not (NaN? parse-float x))))

(defn ^number NaN?
  "Is it NaN (not a number)?
  More accurate than the native isNaN function"
  [x]
  (if (identical? x x) false true))

(defn ^boolean symbol?
  "Check if the given value is a symbol type"
  [x] (? (->str x) "[object Symbol]"))

(defn ^boolean string?
  "Check if the given value is a string type"
  [x] (? (->str x) "[object String]"))

(defn ^boolean date?
  "Check if the given value is a date type"
  [x] (? (->str x) "[object Date]"))

(defn ^boolean reg-exp?
  "Check if the given value is a regexp type"
  [x] (? (->str x) "[object RegExp]"))

(def ^boolean pattern? reg-exp?)

(defn ^boolean args?
  "Check if the given value is a arguments object type"
  [x] (? (->str x) "[object Arguments]"))

(def ^boolean arguments? args?)

(defn ^boolean function?
  "Check if the given value is a function type"
  [x] (? (typeof x) "function"))

(def ^boolean fn? function?)

(defn ^boolean object?
  "Check if the given value is a object type"
  [x] (? (->str x) "[object Object]"))

(def ^:boolean array?
  "Check if the given value is an array type"
  (if (fn? (.-isArray Array))
    (.-isArray Array)
    (fn [x] (? (->str x) "[object Array]"))))

(defn ^boolean error?
  "Check if the given value is an Error object instance"
  [x] (? (->str x) "[object Error]"))

(defn ^boolean plain-object?
  "Checks if the given value is a native object type
  (it was createdd by the Object native constructor)"
  [x]
  (and
    (object? x)
    (object? (.get-prototype-of Object x))
    (null? (.get-prototype-of Object (.get-prototype-of Object x)))))

(defn ^boolean element?
  "Checks if the given value is a DOM element object instance"
  [x]
  (if browser?
    (>= (.index-of (->str x) :Element) 0)
    false))

(defn ^boolean mutable?
  "Checks if the given value is a mutable data type.
  Objects, arrays, date objects, arguments objects and
  functions are considered mutable data types"
  [x]
  (or
    (object? x)
    (array? x)
    (error? x)
    (args? x)
    (date? x)
    (function? x)))

(defn ^boolean empty?
  "Checks if the given value is empty.
  Arrays, strings, or arguments objects with
  a length of 0 and objects with no own enumerable
  properties are considered empty values"
  [x]
  (or
    (undef? x)
    (if (object? x)
      (if (? (.-length (.keys Object x)) 0) true false) false)
    (? (.-length x) 0)))

(defn ^boolean not-empty
  "Checks if the given value is not empty"
  [x]
  (not (empty? x)))

(def ^boolean not-empty? not-empty)

(defn ^boolean primitive?
  "Checks if the given value is a primitive value type.
  Strings, numbers, booleans, symbols and null are
  considered primitives values"
  [x]
  (or
    (null? x)
    (bool? x)
    (reg-exp? x)
    (string? x)
    (number? x)
    (symbol? x)))

(defn ^boolean iterable?
  "Checks if the given value can be iterated.
  Objects, arrays, and arguments objects are
  considered iterables data types"
  [x]
  (or
    (object? x)
    (array? x)
    (args? x)))

(def ^boolean can-iterate iterable?)


chore: refactor compose macro, more clojure-like syntax

(ns hu.lib.macros
  (:require
    [hu.lib.function :refer [curry compose]]
    [hu.lib.type :refer [string? array? object?]]))

(defmacro str
  [x expr]
  `(if (string? x) ~expr ~x))

(defmacro arr
  [x expr]
  `(if (array? x) ~expr ~x))

(defmacro obj
  [x expr]
  `(if (object? x) ~expr ~x))

(defmacro ?
  [x y]
  `(identical? ~x ~y))

(defmacro not?
  [x y]
  `(if (? ~x ~y) false true))

(defmacro unless
  [condition form]
  (list 'if condition nil form))

(defmacro when
  [condition form eform]
  (list 'if condition form eform))

(defmacro defcurry
  [name & etc]
  (let [doc? (string? (first etc))
        doc (if doc? (first etc) " ")
        body (if doc? (rest etc) etc)]
    `(defn ~name
       ~doc
       [& args]
       (apply (curry (fn ~@body)) args))))

(defmacro defcompose
  [name & etc]
  (let [doc? (string? (first etc))
        doc (if doc? (first etc) " ")
        body (if doc? (rest etc) etc)]
    `(defn ~name
       ~doc
       [& args]
       (apply (compose (fn args) args)))))

(ns hu.lib.macros
  (:require
    [hu.lib.function :refer [curry compose]]
    [hu.lib.type :refer [string? array? object?]]))

(defmacro str
  [x expr]
  `(if (string? x) ~expr ~x))

(defmacro arr
  [x expr]
  `(if (array? x) ~expr ~x))

(defmacro obj
  [x expr]
  `(if (object? x) ~expr ~x))

(defmacro ?
  [x y]
  `(identical? ~x ~y))

(defmacro not?
  [x y]
  `(if (? ~x ~y) false true))

(defmacro unless
  [condition form]
  (list 'if condition nil form))

(defmacro when
  [condition form eform]
  (list 'if condition form eform))

(defmacro defcurry
  [name & etc]
  (let [doc? (string? (first etc))
        doc (if doc? (first etc) " ")
        body (if doc? (rest etc) etc)]
    `(defn ~name
       ~doc
       [& args]
       (apply (curry (fn ~@body)) args))))

(defmacro defcompose
  [name & args]
  `(def ~name (compose (fn ~@args))))


Implement cond as macro.

Implement cond as macro.

(ns wisp.expander
  "wisp syntax and macro expander module"
  (:require [wisp.ast :refer [meta with-meta symbol? keyword?
                              quote? symbol namespace name
                              unquote? unquote-splicing?]]
            [wisp.sequence :refer [list? list conj partition seq
                                   empty? map vec every? concat
                                   first second third rest last
                                   butlast interleave cons count
                                   some assoc reduce filter seq?]]
            [wisp.runtime :refer [nil? dictionary? vector? keys
                                  vals string? number? boolean?
                                  date? re-pattern? even? = max
                                  dec dictionary subs]]
            [wisp.string :refer [split]]))

(def **macros** {})

(defn- expand
  "Applies macro registered with given `name` to a given `form`"
  [expander form]
  (let [expansion (apply expander (vec (rest form)))
        metadata (conj {} (meta form) (meta expansion))]
    (with-meta expansion metadata)))

(defn install-macro!
  "Registers given `macro` with a given `name`"
  [op expander]
  (set! (get **macros** (name op)) expander))

(defn- macro
  "Returns true if macro with a given name is registered"
  [op]
  (and (symbol? op)
       (get **macros** (name op))))

(defn method-syntax?
  [op]
  (let [id (and (symbol? op) (name op))]
    (and id
         (identical? \. (first id))
         (not (identical? \- (second id)))
         (not (identical? \. id)))))

(defn field-syntax?
  [op]
  (let [id (and (symbol? op) (name op))]
    (and id
         (identical? \. (first id))
         (identical? \- (second id)))))

(defn new-syntax?
  [op]
  (let [id (and (symbol? op) (name op))]
    (and id
         (identical? \. (last id))
         (not (identical? \. id)))))

(defn method-syntax
  "Example:
  '(.substring string 2 5) => '((aget string 'substring) 2 5)"
  [op target & params]
  (let [member (symbol (subs (name op) 1))]
    (if (nil? target)
      (throw (Error "Malformed method expression, expecting (.method object ...)"))
      `((aget ~target (quote ~member)) ~@params))))

(defn field-syntax
  "Example:
  '(.-field object) => '(aget object 'field)"
  [op target & more]
  (let [member (symbol (subs (name op) 2))]
    (if (or (nil? target)
            (count more))
      (throw (Error "Malformed member expression, expecting (.-member target)"))
      `(aget ~target (quote ~member)))))

(defn new-syntax
  "Example:
  '(Point. x y) => '(new Point x y)"
  [op & params]
  (let [id (name op)
        constructor (symbol (subs id 0 (dec (count id))))]
    `(new ~constructor ~@params)))

(defn keyword-invoke
  "Calling a keyword desugars to property access with that
  keyword name on the given argument:
  '(:foo bar) => '(get bar :foo)"
  [keyword target]
  `(get ~target ~keyword))

(defn- desugar
  [expander form]
  (let [desugared (apply expander (vec form))
        metadata (conj {} (meta form) (meta desugared))]
    (with-meta desugared metadata)))

(defn macroexpand-1
  "If form represents a macro form, returns its expansion,
  else returns form."
  [form]
  (let [op (and (list? form)
                (first form))
        expander (macro op)]
    (cond expander (expand expander form)
          ;; Calling a keyword compiles to getting value from given
          ;; object associted with that key:
          ;; '(:foo bar) => '(get bar :foo)
          (keyword? op) (desugar keyword-invoke form)
          ;; '(.-field object) => (aget object 'field)
          (field-syntax? op) (desugar field-syntax form)
          ;; '(.substring string 2 5) => '((aget string 'substring) 2 5)
          (method-syntax? op) (desugar method-syntax form)
          ;; '(Point. x y) => '(new Point x y)
          (new-syntax? op) (desugar new-syntax form)
          :else form)))

(defn macroexpand
  "Repeatedly calls macroexpand-1 on form until it no longer
  represents a macro form, then returns it."
  [form]
  (loop [original form
         expanded (macroexpand-1 form)]
    (if (identical? original expanded)
      original
      (recur expanded (macroexpand-1 expanded)))))

;; Define core macros

(defn syntax-quote [form]
  (cond (symbol? form) (list 'quote form)
        (keyword? form) (list 'quote form)
        (or (number? form)
            (string? form)
            (boolean? form)
            (nil? form)
            (re-pattern? form)) form

        (unquote? form) (second form)
        (unquote-splicing? form) (reader-error "Illegal use of `~@` expression, can only be present in a list")

        (empty? form) form

        ;;
        (dictionary? form) (list 'apply
                                 'dictionary
                                 (cons '.concat
                                       (sequence-expand (apply concat
                                                               (seq form)))))
        ;; If a vector form expand all sub-forms and concatinate
        ;; them togather:
        ;;
        ;; [~a b ~@c] -> (.concat [a] [(quote b)] c)
        (vector? form) (cons '.concat (sequence-expand form))

        ;; If a list form expand all the sub-forms and apply
        ;; concationation to a list constructor:
        ;;
        ;; (~a b ~@c) -> (apply list (.concat [a] [(quote b)] c))
        (list? form) (if (empty? form)
                       (cons 'list nil)
                       (list 'apply
                             'list
                             (cons '.concat (sequence-expand form))))

        :else (reader-error "Unknown Collection type")))
(def syntax-quote-expand syntax-quote)

(defn unquote-splicing-expand
  [form]
  (if (vector? form)
    form
    (list 'vec form)))

(defn sequence-expand
  "Takes sequence of forms and expands them:

  ((unquote a)) -> ([a])
  ((unquote-splicing a) -> (a)
  (a) -> ([(quote b)])
  ((unquote a) b (unquote-splicing a)) -> ([a] [(quote b)] c)"
  [forms]
  (map (fn [form]
         (cond (unquote? form) [(second form)]
               (unquote-splicing? form) (unquote-splicing-expand (second form))
               :else [(syntax-quote-expand form)]))
       forms))
(install-macro! :syntax-quote syntax-quote)

;; TODO: New reader translates not= correctly
;; but for the time being use not-equal name
(defn not-equal
  [& body]
  `(not (= ~@body)))
(install-macro! :not= not-equal)

(defn expand-cond
  "Takes a set of test/expr pairs. It evaluates each test one at a
  time.  If a test returns logical true, cond evaluates and returns
  the value of the corresponding expr and doesn't evaluate any of the
  other tests or exprs. (cond) returns nil."
  [& clauses]
  (if (not (empty? clauses))
    (list 'if (first clauses)
          (if (empty? (rest clauses))
            (throw (Error "cond requires an even number of forms"))
            (second clauses))
          (cons 'cond (rest (rest clauses))))))
(install-macro! :cond expand-cond)


chore: add test coverage for string methods. add API docs

(ns hu.src.index
  (:require
    [hu.src.common :as common]
    [hu.src.string :as string]
    [hu.src.number :as number]
    [hu.src.array :as array]
    [hu.src.object :as object]
    [hu.src.function :as fn]))

(set! (.-exports module)
  (.apply (.-extend object)
    nil [common string number array object fn]))

(ns hu.src.index
  (:require
    [hu.src.common :as common]
    [hu.src.string :as string]
    [hu.src.number :as number]
    [hu.src.array :as array]
    [hu.src.object :as object]
    [hu.src.function :as fn]))

(set! (.-exports module)
  (.apply (.-extend object)
    nil [common string number array object :fn]))