Add tests for function overloads.

Add tests for function overloads.

(import [symbol] "../src/ast")
(import [list] "../src/list")
(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(\"\uFEFFdef\", \"\uFEFFx\", 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 unquote-splicing forms")

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

(.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?
         (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)) "a == b")
        "(= a b) => a == b")
(assert (identical? (transpile '(= a b c)) "a == b && b == c")
        "(= a b c) => a == b && 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 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]]")

(.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")

(import [symbol] "../src/ast")
(import [list] "../src/list")
(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(\"\uFEFFdef\", \"\uFEFFx\", 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")

(.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 unquote-splicing forms")

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

(.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?
         (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)) "a == b")
        "(= a b) => a == b")
(assert (identical? (transpile '(= a b c)) "a == b && b == c")
        "(= a b c) => a == b && 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 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]]")

(.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")


Adding missing subs import into string module.

(import [str subs re-matches nil? string?] "./runtime")
(import [vec empty?] "./sequence")

(defn split
  "Splits string on a regular expression.  Optional argument limit is
  the maximum number of splits. Not lazy. Returns vector of the splits."
  {:added "0.1"}
  [string pattern limit]
  (.split string pattern limit))

(defn ^String join
  "Returns a string of all elements in coll, as returned by (seq coll),
   separated by an optional separator."
  {:added "0.1"}
  ([coll]
     (apply str (vec coll)))
  ([separator coll]
     (.join (vec coll) separator)))

(defn ^String upper-case
  "Converts string to all upper-case."
  {:added "1.2"}
  [string]
  (.toUpperCase string))

(defn ^String upper-case
  "Converts string to all upper-case."
  {:added "1.2"}
  [^CharSequence string]
  (.toUpperCase string))

(defn ^String lower-case
  "Converts string to all lower-case."
  {:added "1.2"}
  [^CharSequence string]
  (.toLowerCase string))

(defn ^String capitalize
  "Converts first character of the string to upper-case, all other
  characters to lower-case."
  {:added "1.2"}
  [^CharSequence string]
  (if (< (count string) 2)
      (upper-case string)
      (str (upper-case (subs s 0 1))
           (lower-case (subs s 1)))))

(defn ^String replace
  "Replaces all instance of match with replacement in s.

   match/replacement can be:

   string / string
   char / char
   pattern / (string or function of match).

   See also replace-first."
  {:added "1.2"}
  [^CharSequence string match replacement]
  (.replace string match replacement))

;(def **WHITESPACE** (str "[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000"
;                          "\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008"
;                          "\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]"))
;(def **LEFT-SPACES** (re-pattern (str "^" **WHITESPACE** **WHITESPACE** "*")))
;(def **RIGHT-SPACES** (re-pattern (str **WHITESPACE** **WHITESPACE** "*$")))
;(def **SPACES** (re-pattern (str "^" **WHITESPACE** "*$")))

(def **LEFT-SPACES** #"^\s\s*")
(def **RIGHT-SPACES** #"\s\s*$")
(def **SPACES** #"^\s\s*$")

(def triml
  (if (nil? (.-trimLeft ""))
    (fn [^CharSequence string] (.replace string **LEFT-SPACES** ""))
    (fn ^String triml
      "Removes whitespace from the left side of string."
      {:added "1.2"}
      [^CharSequence string]
      (.trimLeft string))))

(def trimr
  (if (nil? (.-trimRight ""))
    (fn [^CharSequence string] (.replace string **RIGHT-SPACES** ""))
    (fn ^String trimr
      "Removes whitespace from the right side of string."
      {:added "1.2"}
      [^CharSequence string]
      (.trimRight string))))

(def trim
  (if (nil? (.-trim ""))
    (fn [^CharSequence string]
      (.replace (.replace string **LEFT-SPACES**) **RIGHT-SPACES**))
    (fn ^String trim
      "Removes whitespace from both ends of string."
      {:added "1.2"}
      [^CharSequence string]
      (.trim string))))

(defn blank?
  "True if s is nil, empty, or contains only whitespace."
  {:added "1.2"}
  [^CharSequence string]
  (or (nil? string)
      (empty? string)
      (re-matches **SPACES** string)))

(export split join lower-case upper-case capitalize
        replace trim triml trimr blank?)

(import [str re-matches nil? string?] "./runtime")
(import [vec empty?] "./sequence")

(defn split
  "Splits string on a regular expression.  Optional argument limit is
  the maximum number of splits. Not lazy. Returns vector of the splits."
  {:added "0.1"}
  [string pattern limit]
  (.split string pattern limit))

(defn ^String join
  "Returns a string of all elements in coll, as returned by (seq coll),
   separated by an optional separator."
  {:added "0.1"}
  ([coll]
     (apply str (vec coll)))
  ([separator coll]
     (.join (vec coll) separator)))

(defn ^String upper-case
  "Converts string to all upper-case."
  {:added "1.2"}
  [string]
  (.toUpperCase string))

(defn ^String upper-case
  "Converts string to all upper-case."
  {:added "1.2"}
  [^CharSequence string]
  (.toUpperCase string))

(defn ^String lower-case
  "Converts string to all lower-case."
  {:added "1.2"}
  [^CharSequence string]
  (.toLowerCase string))

(defn ^String capitalize
  "Converts first character of the string to upper-case, all other
  characters to lower-case."
  {:added "1.2"}
  [^CharSequence string]
  (if (< (count string) 2)
      (upper-case string)
      (str (upper-case (subs s 0 1))
           (lower-case (subs s 1)))))

(defn ^String replace
  "Replaces all instance of match with replacement in s.

   match/replacement can be:

   string / string
   char / char
   pattern / (string or function of match).

   See also replace-first."
  {:added "1.2"}
  [^CharSequence string match replacement]
  (.replace string match replacement))

;(def **WHITESPACE** (str "[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000"
;                          "\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008"
;                          "\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]"))
;(def **LEFT-SPACES** (re-pattern (str "^" **WHITESPACE** **WHITESPACE** "*")))
;(def **RIGHT-SPACES** (re-pattern (str **WHITESPACE** **WHITESPACE** "*$")))
;(def **SPACES** (re-pattern (str "^" **WHITESPACE** "*$")))

(def **LEFT-SPACES** #"^\s\s*")
(def **RIGHT-SPACES** #"\s\s*$")
(def **SPACES** #"^\s\s*$")

(def triml
  (if (nil? (.-trimLeft ""))
    (fn [^CharSequence string] (.replace string **LEFT-SPACES** ""))
    (fn ^String triml
      "Removes whitespace from the left side of string."
      {:added "1.2"}
      [^CharSequence string]
      (.trimLeft string))))

(def trimr
  (if (nil? (.-trimRight ""))
    (fn [^CharSequence string] (.replace string **RIGHT-SPACES** ""))
    (fn ^String trimr
      "Removes whitespace from the right side of string."
      {:added "1.2"}
      [^CharSequence string]
      (.trimRight string))))

(def trim
  (if (nil? (.-trim ""))
    (fn [^CharSequence string]
      (.replace (.replace string **LEFT-SPACES**) **RIGHT-SPACES**))
    (fn ^String trim
      "Removes whitespace from both ends of string."
      {:added "1.2"}
      [^CharSequence string]
      (.trim string))))

(defn blank?
  "True if s is nil, empty, or contains only whitespace."
  {:added "1.2"}
  [^CharSequence string]
  (or (nil? string)
      (empty? string)
      (re-matches **SPACES** string)))

(export split join lower-case upper-case capitalize
        replace trim triml trimr blank?)


Write tests for `set!` analyzer.

Write tests for `set!` analyzer.

(ns wisp.test.analyzer
  (:require [wisp.src.analyzer :refer [analyze]]
            [wisp.src.ast :refer [meta symbol]]
            [wisp.src.sequence :refer [map list]]
            [wisp.src.runtime :refer [=]]))

(assert (= (analyze {} ':foo)
           {:op :constant
            :type :keyword
            :env {}
            :form ':foo}))

(assert (= (analyze {} "bar")
           {:op :constant
            :type :string
            :env {}
            :form "bar"}))

(assert (= (analyze {} true)
           {:op :constant
            :type :boolean
            :env {}
            :form true}))

(assert (= (analyze {} false)
           {:op :constant
            :type :boolean
            :env {}
            :form false}))

(assert (= (analyze {} nil)
           {:op :constant
            :type :nil
            :env {}
            :form nil}))

(assert (= (analyze {} 7)
           {:op :constant
            :type :number
            :env {}
            :form 7}))

(assert (= (analyze {} #"foo")
           {:op :constant
            :type :re-pattern
            :env {}
            :form #"foo"}))

(assert (= (analyze {} 'foo)
           {:op :var
            :env {}
            :form 'foo
            :meta nil
            :info nil}))

(assert (= (analyze {} '[])
           {:op :vector
            :env {}
            :form '[]
            :meta nil
            :items []}))

(assert (= (analyze {} '[:foo bar "baz"])
           {:op :vector
            :env {}
            :form '[:foo bar "baz"]
            :meta nil
            :items [{:op :constant
                     :type :keyword
                     :env {}
                     :form ':foo}
                    {:op :var
                     :env {}
                     :form 'bar
                     :meta nil
                     :info nil}
                    {:op :constant
                     :type :string
                     :env {}
                     :form "baz"
                     }]}))

(assert (= (analyze {} {})
           {:op :dictionary
            :env {}
            :form {}
            :hash? true
            :keys []
            :values []}))

(assert (= {:op :dictionary
            :keys [{:op :constant
                    :type :string
                    :env {}
                    :form "foo"}]
            :values [{:op :var
                      :env {}
                      :form 'bar
                      :info nil
                      :meta nil}]
            :hash? true
            :env {}
            :form {:foo 'bar}}
           (analyze {} {:foo 'bar})))

(assert (= (analyze {} ())
           {:op :constant
            :type :list
            :env {}
            :form ()}))

(assert (= (analyze {} '(foo))
           {:op :invoke
            :callee {:op :var
                     :env {}
                     :form 'foo
                     :meta nil
                     :info nil}
            :params []
            :tag nil
            :form '(foo)
            :env {}}))

(assert (= (analyze {} '(foo bar))
           {:op :invoke
            :callee {:op :var
                     :env {}
                     :form 'foo
                     :meta nil
                     :info nil}
            :params [{:op :var
                      :env {}
                      :form 'bar
                      :meta nil
                      :info nil
                      }]
            :tag nil
            :form '(foo bar)
            :env {}}))

(assert (= (analyze {} '(aget foo 'bar))
           {:op :member-expression
            :computed false
            :env {}
            :form '(aget foo 'bar)
            :target {:op :var
                     :env {}
                     :form 'foo
                     :meta nil
                     :info nil}
            :property {:op :var
                       :env {}
                       :form 'bar
                       :meta nil
                       :info nil}}))

(assert (= (analyze {} '(aget foo bar))
           {:op :member-expression
            :computed true
            :env {}
            :form '(aget foo bar)
            :target {:op :var
                     :env {}
                     :form 'foo
                     :meta nil
                     :info nil}
            :property {:op :var
                       :env {}
                       :form 'bar
                       :meta nil
                       :info nil}}))

(assert (= (analyze {} '(aget foo "bar"))
           {:op :member-expression
            :env {}
            :form '(aget foo "bar")
            :computed true
            :target {:op :var
                     :env {}
                     :form 'foo
                     :meta nil
                     :info nil}
            :property {:op :constant
                       :env {}
                       :type :string
                       :form "bar"}}))

(assert (= (analyze {} '(aget foo :bar))
           {:op :member-expression
            :computed true
            :env {}
            :form '(aget foo :bar)
            :target {:op :var
                     :env {}
                     :form 'foo
                     :meta nil
                     :info nil}
            :property {:op :constant
                       :env {}
                       :type :keyword
                       :form ':bar}}))

(assert (= (analyze {} '(aget foo (beep bar)))
           {:op :member-expression
            :env {}
            :form '(aget foo (beep bar))
            :computed true
            :target {:op :var
                     :env {}
                     :form 'foo
                     :meta nil
                     :info nil}
            :property {:op :invoke
                       :env {}
                       :form '(beep bar)
                       :tag nil
                       :callee {:op :var
                                :form 'beep
                                :env {}
                                :meta nil
                                :info nil}
                       :params [{:op :var
                                 :form 'bar
                                 :env {}
                                 :meta nil
                                 :info nil}]}}))

(assert (= (analyze {} '(if x y))
           {:op :if
            :env {}
            :form '(if x y)
            :test {:op :var
                   :env {}
                   :form 'x
                   :meta nil
                   :info nil}
            :consequent {:op :var
                         :env {}
                         :form 'y
                         :meta nil
                         :info nil}
            :alternate {:op :constant
                        :type :nil
                        :env {}
                        :form nil}}))

(assert (= (analyze {} '(if (even? n) (inc n) (+ n 3)))
           {:op :if
            :env {}
            :form '(if (even? n) (inc n) (+ n 3))
            :test {:op :invoke
                   :env {}
                   :form '(even? n)
                   :tag nil
                   :callee {:op :var
                            :env {}
                            :form 'even?
                            :meta nil
                            :info nil}
                   :params [{:op :var
                             :env {}
                             :form 'n
                             :meta nil
                             :info nil}]}
            :consequent {:op :invoke
                         :env {}
                         :form '(inc n)
                         :tag nil
                         :callee {:op :var
                                  :env {}
                                  :form 'inc
                                  :meta nil
                                  :info nil}
                         :params [{:op :var
                                   :env {}
                                   :form 'n
                                   :meta nil
                                   :info nil}]}
            :alternate {:op :invoke
                        :env {}
                        :form '(+ n 3)
                        :tag nil
                        :callee {:op :var
                                 :env {}
                                 :form '+
                                 :meta nil
                                 :info nil}
                        :params [{:op :var
                                  :env {}
                                  :form 'n
                                  :meta nil
                                  :info nil}
                                 {:op :constant
                                  :type :number
                                  :env {}
                                  :form 3}]}}))

(assert (= (analyze {} '(throw error))
           {:op :throw
            :env {}
            :form '(throw error)
            :throw {:op :var
                    :env {}
                    :form 'error
                    :meta nil
                    :info nil}}))

(assert (= (analyze {} '(throw (Error "boom!")))
           {:op :throw
            :env {}
            :form '(throw (Error "boom!"))
            :throw {:op :invoke
                    :tag nil
                    :env {}
                    :form '(Error "boom!")
                    :callee {:op :var
                             :env {}
                             :form 'Error
                             :meta nil
                             :info nil}
                    :params [{:op :constant
                              :type :string
                              :env {}
                              :form "boom!"}]}}))

(assert (= (analyze {} '(new Error "Boom!"))
           {:op :new
            :env {}
            :form '(new Error "Boom!")
            :constructor {:op :var
                          :env {}
                          :form 'Error
                          :meta nil
                          :info nil}
            :params [{:op :constant
                      :type :string
                      :env {}
                      :form "Boom!"}]}))

(assert (= (analyze {} '(try* (read-string unicode-error)))
           {:op :try*
            :env {}
            :form '(try* (read-string unicode-error))
            :body {:env {}
                   :statements []
                   :result {:op :invoke
                            :env {}
                            :form '(read-string unicode-error)
                            :tag nil
                            :callee {:op :var
                                     :env {}
                                     :form 'read-string
                                     :meta nil
                                     :info nil}
                            :params [{:op :var
                                      :env {}
                                      :form 'unicode-error
                                      :meta nil
                                      :info nil}]}}
            :handler nil
            :finalizer nil}))

(assert (= (analyze {} '(try*
                         (read-string unicode-error)
                         (catch error :throw)))

           {:op :try*
            :env {}
            :form '(try*
                    (read-string unicode-error)
                    (catch error :throw))
            :body {:env {}
                   :statements []
                   :result {:op :invoke
                            :env {}
                            :form '(read-string unicode-error)
                            :tag nil
                            :callee {:op :var
                                     :env {}
                                     :form 'read-string
                                     :meta nil
                                     :info nil}
                            :params [{:op :var
                                      :env {}
                                      :form 'unicode-error
                                      :meta nil
                                      :info nil}]}}
            :handler {:env {}
                      :name {:op :var
                             :env {}
                             :form 'error
                             :meta nil
                             :info nil}
                      :statements []
                      :result {:op :constant
                               :type :keyword
                               :env {}
                               :form ':throw}}
            :finalizer nil}))

(assert (= (analyze {} '(try*
                         (read-string unicode-error)
                         (finally :end)))

           {:op :try*
            :env {}
            :form '(try*
                    (read-string unicode-error)
                    (finally :end))
            :body {:env {}
                   :statements []
                   :result {:op :invoke
                            :env {}
                            :form '(read-string unicode-error)
                            :tag nil
                            :callee {:op :var
                                     :env {}
                                     :form 'read-string
                                     :meta nil
                                     :info nil}
                            :params [{:op :var
                                      :env {}
                                      :form 'unicode-error
                                      :meta nil
                                      :info nil}]}}
            :handler nil
            :finalizer {:env {}
                        :statements []
                        :result {:op :constant
                                 :type :keyword
                                 :env {}
                                 :form ':end}}}))

(assert (= (analyze {} '(try* (read-string unicode-error)
                              (catch error
                                (print error)
                                :error)
                              (finally
                               (print "done")
                               :end)))
           {:op :try*
            :env {}
            :form '(try* (read-string unicode-error)
                         (catch error
                           (print error)
                           :error)
                         (finally
                          (print "done")
                          :end))
            :body {:env {}
                   :statements []
                   :result {:op :invoke
                            :env {}
                            :form '(read-string unicode-error)
                            :tag nil
                            :callee {:op :var
                                     :env {}
                                     :form 'read-string
                                     :meta nil
                                     :info nil}
                            :params [{:op :var
                                      :env {}
                                      :form 'unicode-error
                                      :meta nil
                                      :info nil}]}}
            :handler {:env {}
                      :name {:op :var
                             :env {}
                             :form 'error
                             :meta nil
                             :info nil}
                      :statements [{:op :invoke
                                    :env {}
                                    :form '((aget console 'log) error)
                                    :tag nil
                                    :callee {:op :member-expression
                                             :computed false
                                             :env {}
                                             :form '(aget console 'log)
                                             :target {:op :var
                                                      :env {}
                                                      :form 'console
                                                      :meta nil
                                                      :info nil}
                                             :property {:op :var
                                                        :env {}
                                                        :form 'log
                                                        :meta nil
                                                        :info nil}}
                                    :params [{:op :var
                                              :env {}
                                              :form 'error
                                              :meta nil
                                              :info nil}]}]
                      :result {:op :constant
                               :type :keyword
                               :form ':error
                               :env {}}}
            :finalizer {:env {}
                        :statements [{:op :invoke
                                      :env {}
                                      :form '((aget console 'log) "done")
                                      :tag nil
                                      :callee {:op :member-expression
                                               :computed false
                                               :env {}
                                               :form '(aget console 'log)
                                               :target {:op :var
                                                        :env {}

                                                        :form 'console
                                                        :meta nil
                                                        :info nil}
                                               :property {:op :var
                                                          :env {}
                                                          :form 'log
                                                          :meta nil
                                                          :info nil}}
                                      :params [{:op :constant
                                                :env {}
                                                :form "done"
                                                :type :string}]}]
                        :result {:op :constant
                                 :type :keyword
                                 :form ':end
                                 :env {}}}}))

(assert (= (analyze {} '(set! foo bar))
           {:op :set!
            :target {:op :var
                     :env {}
                     :form 'foo
                     :meta nil
                     :info nil}
            :value {:op :var
                    :env {}
                    :form 'bar
                    :meta nil
                    :info nil}}))

(assert (= (analyze {} '(set! *registry* {}))
           {:op :set!
            :target {:op :var
                     :env {}
                     :form '*registry*
                     :meta nil
                     :info nil}
            :value {:op :dictionary
                    :env {}
                    :form {}
                    :keys []
                    :values []
                    :hash? true}}))

(assert (= (analyze {} '(set! (.-log console) print))
           {:op :set!
            :target {:op :member-expression
                     :env {}
                     :form '(aget console 'log)
                     :computed false
                     :target {:op :var
                              :env {}
                              :form 'console
                              :meta nil
                              :info nil}
                     :property {:op :var
                                :env {}
                                :form 'log
                                :meta nil
                                :info nil}}
            :value {:op :var
                    :env {}
                    :form 'print
                    :meta nil
                    :info nil}}))


Start generating js with source maps that contain original source.

(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 source
                                                      :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 "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)))))