Throw exceptions from aget if attribute is missing.

(ns wisp.analyzer
  (: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.expander :refer [macroexpand]]
            [wisp.string :refer [split]]))

(defn analyze-symbol
  "Finds the var associated with symbol
  Example:

  (analyze-symbol {} 'foo) => {:op :var
                               :form 'foo
                               :info nil
                               :env {}}"
  [env form]
  {:op :var
   :form form
   :info (get (:locals env) (name form))
   :env env})

(defn analyze-keyword
  "Example:
  (analyze-keyword {} :foo) => {:op :constant
                                :form ':foo
                                :env {}}"
  [env form]
  {:op :constant
   :form form
   :env env})

(def **specials** {})

(defn install-special!
  [op f]
  (set! (get **specials** (name op)) f))

(defn analyze-if
  "Example:
  (analyze-if {} '(if monday? :yep :nope)) => {:op :if
                                               :form '(if monday? :yep :nope)
                                               :env {}
                                               :test {:op :var
                                                      :form 'monday?
                                                      :info nil
                                                      :env {}}
                                               :consequent {:op :constant
                                                            :form ':yep
                                                            :type :keyword
                                                            :env {}}
                                               :alternate {:op :constant
                                                           :form ':nope
                                                           :type :keyword
                                                           :env {}}}"
  [env form]
  (let [forms (rest form)
        test (analyze env (first forms))
        consequent (analyze env (second forms))
        alternate (analyze env (third forms))]
    (if (< (count forms) 2)
      (throw (SyntaxError "Malformed if expression, too few operands")))
    {:op :if
     :form form
     :test test
     :consequent consequent
     :alternate alternate
     :env env}))

(install-special! :if analyze-if)

(defn analyze-throw
  "Example:
  (analyze-throw {} '(throw (Error :boom))) => {:op :throw
                                                :form '(throw (Error :boom))
                                                :throw {:op :invoke
                                                        :callee {:op :var
                                                                 :form 'Error
                                                                 :info nil
                                                                 :env {}}
                                                        :params [{:op :constant
                                                                  :type :keyword
                                                                  :form ':boom
                                                                  :env {}}]}}"
  [env form]
  (let [expression (analyze env (second form))]
    {:op :throw
     :form form
     :throw expression
     :env env}))

(install-special! :throw analyze-throw)

(defn analyze-try
  [env form]
  (let [forms (vec (rest form))

        ;; Finally
        tail (last forms)
        finalizer-form (if (and (list? tail)
                                (= 'finally (first tail)))
                         (rest tail))
        finalizer (if finalizer-form
                    (analyze-block env finalizer-form))

        ;; catch
        body-form (if finalizer
                    (butlast forms)
                    forms)

        tail (last body-form)
        handler-form (if (and (list? tail)
                              (= 'catch (first tail)))
                       (rest tail))
        handler (if handler-form
                  (conj {:name (analyze env
                                        (first handler-form))}
                        (analyze-block env (rest handler-form))))

        ;; Try
        body (if handler-form
               (analyze-block env (butlast body-form))
               (analyze-block env body-form))]
    {:op :try
     :form form
     :body body
     :handler handler
     :finalizer finalizer
     :env env}))

(install-special! :try analyze-try)

(defn analyze-set!
  [env form]
  (let [body (rest form)
        left (first body)
        right (second body)
        target (cond (symbol? left) (analyze-symbol env left name)
                     (list? left) (analyze-list env left name)
                     :else left)
         value (analyze env right)]
    {:op :set!
     :target target
     :value value
     :form form
     :env env}))
(install-special! :set! analyze-set!)

(defn analyze-new
  [env form]
  (let [body (rest form)
        constructor (analyze env (first body))
        params (vec (map #(analyze env %) (rest body)))]
    {:op :new
     :constructor constructor
     :form form
     :params params
     :env env}))
(install-special! :new analyze-new)

(defn analyze-aget
  [env form]
  (let [body (rest form)
        target (analyze env (first body))
        attribute (second body)
        field (and (quote? attribute)
                   (symbol? (second attribute))
                   (second attribute))]
    (if attribute
      {:op :member-expression
       :computed (not field)
       :form form
       :target target
       :property (analyze env (or field attribute))}
      (throw (SyntaxError "Malformed aget expression expected (aget object member)")))))
(install-special! :aget analyze-aget)

(defn parse-def
  ([id] {:id id})
  ([id init] {:id id :init init})
  ([id doc init] {:id id :doc doc :init init}))

(defn analyze-def
  [env form]
  (let [params (apply parse-def (vec (rest form)))
        id (:id params)
        metadata (meta id)

        variable (analyze env id)

        init (analyze {:parent env
                       :bindings (assoc {} (name id) variable)}
                       (:init params))

        doc (or (:doc params)
                (:doc metadata))]
    {:op :def
     :doc doc
     :var variable
     :init init
     :export (and (not (:parent env))
                  (not (:private metadata)))
     :form form}))
(install-special! :def analyze-def)

(defn analyze-do
  [env form _]
  (let [expressions (rest form)
        body (analyze-block env expressions)]
    (conj body {:op :do
                :form form
                :env env})))
(install-special! :do analyze-do)

(defn analyze-binding
  [env form]
  (let [name (first form)
        init (analyze env (second form))
        init-meta (meta init)
        fn-meta (if (= 'fn (:op init-meta))
                  {:fn-var true
                   :variadic (:variadic init-meta)
                   :max-fixed-arity (:max-fixed-arity init-meta)
                   :method-params (map #(:params %)
                                       (:methods init-meta))}
                  {})
        binding-meta {:name name
                      :init init
                      :tag (or (:tag (meta name))
                               (:tag init-meta)
                               (:tag (:info init-meta)))
                      :local true
                      :shadow (get (:locals env) name)}]
    (assert (not (or (namespace name)
                     (< 1 (count (split \. (str name)))))))
    (conj binding-meta fn-meta)))

(defn analyze-let*
  "Takes let form and enhances it's metadata via analyzed
  info"
  [env form is-loop]
  (let [expressions (rest form)
        bindings (first expressions)
        body (rest expressions)

        valid-bindings? (and (vector? bindings)
                             (even? (count bindings)))

        _ (assert valid-bindings?
                  "bindings must be vector of even number of elements")

        context (:context env)

        locals (map #(analyze-binding env %)
                    (partition 2 bindings))

        params (or (if is-loop locals)
                   (:params env))

        scope (conj {:parent env
                     :bindings locals}
                    (if params {:params params}))

        expressions (analyze-block scope body)]

    {:op :let
     :form form
     :loop is-loop
     :bindings locals
     :statements (:statements expressions)
     :result (:result expressions)
     :env env}))

(defn analyze-let
  [env form _]
  (analyze-let* env form false))
(install-special! :let analyze-let)

(defn analyze-loop
  [env form _]
  (conj (analyze-let* env form true)
        {:op :loop}))
(install-special! :loop analyze-loop)

(defn analyze-recur
  [env form _]
  (let [context (:context env)
        params (:params env)
        forms (vec (map #(analyze env %) (rest form)))]

    (assert (identical? (count params)
                        (count forms))
            "Recurs with unexpected number of arguments")

    {:op :recur
     :form form
     :env env
     :params forms}))
(install-special! :recur analyze-recur)

(defn analyze-quoted-list
  [form]
  {:op :list
   :items (map analyze-quoted (vec form))
   :form form})

(defn analyze-quoted-vector
  [form]
  {:op :vector
   :items (map analyze-quoted form)
   :form form})

(defn analyze-quoted-dictionary
  [form]
  (let [names (vec (map analyze-quoted (keys form)))
        values (vec (map analyze-quoted (vals form)))]
    {:op :dictionary
     :form form
     :keys names
     :values values}))

(defn analyze-quoted-symbol
  [form]
  {:op :symbol
   :name (name form)
   :namespace (namespace form)
   :form form})

(defn analyze-quoted-keyword
 [form]
  {:op :keyword
   :name (name form)
   :namespace (namespace form)
   :form form})

(defn analyze-quoted
  [form]
  (cond (symbol? form) (analyze-quoted-symbol form)
        (list? form) (analyze-quoted-list form)
        (vector? form) (analyze-quoted-vector form)
        (dictionary? form) (analyze-quoted-dictionary form)
        :else {:op :constant
               :form form}))

(defn analyze-quote
  "Examples:
   (analyze-quote {} '(quote foo)) => {:op :constant
                                       :form 'foo
                                       :env env}"
  [env form _]
  (analyze-quoted (second form)))
(install-special! :quote analyze-quote)

(defn analyze-block
  "Examples:
  (analyze-block {} '((foo bar))) => {:statements nil
                                      :result {:op :invoke
                                               :form '(foo bar)
                                               :env {}
                                               :callee {:op :var
                                                        :form 'foo
                                                        :info nil
                                                        :env {}}
                                               :params [{:op :var
                                                         :form 'bar
                                                         :info nil
                                                         :env {}}]}
  (analyze-block {} '((beep bz)
                      (foo bar))) => {:statements [{:op :invoke
                                                    :form '(beep bz)
                                                    :env {}
                                                    :callee {:op :var
                                                             :form 'beep
                                                             :info nil
                                                             :env {}}
                                                    :params [{:op :var
                                                              :form 'bz
                                                              :info nil
                                                              :env {}}]}]
                                      :result {:op :invoke
                                               :form '(foo bar)
                                               :env {}
                                               :callee {:op :var
                                                        :form 'foo
                                                        :info nil
                                                        :env {}}
                                               :params [{:op :var
                                                         :form 'bar
                                                         :info nil
                                                         :env {}}]}"
  [env form]
  (let [statements (if (> (count form) 1)
                     (vec (map #(analyze env %)
                               (butlast form))))
        result (analyze env (last form))]
    {:statements statements
     :result result
     :env env}))

(defn analyze-fn-param
  [env id]
  (let [locals (:locals env)
        param {:name id
               :tag (:tag (meta id))
               :shadow (aget locals (name id))}]
    (conj env
          {:locals (assoc locals (name id) param)
           :params (conj (:params env)
                         param)})))

(defn analyze-fn-method
  "
  {} -> '([x y] (+ x y)) -> {:env {}
                             :form '([x y] (+ x y))
                             :variadic false
                             :arity 2
                             :params [{:op :var :form 'x}
                                      {:op :var :form 'y}]
                             :statements []
                             :return {:op :invoke
                                      :callee {:op :var
                                               :form '+
                                               :env {:parent {}
                                                     :locals {x {:name 'x
                                                                 :shadow nil
                                                                 :local true
                                                                 :tag nil}
                                                              y {:name 'y
                                                                 :shadow nil
                                                                 :local true
                                                                 :tag nil}}}}
                                      :params [{:op :var
                                                :form 'x
                                                :info nil
                                                :tag nil}
                                               {:op :var
                                                :form 'y
                                                :info nil
                                                :tag nil}]}}"
  [env form]
  (let [signature (first form)
        body (rest form)
        ;; If param signature contains & fn is variadic.
        variadic (some #(= '& %) signature)

        ;; All named params of the fn.
        params (if variadic
                 (filter #(not (= '& %)) signature)
                 signature)

        ;; Number of parameters fixed parameters fn takes.
        arity (if variadic
                (dec (count params))
                (count params))

        ;; Analyze parameters in correspondence to environment
        ;; locals to identify binding shadowing.
        bindings (reduce analyze-fn-param
                         {:locals (:locals env)
                          :params []}
                         params)

        scope (conj env {:locals (:locals bindings)})]
    (conj (analyze-block scope body)
          {:op :overload
           :variadic variadic
           :arity arity
           :params (:params bindings)
           :form form})))

(defn analyze-fn
  [env form]
  (let [forms (rest form)
        ;; Normalize fn form so that it contains name
        ;; '(fn [x] x) -> '(fn nil [x] x)
        forms (if (symbol? (first forms))
                forms
                (cons nil forms))

        id (first forms)

        ;; Make sure that fn definition is strucutered
        ;; in method overload style:
        ;; (fn a [x] y) -> (([x] y))
        ;; (fn a ([x] y)) -> (([x] y))
        overloads (if (vector? (second forms))
                    (list (rest forms))
                    (rest forms))

        ;; Hash map of local bindings
        locals (or (:locals env) {})

        scope {:parent env
               :locals (if id
                         (assoc locals
                           (name id)
                           {:op :var
                            :fn-var true
                            :form id
                            :env env
                            :shadow (get locals (name id))})
                         locals)}

        methods (map #(analyze-fn-method scope %)
                     (vec overloads))

        arity (apply max (map #(:arity %) methods))
        variadic (some #(:variadic %) methods)]
    {:op :fn
     :name id
     :variadic variadic
     :methods methods
     :form form
     :env env}))
(install-special! :fn analyze-fn)

(defn parse-references
  "Takes part of namespace difinition and creates hash
  of reference forms"
  [forms]
  (reduce (fn [references form]
            ;; If not a vector than it's not a reference
            ;; form that wisp understands so just skip it.
            (if (seq? form)
              (assoc references
                (name (first form))
                (vec (rest form)))
              references))
          {}
          forms))

(defn parse-require
  [form]
  (let [;; require form may be either vector with id in the
        ;; head or just an id symbol. normalizing to a vector
        requirement (if (symbol? form) [form] (vec form))
        id (first requirement)
        ;; bunch of directives may follow require form but they
        ;; all come in pairs. wisp supports following pairs:
        ;; :as foo
        ;; :refer [foo bar]
        ;; :rename {foo bar}
        ;; join these pairs in a hash for key based access.
        params (apply dictionary (rest requirement))
        renames (get params ':rename)
        names (get params ':refer)
        alias (get params ':as)
        references (if (not (empty? names))
                     (reduce (fn [refers reference]
                      (conj refers
                            {:op :refer
                             :form reference
                             :name reference
                             ;; Look up by reference symbol and by symbol
                             ;; name since reading dictionaries is little
                             ;; bit in a fuzz right now.
                             :rename (or (get renames reference)
                                         (get renames (name reference)))
                             :ns id}))
                             []
                             names))]
    {:op :require
     :alias alias
     :ns id
     :refer references
     :form form}))

(defn analyze-ns
  [env form]
  (let [forms (rest form)
        name (first forms)
        body (rest forms)
        ;; Optional docstring that follows name symbol
        doc (if (string? (first body)) (first body))
        ;; If second form is not a string than treat it
        ;; as regular reference form
        references (parse-references (if doc
                                       (rest body)
                                       body))
        requirements (if (:require references)
                       (map parse-require (:require references)))]
    {:op :ns
     :name name
     :doc doc
     :require (if requirements
                (vec requirements))
     :form form
     :env env}))
(install-special! :ns analyze-ns)

(defn analyze-list
  [env form]
  (let [expansion (macroexpand form)
        operator (first form)
        analyze-special (and (symbol? operator)
                             (get **specials** (name operator)))]
    (cond (not (identical? expansion form)) (analyze env expansion)
          analyze-special (analyze-special env expansion)
          :else (analyze-invoke env expansion))))

(defn analyze-vector
  [env form name]
  (let [items (vec (map #(analyze env % name) form))]
    {:op :vector
     :form form
     :items items
     :env env}))

(defn hash-key?
  [form]
  (or (and (string? form)
           (not (symbol? form)))
      (keyword? form)))

(defn analyze-dictionary
  [env form name]
  (let [names (vec (map #(analyze env % name) (keys form)))
        values (vec (map #(analyze env % name) (vals form)))]
    {:op :dictionary
     :form form
     :keys names
     :values values
     :env env}))

(defn analyze-invoke
  [env form]
  (let [callee (analyze env (first form))
        params (vec (map #(analyze env %) (rest form)))]
    {:op :invoke
     :callee callee
     :form form
     :params params
     :env env}))

(defn analyze-constant
  [env form]
  {:op :constant
   :form form
   :env env})

(defn analyze
  "Given an environment, a map containing {:locals (mapping of names to bindings), :context
  (one of :statement, :expr, :return), :ns (a symbol naming the
  compilation ns)}, and form, returns an expression object (a map
  containing at least :form, :op and :env keys). If expr has any (immediately)
  nested exprs, must have :children [exprs...] entry. This will
  facilitate code walking without knowing the details of the op set."
  ([env form] (analyze env form nil))
  ([env form name]
   (cond (nil? form) (analyze-constant env form)
         (symbol? form) (analyze-symbol env form)
         (list? form) (if (empty? form)
                        (analyze-quoted form)
                        (analyze-list env form name))
         (dictionary? form) (analyze-dictionary env form name)
         (vector? form) (analyze-vector env form name)
         ;(set? form) (analyze-set env form name)
         (keyword? form) (analyze-keyword env form)
         :else (analyze-constant env form))))

(ns wisp.analyzer
  (: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.expander :refer [macroexpand]]
            [wisp.string :refer [split]]))

(defn analyze-symbol
  "Finds the var associated with symbol
  Example:

  (analyze-symbol {} 'foo) => {:op :var
                               :form 'foo
                               :info nil
                               :env {}}"
  [env form]
  {:op :var
   :form form
   :info (get (:locals env) (name form))
   :env env})

(defn analyze-keyword
  "Example:
  (analyze-keyword {} :foo) => {:op :constant
                                :form ':foo
                                :env {}}"
  [env form]
  {:op :constant
   :form form
   :env env})

(def **specials** {})

(defn install-special!
  [op f]
  (set! (get **specials** (name op)) f))

(defn analyze-if
  "Example:
  (analyze-if {} '(if monday? :yep :nope)) => {:op :if
                                               :form '(if monday? :yep :nope)
                                               :env {}
                                               :test {:op :var
                                                      :form 'monday?
                                                      :info nil
                                                      :env {}}
                                               :consequent {:op :constant
                                                            :form ':yep
                                                            :type :keyword
                                                            :env {}}
                                               :alternate {:op :constant
                                                           :form ':nope
                                                           :type :keyword
                                                           :env {}}}"
  [env form]
  (let [forms (rest form)
        test (analyze env (first forms))
        consequent (analyze env (second forms))
        alternate (analyze env (third forms))]
    (if (< (count forms) 2)
      (throw (SyntaxError "Malformed if expression, too few operands")))
    {:op :if
     :form form
     :test test
     :consequent consequent
     :alternate alternate
     :env env}))

(install-special! :if analyze-if)

(defn analyze-throw
  "Example:
  (analyze-throw {} '(throw (Error :boom))) => {:op :throw
                                                :form '(throw (Error :boom))
                                                :throw {:op :invoke
                                                        :callee {:op :var
                                                                 :form 'Error
                                                                 :info nil
                                                                 :env {}}
                                                        :params [{:op :constant
                                                                  :type :keyword
                                                                  :form ':boom
                                                                  :env {}}]}}"
  [env form]
  (let [expression (analyze env (second form))]
    {:op :throw
     :form form
     :throw expression
     :env env}))

(install-special! :throw analyze-throw)

(defn analyze-try
  [env form]
  (let [forms (vec (rest form))

        ;; Finally
        tail (last forms)
        finalizer-form (if (and (list? tail)
                                (= 'finally (first tail)))
                         (rest tail))
        finalizer (if finalizer-form
                    (analyze-block env finalizer-form))

        ;; catch
        body-form (if finalizer
                    (butlast forms)
                    forms)

        tail (last body-form)
        handler-form (if (and (list? tail)
                              (= 'catch (first tail)))
                       (rest tail))
        handler (if handler-form
                  (conj {:name (analyze env
                                        (first handler-form))}
                        (analyze-block env (rest handler-form))))

        ;; Try
        body (if handler-form
               (analyze-block env (butlast body-form))
               (analyze-block env body-form))]
    {:op :try
     :form form
     :body body
     :handler handler
     :finalizer finalizer
     :env env}))

(install-special! :try analyze-try)

(defn analyze-set!
  [env form]
  (let [body (rest form)
        left (first body)
        right (second body)
        target (cond (symbol? left) (analyze-symbol env left name)
                     (list? left) (analyze-list env left name)
                     :else left)
         value (analyze env right)]
    {:op :set!
     :target target
     :value value
     :form form
     :env env}))
(install-special! :set! analyze-set!)

(defn analyze-new
  [env form]
  (let [body (rest form)
        constructor (analyze env (first body))
        params (vec (map #(analyze env %) (rest body)))]
    {:op :new
     :constructor constructor
     :form form
     :params params
     :env env}))
(install-special! :new analyze-new)

(defn analyze-aget
  [env form]
  (let [body (rest form)
        target (analyze env (first body))
        attribute (second body)
        field (and (quote? attribute)
                   (symbol? (second attribute))
                   (second attribute))
        property (analyze env (or field attribute))]
    {:op :member-expression
     :computed (not field)
     :form form
     :target target
     :property property
     :env env}))
(install-special! :aget analyze-aget)

(defn parse-def
  ([id] {:id id})
  ([id init] {:id id :init init})
  ([id doc init] {:id id :doc doc :init init}))

(defn analyze-def
  [env form]
  (let [params (apply parse-def (vec (rest form)))
        id (:id params)
        metadata (meta id)

        variable (analyze env id)

        init (analyze {:parent env
                       :bindings (assoc {} (name id) variable)}
                       (:init params))

        doc (or (:doc params)
                (:doc metadata))]
    {:op :def
     :doc doc
     :var variable
     :init init
     :export (and (not (:parent env))
                  (not (:private metadata)))
     :form form}))
(install-special! :def analyze-def)

(defn analyze-do
  [env form _]
  (let [expressions (rest form)
        body (analyze-block env expressions)]
    (conj body {:op :do
                :form form
                :env env})))
(install-special! :do analyze-do)

(defn analyze-binding
  [env form]
  (let [name (first form)
        init (analyze env (second form))
        init-meta (meta init)
        fn-meta (if (= 'fn (:op init-meta))
                  {:fn-var true
                   :variadic (:variadic init-meta)
                   :max-fixed-arity (:max-fixed-arity init-meta)
                   :method-params (map #(:params %)
                                       (:methods init-meta))}
                  {})
        binding-meta {:name name
                      :init init
                      :tag (or (:tag (meta name))
                               (:tag init-meta)
                               (:tag (:info init-meta)))
                      :local true
                      :shadow (get (:locals env) name)}]
    (assert (not (or (namespace name)
                     (< 1 (count (split \. (str name)))))))
    (conj binding-meta fn-meta)))

(defn analyze-let*
  "Takes let form and enhances it's metadata via analyzed
  info"
  [env form is-loop]
  (let [expressions (rest form)
        bindings (first expressions)
        body (rest expressions)

        valid-bindings? (and (vector? bindings)
                             (even? (count bindings)))

        _ (assert valid-bindings?
                  "bindings must be vector of even number of elements")

        context (:context env)

        locals (map #(analyze-binding env %)
                    (partition 2 bindings))

        params (or (if is-loop locals)
                   (:params env))

        scope (conj {:parent env
                     :bindings locals}
                    (if params {:params params}))

        expressions (analyze-block scope body)]

    {:op :let
     :form form
     :loop is-loop
     :bindings locals
     :statements (:statements expressions)
     :result (:result expressions)
     :env env}))

(defn analyze-let
  [env form _]
  (analyze-let* env form false))
(install-special! :let analyze-let)

(defn analyze-loop
  [env form _]
  (conj (analyze-let* env form true)
        {:op :loop}))
(install-special! :loop analyze-loop)

(defn analyze-recur
  [env form _]
  (let [context (:context env)
        params (:params env)
        forms (vec (map #(analyze env %) (rest form)))]

    (assert (identical? (count params)
                        (count forms))
            "Recurs with unexpected number of arguments")

    {:op :recur
     :form form
     :env env
     :params forms}))
(install-special! :recur analyze-recur)

(defn analyze-quoted-list
  [form]
  {:op :list
   :items (map analyze-quoted (vec form))
   :form form})

(defn analyze-quoted-vector
  [form]
  {:op :vector
   :items (map analyze-quoted form)
   :form form})

(defn analyze-quoted-dictionary
  [form]
  (let [names (vec (map analyze-quoted (keys form)))
        values (vec (map analyze-quoted (vals form)))]
    {:op :dictionary
     :form form
     :keys names
     :values values}))

(defn analyze-quoted-symbol
  [form]
  {:op :symbol
   :name (name form)
   :namespace (namespace form)
   :form form})

(defn analyze-quoted-keyword
 [form]
  {:op :keyword
   :name (name form)
   :namespace (namespace form)
   :form form})

(defn analyze-quoted
  [form]
  (cond (symbol? form) (analyze-quoted-symbol form)
        (list? form) (analyze-quoted-list form)
        (vector? form) (analyze-quoted-vector form)
        (dictionary? form) (analyze-quoted-dictionary form)
        :else {:op :constant
               :form form}))

(defn analyze-quote
  "Examples:
   (analyze-quote {} '(quote foo)) => {:op :constant
                                       :form 'foo
                                       :env env}"
  [env form _]
  (analyze-quoted (second form)))
(install-special! :quote analyze-quote)

(defn analyze-block
  "Examples:
  (analyze-block {} '((foo bar))) => {:statements nil
                                      :result {:op :invoke
                                               :form '(foo bar)
                                               :env {}
                                               :callee {:op :var
                                                        :form 'foo
                                                        :info nil
                                                        :env {}}
                                               :params [{:op :var
                                                         :form 'bar
                                                         :info nil
                                                         :env {}}]}
  (analyze-block {} '((beep bz)
                      (foo bar))) => {:statements [{:op :invoke
                                                    :form '(beep bz)
                                                    :env {}
                                                    :callee {:op :var
                                                             :form 'beep
                                                             :info nil
                                                             :env {}}
                                                    :params [{:op :var
                                                              :form 'bz
                                                              :info nil
                                                              :env {}}]}]
                                      :result {:op :invoke
                                               :form '(foo bar)
                                               :env {}
                                               :callee {:op :var
                                                        :form 'foo
                                                        :info nil
                                                        :env {}}
                                               :params [{:op :var
                                                         :form 'bar
                                                         :info nil
                                                         :env {}}]}"
  [env form]
  (let [statements (if (> (count form) 1)
                     (vec (map #(analyze env %)
                               (butlast form))))
        result (analyze env (last form))]
    {:statements statements
     :result result
     :env env}))

(defn analyze-fn-param
  [env id]
  (let [locals (:locals env)
        param {:name id
               :tag (:tag (meta id))
               :shadow (aget locals (name id))}]
    (conj env
          {:locals (assoc locals (name id) param)
           :params (conj (:params env)
                         param)})))

(defn analyze-fn-method
  "
  {} -> '([x y] (+ x y)) -> {:env {}
                             :form '([x y] (+ x y))
                             :variadic false
                             :arity 2
                             :params [{:op :var :form 'x}
                                      {:op :var :form 'y}]
                             :statements []
                             :return {:op :invoke
                                      :callee {:op :var
                                               :form '+
                                               :env {:parent {}
                                                     :locals {x {:name 'x
                                                                 :shadow nil
                                                                 :local true
                                                                 :tag nil}
                                                              y {:name 'y
                                                                 :shadow nil
                                                                 :local true
                                                                 :tag nil}}}}
                                      :params [{:op :var
                                                :form 'x
                                                :info nil
                                                :tag nil}
                                               {:op :var
                                                :form 'y
                                                :info nil
                                                :tag nil}]}}"
  [env form]
  (let [signature (first form)
        body (rest form)
        ;; If param signature contains & fn is variadic.
        variadic (some #(= '& %) signature)

        ;; All named params of the fn.
        params (if variadic
                 (filter #(not (= '& %)) signature)
                 signature)

        ;; Number of parameters fixed parameters fn takes.
        arity (if variadic
                (dec (count params))
                (count params))

        ;; Analyze parameters in correspondence to environment
        ;; locals to identify binding shadowing.
        bindings (reduce analyze-fn-param
                         {:locals (:locals env)
                          :params []}
                         params)

        scope (conj env {:locals (:locals bindings)})]
    (conj (analyze-block scope body)
          {:op :overload
           :variadic variadic
           :arity arity
           :params (:params bindings)
           :form form})))

(defn analyze-fn
  [env form]
  (let [forms (rest form)
        ;; Normalize fn form so that it contains name
        ;; '(fn [x] x) -> '(fn nil [x] x)
        forms (if (symbol? (first forms))
                forms
                (cons nil forms))

        id (first forms)

        ;; Make sure that fn definition is strucutered
        ;; in method overload style:
        ;; (fn a [x] y) -> (([x] y))
        ;; (fn a ([x] y)) -> (([x] y))
        overloads (if (vector? (second forms))
                    (list (rest forms))
                    (rest forms))

        ;; Hash map of local bindings
        locals (or (:locals env) {})

        scope {:parent env
               :locals (if id
                         (assoc locals
                           (name id)
                           {:op :var
                            :fn-var true
                            :form id
                            :env env
                            :shadow (get locals (name id))})
                         locals)}

        methods (map #(analyze-fn-method scope %)
                     (vec overloads))

        arity (apply max (map #(:arity %) methods))
        variadic (some #(:variadic %) methods)]
    {:op :fn
     :name id
     :variadic variadic
     :methods methods
     :form form
     :env env}))
(install-special! :fn analyze-fn)

(defn parse-references
  "Takes part of namespace difinition and creates hash
  of reference forms"
  [forms]
  (reduce (fn [references form]
            ;; If not a vector than it's not a reference
            ;; form that wisp understands so just skip it.
            (if (seq? form)
              (assoc references
                (name (first form))
                (vec (rest form)))
              references))
          {}
          forms))

(defn parse-require
  [form]
  (let [;; require form may be either vector with id in the
        ;; head or just an id symbol. normalizing to a vector
        requirement (if (symbol? form) [form] (vec form))
        id (first requirement)
        ;; bunch of directives may follow require form but they
        ;; all come in pairs. wisp supports following pairs:
        ;; :as foo
        ;; :refer [foo bar]
        ;; :rename {foo bar}
        ;; join these pairs in a hash for key based access.
        params (apply dictionary (rest requirement))
        renames (get params ':rename)
        names (get params ':refer)
        alias (get params ':as)
        references (if (not (empty? names))
                     (reduce (fn [refers reference]
                      (conj refers
                            {:op :refer
                             :form reference
                             :name reference
                             ;; Look up by reference symbol and by symbol
                             ;; name since reading dictionaries is little
                             ;; bit in a fuzz right now.
                             :rename (or (get renames reference)
                                         (get renames (name reference)))
                             :ns id}))
                             []
                             names))]
    {:op :require
     :alias alias
     :ns id
     :refer references
     :form form}))

(defn analyze-ns
  [env form]
  (let [forms (rest form)
        name (first forms)
        body (rest forms)
        ;; Optional docstring that follows name symbol
        doc (if (string? (first body)) (first body))
        ;; If second form is not a string than treat it
        ;; as regular reference form
        references (parse-references (if doc
                                       (rest body)
                                       body))
        requirements (if (:require references)
                       (map parse-require (:require references)))]
    {:op :ns
     :name name
     :doc doc
     :require (if requirements
                (vec requirements))
     :form form
     :env env}))
(install-special! :ns analyze-ns)

(defn analyze-list
  [env form]
  (let [expansion (macroexpand form)
        operator (first form)
        analyze-special (and (symbol? operator)
                             (get **specials** (name operator)))]
    (cond (not (identical? expansion form)) (analyze env expansion)
          analyze-special (analyze-special env expansion)
          :else (analyze-invoke env expansion))))

(defn analyze-vector
  [env form name]
  (let [items (vec (map #(analyze env % name) form))]
    {:op :vector
     :form form
     :items items
     :env env}))

(defn hash-key?
  [form]
  (or (and (string? form)
           (not (symbol? form)))
      (keyword? form)))

(defn analyze-dictionary
  [env form name]
  (let [names (vec (map #(analyze env % name) (keys form)))
        values (vec (map #(analyze env % name) (vals form)))]
    {:op :dictionary
     :form form
     :keys names
     :values values
     :env env}))

(defn analyze-invoke
  [env form]
  (let [callee (analyze env (first form))
        params (vec (map #(analyze env %) (rest form)))]
    {:op :invoke
     :callee callee
     :form form
     :params params
     :env env}))

(defn analyze-constant
  [env form]
  {:op :constant
   :form form
   :env env})

(defn analyze
  "Given an environment, a map containing {:locals (mapping of names to bindings), :context
  (one of :statement, :expr, :return), :ns (a symbol naming the
  compilation ns)}, and form, returns an expression object (a map
  containing at least :form, :op and :env keys). If expr has any (immediately)
  nested exprs, must have :children [exprs...] entry. This will
  facilitate code walking without knowing the details of the op set."
  ([env form] (analyze env form nil))
  ([env form name]
   (cond (nil? form) (analyze-constant env form)
         (symbol? form) (analyze-symbol env form)
         (list? form) (if (empty? form)
                        (analyze-quoted form)
                        (analyze-list env form name))
         (dictionary? form) (analyze-dictionary env form name)
         (vector? form) (analyze-vector env form name)
         ;(set? form) (analyze-set env form name)
         (keyword? form) (analyze-keyword env form)
         :else (analyze-constant env form))))


No longer generate data URIs for source-uri but rather use `anonymous.wisp`.

(ns wisp.compiler
  (:require [wisp.analyzer :refer [analyze]]
            [wisp.reader :refer [read* read push-back-reader]]
            [wisp.string :refer [replace]]
            [wisp.sequence :refer [map conj cons vec first rest empty? count]]
            [wisp.runtime :refer [error?]]

            [wisp.backend.escodegen.generator :refer [generate]
                                              :rename {generate generate-js}]
            [base64-encode :as btoa]))

(def generate generate-js)

(defn read-form
  [reader eof]
  (try (read reader false eof false)
    (catch error error)))

(defn read-forms
  [source uri]
  (let [reader (push-back-reader source uri)
        eof {}]
    (loop [forms []
           form (read-form reader eof)]
      (cond (error? form) {:forms forms :error form}
            (identical? form eof) {:forms forms}
            :else (recur (conj forms form)
                         (read-form reader eof))))))

(defn analyze-form
  [form]
  (try (analyze form) (catch error error)))

(defn analyze-forms
  [forms]
  (loop [nodes []
         forms forms]
    (let [node (analyze-form (first forms))]
      (cond (error? node) {:ast nodes :error node}
            (<= (count forms) 1) {:ast (conj nodes node)}
            :else (recur (conj nodes node) (rest forms))))))

(defn compile
  "Compiler takes wisp code in form of string and returns a hash
  containing `:source` representing compilation result. If
  `(:source-map options)` is `true` then `:source-map` of the returned
  hash will contain source map for it.
  :output-uri
  :source-map-uri

  Returns hash with following fields:

  :code - Generated code.

  :source-map - Generated source map. Only if (:source-map options)
                was true.

  :output-uri - Returns back (:output-uri options) if was passed in,
                otherwise computes one from (:source-uri options) by
                changing file extension.

  :source-map-uri - Returns back (:source-map-uri options) if was passed
                    in, otherwise computes one from (:source-uri options)
                    by adding `.map` file extension."
  ([source] (compile source {}))
  ([source options]
   (let [uri (:source-uri options)
         source-uri (or uri "anonymous.wisp")
         output-uri (or (:output-uri options)
                        (replace source-uri #".wisp$" ".js"))
         forms (read-forms source source-uri)

         ast (if (:error forms)
               forms
               (analyze-forms (:forms forms)))

         output (if (:error ast)
                  ast
                  (try              ;; TODO: Remove this
                                    ;; Old compiler has incorrect apply.
                    (apply generate (vec (cons (conj options
                                                     {:source-uri source-uri
                                                      :output-uri output-uri})
                                               (:ast ast))))
                    (catch error {:error error})))

         result {:source-uri source-uri
                 :output-uri output-uri
                 :source-map-uri (:source-map-uri options)
                 :ast (if (:include-ast options) (:ast ast))
                 :forms (if (:include-forms options) (:forms forms))}]
     (conj output result))))

(defn evaluate
  [source]
  (let [output (compile source)]
    (if (:error output)
      (throw (:error output))
      (eval (:code output)))))

(ns wisp.compiler
  (:require [wisp.analyzer :refer [analyze]]
            [wisp.reader :refer [read* read push-back-reader]]
            [wisp.string :refer [replace]]
            [wisp.sequence :refer [map conj cons vec first rest empty? count]]
            [wisp.runtime :refer [error?]]

            [wisp.backend.escodegen.generator :refer [generate]
                                              :rename {generate generate-js}]
            [base64-encode :as btoa]))

(def generate generate-js)

(defn read-form
  [reader eof]
  (try (read reader false eof false)
    (catch error error)))

(defn read-forms
  [source uri]
  (let [reader (push-back-reader source uri)
        eof {}]
    (loop [forms []
           form (read-form reader eof)]
      (cond (error? form) {:forms forms :error form}
            (identical? form eof) {:forms forms}
            :else (recur (conj forms form)
                         (read-form reader eof))))))

(defn analyze-form
  [form]
  (try (analyze form) (catch error error)))

(defn analyze-forms
  [forms]
  (loop [nodes []
         forms forms]
    (let [node (analyze-form (first forms))]
      (cond (error? node) {:ast nodes :error node}
            (<= (count forms) 1) {:ast (conj nodes node)}
            :else (recur (conj nodes node) (rest forms))))))

(defn compile
  "Compiler takes wisp code in form of string and returns a hash
  containing `:source` representing compilation result. If
  `(:source-map options)` is `true` then `:source-map` of the returned
  hash will contain source map for it.
  :output-uri
  :source-map-uri

  Returns hash with following fields:

  :code - Generated code.

  :source-map - Generated source map. Only if (:source-map options)
                was true.

  :output-uri - Returns back (:output-uri options) if was passed in,
                otherwise computes one from (:source-uri options) by
                changing file extension.

  :source-map-uri - Returns back (:source-map-uri options) if was passed
                    in, otherwise computes one from (:source-uri options)
                    by adding `.map` file extension."
  ([source] (compile source {}))
  ([source options]
   (let [uri (:source-uri options)
         source-uri (or uri
                        (str "data:application/wisp;charset=utf-8;base64,"
                             (btoa source)))
         output-uri (or (:output-uri options)
                        (if uri (replace uri #".wisp$" ".js")))

         forms (read-forms source source-uri)

         ast (if (:error forms)
               forms
               (analyze-forms (:forms forms)))

         output (if (:error ast)
                  ast
                  (try              ;; TODO: Remove this
                                    ;; Old compiler has incorrect apply.
                    (apply generate (vec (cons (conj options
                                                     {:source-uri source-uri
                                                      :output-uri output-uri})
                                               (:ast ast))))
                    (catch error {:error error})))

         result {:source-uri source-uri
                 :output-uri output-uri
                 :source-map-uri (:source-map-uri options)
                 :ast (if (:include-ast options) (:ast ast))
                 :forms (if (:include-forms options) (:forms forms))}]
     (conj output result))))

(defn evaluate
  [source]
  (let [output (compile source)]
    (if (:error output)
      (throw (:error output))
      (eval (:code output)))))


working --output and --print

working --output and --print

(ns wisp.wisp
  "Wisp program that reads wisp code from stdin and prints
  compiled javascript code into stdout"
  (:require [fs :refer [createReadStream writeFileSync]]
            [path :refer [basename dirname join resolve]]
            [module :refer [Module]]
            [commander]

            [wisp.string :refer [split join upper-case replace]]
            [wisp.sequence :refer [first second last count reduce rest map
                                   conj partition assoc drop empty?]]

            [wisp.repl :refer [start] :rename {start start-repl}]
            [wisp.engine.node]
            [wisp.runtime :refer [str subs = nil?]]
            [wisp.ast :refer [pr-str name]]
            [wisp.compiler :refer [compile]]))

(defn compile-stdin
  [options]
  (with-stream-content process.stdin
                       compile-string
                       options))

(defn compile-file
  [path options]
  (map (fn [file] (with-stream-content
                       (createReadStream file)
                       compile-string
                       (conj {:source-uri file} options))) path))

(defn compile-string
  [source options]
  (let [channel (or (:print options) :code)
        output (compile source options)
        content (if (= channel :code)
                  (:code output)
                  (JSON.stringify (get output channel) 2 2))]
    (if (:ast options) (map (fn [item]
                              (.write process.stdout
                                      (str (pr-str item.form) "\n")))
                              output.ast))
    (if (and (:output options) (:source-uri options) content)
      (writeFileSync (path.join (:output options) ;; `join` relies on `path`
                           (str (basename (:source-uri options) ".wisp") ".js"))
                     content)
      (.write process.stdout (or content "nil")))
    (if (:error output) (throw (:error output)))))

(defn with-stream-content
  [input resume options]
  (let [content ""]
    (.setEncoding input "utf8")
    (.resume input)
    (.on input "data" #(set! content (str content %)))
    (.once input "end" (fn [] (resume content options)))))

(defn run
  [path]
  ;; Loading module as main one, same way as nodejs does it:
  ;; https://github.com/joyent/node/blob/master/lib/module.js#L489-493
  (Module._load (resolve (get path 0)) null true))

(defmacro ->
  [& operations]
  (reduce
   (fn [form operation]
     (cons (first operation)
           (cons form (rest operation))))
   (first operations)
   (rest operations)))

(defn main
  []
  (let [options commander]
    (-> options
      (.usage "[options] <file ...>")
      (.option "-r, --run"           "Compile and execute the file")
      (.option "-c, --compile"       "Compile to JavaScript and save as .js files")
      (.option "-i, --interactive"   "Run an interactive wisp REPL")
      (.option "--debug, --print <type>"    "Print debug information. Possible values are `form`, `ast` and `js-ast`")
      (.option "-o, --output <dir>"  "Output to specified directory")
      (.option "--no-map"            "Disable source map generation")
      (.parse process.argv))
    (set! (aget options "no-map") (not (aget options "map"))) ;; commander auto translates to camelCase
    (cond options.run (run options.args)
          (not process.stdin.isTTY) (compile-stdin options)
          options.interactive (start-repl)
          options.compile (compile-file options.args options)
          options.args (run options.args)
          :else (start-repl)
   )))

(ns wisp.wisp
  "Wisp program that reads wisp code from stdin and prints
  compiled javascript code into stdout"
  (:require [fs :refer [createReadStream]]
            [path :refer [basename dirname join resolve]]
            [module :refer [Module]]
            [commander]

            [wisp.string :refer [split join upper-case replace]]
            [wisp.sequence :refer [first second last count reduce rest map
                                   conj partition assoc drop empty?]]

            [wisp.repl :refer [start] :rename {start start-repl}]
            [wisp.engine.node]
            [wisp.runtime :refer [str subs = nil?]]
            [wisp.ast :refer [pr-str name]]
            [wisp.compiler :refer [compile]]))

(defn flag?
  [param]
  ;; HACK: Workaround for segfault #6691
  (identical? (subs param 0 2) (name :--)))

(defn flag->key
  [flag]
  (subs flag 2))

;; Just mungle all the `--param value` pairs into global *env* hash.
(defn parse-params
  [params]
  (loop [input params
         output {}]
    (if (empty? input)
      output
      (let [name (first input)
            value (second input)]
        (if (flag? name)
          (if (or (nil? value) (flag? value))
            (recur (rest input)
                   (assoc output (flag->key name) true))
            (recur (drop 2 input)
                   (assoc output (flag->key name) value)))
          (recur (rest input)
                 output))))))

(defn compile-stdin
  [options]
  (with-stream-content process.stdin
                       compile-string
                       options))

(defn compile-file
  [path options]
  (map (fn [file] (with-stream-content
                       (createReadStream file)
                       compile-string
                       (conj {:source-uri file} options))) path))

(defn compile-string
  [source options]
  (let [channel (or (:print options) :code)
        output (compile source options)
        content (if (= channel :code)
                  (:code output)
                  (JSON.stringify (get output channel) 2 2))]
    (if (:ast options) (map (fn [item]
                              (.write process.stdout
                                      (str (pr-str item.form) "\n")))
                              output.ast))
    (if (:js-ast options) (.write process.stdout
                                      (str (pr-str (:body (:js-ast output))) "\n")))
    (.write process.stdout (or content "nil"))
    (if (:error output) (throw (:error output)))))

(defn with-stream-content
  [input resume options]
  (let [content ""]
    (.setEncoding input "utf8")
    (.resume input)
    (.on input "data" #(set! content (str content %)))
    (.once input "end" (fn [] (resume content options)))))

(defn run
  [path]
  ;; Loading module as main one, same way as nodejs does it:
  ;; https://github.com/joyent/node/blob/master/lib/module.js#L489-493
  (Module._load (resolve (get path 0)) null true))

(defmacro ->
  [& operations]
  (reduce
   (fn [form operation]
     (cons (first operation)
           (cons form (rest operation))))
   (first operations)
   (rest operations)))

(defn main
  []
  (let [options commander]
    (-> options
      (.usage "[options] <file ...>")
      (.option "-r, --run"           "Compile and execute the file")
      (.option "-c, --compile"       "Compile to JavaScript and save as .js files")
      (.option "-i, --interactive"   "Run an interactive wisp REPL")
      (.option "-p, --print"         "Print compiled JavaScript")
      (.option "-o, --output <dir>"  "Output to specified directory")
      (.option "--no-map"            "Disable source map generation")
      (.option "--ast"               "Print the wisp AST produced by the reader")
      (.option "--js-ast"            "Print the JavaScript AST produced by the compiler")
      (.parse process.argv))
    (set! (aget options "no-map") (aget options "noMap")) ;; commander auto translates to camelCase
    (set! (aget options "js-ast") (aget options "jsAst"))
    (cond options.run (run options.args)
          (not process.stdin.isTTY) (compile-stdin options)
          options.interactive (start-repl)
          options.compile (compile-file options.args options)
          options.args (run options.args)
          :else (start-repl)
   )))


Use `**verbose**` mode if wisp started with `--verbose` flag.

(import fs "fs")
(import [rest] "../sequence")
(import [str] "../runtime")
(import [read-from-string] "../reader")
(import [compile-program] "../compiler")

(set! global.**verbose** (<= 0 (.indexOf process.argv :--verbose)))

(defn transpile
  [source uri]
  (str (compile-program
        ;; Wrap program body into a list in order to to read
        ;; all of it.
        (rest (read-from-string (str "(do " source ")") uri))) "\n"))

;; Register `.wisp` file extension so that
;; modules can be simply required.
(set! (get require.extensions ".wisp")
  (fn [module uri]
    (._compile module
               (transpile (.read-file-sync fs uri :utf8))
               uri)))

(export transpile)

(import fs "fs")
(import [rest] "../sequence")
(import [str] "../runtime")
(import [read-from-string] "../reader")
(import [compile-program] "../compiler")

(defn transpile
  [source uri]
  (str (compile-program
        ;; Wrap program body into a list in order to to read
        ;; all of it.
        (rest (read-from-string (str "(do " source ")") uri))) "\n"))

;; Register `.wisp` file extension so that
;; modules can be simply required.
(set! (get require.extensions ".wisp")
  (fn [module uri]
    (._compile module
               (transpile (.read-file-sync fs uri :utf8))
               uri)))

(export transpile)


chore(utils): remove unused method

chore(utils): remove unused method

(ns promitto.lib.utils)

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

(def ^:private args-regex
  (RegExp. "^function(\\s*)(\\w*)[^(]*\\(([^)]*)\\)" "m"))

(def ^:private fn-name-regex
  (RegExp. "^function\\s*(\\w+)\\s*\\(" "i"))

(defn ^boolean fn?
  "Check if the given object is a function type"
  [o]
  (? (typeof o) :function))

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

(defn ^boolean obj?
  "Check if the given value is a plain object"
  [o]
  (? (.call ->string o) "[object Object]"))

(defn ^boolean arr?
  "Check if the given value is an array"
  [o]
  (? (.call ->string o) "[object Array]"))

(defn ^fn chain
  "Make function chainable"
  [obj fn]
  (fn []
    (apply fn arguments) obj))

(defn ^:string fn-name
  "Extract function name"
  [lambda]
  (cond (fn? lambda)
    (if (.-name lambda)
      (.-name lambda)
      (let [name (.exec fn-name-regex (.to-string lambda))]
        (cond (and name (aget name 1))
          (aget name 1))))))

(defn ^array parse-args
  "Parse and extract function arguments"
  [lambda]
  (cond (fn? lambda)
    (let [matches (.exec args-regex (.to-string lambda))]
      (if (and matches (aget matches 3))
        (.split (aget matches 3) (RegExp. "\\s*,\\s*")) nil))))

(ns promitto.lib.utils)

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

(def ^:private args-regex
  (RegExp. "^function(\\s*)(\\w*)[^(]*\\(([^)]*)\\)" "m"))

(def ^:private fn-name-regex
  (RegExp. "^function\\s*(\\w+)\\s*\\(" "i"))

(defn ^boolean fn?
  "Check if the given object is a function type"
  [o]
  (? (typeof o) :function))

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

(defn ^boolean obj?
  "Check if the given value is a plain object"
  [o]
  (? (.call ->string o) "[object Object]"))

(defn ^boolean arr?
  "Check if the given value is an array"
  [o]
  (? (.call ->string o) "[object Array]"))

(defn ^array ->arr
  "Convert arguments object into array"
  [o]
  (.call (.-slice (.-prototype Array)) o))

(defn ^fn chain
  "Make function chainable"
  [obj fn]
  (fn []
    (apply fn arguments) obj))

(defn ^:string fn-name
  "Extract function name"
  [lambda]
  (cond (fn? lambda)
    (if (.-name lambda)
      (.-name lambda)
      (let [name (.exec fn-name-regex (.to-string lambda))]
        (cond (and name (aget name 1))
          (aget name 1))))))

(defn ^array parse-args
  "Parse and extract function arguments"
  [lambda]
  (cond (fn? lambda)
    (let [matches (.exec args-regex (.to-string lambda))]
      (if (and matches (aget matches 3))
        (.split (aget matches 3) (RegExp. "\\s*,\\s*")) nil))))


Add error? function.

Add error? function.

(ns wisp.runtime
  "Core primitives required for runtime")

(defn identity
  "Returns its argument."
  [x] x)

(defn ^boolean odd? [n]
  (identical? (mod n 2) 1))

(defn ^boolean even? [n]
  (identical? (mod n 2) 0))

(defn ^boolean dictionary?
  "Returns true if dictionary"
  [form]
  (and (object? form)
       ;; Inherits right form Object.prototype
       (object? (.get-prototype-of Object form))
       (nil? (.get-prototype-of Object (.get-prototype-of Object form)))))

(defn dictionary
  "Creates dictionary of given arguments. Odd indexed arguments
  are used for keys and evens for values"
  [& pairs]
  ; TODO: We should convert keywords to names to make sure that keys are not
  ; used in their keyword form.
  (loop [key-values pairs
         result {}]
    (if (.-length key-values)
      (do
        (set! (aget result (aget key-values 0))
              (aget key-values 1))
        (recur (.slice key-values 2) result))
      result)))

(defn keys
  "Returns a sequence of the map's keys"
  [dictionary]
  (.keys Object dictionary))

(defn vals
  "Returns a sequence of the map's values."
  [dictionary]
  (.map (keys dictionary)
        (fn [key] (get dictionary key))))

(defn key-values
  [dictionary]
  (.map (keys dictionary)
        (fn [key] [key (get dictionary key)])))

(defn merge
  "Returns a dictionary that consists of the rest of the maps conj-ed onto
  the first. If a key occurs in more than one map, the mapping from
  the latter (left-to-right) will be the mapping in the result."
  []
  (Object.create
   Object.prototype
   (.reduce
    (.call Array.prototype.slice arguments)
    (fn [descriptor dictionary]
      (if (object? dictionary)
        (.for-each
         (Object.keys dictionary)
         (fn [key]
           (set!
            (get descriptor key)
            (Object.get-own-property-descriptor dictionary key)))))
      descriptor)
    (Object.create Object.prototype))))

(defn ^boolean contains-vector?
  "Returns true if vector contains given element"
  [vector element]
  (>= (.index-of vector element) 0))

(defn map-dictionary
  "Maps dictionary values by applying `f` to each one"
  [source f]
  (.reduce (.keys Object source)
           (fn [target key]
              (set! (get target key) (f (get source key)))
              target) {}))

(def to-string Object.prototype.to-string)

(def
  ^{:tag boolean
    :doc "Returns true if x is a function"}
  fn?
  (if (identical? (typeof #".") "function")
    (fn
      [x]
      (identical? (.call to-string x) "[object Function]"))
    (fn
      [x]
      (identical? (typeof x) "function"))))

(defn ^boolean error?
  "Returns true if x is of error type"
  [x]
  (or (instance? Error x)
      (identical? (.call to-string x) "[object Error]")))

(defn ^boolean string?
  "Return true if x is a string"
  [x]
  (or (identical? (typeof x) "string")
      (identical? (.call to-string x) "[object String]")))

(defn ^boolean number?
  "Return true if x is a number"
  [x]
  (or (identical? (typeof x) "number")
      (identical? (.call to-string x) "[object Number]")))

(def
  ^{:tag boolean
    :doc "Returns true if x is a vector"}
  vector?
  (if (fn? Array.isArray)
    Array.isArray
    (fn [x] (identical? (.call to-string x) "[object Array]"))))

(defn ^boolean date?
  "Returns true if x is a date"
  [x]
  (identical? (.call to-string x) "[object Date]"))

(defn ^boolean boolean?
  "Returns true if x is a boolean"
  [x]
  (or (identical? x true)
      (identical? x false)
      (identical? (.call to-string x) "[object Boolean]")))

(defn ^boolean re-pattern?
  "Returns true if x is a regular expression"
  [x]
  (identical? (.call to-string x) "[object RegExp]"))

(defn ^boolean object?
  "Returns true if x is an object"
  [x]
  (and x (identical? (typeof x) "object")))

(defn ^boolean nil?
  "Returns true if x is undefined or null"
  [x]
  (or (identical? x nil)
      (identical? x null)))

(defn ^boolean true?
  "Returns true if x is true"
  [x]
  (identical? x true))

(defn ^boolean false?
  "Returns true if x is true"
  [x]
  (identical? x true))

(defn re-find
  "Returns the first regex match, if any, of s to re, using
  re.exec(s). Returns a vector, containing first the matching
  substring, then any capturing groups if the regular expression contains
  capturing groups."
  [re s]
  (let [matches (.exec re s)]
    (if (not (nil? matches))
      (if (identical? (.-length matches) 1)
        (get matches 0)
        matches))))

(defn re-matches
  [pattern source]
  (let [matches (.exec pattern source)]
    (if (and (not (nil? matches))
             (identical? (get matches 0) source))
      (if (identical? (.-length matches) 1)
        (get matches 0)
        matches))))

(defn re-pattern
  "Returns an instance of RegExp which has compiled the provided string."
  [s]
  (let [match (re-find #"^(?:\(\?([idmsux]*)\))?(.*)" s)]
    (new RegExp (get match 2) (get match 1))))

(defn inc
  [x]
  (+ x 1))

(defn dec
  [x]
  (- x 1))

(defn str
  "With no args, returns the empty string. With one arg x, returns
  x.toString().  (str nil) returns the empty string. With more than
  one arg, returns the concatenation of the str values of the args."
  []
  (.apply String.prototype.concat "" arguments))

(defn char
  "Coerce to char"
  [code]
  (.fromCharCode String code))

(defn int
  "Coerce to int by stripping decimal places."
  [x]
  (if (number? x)
    (if (>= x 0)
      (.floor Math x)
      (.floor Math x))
    (.charCodeAt x 0)))

(defn subs
  "Returns the substring of s beginning at start inclusive, and ending
  at end (defaults to length of string), exclusive."
  {:added "1.0"
   :static true}
   [string start end]
   (.substring string start end))

(defn- ^boolean pattern-equal?
  [x y]
  (and (re-pattern? x)
       (re-pattern? y)
       (identical? (.-source x) (.-source y))
       (identical? (.-global x) (.-global y))
       (identical? (.-multiline x) (.-multiline y))
       (identical? (.-ignoreCase x) (.-ignoreCase y))))

(defn- ^boolean date-equal?
  [x y]
  (and (date? x)
       (date? y)
       (identical? (Number x) (Number y))))

(defn- ^boolean dictionary-equal?
  [x y]
  (and (object? x)
       (object? y)
       (let [x-keys (keys x)
             y-keys (keys y)
             x-count (.-length x-keys)
             y-count (.-length y-keys)]
         (and (identical? x-count y-count)
              (loop [index 0
                     count x-count
                     keys x-keys]
                (if (< index count)
                  (if (equivalent? (get x (get keys index))
                                   (get y (get keys index)))
                    (recur (inc index) count keys)
                    false)
                  true))))))

(defn- ^boolean vector-equal?
  [x y]
  (and (vector? x)
       (vector? y)
       (identical? (.-length x) (.-length y))
       (loop [xs x
              ys y
              index 0
              count (.-length x)]
        (if (< index count)
          (if (equivalent? (get xs index) (get ys index))
              (recur xs ys (inc index) count)
              false)
          true))))

(defn- ^boolean equivalent?
  "Equality. Returns true if x equals y, false if not. Compares
  numbers and collections in a type-independent manner. Clojure's
  immutable data structures define -equiv (and thus =) as a value,
  not an identity, comparison."
  ([x] true)
  ([x y] (or (identical? x y)
             (cond (nil? x) (nil? y)
                   (nil? y) (nil? x)
                   (string? x) false
                   (number? x) false
                   (fn? x) false
                   (boolean? x) false
                   (date? x) (date-equal? x y)
                   (vector? x) (vector-equal? x y [] [])
                   (re-pattern? x) (pattern-equal? x y)
                   :else (dictionary-equal? x y))))
  ([x y & more]
   (loop [previous x
          current y
          index 0
          count (.-length more)]
    (and (equivalent? previous current)
         (if (< index count)
          (recur current
                 (get more index)
                 (inc index)
                 count)
          true)))))

(def = equivalent?)

(defn ^boolean ==
  "Equality. Returns true if x equals y, false if not. Compares
  numbers and collections in a type-independent manner. Clojure's
  immutable data structures define -equiv (and thus =) as a value,
  not an identity, comparison."
  ([x] true)
  ([x y] (identical? x y))
  ([x y & more]
   (loop [previous x
          current y
          index 0
          count (.-length more)]
    (and (== previous current)
         (if (< index count)
          (recur current
                 (get more index)
                 (inc index)
                 count)
          true)))))

(defn ^boolean >
  "Returns non-nil if nums are in monotonically decreasing order,
  otherwise false."
  ([x] true)
  ([x y] (> x y))
  ([x y & more]
   (loop [previous x
          current y
          index 0
          count (.-length more)]
    (and (> previous current)
         (if (< index count)
          (recur current
                 (get more index)
                 (inc index)
                 count)
          true)))))

(defn ^boolean >=
  "Returns non-nil if nums are in monotonically decreasing order,
  otherwise false."
  ([x] true)
  ([x y] (>= x y))
  ([x y & more]
   (loop [previous x
          current y
          index 0
          count (.-length more)]
    (and (>= previous current)
         (if (< index count)
          (recur current
                 (get more index)
                 (inc index)
                 count)
          true)))))

(defn ^boolean <
  "Returns non-nil if nums are in monotonically decreasing order,
  otherwise false."
  ([x] true)
  ([x y] (< x y))
  ([x y & more]
   (loop [previous x
          current y
          index 0
          count (.-length more)]
    (and (< previous current)
         (if (< index count)
          (recur current
                 (get more index)
                 (inc index)
                 count)
          true)))))

(defn ^boolean <=
  "Returns non-nil if nums are in monotonically decreasing order,
  otherwise false."
  ([x] true)
  ([x y] (<= x y))
  ([x y & more]
   (loop [previous x
          current y
          index 0
          count (.-length more)]
    (and (<= previous current)
         (if (< index count)
          (recur current
                 (get more index)
                 (inc index)
                 count)
          true)))))

(defn ^boolean +
  ([] 0)
  ([a] a)
  ([a b] (+ a b))
  ([a b c] (+ a b c))
  ([a b c d] (+ a b c d))
  ([a b c d e] (+ a b c d e))
  ([a b c d e f] (+ a b c d e f))
  ([a b c d e f & more]
   (loop [value (+ a b c d e f)
          index 0
          count (.-length more)]
     (if (< index count)
       (recur (+ value (get more index))
              (inc index)
              count)
       value))))

(defn ^boolean -
  ([] (throw (TypeError "Wrong number of args passed to: -")))
  ([a] (- 0 a))
  ([a b] (- a b))
  ([a b c] (- a b c))
  ([a b c d] (- a b c d))
  ([a b c d e] (- a b c d e))
  ([a b c d e f] (- a b c d e f))
  ([a b c d e f & more]
   (loop [value (- a b c d e f)
          index 0
          count (.-length more)]
     (if (< index count)
       (recur (- value (get more index))
              (inc index)
              count)
       value))))

(defn ^boolean /
  ([] (throw (TypeError "Wrong number of args passed to: /")))
  ([a] (/ 1 a))
  ([a b] (/ a b))
  ([a b c] (/ a b c))
  ([a b c d] (/ a b c d))
  ([a b c d e] (/ a b c d e))
  ([a b c d e f] (/ a b c d e f))
  ([a b c d e f & more]
   (loop [value (/ a b c d e f)
          index 0
          count (.-length more)]
     (if (< index count)
       (recur (/ value (get more index))
              (inc index)
              count)
       value))))

(defn ^boolean *
  ([] 1)
  ([a] a)
  ([a b] (* a b))
  ([a b c] (* a b c))
  ([a b c d] (* a b c d))
  ([a b c d e] (* a b c d e))
  ([a b c d e f] (* a b c d e f))
  ([a b c d e f & more]
   (loop [value (* a b c d e f)
          index 0
          count (.-length more)]
     (if (< index count)
       (recur (* value (get more index))
              (inc index)
              count)
       value))))

(defn ^boolean and
  ([] true)
  ([a] a)
  ([a b] (and a b))
  ([a b c] (and a b c))
  ([a b c d] (and a b c d))
  ([a b c d e] (and a b c d e))
  ([a b c d e f] (and a b c d e f))
  ([a b c d e f & more]
   (loop [value (and a b c d e f)
          index 0
          count (.-length more)]
     (if (< index count)
       (recur (and value (get more index))
              (inc index)
              count)
       value))))

(defn ^boolean or
  ([] nil)
  ([a] a)
  ([a b] (or a b))
  ([a b c] (or a b c))
  ([a b c d] (or a b c d))
  ([a b c d e] (or a b c d e))
  ([a b c d e f] (or a b c d e f))
  ([a b c d e f & more]
   (loop [value (or a b c d e f)
          index 0
          count (.-length more)]
     (if (< index count)
       (recur (or value (get more index))
              (inc index)
              count)
       value))))

(defn print
  [& more]
  (apply console.log more))

(def max Math.max)
(def min Math.min)

(ns wisp.runtime
  "Core primitives required for runtime")

(defn identity
  "Returns its argument."
  [x] x)

(defn ^boolean odd? [n]
  (identical? (mod n 2) 1))

(defn ^boolean even? [n]
  (identical? (mod n 2) 0))

(defn ^boolean dictionary?
  "Returns true if dictionary"
  [form]
  (and (object? form)
       ;; Inherits right form Object.prototype
       (object? (.get-prototype-of Object form))
       (nil? (.get-prototype-of Object (.get-prototype-of Object form)))))

(defn dictionary
  "Creates dictionary of given arguments. Odd indexed arguments
  are used for keys and evens for values"
  [& pairs]
  ; TODO: We should convert keywords to names to make sure that keys are not
  ; used in their keyword form.
  (loop [key-values pairs
         result {}]
    (if (.-length key-values)
      (do
        (set! (aget result (aget key-values 0))
              (aget key-values 1))
        (recur (.slice key-values 2) result))
      result)))

(defn keys
  "Returns a sequence of the map's keys"
  [dictionary]
  (.keys Object dictionary))

(defn vals
  "Returns a sequence of the map's values."
  [dictionary]
  (.map (keys dictionary)
        (fn [key] (get dictionary key))))

(defn key-values
  [dictionary]
  (.map (keys dictionary)
        (fn [key] [key (get dictionary key)])))

(defn merge
  "Returns a dictionary that consists of the rest of the maps conj-ed onto
  the first. If a key occurs in more than one map, the mapping from
  the latter (left-to-right) will be the mapping in the result."
  []
  (Object.create
   Object.prototype
   (.reduce
    (.call Array.prototype.slice arguments)
    (fn [descriptor dictionary]
      (if (object? dictionary)
        (.for-each
         (Object.keys dictionary)
         (fn [key]
           (set!
            (get descriptor key)
            (Object.get-own-property-descriptor dictionary key)))))
      descriptor)
    (Object.create Object.prototype))))

(defn ^boolean contains-vector?
  "Returns true if vector contains given element"
  [vector element]
  (>= (.index-of vector element) 0))

(defn map-dictionary
  "Maps dictionary values by applying `f` to each one"
  [source f]
  (.reduce (.keys Object source)
           (fn [target key]
              (set! (get target key) (f (get source key)))
              target) {}))

(def to-string Object.prototype.to-string)

(def
  ^{:tag boolean
    :doc "Returns true if x is a function"}
  fn?
  (if (identical? (typeof #".") "function")
    (fn
      [x]
      (identical? (.call to-string x) "[object Function]"))
    (fn
      [x]
      (identical? (typeof x) "function"))))

(defn ^boolean string?
  "Return true if x is a string"
  [x]
  (or (identical? (typeof x) "string")
      (identical? (.call to-string x) "[object String]")))

(defn ^boolean number?
  "Return true if x is a number"
  [x]
  (or (identical? (typeof x) "number")
      (identical? (.call to-string x) "[object Number]")))

(def
  ^{:tag boolean
    :doc "Returns true if x is a vector"}
  vector?
  (if (fn? Array.isArray)
    Array.isArray
    (fn [x] (identical? (.call to-string x) "[object Array]"))))

(defn ^boolean date?
  "Returns true if x is a date"
  [x]
  (identical? (.call to-string x) "[object Date]"))

(defn ^boolean boolean?
  "Returns true if x is a boolean"
  [x]
  (or (identical? x true)
      (identical? x false)
      (identical? (.call to-string x) "[object Boolean]")))

(defn ^boolean re-pattern?
  "Returns true if x is a regular expression"
  [x]
  (identical? (.call to-string x) "[object RegExp]"))

(defn ^boolean object?
  "Returns true if x is an object"
  [x]
  (and x (identical? (typeof x) "object")))

(defn ^boolean nil?
  "Returns true if x is undefined or null"
  [x]
  (or (identical? x nil)
      (identical? x null)))

(defn ^boolean true?
  "Returns true if x is true"
  [x]
  (identical? x true))

(defn ^boolean false?
  "Returns true if x is true"
  [x]
  (identical? x true))

(defn re-find
  "Returns the first regex match, if any, of s to re, using
  re.exec(s). Returns a vector, containing first the matching
  substring, then any capturing groups if the regular expression contains
  capturing groups."
  [re s]
  (let [matches (.exec re s)]
    (if (not (nil? matches))
      (if (identical? (.-length matches) 1)
        (get matches 0)
        matches))))

(defn re-matches
  [pattern source]
  (let [matches (.exec pattern source)]
    (if (and (not (nil? matches))
             (identical? (get matches 0) source))
      (if (identical? (.-length matches) 1)
        (get matches 0)
        matches))))

(defn re-pattern
  "Returns an instance of RegExp which has compiled the provided string."
  [s]
  (let [match (re-find #"^(?:\(\?([idmsux]*)\))?(.*)" s)]
    (new RegExp (get match 2) (get match 1))))

(defn inc
  [x]
  (+ x 1))

(defn dec
  [x]
  (- x 1))

(defn str
  "With no args, returns the empty string. With one arg x, returns
  x.toString().  (str nil) returns the empty string. With more than
  one arg, returns the concatenation of the str values of the args."
  []
  (.apply String.prototype.concat "" arguments))

(defn char
  "Coerce to char"
  [code]
  (.fromCharCode String code))

(defn int
  "Coerce to int by stripping decimal places."
  [x]
  (if (number? x)
    (if (>= x 0)
      (.floor Math x)
      (.floor Math x))
    (.charCodeAt x 0)))

(defn subs
  "Returns the substring of s beginning at start inclusive, and ending
  at end (defaults to length of string), exclusive."
  {:added "1.0"
   :static true}
   [string start end]
   (.substring string start end))

(defn- ^boolean pattern-equal?
  [x y]
  (and (re-pattern? x)
       (re-pattern? y)
       (identical? (.-source x) (.-source y))
       (identical? (.-global x) (.-global y))
       (identical? (.-multiline x) (.-multiline y))
       (identical? (.-ignoreCase x) (.-ignoreCase y))))

(defn- ^boolean date-equal?
  [x y]
  (and (date? x)
       (date? y)
       (identical? (Number x) (Number y))))

(defn- ^boolean dictionary-equal?
  [x y]
  (and (object? x)
       (object? y)
       (let [x-keys (keys x)
             y-keys (keys y)
             x-count (.-length x-keys)
             y-count (.-length y-keys)]
         (and (identical? x-count y-count)
              (loop [index 0
                     count x-count
                     keys x-keys]
                (if (< index count)
                  (if (equivalent? (get x (get keys index))
                                   (get y (get keys index)))
                    (recur (inc index) count keys)
                    false)
                  true))))))

(defn- ^boolean vector-equal?
  [x y]
  (and (vector? x)
       (vector? y)
       (identical? (.-length x) (.-length y))
       (loop [xs x
              ys y
              index 0
              count (.-length x)]
        (if (< index count)
          (if (equivalent? (get xs index) (get ys index))
              (recur xs ys (inc index) count)
              false)
          true))))

(defn- ^boolean equivalent?
  "Equality. Returns true if x equals y, false if not. Compares
  numbers and collections in a type-independent manner. Clojure's
  immutable data structures define -equiv (and thus =) as a value,
  not an identity, comparison."
  ([x] true)
  ([x y] (or (identical? x y)
             (cond (nil? x) (nil? y)
                   (nil? y) (nil? x)
                   (string? x) false
                   (number? x) false
                   (fn? x) false
                   (boolean? x) false
                   (date? x) (date-equal? x y)
                   (vector? x) (vector-equal? x y [] [])
                   (re-pattern? x) (pattern-equal? x y)
                   :else (dictionary-equal? x y))))
  ([x y & more]
   (loop [previous x
          current y
          index 0
          count (.-length more)]
    (and (equivalent? previous current)
         (if (< index count)
          (recur current
                 (get more index)
                 (inc index)
                 count)
          true)))))

(def = equivalent?)

(defn ^boolean ==
  "Equality. Returns true if x equals y, false if not. Compares
  numbers and collections in a type-independent manner. Clojure's
  immutable data structures define -equiv (and thus =) as a value,
  not an identity, comparison."
  ([x] true)
  ([x y] (identical? x y))
  ([x y & more]
   (loop [previous x
          current y
          index 0
          count (.-length more)]
    (and (== previous current)
         (if (< index count)
          (recur current
                 (get more index)
                 (inc index)
                 count)
          true)))))

(defn ^boolean >
  "Returns non-nil if nums are in monotonically decreasing order,
  otherwise false."
  ([x] true)
  ([x y] (> x y))
  ([x y & more]
   (loop [previous x
          current y
          index 0
          count (.-length more)]
    (and (> previous current)
         (if (< index count)
          (recur current
                 (get more index)
                 (inc index)
                 count)
          true)))))

(defn ^boolean >=
  "Returns non-nil if nums are in monotonically decreasing order,
  otherwise false."
  ([x] true)
  ([x y] (>= x y))
  ([x y & more]
   (loop [previous x
          current y
          index 0
          count (.-length more)]
    (and (>= previous current)
         (if (< index count)
          (recur current
                 (get more index)
                 (inc index)
                 count)
          true)))))

(defn ^boolean <
  "Returns non-nil if nums are in monotonically decreasing order,
  otherwise false."
  ([x] true)
  ([x y] (< x y))
  ([x y & more]
   (loop [previous x
          current y
          index 0
          count (.-length more)]
    (and (< previous current)
         (if (< index count)
          (recur current
                 (get more index)
                 (inc index)
                 count)
          true)))))

(defn ^boolean <=
  "Returns non-nil if nums are in monotonically decreasing order,
  otherwise false."
  ([x] true)
  ([x y] (<= x y))
  ([x y & more]
   (loop [previous x
          current y
          index 0
          count (.-length more)]
    (and (<= previous current)
         (if (< index count)
          (recur current
                 (get more index)
                 (inc index)
                 count)
          true)))))

(defn ^boolean +
  ([] 0)
  ([a] a)
  ([a b] (+ a b))
  ([a b c] (+ a b c))
  ([a b c d] (+ a b c d))
  ([a b c d e] (+ a b c d e))
  ([a b c d e f] (+ a b c d e f))
  ([a b c d e f & more]
   (loop [value (+ a b c d e f)
          index 0
          count (.-length more)]
     (if (< index count)
       (recur (+ value (get more index))
              (inc index)
              count)
       value))))

(defn ^boolean -
  ([] (throw (TypeError "Wrong number of args passed to: -")))
  ([a] (- 0 a))
  ([a b] (- a b))
  ([a b c] (- a b c))
  ([a b c d] (- a b c d))
  ([a b c d e] (- a b c d e))
  ([a b c d e f] (- a b c d e f))
  ([a b c d e f & more]
   (loop [value (- a b c d e f)
          index 0
          count (.-length more)]
     (if (< index count)
       (recur (- value (get more index))
              (inc index)
              count)
       value))))

(defn ^boolean /
  ([] (throw (TypeError "Wrong number of args passed to: /")))
  ([a] (/ 1 a))
  ([a b] (/ a b))
  ([a b c] (/ a b c))
  ([a b c d] (/ a b c d))
  ([a b c d e] (/ a b c d e))
  ([a b c d e f] (/ a b c d e f))
  ([a b c d e f & more]
   (loop [value (/ a b c d e f)
          index 0
          count (.-length more)]
     (if (< index count)
       (recur (/ value (get more index))
              (inc index)
              count)
       value))))

(defn ^boolean *
  ([] 1)
  ([a] a)
  ([a b] (* a b))
  ([a b c] (* a b c))
  ([a b c d] (* a b c d))
  ([a b c d e] (* a b c d e))
  ([a b c d e f] (* a b c d e f))
  ([a b c d e f & more]
   (loop [value (* a b c d e f)
          index 0
          count (.-length more)]
     (if (< index count)
       (recur (* value (get more index))
              (inc index)
              count)
       value))))

(defn ^boolean and
  ([] true)
  ([a] a)
  ([a b] (and a b))
  ([a b c] (and a b c))
  ([a b c d] (and a b c d))
  ([a b c d e] (and a b c d e))
  ([a b c d e f] (and a b c d e f))
  ([a b c d e f & more]
   (loop [value (and a b c d e f)
          index 0
          count (.-length more)]
     (if (< index count)
       (recur (and value (get more index))
              (inc index)
              count)
       value))))

(defn ^boolean or
  ([] nil)
  ([a] a)
  ([a b] (or a b))
  ([a b c] (or a b c))
  ([a b c d] (or a b c d))
  ([a b c d e] (or a b c d e))
  ([a b c d e f] (or a b c d e f))
  ([a b c d e f & more]
   (loop [value (or a b c d e f)
          index 0
          count (.-length more)]
     (if (< index count)
       (recur (or value (get more index))
              (inc index)
              count)
       value))))

(defn print
  [& more]
  (apply console.log more))

(def max Math.max)
(def min Math.min)