ki
Version:
lisp + mori, sweet.js
747 lines (600 loc) • 20.3 kB
JavaScript
var expect = require("expect.js");
describe("require core", function() {
it("should create a _ki object", function() {
ki require core
expect(typeof _ki).to.not.eql('undefined')
});
it("should make mori available", function() {
ki require core
var mori = _ki.modules.mori;
expect(ki (vector 1 2 3)).to.eql(mori.vector(1,2,3))
});
});
describe("sexpressions", function() {
it("should allow to call js functions", function() {
ki require core
var f0 = function() { return 1; }
var f1 = function(a) { return a; }
var f2 = function(a, b) { return [a, b]; }
expect(ki (f0)).to.eql(1);
expect(ki (f1 1)).to.eql(1);
expect(ki (f2 1 2)).to.eql([1,2]);
});
it("should allow to use attribute access notation as function name", function() {
ki require core
var foo0 = { bar: function() { return 1; } }
var foo1 = { bar: function(a) { return a; } }
var foo2 = { bar: function(a,b) { return [a, b]; } }
var goo0 = { bar: { baz : function() { return 1; } } }
var goo1 = { bar: { baz : function(a) { return a; } } }
var goo2 = { bar: { baz : function(a, b) { return [a, b]; } } }
expect(ki (foo0.bar)).to.eql(1);
expect(ki (foo1.bar 1)).to.eql(1);
expect(ki (foo2.bar 1 2)).to.eql([1,2]);
expect(ki (goo0.bar.baz)).to.eql(1);
expect(ki (goo1.bar.baz 1)).to.eql(1);
expect(ki (goo2.bar.baz 1 2)).to.eql([1,2]);
});
it("should allow to use attribute access notation as function argument", function() {
ki require core
var goo1 = { bar: { baz : function(a) { return a; } } }
var goo2 = { bar: { baz : function(a, b) { return [a, b]; } } }
var goo3 = { bar: { baz : function(a, b, c) { return [a, b, c]; } } }
var data = { a: { b: 0 }}
expect(ki (goo1.bar.baz data.a.b)).to.eql(0);
expect(ki (goo2.bar.baz data.a.b 1)).to.eql([0,1]);
expect(ki (goo3.bar.baz data.a.b 1 data.a.b)).to.eql([0,1,0]);
});
it("should allow to call mori functions on mori data structures", function() {
ki require core
var foo = ki (vector 1 2 3)
expect(ki (conj foo 4)).to.eql(ki (vector 1 2 3 4));
});
});
describe("lambdas", function() {
it("should allow to define anonymous functions and call them from js", function() {
ki require core
var f = ki (fn [x] (sum x 1))
expect(f(1)).to.eql(2);
});
it("should allow to define anonymous functions and use them in ki", function() {
ki require core
expect(
ki (toJs (map (fn [x] (sum x 1)) (vector 1 2 3)))
).to.eql([2,3,4]);
});
it("should allow to define named anonymous functions and call them recursively", function() {
ki require core
expect(
ki (toJs (map (fn foobar[x] (if (eq x 1) x (foobar (dec x)))) (vector 1 2 3)))
).to.eql([1,1,1]);
});
});
describe("interoperability", function() {
it("should allow to call js within ki", function() {
ki require core
expect(
ki (toJs (map (js function(x) { return x + 1; }) (vector 1 2 3)))
).to.eql([2,3,4]);
});
it("should allow to pass a ki fn as a js callback", function() {
ki require core
expect(
[1,2,3,4].map(ki (fn [x] (isEven x)))).to.eql([false,true,false,true]);
});
});
describe("local bindings and lexical scope", function() {
it("should allow to define local bindings in a let form and ensure proper lexical scope", function() {
ki require core
expect(
ki (toJs
(let [a 1
b 2]
(vector a b)))
).to.eql([1,2]);
expect(
ki (toJs
(let [a 0]
(let [a (inc a)
b (inc a)]
(vector a b))))
).to.eql([1,2]);
var c = {d: 1};
var mori = _ki.modules.mori;
expect(
ki (let [a c.d
b (inc a)
e :e]
(let [a (inc a)
b (inc b)]
a)
(vector a b e))
).to.eql(mori.vector(1,2,mori.keyword('e')));
});
});
describe("namespaces", function() {
it("should allow to define multiple namespaces and an anonymous namespace", function() {
ki require core
ki (def a 0);
ki (defn b [x] x);
ki (ns foo (def a 1));
ki (ns bar (def a 2));
expect(ki (identity a)).to.eql(0);
expect(ki (b 0)).to.eql(0);
expect(ki (ns foo a)).to.eql(1);
expect(ki (ns bar a)).to.eql(2);
});
it("should allow to use fully qualified identifiers", function() {
ki require core
ki (def a 0);
ki (ns foo (def a 1));
ki (ns bar (def a 2));
expect(
ki (toJs (vector a foo/a bar/a))
).to.eql([0,1,2]);
});
it("should allow to intern modules", function() {
ki require core
_ki.modules['amodule'] = { bar: function() { return 1; }};
_ki.modules['bmodule'] = { baz: function() { return 2; }};
ki (ns foo
(use amodule bmodule));
expect(
ki (toJs (ns foo (vector (bar) (baz))))
).to.eql([1,2]);
});
});
describe("truthiness", function() {
it("should have truthy return false only for boolean false, nil (and js null and undefined)", function() {
ki require core
expect(ki (truthy false)).to.eql(false);
expect(ki (truthy nil)).to.eql(false);
expect(ki (truthy (js null))).to.eql(false);
expect(ki (truthy (js undefined))).to.eql(false);
expect(ki (truthy "")).to.eql(true);
expect(ki (truthy 0)).to.eql(true);
expect(ki (falsey false)).to.eql(true);
expect(ki (falsey 0)).to.eql(false);
expect(ki (not (falsey false))).to.eql(false);
expect(ki (not (falsey 0))).to.eql(true);
});
});
describe("logical operators", function() {
it("should be consistent with definition of truthiness", function() {
ki require core
expect(ki (and "" 0)).to.eql(true);
expect(ki (and "" 0 nil)).to.eql(false);
expect(ki (or "" 0)).to.eql(true);
expect(ki (or false nil)).to.eql(false);
expect(ki (and "" (not (or false nil)) 0)).to.eql(true);
});
it("should short circuit", function() {
ki require core
expect(ki (and true false undefined_symbol)).to.eql(false);
expect(ki (or false true undefined_symbol)).to.eql(true);
});
});
describe("equality", function() {
it("should operate on deep data structures", function() {
ki require core
expect(ki (eq {"a" 1 "b" [{"c" 1} 2]} {"a" 1 "b" [{"c" 1} 2]})).to.eql(true);
expect(ki (eq {"a" 1 "b" [{"c" 3} 2]} {"a" 1 "b" [{"c" 1} 2]})).to.eql(false);
expect(ki (neq {"a" 1 "b" [{"c" 3} 2]} {"a" 1 "b" [{"c" 1} 2]})).to.eql(true);
});
});
describe("flow control", function() {
it("should allow branching consistently with definition of truthiness", function() {
ki require core
expect(ki (when (eq 1 1) "foo")).to.eql("foo");
expect(ki (whenNot (eq 1 2) "foo")).to.eql("foo");
expect(ki (if "" "foo" "bar")).to.eql("foo");
expect(ki (if 0 "foo" "bar")).to.eql("foo");
expect(ki (if nil "foo" "bar")).to.eql("bar");
expect(ki (ifNot "" "foo" "bar")).to.eql("bar");
});
it("should have cond be consistent with definition of truthiness", function() {
ki require core
expect(
ki (cond
(eq 1 2) "foo"
nil "bar"
"" "baz")).to.eql("baz");
expect(
ki (cond
(eq 1 2) "foo"
nil "bar"
:else "baz")).to.eql("baz");
});
it("should have cond short circuit", function() {
ki require core
expect(ki (cond
(eq 1 2) "foo"
true "bar"
undefined_symbol "baz")).to.eql("bar");
});
});
describe("data literals", function() {
it("should allow to create vectors", function() {
ki require core
expect(ki (eq [1 2 3 4] (vector 1 2 3 4))).to.eql(true);
});
it("should allow to create hash maps", function() {
ki require core
expect(ki (eq {"a" 2 "b" 4} (hashMap "a" 2 "b" 4))).to.eql(true);
});
it("should allow to create hash maps and evaluate forms", function() {
ki require core
expect(ki (eq {"a" (inc 1) (str "b") 4} (hashMap "a" 2 "b" 4))).to.eql(true);
});
it("should allow to create deeply nested data structures", function() {
ki require core
expect(ki (eq {"a" [2 [3 4]] "b" {"c" 5 [6 7] "d"}}
(hashMap "a" (vector 2 (vector 3 4))
"b" (hashMap "c" 5 (vector 6 7) "d")))).to.eql(true);
});
it("should allow to create js arrays", function() {
ki require core
expect(ki (do [$ 1 2 3 4])).to.eql([1,2,3,4]);
});
it("should allow to create js objects", function() {
ki require core
expect(ki (do {$ "a" 1 "b" 2})).to.eql({a: 1, b: 2});
});
it("should allow to create nested js objects", function() {
ki require core
expect(ki (do {$ "a" {$ "c" [$ 3 4]} "b" 2})).to.eql({a: {c: [3, 4]}, b: 2});
});
});
describe("recursion", function() {
it("should allow to express simple recursion", function() {
ki require core
ki (defn fib [n]
(cond
(eq n 0) 0
(eq n 1) 1
"else" (sum (fib (js n-1)) (fib (js n-2)))));
expect(ki (fib 20)).to.eql(6765);
});
it("should allow to recur using loop/recur without blowing the stack", function() {
ki require core
ki (defn fib [n]
(loop [a 0 b (inc a) iter 0]
(if (js iter == n) a
(recur b (js a + b) (inc iter)))));
expect(ki (fib 20)).to.eql(6765);
expect(ki (fib 500)).to.eql(1.394232245616977e+104);
});
});
describe("keywords", function() {
it("should be usable in collections", function() {
ki require core
var mori = _ki.modules.mori;
expect(ki (do [:a 1 :b {:c 2}])).to.eql(
mori.vector(mori.keyword('a'),1,
mori.keyword('b'),mori.hashMap(mori.keyword('c'),2)));
});
//it("should evaluate to themselves", function() {
// ki require core
// var mori = _ki.modules.mori;
// expect(ki (do (:a))).to.eql(mori.keyword('a'));
//});
//it("should evaluate as keys to get values from collections", function() {
// ki require core
// var mori = _ki.modules.mori;
// expect(ki (:a {:a 1 :b 2})).to.eql(1);
//});
});
describe("arity", function() {
it("should allow calling functions without arity constraints, as in js", function() {
ki require core
ki (defn foo [a] (str "Hello " a))
expect(
ki (foo 1 2)
).to.eql("Hello 1");
expect(
ki (foo)
).to.eql("Hello undefined");
});
it("should allow to define functions with multiple arities", function() {
ki require core
ki (defn foo
([a] (str "Hello " a))
([a b] (str "There " a " " b)))
expect(
ki (foo 1)
).to.eql("Hello 1");
expect(
ki (foo 1 2)
).to.eql("There 1 2");
});
it("should allow to define named anonymous functions with multiple arities and refer to the name within the body", function() {
ki require core
var f = ki (fn self
([] (self "world"))
([who] (str "Hello " who "!")))
expect(f()).to.eql("Hello world!");
expect(f("yellow")).to.eql("Hello yellow!");
});
it("should fallback to max arity in case supplied arguments do not match the specified arities", function() {
ki require core
ki (defn foo
([a] (str "Hello " a))
([a b] (str "There " a " " b)))
expect(
ki (foo)
).to.eql("There undefined undefined");
expect(
ki (foo 1 2 3)
).to.eql("There 1 2");
});
//it("should allow to define functions with optional arguments", function() {
// throw "Not implemented"
//});
});
describe("dot notation", function() {
it("should allow to use dot notation to invoke methods on JavaScript objects", function() {
ki require core
var a = {
bar: function(x) {
return x*2;
}
};
var b = {
foo: function(x) {
return a;
}
};
expect(
ki (.bar a 2)
).to.eql(4);
expect(
ki (threadf b (.foo) (.bar 2))
).to.eql(4);
});
});
describe("chaining and doto", function() {
it("should allow to use JavaScript chained APIs", function() {
ki require core
var A = function() {
var self = this;
this.v = "init ";
this.foo = function(x) {
self.v += "foo called with " + x + " ";
return self;
};
this.bar = function(x) {
self.v += "bar called with " + x + " ";
return self;
};
}
var a = new A();
var mori = _ki.modules.mori;
expect(
ki (chain a (foo 1) (bar 2) v)
).to.eql('init foo called with 1 bar called with 2 ');
});
it("should allow to repeatedly call methods on a JavaScript object", function() {
ki require core
var A = function() {
var self = this;
this.foo = null;
this.bar = null;
this.setFoo = function(x) {
self.foo = x;
};
this.setBar = function(x) {
self.bar = x;
};
this.getFooBar = function() {
return self.foo + " " + self.bar;
}
}
var a = new A();
var mori = _ki.modules.mori;
expect(
ki (doto a (setFoo 'a') (setBar 'b')).getFooBar()
).to.eql('a b');
});
});
describe("threading", function() {
it("should allow to thread first a value through a sequence of computations", function() {
ki require core
var a = 1;
expect(
ki (threadf a inc inc dec)
).to.eql(2);
expect(
ki (threadf a (sum 2) (sum 3))
).to.eql(6);
expect(
ki (threadf [] (conj 1) first)
).to.eql(1);
});
it("should allow to thread last a value through a sequence of computations", function() {
ki require core
var a = 1;
expect(
ki (threadl a (conj []) (map (fn [x] (inc x))) first)
).to.eql(2);
});
});
describe("math operations", function() {
it("should allow to add, subtract, multiply, divide a sequence of numbers and compute the modulo of two numbers", function() {
ki require core
expect(ki (add 1 2 3)).to.eql(6);
expect(ki (sub 3 2 1)).to.eql(0);
expect(ki (mul 1 2 3)).to.eql(6);
expect(ki (div 3 2 1)).to.eql(1.5);
expect(ki (mod 3 2)).to.eql(1);
});
it("should allow to compare sequences of numbers", function() {
ki require core
expect(ki (lt 1 2 3)).to.eql(true);
expect(ki (lt 3 2 1)).to.eql(false);
expect(ki (lt 1 2 2)).to.eql(false);
expect(ki (gt 1 2 3)).to.eql(false);
expect(ki (gt 3 2 1)).to.eql(true);
expect(ki (gt 3 2 2)).to.eql(false);
expect(ki (leq 1 2 3)).to.eql(true);
expect(ki (leq 3 2 1)).to.eql(false);
expect(ki (leq 1 2 2)).to.eql(true);
expect(ki (geq 1 2 3)).to.eql(false);
expect(ki (geq 3 2 1)).to.eql(true);
expect(ki (geq 3 2 2)).to.eql(true);
});
});
describe("continuations", function() {
it("should allow to write asynchronous code in a synchronous fashion", function() {
ki require core
var foo = function(x, cb) {
var y = x * 2;
cb(y);
};
var bar = function(x, cb) {
var y = x + 1;
cb(y);
};
var baz = function(x, cb) {
var y = x + 1;
var z = x * 2;
cb(y,z);
};
ki (letc [a (foo 2)
b (bar a)
[c d] (baz b)]
(js expect(b).to.eql(5))
(js expect(c).to.eql(6))
(js expect(d).to.eql(10)))
ki require core
var log = "";
ki (do
(defn fake_request [url cb]
(setTimeout (fn [] (cb 1234)) 1000))
(letc [data (fake_request "fakeurl")]
(js log += "Response received: " + data + ".")
(js expect(log).to.eql("Request sent. Response received: 1234.")))
(js log += "Request sent. "))
expect(log).to.eql("Request sent. ");
});
});
describe("apply", function() {
it("should call a function given a list of arguments supplied as a collection", function() {
ki require core
var mori = _ki.modules.mori;
expect(
ki (apply list [1 2 3 4])
).to.eql(mori.list(1,2,3,4));
});
});
describe("bind", function() {
it("should return a function with this set to the provided object", function() {
ki require core
ki (do
(def a {:a 1 :b 2})
(defn f [] (get this :a)))
expect(ki ((bind a f))).to.eql(1);
expect(ki ((bind a (fn [] (get this :a))))).to.eql(1);
});
});
describe("multimethods", function() {
it("should allow to define functions that dispatch according to the result of the evaluation of another function", function() {
ki require core
ki (do
(defmulti boss (fn [x] (get x :type)))
(defmethod boss :employee [x] (get x :employer))
(defmethod boss :employer [x] (get x :name)));
expect(ki (boss {:type :employee :name "Barnie" :employer "Fred"})).to.eql("Fred");
expect(ki (boss {:type :employer :name "Fred"})).to.eql("Fred");
});
});
describe("atoms", function() {
it("should allow to define reference types with read and write callbacks", function() {
ki require core
ki (do
(let [r (atom 1 (fn [n o] (js expect(n).to.eql(2); expect(o).to.eql(1)))
(fn [x] (js expect(x).to.eql(2))))]
(reset r 2)
(deref r)));
ki (do
(let [r (atom 1 (fn [n o] (js expect(n).to.eql(2); expect(o).to.eql(1)))
(fn [x] (js expect(x).to.eql(2))))]
(swap r inc)
(js expect(ki (deref r)).to.eql(2))));
});
});
describe("exceptions", function() {
it("should allow to try expressions and catch exceptions", function() {
ki require core
ki (try foo.bar (catch e (js expect(e).to.be.a(ReferenceError))));
var side_effect = false;
ki (try foo.bar (catch e (js expect(e).to.be.a(ReferenceError))) (finally (js side_effect = true)));
expect(side_effect).to.eql(true);
});
it("should allow to throw exceptions", function() {
ki require core
expect(ki (fn [] (throw (Error "foo")))).to.throwError();
});
});
describe("this and fnth", function() {
it("should handle binding this fn-wise correctly from within IIFN", function() {
ki require core
ki (defn somefn [] (let [a 1] this.someprop));
var bar = {someprop: 1};
var baz = {};
expect(ki ((bind bar somefn))).to.eql(1);
expect(ki ((bind baz somefn))).to.eql(undefined);
expect(ki (somefn)).to.eql(undefined);
});
it("should allow a shorthand notation for defining a fn bound to the enclosing this, both named and unnamed", function() {
ki require core
var fn1, fn2;
ki (do
(js this.jee = 1)
(let [a (fn [] this.jee)
b (fnth [] this.jee)
c (fnth cfn[] this.jee)]
(js fn1 = a)
(js fn2 = b)
(js fn3 = c)));
expect(fn1.bind({jee: 2})()).to.eql(2);
expect(fn2.bind({jee: 2})()).to.eql(1);
expect(fn3.bind({jee: 2})()).to.eql(1);
});
});
describe("str", function() {
it("should allow to concatenate strings and literals", function() {
ki require core
expect(ki (str "a" 2 "b" 3 "c")).to.eql("a2b3c");
});
});
describe("destructuring", function() {
it("should destructure nested immutable data structures in let forms", function(){
ki require core
var r = ki (let [[a b {c :c [d e] :d}] [1 2 {:c 3 :d [4 5]}]
f 6]
(eq [a b c d e] [1 2 3 4 5]))
expect(r).to.eql(true);
});
it("should destructure nested JS data structures in let forms", function(){
ki require core
var r = ki (let [[$ a b {$ c 'c' [$ d e] 'd'}] [$ 1 2 {$ c 3 d [$ 4 5]}]]
(eq [a b c d e] [1 2 3 4 5]))
expect(r).to.eql(true);
});
it("should destructure nested immutable data structures in loop forms", function(){
ki require core
var r = ki (loop [[a b _] [1 2 3]]
(if (gt a 3)
(eq [a b] [4 5])
(recur (map inc [a b 3]))))
expect(r).to.eql(true);
});
it("should destructure nested JS data structures in loop forms", function(){
ki require core
var r = ki (loop [[$ a b _] [$ 1 2 3]]
(if (gt a 3)
(eq [a b] [4 5])
(recur (toJs (map inc [a b 3])))))
expect(r).to.eql(true);
});
});