mod common;
use grift_eval::*;
use common::{eval_to_num, eval_is_true, eval_is_false};
#[test]
fn test_1_1_syntax_preserves_creation_context() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
let result = eval.eval_str(r#"
(let ((x 10))
(let ((stx (syntax x)))
(let ((x 20))
(define-syntax use-stx
(lambda (_) stx))
(use-stx))))
"#).unwrap();
let val = lisp.get(result).unwrap();
assert_eq!(val.as_number(), Some(10),
"Syntax object should preserve binding to x=10, not pick up x=20");
}
#[test]
fn test_1_2_syntax_through_procedures() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define (make-syntax-getter val)
(let ((x val))
(syntax x)))
"#).unwrap();
eval.eval_str("(define stx1 (make-syntax-getter 100))").unwrap();
eval.eval_str("(define stx2 (make-syntax-getter 200))").unwrap();
eval.eval_str(r#"
(define-syntax test1
(lambda (_) stx1))
"#).unwrap();
eval.eval_str(r#"
(define-syntax test2
(lambda (_) stx2))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(test1)"), 100,
"test1 should evaluate to 100 (from first make-syntax-getter call)");
assert_eq!(eval_to_num(&lisp, &mut eval, "(test2)"), 200,
"test2 should evaluate to 200 (from second make-syntax-getter call)");
}
#[test]
fn test_2_1_helper_function_returns_syntax() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define (helper)
(let ((secret 42))
(define (inner) (syntax secret))
(inner)))
"#).unwrap();
eval.eval_str("(define borrowed-stx (helper))").unwrap();
eval.eval_str(r#"
(define-syntax use-borrowed
(lambda (_) borrowed-stx))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(use-borrowed)"), 42,
"Borrowed syntax object should resolve 'secret' to 42");
}
#[test]
fn test_2_2_syntax_in_data_structures() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define (make-stx-list)
(let ((a 1) (b 2) (c 3))
(list (syntax a) (syntax b) (syntax c))))
"#).unwrap();
eval.eval_str("(define stx-list (make-stx-list))").unwrap();
eval.eval_str(r#"
(define-syntax sum-stx-list
(lambda (_)
(syntax-case stx-list ()
((x y z)
(syntax (+ x y z))))))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(sum-stx-list)"), 6,
"Syntax objects in list should preserve their bindings (1+2+3=6)");
}
#[test]
fn test_5_1_nested_macro_expansion() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax inner-macro
(lambda (stx)
(syntax-case stx ()
((kw expr)
(syntax expr)))))
"#).unwrap();
eval.eval_str(r#"
(define-syntax outer-macro
(lambda (stx)
(syntax-case stx ()
((kw val)
(let ((captured-val (syntax val)))
(with-syntax ((v captured-val))
(syntax (inner-macro v))))))))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(let ((x 555)) (outer-macro x))"), 555,
"Syntax objects should preserve scope through nested macro expansion");
}
#[test]
fn test_5_2_template_reconstruction() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax reconstruct
(lambda (stx)
(syntax-case stx ()
((kw (a b c))
(with-syntax ((new-a (syntax a))
(new-b (syntax b))
(new-c (syntax c)))
(syntax (+ new-a new-b new-c)))))))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(let ((a 1) (b 2) (c 3)) (reconstruct (a b c)))"), 6,
"Reconstructed syntax objects should maintain original bindings (1+2+3=6)");
}
#[test]
fn test_6_1_bound_identifier_eq() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax test-bound-id-eq
(lambda (stx)
(syntax-case stx ()
((kw)
(let ((id1 (datum->syntax (syntax kw) 'foo))
(id2 (datum->syntax (syntax kw) 'foo)))
(if (bound-identifier=? id1 id2)
(syntax #t)
(syntax #f)))))))
"#).unwrap();
assert!(eval_is_true(&lisp, &mut eval, "(test-bound-id-eq)"),
"Two identifiers created with same name and template should be bound-identifier=?");
}
#[test]
fn test_6_2_free_identifier_eq() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax test-free-id-eq
(lambda (stx)
(syntax-case stx ()
((kw x)
(let ((user-x (syntax x)))
(let ((x 999))
(let ((macro-x (syntax x)))
(if (free-identifier=? user-x macro-x)
(syntax #t)
(syntax #f)))))))))
"#).unwrap();
assert!(eval_is_false(&lisp, &mut eval, "(let ((x 111)) (test-free-id-eq x))"),
"x from user vs. x from macro's internal binding should not be free-identifier=?");
}
#[test]
#[ignore = "requires call-site environment propagation through syntax-case"]
fn test_7_1_syntax_with_mutation() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str("(define box #f)").unwrap();
eval.eval_str(r#"
(define-syntax capture
(lambda (stx)
(syntax-case stx ()
((kw val)
(begin
(set! box (syntax val))
(syntax #f))))))
"#).unwrap();
eval.eval_str("(let ((x 333)) (capture x))").unwrap();
eval.eval_str(r#"
(define-syntax use-captured
(lambda (_) box))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(let ((x 444)) (use-captured))"), 333,
"Captured syntax object should preserve scope (x=333, not x=444)");
}
#[test]
fn test_7_2_deep_nesting() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
let result = eval.eval_str(r#"
(let ((level1 1))
(let ((level2 2))
(let ((level3 3))
(let ((stx (syntax (+ level1 level2 level3))))
(define-syntax eval-stx
(lambda (_) stx))
(let ((level1 100) (level2 200) (level3 300))
(eval-stx))))))
"#).unwrap();
let val = lisp.get(result).unwrap();
assert_eq!(val.as_number(), Some(6),
"Deep nesting should preserve original bindings (1+2+3=6, not 100+200+300=600)");
}
#[test]
fn test_7_3_recursive_macro() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax count-down
(lambda (stx)
(syntax-case stx ()
((kw n)
(let ((num (syntax->datum (syntax n))))
(if (= num 0)
(syntax 'done)
(with-syntax ((m-1 (- num 1)))
(syntax (cons n (count-down m-1))))))))))
"#).unwrap();
let result = eval.eval_str("(count-down 3)").unwrap();
let car1 = lisp.car(result).unwrap();
assert_eq!(lisp.get(car1).unwrap().as_number(), Some(3));
let cdr1 = lisp.cdr(result).unwrap();
let car2 = lisp.car(cdr1).unwrap();
assert_eq!(lisp.get(car2).unwrap().as_number(), Some(2));
let cdr2 = lisp.cdr(cdr1).unwrap();
let car3 = lisp.car(cdr2).unwrap();
assert_eq!(lisp.get(car3).unwrap().as_number(), Some(1));
let cdr3 = lisp.cdr(cdr2).unwrap();
assert!(lisp.symbol_matches(cdr3, "done").unwrap(),
"Recursive macro should produce (3 2 1 . done)");
}
#[test]
fn test_basic_hygiene() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax swap
(lambda (stx)
(syntax-case stx ()
((swap a b)
(syntax (let ((temp a))
(set! a b)
(set! b temp)))))))
"#).unwrap();
eval.eval_str("(define x 1)").unwrap();
eval.eval_str("(define y 2)").unwrap();
eval.eval_str("(define temp 999)").unwrap();
eval.eval_str("(swap x y)").unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "x"), 2);
assert_eq!(eval_to_num(&lisp, &mut eval, "y"), 1);
assert_eq!(eval_to_num(&lisp, &mut eval, "temp"), 999,
"Macro's temp should not capture user's temp");
}
#[test]
fn test_pattern_substitution() {
let lisp: Lisp<30000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.eval_str(r#"
(define-syntax my-let
(lambda (stx)
(syntax-case stx ()
((my-let ((name val) ...) body ...)
(syntax ((lambda (name ...) body ...) val ...))))))
"#).unwrap();
assert_eq!(eval_to_num(&lisp, &mut eval, "(my-let ((x 5) (y 10)) (+ x y))"), 15);
}