Write tests for new writer backend.

Write tests for new writer backend.

(ns wisp.test.escodegen
  (:require [wisp.src.sequence :refer [concat cons vec take first rest
                                       second third list list? count drop
                                       lazy-seq? seq nth map]]
            [wisp.src.runtime :refer [subs = dec identity keys nil? vector?
                                      string? dec re-find]]
            [wisp.src.analyzer :refer [empty-env analyze analyze*]]
            [wisp.src.reader :refer [read* read-from-string]
                             :rename {read-from-string read-string}]
            [wisp.src.ast :refer [meta name pr-str symbol]]
            [wisp.src.backend.escodegen.writer :refer [write compile write*]]))

(set! **print-compiled** false)
(set! **print-as-js** true)

(defn transpile
  [code options]
  (let [forms (read* code)
        analyzed (map #(analyze {} %) forms)
        compiled (apply compile options analyzed)]
    compiled))

(defmacro is
  "Generic assertion macro. 'form' is any predicate test.
  'msg' is an optional message to attach to the assertion.
  Example: (is (= 4 (+ 2 2)) \"Two plus two should be 4\")

  Special forms:

  (is (thrown? c body)) checks that an instance of c is thrown from
  body, fails if not; then returns the thing thrown.

  (is (thrown-with-msg? c re body)) checks that an instance of c is
  thrown AND that the message on the exception matches (with
  re-find) the regular expression re."
  ([form] `(is ~form ""))
  ([form msg]
   (let [op (first form)
         actual (second form)
         expected (third form)]
     `(if ~form
       true
       (do
         (print (str "Fail: " ~msg "\n"
                     "expected: "
                     (pr-str '~form) "\n"
                     "  actual: "
                     (pr-str (list '~op
                                   (try ~actual (catch error (list 'throw (list 'Error (.-message error)))))
                                   (try '~expected (catch error error))))))
         false)))))

(defmacro thrown?
  [expression pattern]
  `(try
     (do
       ~expression
       false)
    (catch error
      (if (re-find ~pattern (str error))
        true
        false))))

;; literals

(is (= (transpile "nil") "void 0;"))
(is (= (transpile "true") "true;"))
(is (= (transpile "false") "false;"))
(is (= (transpile "1") "1;"))
(is (= (transpile "-1") "-1;"))
(is (= (transpile "\"hello world\"") "'hello world';"))
(is (= (transpile "()") "list();"))
(is (= (transpile "[]") "[];"))
(is (= (transpile "{}") "({});"))

;; identifiers

(is (= (transpile "foo") "foo;"))
(is (= (transpile "foo-bar") "fooBar;"))
(is (= (transpile "ba-ra-baz") "baRaBaz;"))
(is (= (transpile "-boom") "boom;"))
(is (= (transpile "foo?") "isFoo;"))
(is (= (transpile "foo-bar?") "isFooBar;"))
(is (= (transpile "**private**") "__private__;"))
(is (= (transpile "dot.chain") "dot.chain;"))
(is (= (transpile "make!") "make;"))
(is (= (transpile "red=blue") "redEqualBlue;"))
(is (= (transpile "red+blue") "redPlusBlue;"))
(is (= (transpile "red+blue") "redPlusBlue;"))
(is (= (transpile "->string") "toString;"))
(is (= (transpile "%a") "$a;"))

;; re-pattern

(is (= (transpile "#\"foo\"") "/foo/;"))
(is (= (transpile "#\"(?m)foo\"") "/foo/m;"))
(is (= (transpile "#\"(?i)foo\"") "/foo/i;"))
(is (= (transpile "#\"^$\"") "/^$/;"))
(is (= (transpile "#\"/.\"") "/\\/./;"))

;; invoke forms

(is (= (transpile "(foo)")"foo();")
    "function calls compile")

(is (= (transpile "(foo bar)") "foo(bar);")
    "function calls with single arg compile")

(is (= (transpile "(foo bar baz)") "foo(bar, baz);")
    "function calls with multi arg compile")

(is (= (transpile "(foo ((bar baz) beep))")
       "foo(bar(baz)(beep));")
    "nested function calls compile")

(is (= (transpile "(beep name 4 \"hello\")")
       "beep(name, 4, 'hello');"))

(is (= (transpile "(swap! foo bar)")
       "swap(foo, bar);"))

(is (= (transpile "(create-server options)")
       "createServer(options);"))

(is (= (transpile "(.create-server http options)")
       "http.createServer(options);"))

;; vectors

(is (= (transpile "[]")
"[];"))

(is (= (transpile "[a b]")
"[
    a,
    b
];"))

(is (= (transpile "[a (b c)]")
"[
    a,
    b(c)
];"))

;; public defs

(is (= (transpile "(def x)")
       "var x = exports.x = void 0;")
    "def without initializer")

(is (= (transpile "(def y 1)")
       "var y = exports.y = 1;")
    "def with initializer")

(is (= (transpile "'(def x 1)")
       "list(symbol(void 0, 'def'), symbol(void 0, 'x'), 1);")
    "quoted def")

(is (= (transpile "(def a \"docs\" 1)")
       "var a = exports.a = 1;")
    "def is allowed an optional doc-string")

(is (= (transpile "(def ^{:private true :dynamic true} x 1)")
       "var x = 1;")
    "def with extended metadata")

(is (= (transpile "(def ^{:private true} a \"doc\" b)")
       "var a = b;")
    "def with metadata and docs")

(is (= (transpile "(def under_dog)")
       "var under_dog = exports.under_dog = void 0;"))

;; private defs

(is (= (transpile "(def ^:private x)")
       "var x = void 0;"))

(is (= (transpile "(def ^:private y 1)")
       "var y = 1;"))

;; throw

(is (= (transpile "(throw error)")
"(function () {
    throw error;
})();") "throw reference")

(is (= (transpile "(throw (Error message))")
"(function () {
    throw Error(message);
})();") "throw expression")

(is (= (transpile "(throw (Error. message))")
"(function () {
    throw new Error(message);
})();") "throw instance")

(is (= (transpile "(throw \"boom\")")
"(function () {
    throw 'boom';
})();") "throw string literal")

;; new

(is (= (transpile "(new Type)")
       "new Type();"))

(is (= (transpile "(Type.)")
       "new Type();"))

(is (= (transpile "(new Point x y)")
       "new Point(x, y);"))

(is (= (transpile "(Point. x y)")
       "new Point(x, y);"))

;; macro syntax

(is (thrown? (transpile "(.-field)")
             #"Malformed member expression, expecting \(.-member target\)"))

(is (thrown? (transpile "(.-field a b)")
             #"Malformed member expression, expecting \(.-member target\)"))

(is (= (transpile "(.-field object)")
       "object.field;"))

(is (= (transpile "(.-field (foo))")
       "foo().field;"))

(is (= (transpile "(.-field (foo bar))")
       "foo(bar).field;"))

(is (thrown? (transpile "(.substring)")
             #"Malformed method expression, expecting \(.method object ...\)"))

(is (= (transpile "(.substr text)")
       "text.substr();"))
(is (= (transpile "(.substr text 0)")
       "text.substr(0);"))
(is (= (transpile "(.substr text 0 5)")
       "text.substr(0, 5);"))
(is (= (transpile "(.substr (read file) 0 5)")
       "read(file).substr(0, 5);"))

(is (= (transpile "(.log console message)")
       "console.log(message);"))

(is (= (transpile "(.-location window)")
       "window.location;"))

(is (= (transpile "(.-foo? bar)")
       "bar.isFoo;"))

(is (= (transpile "(.-location (.open window url))")
       "window.open(url).location;"))

(is (= (transpile "(.slice (.splice arr 0))")
       "arr.splice(0).slice();"))

(is (= (transpile "(.a (.b \"/\"))")
       "'/'.b().a();"))

(is (= (transpile "(:foo bar)")
       "(bar || 0)['foo'];"))

;; syntax quotes

(is (= (transpile "`(1 ~@'(2 3))")
       "list.apply(void 0, [1].concat(vec(list(2, 3))));"))

(is (= (transpile "`()")
       "list();"))

(is (= (transpile "`[1 ~@[2 3]]")
"[1].concat([
    2,
    3
]);"))

(is (= (transpile "`[]")
       "[];"))

(is (= (transpile "'()")
       "list();"))

(is (= (transpile "()")
       "list();"))

(is (= (transpile "'(1)")
       "list(1);"))

(is (= (transpile "'[]")
       "[];"))

;; set!

(is (= (transpile "(set! x 1)")
       "x = 1;"))

(is (= (transpile "(set! x (foo bar 2))")
       "x = foo(bar, 2);"))

(is (= (transpile "(set! x (.m o))")
       "x = o.m();"))

(is (= (transpile "(set! (.-field object) x)")
       "object.field = x;"))

;; aget

(is (thrown? (transpile "(aget foo)")
             #"Malformed aget expression expected \(aget object member\)"))

(is (= (transpile "(aget foo bar)")
       "foo[bar];"))

(is (= (transpile "(aget array 1)")
       "array[1];"))

(is (= (transpile "(aget json \"data\")")
       "json['data'];"))

(is (= (transpile "(aget foo (beep baz))")
       "foo[beep(baz)];"))

(is (= (transpile "(aget (beep foo) 'bar)")
       "beep(foo).bar;"))

(is (= (transpile "(aget (beep foo) (boop bar))")
       "beep(foo)[boop(bar)];"))

;; functions

(is (= (transpile "(fn [] (+ x y))")
"(function () {
    return x + y;
});"))

(is (= (transpile "(fn [x] (def y 7) (+ x y))")
"(function (x) {
    var y = 7;
    return x + y;
});"))

(is (= (transpile "(fn [])")
"(function () {
    return void 0;
});"))

(is (= (transpile "(fn ([]))")
"(function () {
    return void 0;
});"))

(is (= (transpile "(fn ([]))")
"(function () {
    return void 0;
});"))

(is (thrown? (transpile "(fn a b)")
             #"parameter declaration \(b\) must be a vector"))

(is (thrown? (transpile "(fn a ())")
             #"parameter declaration \(\(\)\) must be a vector"))

(is (thrown? (transpile "(fn a (b))")
             #"parameter declaration \(\(b\)\) must be a vector"))

(is (thrown? (transpile "(fn)")
             #"parameter declaration \(nil\) must be a vector"))

(is (thrown? (transpile "(fn {} a)")
             #"parameter declaration \({}\) must be a vector"))

(is (thrown? (transpile "(fn ([]) a)")
             #"Malformed fn overload form"))

(is (thrown? (transpile "(fn ([]) (a))")
             #"Malformed fn overload form"))

(is (= (transpile "(fn [x] x)")
       "(function (x) {\n    return x;\n});")
    "function compiles")

(is (= (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")

(is (= (transpile "(fn identity [x] x)")
                  "(function identity(x) {\n    return x;\n});")
    "named function compiles")

(is (thrown? (transpile "(fn \"doc\" a [x] x)")
             #"parameter declaration (.*) must be a vector"))

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

(is (= (transpile "(fn ^:static x [y] y)")
       "(function x(y) {\n    return y;\n});")
    "fn name metadata")

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

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

(is (= (transpile "(fn
                     ([] 0)
                     ([x] x))")
"(function () {
    switch (arguments.length) {
    case 0:
        return 0;
    case 1:
        var x = arguments[0];
        return x;
    default:
        throw Error('Wrong number of arguments passed');
    }
});") "function with overloads")

(is (= (transpile "(fn sum
                    ([] 0)
                    ([x] x)
                    ([x y] (+ x y))
                    ([x y & rest] (reduce sum
                                          (sum x y)
                                          rest)))")
"(function sum() {
    switch (arguments.length) {
    case 0:
        return 0;
    case 1:
        var x = arguments[0];
        return x;
    case 2:
        var x = arguments[0];
        var y = arguments[1];
        return x + y;
    default:
        var x = arguments[0];
        var y = arguments[1];
        var rest = Array.prototype.slice.call(arguments, 2);
        return reduce(sum, sum(x, y), rest);
    }
});") "variadic with overloads")

(is (= (transpile "(fn vector->list [v] (make list v))")
"(function vectorToList(v) {
    return make(list, v);
});"))

;; Conditionals

(is (thrown? (transpile "(if x)")
             #"Malformed if expression, too few operands"))

(is (= (transpile "(if x y)")
       "x ? y : void 0;"))

(is (= (transpile "(if foo (bar))")
       "foo ? bar() : void 0;")
    "if compiles")

(is (= (transpile "(if foo (bar) baz)")
       "foo ? bar() : baz;")
    "if-else compiles")

(is (= (transpile "(if monday? (.log console \"monday\"))")
       "isMonday ? console.log('monday') : void 0;")
    "macros inside blocks expand properly")

(is (= (transpile "(if a (make a))")
       "a ? make(a) : void 0;"))

(is (= (transpile "(if (if foo? bar) (make a))")
       "(isFoo ? bar : void 0) ? make(a) : void 0;"))

;; Do

(is (= (transpile "(do (foo bar) bar)")
"(function () {
    foo(bar);
    return bar;
})();") "do compiles")

(is (= (transpile "(do)")
"(function () {
    return void 0;
})();") "empty do compiles")

(is (= (transpile "(do (buy milk) (sell honey))")
"(function () {
    buy(milk);
    return sell(honey);
})();"))

(is (= (transpile "(do
                    (def a 1)
                    (def a 2)
                    (plus a b))")
"(function () {
    var a = exports.a = 1;
    var a = exports.a = 2;
    return plus(a, b);
})();"))

(is (= (transpile "(fn [a]
                    (do
                      (def b 2)
                      (plus a b)))")
"(function (a) {
    return (function () {
        var b = 2;
        return plus(a, b);
    })();
});") "only top level defs are public")

;; Let

(is (= (transpile "(let [])")
"(function () {
    return void 0;
})();"))

(is (= (transpile "(let [] x)")
"(function () {
    return x;
})();"))

(is (= (transpile "(let [x 1 y 2] (+ x y))")
"(function (x, y) {
    return x + y;
})(1, 2);"))

(is (= (transpile "(let [x y
                         y x]
                     [x y])")
"(function (x, y) {
    return [
        x,
        y
    ];
})(y, x);") "same named bindings can be used")

(is (= (transpile "(let []
                     (+ x y))")
"(function () {
    return x + y;
})();"))

(is (= (transpile "(let [x 1
                         y y]
                     (+ x y))")
"(function (x, y) {
    return x + y;
})(1, y);"))

;; throw

(is (= (transpile "(throw)")
"(function () {
    throw void 0;
})();"))

(is (= (transpile "(throw error)")
"(function () {
    throw error;
})();"))

(is (= (transpile "(throw (Error message))")
"(function () {
    throw Error(message);
})();"))

(is (= (transpile "(throw \"boom\")")
"(function () {
    throw 'boom';
})();"))

(is (= (transpile "(throw (Error. message))")
"(function () {
    throw new Error(message);
})();"))

;; TODO: Consider submitting a bug to clojure
;; to raise compile time error on such forms
(is (= (transpile "(throw a b)")
"(function () {
    throw a;
})();"))

;; try

(is (= (transpile "(try
                     (/ 1 0)
                     (catch e
                       (console.error e)))")
"(function () {
    try {
        return 1 / 0;
    } catch (e) {
        return console.error(e);
    }
})();"))

(is (= (transpile "(try
                     (/ 1 0)
                     (catch e (console.error e))
                     (finally (print \"final exception.\")))")
"(function () {
    try {
        return 1 / 0;
    } catch (e) {
        return console.error(e);
    } finally {
        return console.log('final exception.');
    }
})();"))

(is (= (transpile "(try
                        (open file)
                        (read file)
                      (finally (close file)))")
"(function () {
    try {
        open(file);
        return read(file);
    } finally {
        return close(file);
    }
})();"))

(is (= (transpile "(try)")
"(function () {
    try {
        return void 0;
    } finally {
    }
})();"))

(is (= (transpile "(try me)")
"(function () {
    try {
        return me;
    } finally {
    }
})();"))

(is (= (transpile "(try (boom) (catch error))")
"(function () {
    try {
        return boom();
    } catch (error) {
        return void 0;
    }
})();"))

(is (= (transpile "(try (m 1 0) (catch e e))")
"(function () {
    try {
        return m(1, 0);
    } catch (e) {
        return e;
    }
})();"))

(is (= (transpile "(try (m 1 0) (finally 0))")
"(function () {
    try {
        return m(1, 0);
    } finally {
        return 0;
    }
})();"))

(is (= (transpile "(try (m 1 0) (catch e e) (finally 0))")
"(function () {
    try {
        return m(1, 0);
    } catch (e) {
        return e;
    } finally {
        return 0;
    }
})();"))

;; loop

(is (= (transpile "(loop [x 10]
                        (if (< x 7)
                          (print x)
                          (recur (- x 2))))")
"(function loop(x) {
    var recur = loop;
    do {
        recur = x < 7 ? console.log(x) : (loop[0] = x - 2, loop);
    } while (x = loop[0], recur === loop);
    return recur;
})(10);"))

(is (= (transpile "(loop [forms forms
                             result []]
                        (if (empty? items)
                          result
                          (recur (rest forms)
                                 (conj result (process (first forms))))))")
"(function loop(forms, result) {
    var recur = loop;
    do {
        recur = isEmpty(items) ? result : (loop[0] = rest(forms), loop[1] = conj(result, process(first(forms))), loop);
    } while (forms = loop[0], result = loop[1], recur === loop);
    return recur;
})(forms, []);"))

;; ns

(is (= (transpile "(ns foo.bar
                      \"hello world\"
                      (:require lib.a
                                [lib.b]
                                [lib.c :as c]
                                [lib.d :refer [foo bar]]
                                [lib.e :refer [beep baz] :as e]
                                [lib.f :refer [faz] :rename {faz saz}]
                                [lib.g :refer [beer] :rename {beer coffee} :as booze]))")

"{
    var _ns_ = {
            id: 'foo.bar',
            doc: 'hello world'
        };
    var lib_a = require('lib/a');
    var lib_b = require('lib/b');
    var lib_c = require('lib/c');
    var c = lib_c;
    var lib_d = require('lib/d');
    var foo = lib_d.foo;
    var bar = lib_d.bar;
    var lib_e = require('lib/e');
    var e = lib_e;
    var beep = lib_e.beep;
    var baz = lib_e.baz;
    var lib_f = require('lib/f');
    var saz = lib_f.faz;
    var lib_g = require('lib/g');
    var booze = lib_g;
    var coffee = lib_g.beer;
}"))

(is (= (transpile "(ns wisp.example.main
                         (:refer-clojure :exclude [macroexpand-1])
                         (:require [clojure.java.io]
                                   [wisp.example.dependency :as dep]
                                   [wisp.foo :as wisp.bar]
                                   [clojure.string :as string :refer [join split]]
                                   [wisp.sequence :refer [first rest] :rename {first car rest cdr}]
                                   [wisp.ast :as ast :refer [symbol] :rename {symbol ast-symbol}])
                         (:use-macros [cljs.analyzer-macros :only [disallowing-recur]]))")
"{
    var _ns_ = {
            id: 'wisp.example.main',
            doc: void 0
        };
    var clojure_java_io = require('clojure/java/io');
    var wisp_example_dependency = require('./dependency');
    var dep = wisp_example_dependency;
    var wisp_foo = require('./../foo');
    var wisp_bar = wisp_foo;
    var clojure_string = require('clojure/string');
    var string = clojure_string;
    var join = clojure_string.join;
    var split = clojure_string.split;
    var wisp_sequence = require('./../sequence');
    var car = wisp_sequence.first;
    var cdr = wisp_sequence.rest;
    var wisp_ast = require('./../ast');
    var ast = wisp_ast;
    var astSymbol = wisp_ast.symbol;
}"))

(is (= (transpile "(ns foo.bar)")
"{
    var _ns_ = {
            id: 'foo.bar',
            doc: void 0
        };
}"))

(is (= (transpile "(ns foo.bar \"my great lib\")")
"{
    var _ns_ = {
            id: 'foo.bar',
            doc: 'my great lib'
        };
}"))

;; Logical operators

(is (= (transpile "(or)")
       "void 0;"))

(is (= (transpile "(or 1)")
       "1;"))

(is (= (transpile "(or 1 2)")
       "1 || 2;"))

(is (= (transpile "(or 1 2 3)")
       "1 || 2 || 3;"))

(is (= (transpile "(and)")
       "true;"))

(is (= (transpile "(and 1)")
       "1;"))

(is (= (transpile "(and 1 2)")
       "1 && 2;"))

(is (= (transpile "(and 1 2 a b)")
       "1 && 2 && a && b;"))

(is (thrown? (transpile "(not)")
             #"Wrong number of arguments \(0\) passed to: not"))

(is (= (transpile "(not x)")
       "!x;"))

(is (thrown? (transpile "(not x y)")
             #"Wrong number of arguments \(2\) passed to: not"))\

(is (= (transpile "(not (not x))")
       "!!x;"))

;; Bitwise Operators

(is (thrown? (transpile "(bit-and)")
             #"Wrong number of arguments \(0\) passed to: bit-and"))

;; =>

(is (thrown? (transpile "(bit-and 1)")
             #"Wrong number of arguments \(1\) passed to: bit-and"))

;; =>

(is (= (transpile "(bit-and 1 0)")
       "1 & 0;"))

;; =>

(is (= (transpile "(bit-and 1 1 0)")
       "1 & 1 & 0;"))
;; =>

(is (thrown? (transpile "(bit-or)")
             #"Wrong number of arguments \(0\) passed to: bit-or"))
;; =>

(is (thrown? (transpile "(bit-or a)")
             #"Wrong number of arguments \(1\) passed to: bit-or"))
;; =>

(is (= (transpile "(bit-or a b)")
       "a | b;"))

;; =>

(is (= (transpile "(bit-or a b c d)")
       "a | b | c | d;"))

;; =>

(is (thrown? (transpile "(bit-xor)")
             #"Wrong number of arguments \(0\) passed to: bit-xor"))

;; =>

(is (thrown? (transpile "(bit-xor a)")
             #"Wrong number of arguments \(1\) passed to: bit-xor"))

;; =>

(is (= (transpile "(bit-xor a b)")
       "a ^ b;"))

;; =>

(is (= (transpile "(bit-xor 1 4 3)")
       "1 ^ 4 ^ 3;"))

;; =>

(is (thrown? (transpile "(bit-not)")
             #"Wrong number of arguments \(0\) passed to: bit-not"))
;; =>

(is (= (transpile "(bit-not 4)")
       "~4;"))

;; =>

(is (thrown? (transpile "(bit-not 4 5)")
             #"Wrong number of arguments \(2\) passed to: bit-not"))

;; =>

(is (thrown? (transpile "(bit-shift-left)")
             #"Wrong number of arguments \(0\) passed to: bit-shift-left"))

;; =>

(is (thrown? (transpile "(bit-shift-left a)")
             #"Wrong number of arguments \(1\) passed to: bit-shift-left"))
;; =>

(is (= (transpile "(bit-shift-left 1 4)")
       "1 << 4;"))

;; =>

(is (= (transpile "(bit-shift-left 1 4 3)")
       "1 << 4 << 3;"))

;; =>

;; Comparison operators

(is (thrown? (transpile "(<)")
             #"Wrong number of arguments \(0\) passed to: <"))

;; =>

(is (= (transpile "(< (foo))")
       "foo(), true;"))

;; =>

(is (= (transpile "(< x y)")
       "x < y;"))

;; =>

(is (= (transpile "(< a b c)")
       "a < b && b < c;"))

;; =>

(is (= (transpile "(< a b c d e)")
       "a < b && b < c && c < d && d < e;"))

;; =>

(is (thrown? (transpile "(>)")
             #"Wrong number of arguments \(0\) passed to: >"))

;; =>

(is (= (transpile "(> (foo))")
       "foo(), true;"))

;; =>

(is (= (transpile "(> x y)")
       "x > y;"))

;; =>

(is (= (transpile "(> a b c)")
       "a > b && b > c;"))

;; =>

(is (= (transpile "(> a b c d e)")
       "a > b && b > c && c > d && d > e;"))

;; =>

(is (thrown? (transpile "(<=)")
             #"Wrong number of arguments \(0\) passed to: <="))

;; =>

(is (= (transpile "(<= (foo))")
       "foo(), true;"))

;; =>

(is (= (transpile "(<= x y)")
       "x <= y;"))

;; =>

(is (= (transpile "(<= a b c)")
       "a <= b && b <= c;"))

;; =>

(is (= (transpile "(<= a b c d e)")
       "a <= b && b <= c && c <= d && d <= e;"))

;; =>

(is (thrown? (transpile "(>=)")
             #"Wrong number of arguments \(0\) passed to: >="))

;; =>

(is (= (transpile "(>= (foo))")
       "foo(), true;"))

;; =>

(is (= (transpile "(>= x y)")
       "x >= y;"))

;; =>

(is (= (transpile "(>= a b c)")
       "a >= b && b >= c;"))

;; =>

(is (= (transpile "(>= a b c d e)")
       "a >= b && b >= c && c >= d && d >= e;"))

;; =>

(is (= (transpile "(not= x y)")
       (transpile "(not (= x y))")))

;; =>

(is (thrown? (transpile "(identical?)")
             #"Wrong number of arguments \(0\) passed to: identical?"))

;; =>

(is (thrown? (transpile "(identical? x)")
             #"Wrong number of arguments \(1\) passed to: identical?"))

;; =>

(is (= (transpile "(identical? x y)")
       "x === y;"))

;; =>

;; This does not makes sence but let's let's stay compatible
;; with clojure and hop that it will be fixed.
;; http://dev.clojure.org/jira/browse/CLJ-1219
(is (thrown? (transpile "(identical? x y z)")
             #"Wrong number of arguments \(3\) passed to: identical?"))

;; =>

;; Arithmetic operators

(is (= (transpile "(+)")
       "0;"))
;; =>

(is (= (transpile "(+ 1)")
       "0 + 1;"))

;; =>

(is (= (transpile "(+ -1)")
       "0 + -1;"))

;; =>

(is (= (transpile "(+ 1 2)")
       "1 + 2;"))

;; =>

(is (= (transpile "(+ 1 2 3 4 5)")
       "1 + 2 + 3 + 4 + 5;"))

;; =>

(is (thrown? (transpile "(-)")
             #"Wrong number of arguments \(0\) passed to: -"))

;; =>

(is (= (transpile "(- 1)")
       "0 - 1;"))
;; =>

(is (= (transpile "(- 4 1)")
       "4 - 1;"))

;; =>

(is (= (transpile "(- 4 1 5 7)")
       "4 - 1 - 5 - 7;"))

;; =>

(is (thrown? (transpile "(mod)")
             #"Wrong number of arguments \(0\) passed to: mod"))
;; =>

(is (thrown? (transpile "(mod 1)")
             #"Wrong number of arguments \(1\) passed to: mod"))

;; =>

(is (= (transpile "(mod 1 2)")
       "1 % 2;"))
;; =>

(is (thrown? (transpile "(/)")
             #"Wrong number of arguments \(0\) passed to: /"))
;; =>

(is (= (transpile "(/ 2)")
       "1 / 2;"))

;; =>

(is (= (transpile "(/ 1 2)")
       "1 / 2;"))
;; =>

(is (= (transpile "(/ 1 2 3)")
       "1 / 2 / 3;"))

;; instance?

(is (thrown? (transpile "(instance?)")
             #"Wrong number of arguments \(0\) passed to: instance?"))

;; =>

(is (= (transpile "(instance? Number)")
       "void 0 instanceof Number;"))

;; =>

(is (= (transpile "(instance? Number (Number. 1))")
       "new Number(1) instanceof Number;"))

;; =>

;; Such instance? expression should probably throw
;; exception rather than ignore `y`. Waiting on
;; response for a clojure bug:
;; http://dev.clojure.org/jira/browse/CLJ-1220
(is (= (transpile "(instance? Number x y)")
       "x instanceof Number;"))

;; =>

error: pathspec 'test/escodegen.wisp' did not match any file(s) known to git


Write of tests of protocol usage tests.

Write of tests of protocol usage tests.

(ns wisp.test.escodegen
  (:require [wisp.test.util :refer [is thrown?]]
            [wisp.src.sequence :refer [concat cons vec take first rest
                                       second third list list? count drop
                                       lazy-seq? seq nth map]]
            [wisp.src.runtime :refer [subs = dec identity keys nil? vector?
                                      string? dec re-find satisfies?]]
            [wisp.src.compiler :refer [compile]]
            [wisp.src.reader :refer [read* read-from-string]
                             :rename {read-from-string read-string}]
            [wisp.src.ast :refer [meta name pr-str symbol]]))

(defprotocol INope
  (nope? [self]))

(is (thrown? (nope? 1) #"method")
    "protocol isn't implemented")

(is (not (satisfies? INope js/Number))
    "number doesn't satisfies INope")

(deftype Nope [x]
  INope
  (nope? [_] true))

(is (Nope. 1)
    "Can be instantiated")

(is (satisfies? INope (Nope.))
    "satisfies protocol")

(is (nope? (Nope.))
    "implements protcol method")

(extend-type number
  INope
  (nope? [x] true))

(is (satisfies? INope 4) "numbers implement protocol")
(is (nope? 3) "numbers implement protocol")

(is (not (satisfies? INope "foo"))
    "strings do not satisfy")

(extend-type default
  INope
  (nope? [_] false))

(is (satisfies? INope "foo")
    "everything satisfies protocol now")

(is (= (nope? "foo") false)
    "default implementation")

(is (= (nope? 3) true)
    "overriden implementation")

(is (= (nope? true) false)
    "default implementation")

(defprotocol IType
  (-type [x]))

(defn satisfaction
  [protocol]
  {:nil (satisfies? protocol nil)
   :boolean (satisfies? protocol true)
   :number (satisfies? protocol 1)
   :string (satisfies? protocol "foo")
   :pattern (satisfies? protocol #"foo")
   :fn (satisfies? protocol (fn [x] x))
   :vector (satisfies? protocol [1 2 3])
   :object (satisfies? protocol {})})

(is (= (satisfaction IType)
       {:nil false
        :boolean false
        :number false
        :string false
        :pattern false
        :fn false
        :vector false
        :object false})
    "no types satisfy protocol")

(extend-type nil
  IType
  (-type [_] :nil))

(is (= (satisfaction IType)
       {:nil true
        :boolean false
        :number false
        :string false
        :pattern false
        :fn false
        :vector false
        :object false})
    "only nil satisfyies protocol")

(extend-type boolean
  IType
  (-type [_] :boolean))

(is (= (satisfaction IType)
       {:nil true
        :boolean true
        :number false
        :string false
        :pattern false
        :fn false
        :vector false
        :object false})
    "nil & booleans satisfyies protocol")

(extend-type number
  IType
  (-type [_] :number))

(is (= (satisfaction IType)
       {:nil true
        :boolean true
        :number true
        :string false
        :pattern false
        :fn false
        :vector false
        :object false})
    "nil, booleans & numbers satisfyies protocol")

(extend-type string
  IType
  (-type [_] :string))

(is (= (satisfaction IType)
       {:nil true
        :boolean true
        :number true
        :string true
        :pattern false
        :fn false
        :vector false
        :object false})
    "nil, booleans, numbers & strings satisfyies protocol")

(extend-type re-pattern
  IType
  (-type [_] :pattern))

(is (= (satisfaction IType)
       {:nil true
        :boolean true
        :number true
        :string true
        :pattern true
        :fn false
        :vector false
        :object false})
    "nil, booleans, numbers, strings & patterns satisfyies protocol")

(extend-type function
  IType
  (-type [_] :function))

(is (= (satisfaction IType)
       {:nil true
        :boolean true
        :number true
        :string true
        :pattern true
        :fn true
        :vector false
        :object false})
    "nil, booleans, numbers, strings, patterns & functions satisfyies protocol")

(extend-type vector
  IType
  (-type [_] :vector))

(is (= (satisfaction IType)
       {:nil true
        :boolean true
        :number true
        :string true
        :pattern true
        :fn true
        :vector true
        :object false})
    "nil, booleans, numbers, strings, patterns, functions & vectors satisfyies protocol")

(extend-type default
  IType
  (-type [_] :default))

(is (= (satisfaction IType)
       {:nil true
        :boolean true
        :number true
        :string true
        :pattern true
        :fn true
        :vector true
        :object true})
    "all types satisfyies protocol")

(is (= (-type nil) :nil))
(is (= (-type true) :boolean))
(is (= (-type false) :boolean))
(is (= (-type 1) :number))
(is (= (-type 0) :number))
(is (= (-type 17) :number))
(is (= (-type "hello") :string))
(is (= (-type "") :string))
(is (= (-type #"foo") :pattern))
(is (= (-type (fn [x] x)) :function))
(is (= (-type #(inc %)) :function))
(is (= (-type []) :vector))
(is (= (-type [1]) :vector))
(is (= (-type [1 2 3]) :vector))
(is (= (-type {}) :default))
(is (= (-type {:a 1}) :default))

(defprotocol IFoo
  (foo? [x]))

(is (= (satisfaction IFoo)
       {:nil false
        :boolean false
        :number false
        :string false
        :pattern false
        :fn false
        :vector false
        :object false})
    "no types satisfyies protocol")

(extend-type default
  IFoo
  (foo? [_] false))

(is (= (satisfaction IFoo)
       {:nil true
        :boolean true
        :number true
        :string true
        :pattern true
        :fn true
        :vector true
        :object true})
    "all types satisfy protocol")

(defprotocol IBar
  (bar? [x]))

(extend-type js/Object
  IBar
  (bar? [_] true))

(is (= (satisfaction IBar)
       {:nil false
        :boolean false
        :number false
        :string false
        :pattern false
        :fn false
        :vector false
        :object true})
    "only objects satisfy protocol")

(extend-type js/Number
  IBar
  (bar? [_] true))

(is (= (satisfaction IBar)
       {:nil false
        :boolean false
        :number true
        :string false
        :pattern false
        :fn false
        :vector false
        :object true})
    "only objects & numbers satisfy protocol")

(extend-type js/String
  IBar
  (bar? [_] true))

(is (= (satisfaction IBar)
       {:nil false
        :boolean false
        :number true
        :string true
        :pattern false
        :fn false
        :vector false
        :object true})
    "only objects, numbers & strings satisfy protocol")

(extend-type js/Boolean
  IBar
  (bar? [_] true))

(is (= (satisfaction IBar)
       {:nil false
        :boolean true
        :number true
        :string true
        :pattern false
        :fn false
        :vector false
        :object true})
    "only objects, numbers, strings & booleans satisfy protocol")

(extend-type js/Function
  IBar
  (bar? [_] true))

(is (= (satisfaction IBar)
       {:nil false
        :boolean true
        :number true
        :string true
        :pattern false
        :fn true
        :vector false
        :object true})
    "only objects, numbers, strings, booleans & functions satisfy protocol")

(extend-type js/Array
  IBar
  (bar? [_] true))

(is (= (satisfaction IBar)
       {:nil false
        :boolean true
        :number true
        :string true
        :pattern false
        :fn true
        :vector true
        :object true})
    "only objects, numbers, strings, booleans, functions & array satisfy protocol")

(extend-type js/RegExp
  IBar
  (bar? [_] true))

(is (= (satisfaction IBar)
       {:nil false
        :boolean true
        :number true
        :string true
        :pattern true
        :fn true
        :vector true
        :object true})
    "only objects, numbers, strings, booleans, functions & patterns satisfy protocol")

error: pathspec 'test/protocols.wisp' did not match any file(s) known to git