;; lib/scheme/tests/syntax.sx — exercises Phase 3 syntactic operators. (define scm-syn-pass 0) (define scm-syn-fail 0) (define scm-syn-fails (list)) (define scm-syn-test (fn (name actual expected) (if (= actual expected) (set! scm-syn-pass (+ scm-syn-pass 1)) (begin (set! scm-syn-fail (+ scm-syn-fail 1)) (append! scm-syn-fails {:name name :actual actual :expected expected}))))) (define scm-syn-eval (fn (src env) (scheme-eval (scheme-parse src) env))) (define scm-syn-eval-all (fn (src env) (scheme-eval-program (scheme-parse-all src) env))) ;; Test env with arithmetic primitives. (define scm-syn-env (fn () (let ((env (scheme-make-env))) (scheme-env-bind! env "+" (fn (args) (+ (first args) (nth args 1)))) (scheme-env-bind! env "-" (fn (args) (- (first args) (nth args 1)))) (scheme-env-bind! env "*" (fn (args) (* (first args) (nth args 1)))) (scheme-env-bind! env "/" (fn (args) (/ (first args) (nth args 1)))) (scheme-env-bind! env "<=" (fn (args) (<= (first args) (nth args 1)))) (scheme-env-bind! env "<" (fn (args) (< (first args) (nth args 1)))) (scheme-env-bind! env "=" (fn (args) (= (first args) (nth args 1)))) (scheme-env-bind! env "list" (fn (args) args)) (scheme-env-bind! env "cons" (fn (args) (cons (first args) (nth args 1)))) (scheme-env-bind! env "car" (fn (args) (first (first args)))) (scheme-env-bind! env "cdr" (fn (args) (rest (first args)))) env))) ;; ── if ─────────────────────────────────────────────────────────── (scm-syn-test "if: true" (scm-syn-eval "(if #t 1 2)" (scm-syn-env)) 1) (scm-syn-test "if: false" (scm-syn-eval "(if #f 1 2)" (scm-syn-env)) 2) (scm-syn-test "if: predicate" (scm-syn-eval "(if (<= 1 2) 99 nope)" (scm-syn-env)) 99) (scm-syn-test "if: no else returns nil" (scm-syn-eval "(if #f 99)" (scm-syn-env)) nil) (scm-syn-test "if: truthy non-#f" (scm-syn-eval "(if 0 'yes 'no)" (scm-syn-env)) "yes") ;; ── define ─────────────────────────────────────────────────────── (scm-syn-test "define: bind value" (let ((env (scm-syn-env))) (scm-syn-eval "(define x 42)" env) (scm-syn-eval "x" env)) 42) (scm-syn-test "define: function sugar" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define (double n) (+ n n)) (double 21)" env)) 42) (scm-syn-test "define: redefine" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define x 1) (define x 2) x" env)) 2) ;; ── set! ───────────────────────────────────────────────────────── (scm-syn-test "set!: mutate" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define x 1) (set! x 99) x" env)) 99) (scm-syn-test "set!: walks parent" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define x 1) ((lambda () (set! x 100))) x" env)) 100) (scm-syn-test "set!: errors on unbound" (guard (e (true :raised)) (scm-syn-eval-all "(set! never-defined 1)" (scm-syn-env))) :raised) ;; ── begin ──────────────────────────────────────────────────────── (scm-syn-test "begin: empty returns nil" (scm-syn-eval "(begin)" (scm-syn-env)) nil) (scm-syn-test "begin: returns last" (scm-syn-eval "(begin 1 2 3)" (scm-syn-env)) 3) (scm-syn-test "begin: side effects in order" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define x 0) (begin (set! x 1) (set! x 2) (set! x 3)) x" env)) 3) ;; ── lambda ─────────────────────────────────────────────────────── (scm-syn-test "lambda: identity" (scm-syn-eval "((lambda (x) x) 42)" (scm-syn-env)) 42) (scm-syn-test "lambda: arithmetic" (scm-syn-eval "((lambda (x y) (+ x y)) 3 4)" (scm-syn-env)) 7) (scm-syn-test "lambda: zero args" (scm-syn-eval "((lambda () 99))" (scm-syn-env)) 99) (scm-syn-test "lambda: multi-body" (scm-syn-eval "((lambda (x) (define t (+ x 1)) (+ t t)) 5)" (scm-syn-env)) 12) (scm-syn-test "lambda: rest-arg as bare symbol" (scm-syn-eval "((lambda args args) 1 2 3)" (scm-syn-env)) (list 1 2 3)) ;; ── closures ───────────────────────────────────────────────────── (scm-syn-test "closure: captures binding" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define (make-adder n) (lambda (x) (+ x n))) ((make-adder 10) 5)" env)) 15) (scm-syn-test "closure: counter via set!" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define (make-counter) (define n 0) (lambda () (set! n (+ n 1)) n)) (define c (make-counter)) (c) (c) (c)" env)) 3) (scm-syn-test "closure: curried" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define curry+ (lambda (a) (lambda (b) (lambda (c) (+ a (+ b c)))))) (((curry+ 1) 2) 3)" env)) 6) ;; ── recursion ──────────────────────────────────────────────────── (scm-syn-test "recursive: factorial 5" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define (fact n) (if (<= n 1) 1 (* n (fact (- n 1))))) (fact 5)" env)) 120) (scm-syn-test "recursive: factorial 10" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define (fact n) (if (<= n 1) 1 (* n (fact (- n 1))))) (fact 10)" env)) 3628800) (scm-syn-test "recursive: list length" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define (len xs) (if (= 0 (- 0 0)) (if (= xs (quote ())) 0 (+ 1 (len (cdr xs)))) 0)) (len '(a b c d))" env)) 4) ;; ── quote vs eval distinction ──────────────────────────────────── (scm-syn-test "quote: list literal" (scm-syn-eval "'(1 2 3)" (scm-syn-env)) (list 1 2 3)) (scm-syn-test "quote: nested" (scm-syn-eval "'(a (b c) d)" (scm-syn-env)) (list "a" (list "b" "c") "d")) (scm-syn-test "quote: symbol vs evaluated" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define x 42) 'x" env)) "x") ;; ── let / let* ─────────────────────────────────────────────────── (scm-syn-test "let: returns body" (scm-syn-eval "(let ((x 5)) (+ x 1))" (scm-syn-env)) 6) (scm-syn-test "let: multiple bindings" (scm-syn-eval "(let ((x 3) (y 4)) (+ x y))" (scm-syn-env)) 7) (scm-syn-test "let: parallel (RHS sees outer)" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define x 1) (let ((x 10) (y x)) y)" env)) 1) (scm-syn-test "let: bindings don't leak" (let ((env (scm-syn-env))) (scm-syn-eval-all "(define x 1) (let ((x 99)) x) x" env)) 1) (scm-syn-test "let*: sequential" (scm-syn-eval "(let* ((x 1) (y (+ x 1)) (z (+ y 1))) z)" (scm-syn-env)) 3) (scm-syn-test "let*: shadow earlier" (scm-syn-eval "(let* ((x 1) (x 2)) x)" (scm-syn-env)) 2) ;; ── cond / when / unless ───────────────────────────────────────── (scm-syn-test "cond: first match" (scm-syn-eval "(cond (#f 1) (#t 2) (#t 3))" (scm-syn-env)) 2) (scm-syn-test "cond: else" (scm-syn-eval "(cond (#f 1) (else 99))" (scm-syn-env)) 99) (scm-syn-test "cond: untaken not evaluated" (scm-syn-eval "(cond (#t 7) (nope ignored))" (scm-syn-env)) 7) (scm-syn-test "cond: no match returns nil" (scm-syn-eval "(cond (#f 1) (#f 2))" (scm-syn-env)) nil) (scm-syn-test "cond: test-only clause" (scm-syn-eval "(cond (42))" (scm-syn-env)) 42) (scm-syn-test "when: true" (scm-syn-eval "(when #t 1 2 3)" (scm-syn-env)) 3) (scm-syn-test "when: false" (scm-syn-eval "(when #f nope)" (scm-syn-env)) nil) (scm-syn-test "unless: false" (scm-syn-eval "(unless #f 42)" (scm-syn-env)) 42) (scm-syn-test "unless: true" (scm-syn-eval "(unless #t nope)" (scm-syn-env)) nil) ;; ── and / or ───────────────────────────────────────────────────── (scm-syn-test "and: empty" (scm-syn-eval "(and)" (scm-syn-env)) true) (scm-syn-test "and: all truthy returns last" (scm-syn-eval "(and 1 2 3)" (scm-syn-env)) 3) (scm-syn-test "and: short-circuit on #f" (scm-syn-eval "(and 1 #f nope)" (scm-syn-env)) false) (scm-syn-test "or: empty" (scm-syn-eval "(or)" (scm-syn-env)) false) (scm-syn-test "or: first truthy" (scm-syn-eval "(or #f 42 nope)" (scm-syn-env)) 42) (scm-syn-test "or: all #f" (scm-syn-eval "(or #f #f #f)" (scm-syn-env)) false) (define scm-syn-tests-run! (fn () {:total (+ scm-syn-pass scm-syn-fail) :passed scm-syn-pass :failed scm-syn-fail :fails scm-syn-fails}))