UNPKG

ki

Version:

lisp + mori, sweet.js

747 lines (600 loc) 20.3 kB
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); }); });