mod common;
use grift_eval::*;
use common::eval_to_num;
#[test]
fn test_a1_syntax_in_variable() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define my-stx
(let ((x 42))
(syntax x)))
"#).unwrap();
eval.eval_str(r#"
(define-syntax use-my-stx
(lambda (_) my-stx))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(use-my-stx)"), 42,
"Syntax stored in variable should preserve its binding");
}
#[test]
fn test_a2_syntax_in_pair() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define stx-pair
(let ((a 10) (b 20))
(cons (syntax a) (syntax b))))
"#).unwrap();
eval.eval_str(r#"
(define-syntax use-car
(lambda (_) (car stx-pair)))
"#).unwrap();
eval.eval_str(r#"
(define-syntax use-cdr
(lambda (_) (cdr stx-pair)))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(use-car)"), 10);
assert_eq!(eval_to_num(&lisp, &mut eval, "(use-cdr)"), 20);
}
#[test]
fn test_a3_syntax_in_vector() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define stx-vec
(let ((x 100))
(vector (syntax x) (syntax x))))
"#).unwrap();
eval.eval_str(r#"
(define-syntax use-first
(lambda (_) (vector-ref stx-vec 0)))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(use-first)"), 100);
}
#[test]
fn test_b1_function_returning_syntax() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define (make-const-syntax n)
(let ((val n))
(syntax val)))
"#).unwrap();
eval.eval_str("(define stx-5 (make-const-syntax 5))").unwrap();
eval.eval_str("(define stx-10 (make-const-syntax 10))").unwrap();
eval.eval_str(r#"
(define-syntax get-5
(lambda (_) stx-5))
"#).unwrap();
eval.eval_str(r#"
(define-syntax get-10
(lambda (_) stx-10))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(get-5)"), 5);
assert_eq!(eval_to_num(&lisp, &mut eval, "(get-10)"), 10);
}
#[test]
fn test_b2_conditional_syntax_creation() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define (make-syntax-or-literal use-syntax)
(let ((x 42))
(if use-syntax
(syntax x)
x)))
"#).unwrap();
eval.eval_str("(define my-stx (make-syntax-or-literal #t))").unwrap();
eval.eval_str(r#"
(define-syntax use-stx
(lambda (_) my-stx))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(use-stx)"), 42);
}
#[test]
fn test_b3_recursive_syntax_creation() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define (nested-syntax depth)
(let ((x depth))
(if (= depth 0)
(syntax x)
(nested-syntax (- depth 1)))))
"#).unwrap();
eval.eval_str("(define deep-stx (nested-syntax 5))").unwrap();
eval.eval_str(r#"
(define-syntax use-deep
(lambda (_) deep-stx))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(use-deep)"), 0);
}
#[test]
fn test_c1_multiple_expansions_hygiene() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax with-temp
(lambda (stx)
(syntax-case stx ()
((_ val body)
(syntax (let ((temp val))
body))))))
"#).unwrap();
let result = eval.eval_str(r#"
(with-temp 10
(with-temp 20
(+ temp temp)))
"#).unwrap();
assert_eq!(lisp.get(result).unwrap().as_number(), Some(40));
}
#[test]
fn test_c2_pattern_shadowing() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax shadow-test
(lambda (stx)
(syntax-case stx ()
((kw x)
(let ((outer-x (syntax x)))
(let ((x 999)) ; Shadows pattern variable x
(let ((inner-x (syntax x)))
;; outer-x refers to pattern x, inner-x refers to local x
(if (free-identifier=? outer-x inner-x)
(syntax 'same)
(syntax 'different)))))))))
"#).unwrap();
let result = eval.eval_str("(shadow-test 123)").unwrap();
assert!(lisp.symbol_matches(result, "different").unwrap(),
"Pattern variable and local binding should be different identifiers");
}
#[test]
fn test_c3_recursive_macro_hygiene() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax sum-to
(lambda (stx)
(syntax-case stx ()
((kw n)
(let ((num (syntax->datum (syntax n))))
(if (= num 0)
(syntax 0)
(with-syntax ((m (- num 1)))
(syntax (+ n (sum-to m))))))))))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(sum-to 3)"), 6);
}
#[test]
fn test_d1_datum_to_syntax_context() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax make-ref
(lambda (stx)
(syntax-case stx ()
((kw name)
(datum->syntax (syntax kw) (syntax->datum (syntax name)))))))
"#).unwrap();
eval.eval_str("(define foo 42)").unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(make-ref foo)"), 42);
}
#[test]
fn test_d2_syntax_datum_roundtrip() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax structure-test
(lambda (stx)
(syntax-case stx ()
((kw (a b c))
(let ((datum (syntax->datum (syntax (a b c)))))
(if (and (pair? datum)
(eq? (car datum) 'a)
(eq? (cadr datum) 'b)
(eq? (caddr datum) 'c))
(syntax 'correct)
(syntax 'wrong)))))))
"#).unwrap();
let result = eval.eval_str("(structure-test (a b c))").unwrap();
assert!(lisp.symbol_matches(result, "correct").unwrap());
}
#[test]
fn test_e1_with_syntax_computed() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax double-literal
(lambda (stx)
(syntax-case stx ()
((kw n)
(let ((doubled (* 2 (syntax->datum (syntax n)))))
(with-syntax ((result doubled))
(syntax result)))))))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(double-literal 21)"), 42);
}
#[test]
fn test_e2_with_syntax_multiple() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax swap-and-add
(lambda (stx)
(syntax-case stx ()
((kw a b)
(with-syntax ((x (syntax b))
(y (syntax a)))
(syntax (+ x y)))))))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(swap-and-add 10 20)"), 30);
}
#[test]
fn test_f1_empty_ellipsis() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax list-or-zero
(lambda (stx)
(syntax-case stx ()
((kw x ...)
(syntax (list x ...))))))
"#).unwrap();
let result = eval.eval_str("(list-or-zero)").unwrap();
assert!(lisp.get(result).unwrap().is_nil(),
"Empty ellipsis should produce empty list");
assert_eq!(eval_to_num(&lisp, &mut eval, "(length (list-or-zero 1 2 3))"), 3);
}
#[test]
fn test_f2_parallel_ellipsis() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax zip-add
(lambda (stx)
(syntax-case stx ()
((kw (a b) ...)
(syntax (list (+ a b) ...))))))
"#).unwrap();
let result = eval.eval_str("(zip-add (1 10) (2 20) (3 30))").unwrap();
let first = lisp.car(result).unwrap();
assert_eq!(lisp.get(first).unwrap().as_number(), Some(11));
}
#[test]
fn test_f3_fixed_then_ellipsis() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax first-then-rest
(lambda (stx)
(syntax-case stx ()
((kw first rest ...)
(syntax (cons first (list rest ...)))))))
"#).unwrap();
let result = eval.eval_str("(first-then-rest 1 2 3 4)").unwrap();
let car = lisp.car(result).unwrap();
let cdr = lisp.cdr(result).unwrap();
assert_eq!(lisp.get(car).unwrap().as_number(), Some(1));
assert_eq!(lisp.list_len(cdr).unwrap(), 3);
}
#[test]
fn test_g1_different_contexts_not_bound_eq() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax check-different-contexts
(lambda (stx)
(syntax-case stx ()
((kw)
(let ((id1 (datum->syntax (syntax kw) 'x))
(id2 (datum->syntax (syntax kw) 'x)))
;; Same name, same template context - should be bound-identifier=?
(if (bound-identifier=? id1 id2)
(syntax 'same)
(syntax 'different)))))))
"#).unwrap();
let result = eval.eval_str("(check-different-contexts)").unwrap();
assert!(lisp.symbol_matches(result, "same").unwrap(),
"Identifiers with same name and template context should be bound-identifier=?");
}
#[test]
fn test_g2_free_identifier_same_binding() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str("(define global-x 100)").unwrap();
eval.eval_str(r#"
(define-syntax check-free-eq
(lambda (stx)
(syntax-case stx ()
((kw a b)
(if (free-identifier=? (syntax a) (syntax b))
(syntax 'same)
(syntax 'different))))))
"#).unwrap();
let result = eval.eval_str("(check-free-eq global-x global-x)").unwrap();
assert!(lisp.symbol_matches(result, "same").unwrap());
}
#[test]
fn test_h1_macro_defining_macro() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax define-constant-macro
(lambda (stx)
(syntax-case stx ()
((kw name val)
(syntax (define-syntax name
(lambda (_) (syntax val))))))))
"#).unwrap();
eval.eval_str("(define-constant-macro forty-two 42)").unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(forty-two)"), 42);
}
#[test]
fn test_h2_parameterized_macro_generator() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax define-adder-macro
(lambda (stx)
(syntax-case stx ()
((kw name amount)
(syntax (define-syntax name
(lambda (inner-stx)
(syntax-case inner-stx ()
((_ x) (syntax (+ x amount)))))))))))
"#).unwrap();
eval.eval_str("(define-adder-macro add-5 5)").unwrap();
eval.eval_str("(define-adder-macro add-10 10)").unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(add-5 100)"), 105);
assert_eq!(eval_to_num(&lisp, &mut eval, "(add-10 100)"), 110);
}
#[test]
fn test_i1_deeply_nested_let_syntax() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
let result = eval.eval_str(r#"
(let-syntax ((a (lambda (stx) (syntax 1))))
(let-syntax ((b (lambda (stx) (syntax (+ (a) 2)))))
(let-syntax ((c (lambda (stx) (syntax (+ (b) 3)))))
(c))))
"#).unwrap();
assert_eq!(lisp.get(result).unwrap().as_number(), Some(6));
}
#[test]
fn test_i2_large_ellipsis() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax sum-all
(lambda (stx)
(syntax-case stx ()
((kw x ...)
(syntax (+ x ...))))))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval,
"(sum-all 1 2 3 4 5 6 7 8 9 10)"), 55);
}
#[test]
fn test_j1_compile_time_arithmetic() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax static-compute
(lambda (stx)
(syntax-case stx ()
((_ n m)
(datum->syntax stx
(+ (syntax->datum (syntax n))
(syntax->datum (syntax m))))))))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(static-compute 10 20)"), 30,
"datum->syntax should properly convert computed numeric results");
}
#[test]
fn test_j2_flexible_macro_simple_arities() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax flexible-macro
(lambda (stx)
(syntax-case stx ()
((_ a)
(syntax (list a)))
((_ a b)
(syntax (list a b)))
((_ a b c)
(syntax (list a b c))))))
"#).unwrap();
let result1 = eval.eval_str("(flexible-macro 1)").unwrap();
assert_eq!(lisp.list_len(result1), Ok(1));
let result2 = eval.eval_str("(flexible-macro 1 2)").unwrap();
assert_eq!(lisp.list_len(result2), Ok(2));
let result3 = eval.eval_str("(flexible-macro 1 2 3)").unwrap();
assert_eq!(lisp.list_len(result3), Ok(3));
}
#[test]
fn test_j3_nested_ellipsis_patterns() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax matrix-transpose
(lambda (stx)
(syntax-case stx ()
((_ ((a ...) ...))
(syntax (quote ((a ...) ...)))))))
"#).unwrap();
let result = eval.eval_str("(matrix-transpose ((1 2 3) (4 5 6)))").unwrap();
assert_eq!(lisp.list_len(result), Ok(2),
"Nested ellipsis patterns should preserve structure");
let first = lisp.car(result).unwrap();
assert_eq!(lisp.list_len(first), Ok(3));
let first_car = lisp.car(first).unwrap();
assert_eq!(lisp.get(first_car).unwrap().as_number(), Some(1));
}
#[test]
fn test_j4_fenders_guards() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax only-positive
(lambda (stx)
(syntax-case stx ()
((_ n)
(> (syntax->datum (syntax n)) 0)
(syntax (quote positive)))
((_ n)
(syntax (quote non-positive))))))
"#).unwrap();
let result_pos = eval.eval_str("(only-positive 5)").unwrap();
assert!(lisp.symbol_matches(result_pos, "positive").unwrap(),
"Fender should match positive numbers");
let result_neg = eval.eval_str("(only-positive -3)").unwrap();
assert!(lisp.symbol_matches(result_neg, "non-positive").unwrap(),
"Fender should reject negative numbers and fall through to next clause");
let result_zero = eval.eval_str("(only-positive 0)").unwrap();
assert!(lisp.symbol_matches(result_zero, "non-positive").unwrap(),
"Fender should reject zero and fall through to next clause");
}
#[test]
fn test_j5_complex_literal_matching() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax match-literals
(lambda (stx)
(syntax-case stx (foo bar baz)
((_ foo x) (syntax (quote (matched-foo x))))
((_ bar x) (syntax (quote (matched-bar x))))
((_ baz x) (syntax (quote (matched-baz x))))
((_ other x) (syntax (quote (matched-other other x)))))))
"#).unwrap();
let result_foo = eval.eval_str("(match-literals foo 1)").unwrap();
let foo_car = lisp.car(result_foo).unwrap();
assert!(lisp.symbol_matches(foo_car, "matched-foo").unwrap(),
"Literal 'foo' should be matched as literal");
let result_bar = eval.eval_str("(match-literals bar 2)").unwrap();
let bar_car = lisp.car(result_bar).unwrap();
assert!(lisp.symbol_matches(bar_car, "matched-bar").unwrap(),
"Literal 'bar' should be matched as literal");
let result_qux = eval.eval_str("(match-literals qux 3)").unwrap();
let qux_car = lisp.car(result_qux).unwrap();
assert!(lisp.symbol_matches(qux_car, "matched-other").unwrap(),
"Non-literal 'qux' should be captured by pattern variable 'other'");
}
#[test]
fn test_j6_identifier_comparison() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax test-hygiene
(lambda (stx)
(syntax-case stx ()
((_ x)
(let ((id1 (datum->syntax (syntax x) (quote temp)))
(id2 (datum->syntax (syntax x) (quote temp))))
(if (bound-identifier=? id1 id2)
(syntax (lambda (temp) temp))
(syntax (lambda (y) y))))))))
"#).unwrap();
eval.eval_str("(define f (test-hygiene dummy))").unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(f 42)"), 42,
"bound-identifier=? should identify that id1 and id2 are the same identifier");
}
#[test]
fn test_i1_datum_to_syntax_object_basic() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax make-ref
(lambda (stx)
(syntax-case stx ()
((kw name)
(datum->syntax-object (syntax kw) (syntax-object->datum (syntax name)))))))
"#).unwrap();
eval.eval_str("(define foo 42)").unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(make-ref foo)"), 42);
}
#[test]
fn test_i2_define_structure() {
let lisp: Lisp<80000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax define-structure
(lambda (x)
(define gen-id
(lambda (template-id . args)
(datum->syntax-object template-id
(string->symbol
(apply string-append
(map (lambda (x)
(if (string? x)
x
(symbol->string
(syntax-object->datum x))))
args))))))
(syntax-case x ()
((_ name field ...)
(with-syntax
((constructor (gen-id (syntax name) "make-" (syntax name)))
(predicate (gen-id (syntax name) (syntax name) "?"))
((access ...)
(map (lambda (x) (gen-id x (syntax name) "-" x))
(syntax (field ...))))
((assign ...)
(map (lambda (x) (gen-id x "set-" (syntax name) "-" x "!"))
(syntax (field ...))))
(structure-length (+ (length (syntax (field ...))) 1))
((index ...) (let f ((i 1) (ids (syntax (field ...))))
(if (null? ids)
'()
(cons i (f (+ i 1) (cdr ids)))))))
(syntax (begin
(define constructor
(lambda (field ...)
(vector 'name field ...)))
(define predicate
(lambda (x)
(and (vector? x)
(= (vector-length x) structure-length)
(eq? (vector-ref x 0) 'name))))
(define access
(lambda (x)
(vector-ref x index)))
...
(define assign
(lambda (x update)
(vector-set! x index update)))
...)))))))
"#).unwrap();
eval.eval_str("(define-structure tree left right)").unwrap();
eval.eval_str(r#"
(define t
(make-tree
(make-tree 0 1)
(make-tree 2 3)))
"#).unwrap();
let result = eval.eval_str("(tree? t)").unwrap();
assert!(matches!(lisp.get(result).unwrap(), Value::True));
let result = eval.eval_str("(tree? (tree-left t))").unwrap();
assert!(matches!(lisp.get(result).unwrap(), Value::True));
assert_eq!(eval_to_num(&lisp, &mut eval, "(tree-left (tree-left t))"), 0);
assert_eq!(eval_to_num(&lisp, &mut eval, "(tree-right (tree-left t))"), 1);
assert_eq!(eval_to_num(&lisp, &mut eval, "(tree-left (tree-right t))"), 2);
assert_eq!(eval_to_num(&lisp, &mut eval, "(tree-right (tree-right t))"), 3);
eval.eval_str("(set-tree-left! t 0)").unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(tree-left t)"), 0);
assert_eq!(eval_to_num(&lisp, &mut eval, "(tree-left (tree-right t))"), 2);
}