#!/usr/bin/env bin/lumen

(define passed 0)
(define failed 0)
(define tests ())

(define reader (require 'reader))
(define compiler (require 'compiler))

(define-macro test (x msg)
  `(if (not ,x)
       (do (set failed (+ failed 1))
           (return ,msg))
     (inc passed)))

(define equal? (a b)
  (if (atom? a) (= a b)
    (= (str a) (str b))))

(define-macro test= (a b)
  (let-unique (x y)
    `(let (,x ,a ,y ,b)
       (test (equal? ,x ,y)
             (cat "failed: expected " (str ,x) ", was " (str ,y))))))

(define-macro define-test (name rest: body)
  `(add tests (list ',name (fn () ,@body))))

(define-global run ()
  (each ((name f)) tests
    (let result (f)
      (when (string? result)
        (print (cat " " name " " result)))))
  (print (cat " " passed " passed, " failed " failed")))

(define-test reader
  (let read (get reader 'read-string)
    (test= nil (read ""))
    (test= "nil" (read "nil"))
    (test= 17 (read "17"))
    (test= 0.015 (read "1.5e-2"))
    (test= 15 (read "0xF"))
    (test= -15 (read "-0Xf"))
    (test= "0x" (read "0x"))
    (test= "-0X" (read "-0X"))
    (test= "-0Xg" (read "-0Xg"))
    (test= true (read "true"))
    (test= (not true) (read "false"))
    (test= 'hi (read "hi"))
    (test= '"hi" (read "\"hi\""))
    (test= "|hi|" (read "|hi|"))
    (test= '(1 2) (read "(1 2)"))
    (test= '(1 (a)) (read "(1 (a))"))
    (test= '(quote a) (read "'a"))
    (test= '(quasiquote a) (read "`a"))
    (test= '(quasiquote (unquote a)) (read "`,a"))
    (test= '(quasiquote (unquote-splicing a)) (read "`,@a"))
    (test= 2 (# (read "(1 2 a: 7)")))
    (test= 7 (get (read "(1 2 a: 7)") 'a))
    (test= true (get (read "(:a)") 'a))
    (test= 1 (- -1))
    (test= "0?" (read "0?"))
    (test= "0!" (read "0!"))
    (test= "0." (read "0."))))

(define-test read-more
  (let read (get reader 'read-string)
    (test= 17 (read "17" true))
    (let more ()
      (test= more (read "(open" more))
      (test= more (read "\"unterminated " more))
      (test= more (read "|identifier" more))
      (test= more (read "'(a b c" more))
      (test= more (read "`(a b c" more))
      (test= more (read "`(a b ,(z" more))
      (test= more (read "`\"biz" more))
      (test= more (read "'\"boz" more)))
    (let ((ok e) (guard (read "(open")))
      (test= false ok)
      (test= "Expected ) at 5" (get e 'message)))))

(define-test nil?
  (test= true (nil? nil))
  (test= true (nil? null))
  (test= false (nil? true))
  (test= false (nil? false))
  (test= false (nil? (obj))))

(define-test is?
  (test= false (is? nil))
  (test= false (is? null))
  (test= true (is? true))
  (test= true (is? false))
  (test= true (is? (obj))))

(define-test no
  (test= true (no nil))
  (test= true (no null))
  (test= false (no true))
  (test= true (no false))
  (test= false (no (obj)))
  (test= false (no 0)))

(define-test yes
  (test= false (yes nil))
  (test= false (yes null))
  (test= true (yes true))
  (test= false (yes false))
  (test= true (yes (obj)))
  (test= true (yes 0)))

(define-test boolean
  (test= true (or true false))
  (test= false (or false false))
  (test= true (or false false true))
  (test= true (not false))
  (test= true (not (and false true)))
  (test= false (not (or false true)))
  (test= true (and true true))
  (test= false (and true false))
  (test= false (and true true false)))

(define-test short
  (test= true (or true (error 'bad)))
  (test= false (and false (error 'bad)))
  (let a true
    (test= true (or true (do (set a false) false)))
    (test= true a)
    (test= false (and false (do (set a false) true)))
    (test= true a))
  (let b true
    (test= true (or (do (set b false) false) (do (set b true) b)))
    (test= true b)
    (test= true (or (do (set b true) b) (do (set b true) b)))
    (test= true b)
    (test= true (and (do (set b false) true) (do (set b true) b)))
    (test= true b)
    (test= false (and (do (set b false) b) (do (set b true) b)))
    (test= false b)))

(define-test numeric
  (test= 4 (+ 2 2))
  (test= 0 (apply * '(0 0)))
  (test= 4 (apply + '(2 2)))
  (test= 0 (apply + ()))
  (test= 18 18.00)
  (test= 4 (- 7 3))
  (test= 4 (apply - '(7 3)))
  (test= 0 (apply - ()))
  (test= 5.0 (/ 10 2))
  (test= 5.0 (apply / '(10 2)))
  (test= 1 (apply / ()))
  (test= 6 (* 2 3.00))
  (test= 6 (apply * '(2 3.00)))
  (test= 1 (apply * ()))
  (test= true (> 2.01 2))
  (test= true (>= 5.0 5.0))
  (test= true (> 2.1e3 2000))
  (test= true (< 2e-3 0.0021))
  (test= false (< 2 2))
  (test= true (<= 2 2))
  (test= -7 (- 7))
  (test= false (numeric? "")))

(define-test math
  (test= 3 (max 1 3))
  (test= 2 (min 2 7))
  (let n (random)
    (test= true (and (> n 0) (< n 1))))
  (test= 4 (floor 4.78)))

(define-test precedence
  (test= -3 (- (+ 1 2)))
  (test= 10 (- 12 (+ 1 1)))
  (test= 11 (- 12 (* 1 1)))
  (test= 10 (+ (/ 4 2) 8)))

(define-test infix
  (let (l '(1 1 2 3)
        (a b c d) l)
    (test= true (apply <= l))
    (test= false (apply < l))
    (test= false (apply = l))
    (test= true (call = 1 a b))
    (test= false (apply > (reverse l)))
    (test= true (apply >= (reverse l)))
    (test= true (<= a b c d))
    (test= true (<= a b c d))
    (test= false (< a b c d))
    (test= false (= a b c d))
    (test= true (= 1 a b))
    (test= false (> d c b a))
    (test= true (>= d c b a))))

(define-test standalone
  (test= 10 (do (+ illegal) 10))
  (let x nil
    (test= 9 (do (list nothing fooey (set x 10)) 9))
    (test= 10 x))
  (test= 12 (do (get but zz) 12))
  (let y nil
    (let ignore (do (%literal y | = 10;|) 42)
      (test= 10 y))))

(define-test string
  (test= 3 (# "foo"))
  (test= 3 (# "\"a\""))
  (test= 'a "a")
  (test= "a" (char "bar" 1))
  (let s "a
b"
    (test= 3 (# s)))
  (let s "a
b
c"
    (test= 5 (# s)))
  (test= 3 (# "a\nb"))
  (test= 3 (# "a\\b"))
  (test= "x3" (cat "x" (+ 1 2))))

(define-test quote
  (test= 7 (quote 7))
  (test= true (quote true))
  (test= false (quote false))
  (test= (quote a) 'a)
  (test= (quote (quote a)) ''a)
  (test= "\"a\"" '"a")
  (test= "\"\\n\"" (quote "\n"))
  (test= "\"\\r\\n\"" (quote "\r\n"))
  (test= "\"\\\\\"" (quote "\\"))
  (test= '(quote "a") ''"a")
  (test= "|(|" '|(|)
  (test= (quote unquote) 'unquote)
  (test= (quote (unquote)) '(unquote))
  (test= (quote (unquote a)) '(unquote a))
  (let x '(10 20 a: 33 1a: 44)
    (test= 20 (at x 1))
    (test= 33 (get x 'a))
    (test= 44 (get x '1a))))

(define-test list
  (test= '() (list))
  (test= () (list))
  (test= '(a) (list 'a))
  (test= '(a) (quote (a)))
  (test= '(()) (list (list)))
  (test= 0 (# (list)))
  (test= 2 (# (list 1 2)))
  (test= '(1 2 3) (list 1 2 3))
  (test= 17 (get (list foo: 17) 'foo))
  (test= 17 (get (list 1 foo: 17) 'foo))
  (test= true (get (list :foo) 'foo))
  (test= true (get '(:foo) 'foo))
  (test= true (get (hd '((:foo))) 'foo))
  (test= '(:a) (list :a))
  (test= '(b: false) (list b: false))
  (test= '(c: 0) (list c: 0)))

(define-test quasiquote
  (test= (quote a) (quasiquote a))
  (test= 'a `a)
  (test= () `())
  (test= 2 `,2)
  (test= nil `(,@nil))
  (let a 42
    (test= 42 `,a)
    (test= 42 (quasiquote (unquote a)))
    (test= '(quasiquote (unquote a)) ``,a)
    (test= '(quasiquote (unquote 42)) ``,,a)
    (test= '(quasiquote (quasiquote (unquote (unquote a)))) ```,,a)
    (test= '(quasiquote (quasiquote (unquote (unquote 42)))) ```,,,a)
    (test= '(a (quasiquote (b (unquote c)))) `(a `(b ,c)))
    (test= '(a (quasiquote (b (unquote 42)))) `(a `(b ,,a)))
    (let b 'c
      (test= '(quote c) `',b)
      (test= '(42) `(,a))
      (test= '((42)) `((,a)))
      (test= '(41 (42)) `(41 (,a)))))
  (let c '(1 2 3)
    (test= '((1 2 3)) `(,c))
    (test= '(1 2 3) `(,@c))
    (test= '(0 1 2 3) `(0 ,@c))
    (test= '(0 1 2 3 4) `(0 ,@c 4))
    (test= '(0 (1 2 3) 4) `(0 (,@c) 4))
    (test= '(1 2 3 1 2 3) `(,@c ,@c))
    (test= '((1 2 3) 1 2 3) `((,@c) ,@c)))
  (let a 42
    (test= '(quasiquote ((unquote-splicing (list a)))) ``(,@(list a)))
    (test= '(quasiquote ((unquote-splicing (list 42)))) ``(,@(list ,a))))
  (test= true (get `(:foo) 'foo))
  (let (a 17
        b '(1 2)
        c (obj a: 10)
        d (list a: 10))
    (test= 17 (get `(foo: ,a) 'foo))
    (test= 2 (# `(foo: ,a ,@b)))
    (test= 17 (get `(foo: ,@a) 'foo))
    (test= '(1 a: 10) `(1 ,@c))
    (test= '(1 a: 10) `(1 ,@d))
    (test= true (get (hd `((:foo))) 'foo))
    (test= true (get (hd `(,(list :foo))) 'foo))
    (test= true (get `(,@(list :foo)) 'foo))
    (test= true (get `(1 2 3 ,@'(:foo)) 'foo)))
  (let-macro ((a keys `(obj ,@keys)))
    (test= true (get (a :foo) 'foo))
    (test= 17 (get (a bar: 17) 'bar)))
  (let-macro ((a () `(obj baz: (fn () 17))))
    (test= 17 ((get (a) 'baz)))))

(define-test quasiexpand
  (test= 'a (macroexpand 'a))
  (test= '(17) (macroexpand '(17)))
  (test= '(1 z) (macroexpand '(1 z)))
  (test= '(%array 1 "z") (macroexpand '`(1 z)))
  (test= '(%array 1 z) (macroexpand '`(,1 ,z)))
  (test= 'z (macroexpand '`(,@z)))
  (test= '(join (%array 1) z) (macroexpand '`(,1 ,@z)))
  (test= '(join (%array 1) x y) (macroexpand '`(,1 ,@x ,@y)))
  (test= '(join (%array 1) z (%array 2)) (macroexpand '`(,1 ,@z ,2)))
  (test= '(join (%array 1) z (%array "a")) (macroexpand '`(,1 ,@z a)))
  (test= '"x" (macroexpand '`x))
  (test= '(%array "quasiquote" "x") (macroexpand '``x))
  (test= '(%array "quasiquote" (%array "quasiquote" "x")) (macroexpand '```x))
  (test= 'x (macroexpand '`,x))
  (test= '(%array "quote" x) (macroexpand '`',x))
  (test= '(%array "quasiquote" (%array "x")) (macroexpand '``(x)))
  (test= '(%array "quasiquote" (%array "unquote" "a")) (macroexpand '``,a))
  (test= '(%array "quasiquote" (%array (%array "unquote" "x")))
         (macroexpand '``(,x))))

(define-test calls
  (let (f (fn () 42)
        l (list f)
        t (obj f: f))
    (f)
    ((fn ()
      (test= 42 (f))))
    (test= 42 ((at l 0)))
    (test= 42 ((get t 'f)))
    (test= nil ((fn () (return))))
    (test= 10 ((fn (x) (- x 2)) 12))))

(define-test id
  (let (a 10
        b (obj x: 20)
        f (fn () 30))
    (test= 10 a)
    (test= 10 (%literal a))
    (test= 20 (%literal b |.x|))
    (test= 30 (%literal f |()|))))

(define-test names
  (let (a! 0
        b? 1
        -% 2
        ** 3
        break 4)
    (test= 0 a!)
    (test= 1 b?)
    (test= 2 -%)
    (test= 3 **)
    (test= 4 break)))

(define-test set
  (test= 1 (set xx 1))
  (test= 1 xx)
  (test= 2 (set yy 1 zz 2))
  (test= 1 yy)
  (test= 2 zz)
  (let a 42
    (set a 'bar)
    (test= 'bar a)
    (let x (set a 10)
      (test= 10 x)
      (test= 10 a))
    (set a false)
    (test= false a)
    (set a)
    (test= nil a)))

(define-test wipe
  (let x '(:a :b :c)
    (wipe (get x 'a))
    (test= nil (get x 'a))
    (test= true (get x 'b))
    (wipe (get x 'c))
    (test= nil (get x 'c))
    (test= true (get x 'b))
    (wipe (get x 'b))
    (test= nil (get x 'b))
    (test= () x)))

(define-test do
  (let a 17
    (do (set a 10)
        (test= 10 a))
    (test= 10 (do a))
    (let b (do (set a 2) (+ a 5))
      (test= a 2)
      (test= b 7))
    (do (set a 10)
        (do (set a 20)
            (test= 20 a)))
    (test= 20 (do (set a 10)
                  (do (set a 20) a))))
  (test= '(do) (expand '(do))))

(define-test if
  (test= 'a (macroexpand '(if a)))
  (test= '(%if a b) (macroexpand '(if a b)))
  (test= '(%if a b c) (macroexpand '(if a b c)))
  (test= '(%if a b (%if c d)) (macroexpand '(if a b c d)))
  (test= '(%if a b (%if c d e)) (macroexpand '(if a b c d e)))
  (if true
      (test= true true)
    (test= true false))
  (if false (test= true false)
      false (test= false true)
    (test= true true))
  (if false (test= true false)
      false (test= false true)
      false (test= false true)
    (test= true true))
  (if false (test= true false)
      true (test= true true)
      false (test= false true)
    (test= true true))
  (test= false (if false true false))
  (test= 1 (if true 1 2))
  (test= 1 (if (let a 10 a) 1 2))
  (test= 1 (if true (with a 1) 2))
  (test= 1 (if false 2 (let a 1 a)))
  (test= 1 (if false 2 true (with a 1)))
  (test= 1 (if false 2 false 3 (let a 1 a)))
  (test= 0 (if false 1 0)))

(define-test case
  (let x 10
    (test= 2 (case x 9 9 10 2 4))
    (test= 2 (case x 9 9 (10) 2 4))
    (test= 2 (case x 9 9 (10 20) 2 4)))
  (let x 'z
    (test= 9 (case x z 9 10))
    (test= 7 (case x a 1 b 2 7))
    (test= 2 (case x a 1 (z) 2 7))
    (test= 2 (case x a 1 (b z) 2 7)))
  (let (n 0 f (fn () (inc n))) ; no multiple eval
    (test= 'b (case (f) 0 'a 1 'b 'c)))
  (test= 'b ((fn () (case 2 0 (do) 1 'a 2 'b)))))

(define-test while
  (let i 0
    (while (< i 5)
      (if (= i 3) (break) (inc i)))
    (test= 3 i)
    (while (< i 10)
      (inc i))
    (test= 10 i)
    (let a (while (< i 15) (inc i))
      (test= nil a)
      (test= 15 i))
    (let b
        (while (< i 20)
          (if (= i 19)
              (break)
            (inc i)))
      (test= nil b)
      (test= 19 i))))

(define-test for
  (let l ()
    (for i 5
      (add l i))
    (test= '(0 1 2 3 4) l))
  (test= '(0 1) (with l () (for i 2 (add l i))))
  (let (n 0 l '(a b c d e))
    (for i (# l)
      (inc n i)
      (set l '(a b c)))
    (test= 3 n)))

(define-test table
  (test= 10 (get (obj a: 10) 'a))
  (test= true (get (obj :a) 'a)))

(define-test empty
  (test= true (empty? ()))
  (test= true (empty? (obj)))
  (test= false (empty? '(1)))
  (test= false (empty? '(:a)))
  (test= false (empty? (obj :a)))
  (test= false (empty? '(b: false))))

(define-test at
  (let l '(a b c d)
    (test= 'a (at l 0))
    (test= 'b (at l 1))
    (set (at l 0) 9)
    (test= 9 (at l 0))
    (set (at l 3) 10)
    (test= 10 (at l 3))))

(define-test get-set
  (let t (obj)
    (set (get t 'foo) 'bar)
    (test= 'bar (get t 'foo))
    (test= 'bar (get t "foo"))
    (let k 'foo
      (test= 'bar (get t k)))
    (test= 'bar (get t (cat "f" "oo"))))
  (let (t1 (obj) t2 (obj))
    (set (get (or nil t1 t2) 'foo) 'bar)
    (test= 'bar (get t1 'foo))
    (test= nil (get t2 'foo))))

(define-test each
  (let t '(1 2 3 :a b: false)
    (let (a 0 b 0)
      (each (k v) t
        (if (number? k)
            (inc a)
          (inc b)))
      (test= 3 a)
      (test= 2 b))
    (let a 0
      (each x t (inc a))
      (test= 5 a)))
  (let t '((1) (2) b: (3))
    (each x t
      (test= false (atom? x)))
    (each (x) t
      (test= false (atom? x)))
    (each ((x)) t
      (test= true (number? x)))))

(define-test step
  (let n 0
    (step x '(1 2 3) (inc n x))
    (test= 6 n))
  (let n 0
    (step (x y) '((1 2) (3 4))
      (inc n (+ x y)))
    (test= 10 n))
  (let xs ()
    (let l (list (obj a: 1 b: 2) (obj a: 2 b: 4))
      (step (:a :b) l
        (add xs (+ a b))))
    (test= '(3 6) xs))
  (let l '(a b)
    (step x l
      (if (= x 'a) (add l 'c)
          (= x 'c) (add l 'd)))
    (test= '(a b c d) l)))

(define-test inc
  (let x 2 (inc x) (test= 3 x))
  (let x 2 (test= 3 (inc x)))
  (let x 2 (test= 4 (inc x 2)))
  (let x 2 (test= 2 (inc x 0))))

(define-test dec
  (let x 2 (dec x) (test= 1 x))
  (let x 2 (test= 1 (dec x)))
  (let x 4 (test= 2 (dec x 2)))
  (let x 2 (test= 2 (dec x 0))))

(define-test fn
  (let f (fn (n) (+ n 10))
    (test= 20 (f 10))
    (test= 30 (f 20))
    (test= 40 ((fn (n) (+ n 10)) 30))
    (test= '(2 3 4) (map (fn (x) (+ x 1)) '(1 2 3)))))

(define-test define
  (define x 20)
  (define f () 42)
  (test= 20 x)
  (test= 42 (f))
  ((fn ()
     (define f () 38)
     (test= 38 (f))))
  (test= 42 (f)))

(define-test return
  (let a ((fn () 17))
    (test= 17 a))
  (let a ((fn () (if true 10 20)))
    (test= 10 a))
  (let a ((fn () (while false (blah))))
    (test= nil a))
  (let a 11
    (let b ((fn () (inc a)))
      (test= 12 b)
      (test= 12 a)))
  (test= 1 ((fn () (return (if true (return 1) 2))))))

(define-test guard
  (let-macro ((guard1 (x)
                (let-unique (ok v)
                  `(let ((,ok ,v) (guard ,x))
                     (list ,ok (if ,ok ,v (get ,v 'message)))))))
    (test= (list false "") (guard1 (error)))
    (test= (list false "") (guard1 (error nil)))
    (test= (list false "false") (guard1 (error false)))
    (test= (list false "true") (guard1 (error true)))
    (test= (list false "42") (guard1 (error 42)))
    (test= '(true 42) (guard1 42))
    (test= '(false foo) (guard1 (error "foo")))
    (test= '(false foo) (guard1 (do (error "foo") (error "baz"))))
    (test= '(false baz) (guard1 (do (guard1 (error "foo")) (error "baz"))))
    (test= '(true 42) (guard1 (if true 42 (error "baz"))))
    (test= '(false baz) (guard1 (if false 42 (error "baz"))))))

(define-test let
  (let a 10
    (test= 10 a))
  (let (a 10)
    (test= 10 a))
  (let (a 11
        b 12)
    (test= 11 a)
    (test= 12 b))
  (let (a 1)
    (test= 1 a)
    (let (a 2)
      (test= 2 a))
    (test= 1 a))
  (let (a 1)
    (let (a 2)
      (let (a 3)
        (test= a 3))
      (test= a 2))
    (test= a 1))
  (let (a 20)
    (test= 20 a)
    (let (a (+ a 7))
      (test= 27 a))
    (let (a (+ a 10))
      (test= 30 a))
    (test= 20 a))
  (test= 10 (let (a 10) a))
  (let (a (let (b 12) b))
    (test= 12 a))
  (let (a (let (a 10) a))
    (test= 10 a))
  (let (a (+ (let (a 0)
               (set a 10)
               (+ a 2))
             3))
    (test= a 15))
  ((fn (zz)
     (test= 20 zz)
     (let (zz 21)
       (test= 21 zz))
     (test= 20 zz))
   20)
  (let q 9
    ((fn ()
       (let q 10
         (test= 10 q))
       (test= 9 q))))
  (test= 0 (+ (abs -1)
              (let abs (fn (x) x)
                (abs -1)))))

(define-test with
  (test= 10 (with x 9 (inc x))))

(define-test let-when
  (test= nil (let-when frips (= 'a 'b) 19))
  (test= 19 (let-when frips 20 (- frips 1)))
  (test= 20 (let-when (a b) '(19 20) b))
  (test= nil (let-when (a b) nil b))
  (test= 123 (let-when a 0 (+ a 123))))

(define zzop 99)
(define zzap 100)

(let (zzop 10
      zzap (+ zzop 10)
      (zza zzb) '(1 2 3 a: 10 b: 20))
  (define-test let-toplevel1
    (test= 10 zzop)
    (test= 20 zzap)
    (test= 1 zza)
    (test= 2 zzb)))

(define-test let-toplevel
  (test= 99 zzop)
  (test= 100 zzap))

(define-test reserved
  (let (end 'zz
        try 'yy
        return 99)
    (test= 'zz end)
    (test= 'yy try)
    (test= '99 return))
  (define var (if end return)
    (return (+ if end return)))
  (test= 6 (var 1 2 3))
  (define 1+ (x)
    (+ x 1))
  (test= 6 (1+ 5)))

(define-test destructuring
  (let ((a b c) '(1 2 3))
    (test= 1 a)
    (test= 2 b)
    (test= 3 c))
  (let ((w (x (y) z)) '(1 (2 (3) 4)))
    (test= 1 w)
    (test= 2 x)
    (test= 3 y)
    (test= 4 z))
  (let ((a b rest: c) '(1 2 3 4))
    (test= '(3 4) c))
  (let ((w (x rest: y) rest: z) '(1 (2 3 4) 5 6 7))
    (test= '(3 4) y)
    (test= '(5 6 7) z))
  (let ((:foo) (obj foo: 99))
    (test= 99 foo))
  (let ((:foo) (list foo: 99))
    (test= 99 foo))
  (let ((foo: a) (obj foo: 99))
    (test= 99 a))
  (let ((foo: (a b)) (obj foo: '(98 99)))
    (test= 98 a)
    (test= 99 b))
  (let ((:foo bar: (:baz))
        (obj foo: 42 bar: '(99 :baz)))
    (test= 42 foo)
    (test= true baz))
  (let ((a (b :foo) :bar)
        (list 10 (list 20 foo: 17) bar: '(1 2 3)))
    (test= 10 a)
    (test= 20 b)
    (test= 17 foo)
    (test= '(1 2 3) bar))
  (let yy (list 1 2 3)
    (let ((xx yy rest: zz) yy)
      (test= 1 xx)
      (test= 2 yy)
      (test= '(3) zz))))

(define-test let-macro
  (let-macro ((a () 17)
              (b (a) `(+ ,a 10)))
    (test= 17 (a))
    (test= 42 (b 32))
    (let-macro ((a () 1))
      (test= 1 (a)))
    (test= 17 (a)))
  (let-macro ((a () 18))
    (let (b (fn () 20))
      (test= 18 (a))
      (test= 20 (b))))
  (let-macro ((a (x)
                (let (x 10)
                  (set x 20))
                `(+ ,x 1)))
    (test= 2 (a 1))))

(define-test let-symbol
  (let-symbol (a 17
               b (+ 10 7))
    (test= 17 a)
    (test= 17 b)
    (let-symbol (a 1)
      (test= 1 a))
    (test= 17 a))
  (let-symbol (a 18)
    (let (b 20)
      (test= 18 a)
      (test= 20 b))))

(define-test define-symbol
  (define-symbol zzz 42)
  (test= zzz 42))

(define-test macros-and-symbols
  (let-symbol (a 1)
    (let-macro ((a () 2))
      (test= 2 (a)))
    (test= 1 a))
  (let-macro ((a () 2))
    (let-symbol (a 1)
      (test= 1 a))
    (test= 2 (a))))

(define-test macros-and-let
  (let a 10
    (test= a 10)
    (let-macro ((a () 12))
      (test= 12 (a)))
    (test= a 10))
  (let b 20
    (test= b 20)
    (let-symbol (b 22)
      (test= 22 b))
    (test= b 20))
  (let-macro ((c () 30))
    (test= 30 (c))
    (let c 32
      (test= 32 c))
    (test= 30 (c)))
  (let-symbol (d 40)
    (test= 40 d)
    (let d 42
      (test= 42 d))
    (test= 40 d)))

(define-test let-unique
  (let-unique (ham chap)
    (test= '__ham2 ham)
    (test= '__chap1 chap)
    (let-unique (ham)
      (test= '__ham3 ham))))

(define-test literals
  (test= true true)
  (test= false false)
  (test= true (< -inf -1e10))
  (test= false (< inf -1e10))
  (test= false (= nan nan))
  (test= true (nan? nan))
  (test= true (nan? (- nan)))
  (test= true (nan? (* nan 20)))
  (test= -inf (- inf))
  (test= inf (- -inf))
  (let (Inf 1 NaN 2 -Inf 'a -NaN 'b)
    (test= Inf 1)
    (test= NaN 2)
    (test= -Inf 'a)
    (test= -NaN 'b)))

(define-test add
  (let l ()
    (add l 'a)
    (add l 'b)
    (add l 'c)
    (test= '(a b c) l)
    (test= nil (add () 'a))))

(define-test drop
  (let l '(a b c)
    (test= 'c (drop l))
    (test= 'b (drop l))
    (test= 'a (drop l))
    (test= nil (drop l))))

(define-test last
  (test= 3 (last '(1 2 3)))
  (test= nil (last ()))
  (test= 'c (last '(a b c))))

(define-test join
  (test= '(1 2 3) (join '(1 2) '(3)))
  (test= '(1 2) (join () '(1 2)))
  (test= () (join () ()))
  (test= () (join nil nil))
  (test= () (join nil ()))
  (test= () (join))
  (test= () (join ()))
  (test= '(1) (join '(1) nil))
  (test= '(a) (join '(a) ()))
  (test= '(a) (join nil '(a)))
  (test= '(a) (join '(a)))
  (test= '(a :b) (join '(a) (obj :b)))
  (test= '(a b :b) (join '(a) '(b :b)))
  (test= '(a b: 10) (join '(a :b) (obj b: 10)))
  (test= '(b: 10) (join (obj :b) '(b: 10)))
  (let t (join '(a b: 1) '(b c: 2))
    (test= 1 (get t 'b))
    (test= 2 (get t 'c))
    (test= 'b (at t 1))))

(define-test reverse
  (test= () (reverse ()))
  (test= '(3 2 1) (reverse '(1 2 3)))
  (test= '(3 2 1 :a) (reverse '(1 2 3 :a))))

(define-test map
  (test= () (map (fn (x) x) ()))
  (test= '(1) (map (fn (x) x) '(1)))
  (test= '(2 3 4) (map (fn (x) (+ x 1)) '(1 2 3)))
  (test= '(2 3 4 a: 5) (map (fn (x) (+ x 1)) (list 1 2 3 a: 4)))
  (test= '(:a) (map (fn (x) x) '(:a)))
  (test= '(b: false) (map (fn (x) x) '(b: false)))
  (test= '(:a b: false) (map (fn (x) x) '(:a b: false)))
  (let evens (fn (x) (when (= (% x 2) 0) x))
    (test= '(2 4 6) (map evens '(1 2 3 4 5 6)))
    (test= '(2 4 6 b: 8) (map evens '(1 2 3 4 5 6 a: 7 b: 8)))))

(define-test cut
  (test= () (cut ()))
  (test= '(a) (cut '(a)))
  (test= '(b c) (cut '(a b c) 1))
  (test= '(b c) (cut '(a b c d) 1 3))
  (test= '(1 2 3) (cut '(1 2 3) 0 10))
  (test= '(1) (cut '(1 2 3) -4 1))
  (test= '(1 2 3) (cut '(1 2 3) -4))
  (test= '(2 :a) (cut '(1 2 :a) 1))
  (test= '(:a b: 2) (cut '(:a b: 2)))
  (let t '(1 2 3)
    (test= () (cut t (# t))))
  (let t '(1 2 3 :a)
    (test= '(:a) (cut t (# t)))))

(define-test clip
  (test= "uux" (clip "quux" 1))
  (test= "uu" (clip "quux" 1 3))
  (test= "" (clip "quux" 5))
  (test= "ab" (clip "ab" 0 4))
  (test= "ab" (clip "ab" -4 4))
  (test= "a" (clip "ab" -1 1)))

(define-test search
  (test= nil (search "" "a"))
  (test= 0 (search "" ""))
  (test= 0 (search "a" ""))
  (test= 0 (search "abc" "a"))
  (test= 2 (search "abcd" "cd"))
  (test= nil (search "abcd" "ce"))
  (test= nil (search "abc" "z")))

(define-test split
  (test= () (split "" ""))
  (test= () (split "" ","))
  (test= (list "a") (split "a" ","))
  (test= (list "a" "") (split "a," ","))
  (test= (list "a" "b") (split "a,b" ","))
  (test= (list "a" "b" "") (split "a,b," ","))
  (test= (list "a" "b") (split "azzb" "zz"))
  (test= (list "a" "b" "") (split "azzbzz" "zz")))

(define-test reduce
  (test= 'a (either (reduce (fn (a b) (+ a b)) '()) 'a))
  (test= 'a (either (reduce (fn (a b) (+ a b)) '(a)) 'a))
  (test= 6 (reduce (fn (a b) (+ a b)) '(1 2 3)))
  (test= '(1 (2 3))
         (reduce
          (fn (a b) (list a b))
          '(1 2 3)))
  (test= '(1 2 3 4 5)
         (reduce
          (fn (a b) (join a b))
          '((1) (2 3) (4 5)))))

(define-test keep
  (test= () (keep (fn (x) x) ()))
  (test= '(0 1 2) (keep (fn (x) x) '(0 1 2)))
  (test= '((1) (2 3)) (keep some? '(() (1) () (2 3))))
  (let evens (fn (x) (= (% x 2) 0))
    (test= '(6) (keep evens '(5 6 7)))
    (test= '(2 4 6) (keep evens '(1 2 3 4 5 6)))
    (test= '(2 4 6 b: 8) (keep evens '(1 2 3 4 5 6 a: 7 b: 8)))))

(define-test in?
  (test= true (in? 'x '(x y z)))
  (test= true (in? 7 '(5 6 7)))
  (test= nil (in? 'baz '(no can do))))

(define-test find
  (test= nil (find (fn (x) x) ()))
  (test= 7 (find (fn (x) x) '(7)))
  (test= true (find (fn (x) (= x 7)) '(2 4 7)))
  (test= true (find (fn (x) (= x 7)) '(2 4 foo: 7)))
  (test= true (find (fn (x) (= x true)) '(2 4 :bar)))
  (test= true (in? 7 '(2 4 7)))
  (test= true (in? 7 '(2 4 foo: 7)))
  (test= true (in? true '(2 4 :bar))))

(define-test first
  (test= nil (first (fn (x) x) ()))
  (test= 7 (first (fn (x) x) '(7)))
  (test= true (first (fn (x) (= x 7)) '(2 4 7)))
  (test= 4 (first (fn (x) (and (> x 3) x)) '(1 2 3 4 5 6))))

(define-test sort
  (test= '(a b c) (sort '(c a b)))
  (test= '(3 2 1) (sort '(1 2 3) >)))

(define-test type
  (test= true (string? "abc"))
  (test= false (string? 17))
  (test= false (string? '(a)))
  (test= false (string? true))
  (test= false (string? (obj)))
  (test= false (number? "abc"))
  (test= true (number? 17))
  (test= false (number? '(a)))
  (test= false (number? true))
  (test= false (number? (obj)))
  (test= false (boolean? "abc"))
  (test= false (boolean? 17))
  (test= false (boolean? '(a)))
  (test= true (boolean? true))
  (test= false (boolean? (obj)))
  (test= true (atom? nil))
  (test= true (atom? "abc"))
  (test= true (atom? 42))
  (test= true (atom? true))
  (test= false (atom? (fn ())))
  (test= false (atom? '(1)))
  (test= false (atom? (obj)))
  (test= true (obj? (obj a: 10)))
  (test= false (obj? null))
  (test= false (obj? 1))
  (test= false (obj? 'zz))
  (test= false (obj? (fn ()))))

(define-test str
  (define f (x) x)
  (let (l () l2 ())
    (set (get l f) true)
    (set (get l2 (target js: (str l) lua: l)) true)
    (let k (target js: f lua: 'function)
      (test= (cat "(" k ": true)") (str l))
      (test= (cat "((" k ": true): true)") (str l2)))))

(define-test apply
  (test= 4 (apply (fn (a b) (+ a b)) '(2 2)))
  (test= '(2 2) (apply (fn a a) '(2 2)))
  (let t '(1)
    (set (get t 'foo) 17)
    (test= 17 (apply (fn a (get a 'foo)) t)))
  (test= 42 (apply (fn (:foo) foo) (list foo: 42)))
  (test= 42 (apply (fn ((:foo)) foo) (list (list foo: 42))))
  (test= 116 (apply + 1 5 '(100 10))))

(define-test eval
  (let eval (get compiler 'eval)
    (test= 4 (eval '(+ 2 2)))
    (test= 5 (eval '(let a 3 (+ 2 a))))
    (test= 9 (eval '(do (define x 7) (+ x 2))))
    (test= 6 (eval '(apply + '(1 2 3))))))

(define-test call
  (let f (fn () 42)
    (test= 42 (call f)))
  (let fs (list (fn () 1) (fn () 10))
    (test= '(1 10) (map call fs)))
  (let f (fn (x y) (+ x y 1))
    (test= 42 (call f 40 1))))

(define-test parameters
  (test= 42 ((fn ((a)) a) '(42)))
  (let f (fn (a (b c)) (list a b c))
    (test= '(1 2 3) (f 1 '(2 3))))
  (let f (fn (a (b rest: c) rest: d) (list a b c d))
    (test= '(1 2 (3 4) (5 6 7)) (f 1 '(2 3 4) 5 6 7)))
  (let f (fn (a (b rest: c) rest: d) (list a b c d))
    (test= '(1 2 (3 4) (5 6 7)) (apply f '(1 (2 3 4) 5 6 7))))
  (test= '(3 4) ((fn (a b rest: c) c) 1 2 3 4))
  (let f (fn (w (x rest: y) rest: z) (list y z))
    (test= '((3 4) (5 6 7)) (f 1 '(2 3 4) 5 6 7)))
  (test= 42 ((fn (:foo) foo) foo: 42))
  (test= 42 (apply (fn (:foo) foo) '(foo: 42)))
  (test= 42 ((fn ((:foo)) foo) (list foo: 42)))
  (let f (fn (a bar: b (:foo)) (list a b foo))
    (test= '(10 20 42) (f 10 bar: 20 (list foo: 42))))
  (let f (fn (a bar: b (:foo)) (list a b foo))
    (test= '(10 20 42) (apply f '(10 bar: 20 (foo: 42)))))
  (test= 1 ((fn (a :b) (+ (or a 0) b)) b: 1))
  (test= 1 (apply (fn (a :b) (+ (or a 0) b)) (list b: 1)))
  (let f (fn args args)
    (test= '(1 2 3) (f 1 2 3)))
  (let f (fn args args)
    (test= '(1 2 3) (apply f (list 1 2 3))))
  (let l ()
    (define f (:a) (add l a) a)
    (define g (a b :c) (add l (list a b c)) c)
    (test= 42 (f a: (g (f a: 10) (f a: 20) c: (f a: 42))))
    (test= '(10 20 42 (10 20 42) 42) l))
  (let l nil
    (define f x (set l x))
    (define g (a b) (+ a b))
    (f (g 1 2 foo: 7))
    (test= '(3) l))
  (let l nil
    (define f x (set l x) 10)
    (define g (a b) (+ (f) a b))
    (test= 13 (g 1 2 foo: 7))
    (test= '() l))
  (let l nil
    (define f (x rest: ys) (set l ys))
    (define g () nil)
    (f 1 'x g)
    (test= (list 'x g) l)))

(define-test hd?
  (test= true (hd? '(%if 1 2) '%if))
  (test= false (hd? '((%if) 1 2) '(%if)))
  (test= false (hd? 42 '%if))
  (test= true (hd? '() nil)))

(run)
