diff --git a/Tests/KiteCoreTests/EvalTests.swift b/Tests/KiteCoreTests/EvalTests.swift new file mode 100644 index 0000000..b5ed93f --- /dev/null +++ b/Tests/KiteCoreTests/EvalTests.swift @@ -0,0 +1,139 @@ +import XCTest +@testable import KiteCore + +final class EvalTests: XCTestCase { + private func eval(_ src: String) throws -> Value { + try Interpreter().evalString(src) + } + + private func evalShow(_ src: String) throws -> String { + displayString(try eval(src)) + } + + func testArithmetic() throws { + XCTAssertEqual(try evalShow("(+ 1 2 3 4)"), "10") + XCTAssertEqual(try evalShow("(- 10 3 2)"), "5") + XCTAssertEqual(try evalShow("(* 2 3 4)"), "24") + XCTAssertEqual(try evalShow("(- 5)"), "-5") + XCTAssertEqual(try evalShow("(+ )"), "0") + } + + func testRationalArithmetic() throws { + XCTAssertEqual(try evalShow("(/ 3 2)"), "3/2") + XCTAssertEqual(try evalShow("(/ 6 2)"), "3") + XCTAssertEqual(try evalShow("(+ 1/2 1/3)"), "5/6") + XCTAssertEqual(try evalShow("(* 2/3 3/4)"), "1/2") + } + + func testRealArithmetic() throws { + XCTAssertEqual(try evalShow("(+ 1.5 2.5)"), "4.0") + XCTAssertEqual(try evalShow("(* 2 1.5)"), "3.0") + } + + func testComparisons() throws { + XCTAssertEqual(try evalShow("(< 1 2 3)"), "#t") + XCTAssertEqual(try evalShow("(< 1 3 2)"), "#f") + XCTAssertEqual(try evalShow("(= 2 2 2)"), "#t") + XCTAssertEqual(try evalShow("(>= 3 3 2)"), "#t") + } + + func testIntegerOps() throws { + XCTAssertEqual(try evalShow("(modulo 7 3)"), "1") + XCTAssertEqual(try evalShow("(modulo -7 3)"), "2") + XCTAssertEqual(try evalShow("(remainder -7 3)"), "-1") + XCTAssertEqual(try evalShow("(quotient 17 5)"), "3") + XCTAssertEqual(try evalShow("(expt 2 10)"), "1024") + XCTAssertEqual(try evalShow("(gcd 12 18)"), "6") + } + + func testDefineAndLambda() throws { + XCTAssertEqual(try evalShow("(define (square x) (* x x)) (square 9)"), "81") + XCTAssertEqual(try evalShow("((lambda (a b) (+ a b)) 3 4)"), "7") + XCTAssertEqual(try evalShow("((lambda args (length args)) 1 2 3)"), "3") + XCTAssertEqual(try evalShow("((lambda (a . rest) rest) 1 2 3)"), "(2 3)") + } + + func testClosuresAndSet() throws { + let counter = """ + (define (make-counter) + (let ((n 0)) + (lambda () (set! n (+ n 1)) n))) + (define c (make-counter)) + (c) (c) + (c) + """ + XCTAssertEqual(try evalShow(counter), "3") + } + + func testLetForms() throws { + XCTAssertEqual(try evalShow("(let ((a 1) (b 2)) (+ a b))"), "3") + XCTAssertEqual(try evalShow("(let* ((a 1) (b (+ a 1))) (* a b))"), "2") + XCTAssertEqual(try evalShow("(letrec ((e? (lambda (n) (if (= n 0) #t (o? (- n 1))))) (o? (lambda (n) (if (= n 0) #f (e? (- n 1)))))) (e? 10))"), "#t") + } + + func testCondCaseAndOr() throws { + XCTAssertEqual(try evalShow("(cond ((= 1 2) 'a) ((= 1 1) 'b) (else 'c))"), "b") + XCTAssertEqual(try evalShow("(cond ((assv 2 '((1 . a) (2 . b))) => cdr) (else 'none))"), "b") + XCTAssertEqual(try evalShow("(case 3 ((1 2) 'low) ((3 4) 'mid) (else 'high))"), "mid") + XCTAssertEqual(try evalShow("(and 1 2 3)"), "3") + XCTAssertEqual(try evalShow("(and 1 #f 3)"), "#f") + XCTAssertEqual(try evalShow("(or #f #f 7)"), "7") + XCTAssertEqual(try evalShow("(or #f #f)"), "#f") + } + + func testListOps() throws { + XCTAssertEqual(try evalShow("(car '(1 2 3))"), "1") + XCTAssertEqual(try evalShow("(cdr '(1 2 3))"), "(2 3)") + XCTAssertEqual(try evalShow("(cons 1 '(2 3))"), "(1 2 3)") + XCTAssertEqual(try evalShow("(append '(1 2) '(3 4) '(5))"), "(1 2 3 4 5)") + XCTAssertEqual(try evalShow("(reverse '(1 2 3))"), "(3 2 1)") + XCTAssertEqual(try evalShow("(length '(a b c d))"), "4") + XCTAssertEqual(try evalShow("(list-ref '(a b c) 1)"), "b") + XCTAssertEqual(try evalShow("(map (lambda (x) (* x x)) '(1 2 3))"), "(1 4 9)") + XCTAssertEqual(try evalShow("(filter even? '(1 2 3 4 5 6))"), "(2 4 6)") + XCTAssertEqual(try evalShow("(assq 'b '((a . 1) (b . 2)))"), "(b . 2)") + XCTAssertEqual(try evalShow("(member 3 '(1 2 3 4))"), "(3 4)") + } + + func testPredicates() throws { + XCTAssertEqual(try evalShow("(null? '())"), "#t") + XCTAssertEqual(try evalShow("(pair? '(1))"), "#t") + XCTAssertEqual(try evalShow("(pair? '())"), "#f") + XCTAssertEqual(try evalShow("(eq? 'a 'a)"), "#t") + XCTAssertEqual(try evalShow("(equal? '(1 2 (3)) '(1 2 (3)))"), "#t") + XCTAssertEqual(try evalShow("(eqv? 2 2)"), "#t") + XCTAssertEqual(try evalShow("(number? 5)"), "#t") + XCTAssertEqual(try evalShow("(procedure? car)"), "#t") + } + + func testQuasiquote() throws { + XCTAssertEqual(try evalShow("(let ((x 5)) `(a ,x c))"), "(a 5 c)") + XCTAssertEqual(try evalShow("(let ((xs '(2 3))) `(1 ,@xs 4))"), "(1 2 3 4)") + } + + func testApply() throws { + XCTAssertEqual(try evalShow("(apply + '(1 2 3 4))"), "10") + XCTAssertEqual(try evalShow("(apply + 1 2 '(3 4))"), "10") + XCTAssertEqual(try evalShow("(apply max '(3 1 4 1 5 9 2 6))"), "9") + } + + func testStrings() throws { + XCTAssertEqual(try evalShow("(string-append \"foo\" \"bar\")"), "foobar") + XCTAssertEqual(try evalShow("(string-length \"hello\")"), "5") + XCTAssertEqual(try evalShow("(substring \"hello\" 1 4)"), "ell") + XCTAssertEqual(try evalShow("(number->string 42)"), "42") + XCTAssertEqual(try evalShow("(string->number \"3/4\")"), "3/4") + } + + func testUnboundVariableThrows() throws { + XCTAssertThrowsError(try eval("(+ 1 undefined-thing)")) + } + + func testCapturedOutput() throws { + let interp = Interpreter() + var captured = "" + interp.output = { captured += $0 } + try interp.evalString("(display \"result: \") (display (+ 40 2)) (newline)") + XCTAssertEqual(captured, "result: 42\n") + } +}