#lang scheme
(require redex/reduction-semantics)
(provide subst subst-n)

(define-language subst-lang
  (x variable))

(define-metafunction subst-lang
  [(subst-n ((x_1 any_1) (x_2 any_2) ... any_3))
   (subst (x_1 any_1 (subst-n ((x_2 any_2) ... any_3))))]
  [(subst-n (any_3)) any_3])

(define-metafunction subst-lang
  ;; 1. x_1 bound, so don't continue in λ body
  [(subst (x_1 any_1 (λ (x_2 ... x_1 x_3 ...) any_2)))
   (λ (x_2 ... x_1 x_3 ...) any_2)
   (side-condition (not (member (term x_1) (term (x_2 ...)))))]
  ;; 2. general purpose capture avoiding case
  [(subst (x_1 any_1 (λ (x_2 ...) any_2)))
   ,(term-let ([(x_new ...)
                (variables-not-in (term (x_1 any_1 any_2)) 
                                  (term (x_2 ...)))])
              (term 
               (λ (x_new ...) 
                 (subst (x_1 any_1 (subst-vars ((x_2 x_new) ... any_2)))))))]
  ;; 3. replace x_1 with e_1
  [(subst (x_1 any_1 x_1)) any_1]
  ;; 4. x_1 and x_2 are different, so don't replace
  [(subst (x_1 any_1 x_2)) x_2]
  ;; the last two cases cover all other expression forms
  [(subst (x_1 any_1 (any_2 ...)))
   ((subst (x_1 any_1 any_2)) ...)]
  [(subst (x_1 any_1 any_2)) any_2])

(define-metafunction subst-lang
  [(subst-vars ((x_1 any_1) x_1)) any_1]
  [(subst-vars ((x_1 any_1) (any_2 ...))) ((subst-vars ((x_1 any_1) any_2)) ...)]
  [(subst-vars ((x_1 any_1) any_2)) any_2]
  [(subst-vars ((x_1 any_1) (x_2 any_2) ... any_3)) 
   (subst-vars ((x_1 any_1) (subst-vars ((x_2 any_2) ... any_3))))]
  [(subst-vars (any)) any])

(begin
  (test-equal (term (subst (x y x))) (term y))
  (test-equal (term (subst (x y z))) (term z))
  (test-equal (term (subst (x y (x (y z))))) (term (y (y z))))
  (test-equal (term (subst (x y ((λ (x) x) ((λ (y1) y1) (λ (x) z))))))
        (term ((λ (x) x) ((λ (y2) y2) (λ (x) z)))))
  (test-equal (term (subst (x y (if0 (+ 1 x) x x))))
        (term (if0 (+ 1 y) y y)))
  (test-equal (term (subst (x (λ (z) y) (λ (y) x))))
        (term (λ (y1) (λ (z) y))))
  (test-equal (term (subst (x 1 (λ (y) x))))
        (term (λ (y) 1)))
  (test-equal (term (subst (x y (λ (y) x))))
        (term (λ (y1) y)))
  (test-equal (term (subst (x (λ (y) y) (λ (z) (z2 z)))))
        (term (λ (z1) (z2 z1))))
  (test-equal (term (subst (x (λ (z) z) (λ (z) (z1 z)))))
        (term (λ (z2) (z1 z2))))
  (test-equal (term (subst (x z (λ (z) (z1 z)))))
        (term (λ (z2) (z1 z2))))
  (test-equal (term (subst (x3 5 (λ (x2) x2))))
        (term (λ (x1) x1)))
  (test-equal (term (subst (z * (λ (z x) 1))))
        (term (λ (z x) 1)))
  (test-equal (term (subst (q (λ (x) z) (λ (z x) q))))
        (term (λ (z1 x1) (λ (x) z))))
  (test-equal (term (subst (x 1 (λ (x x) x))))
        (term (λ (x x) x)))
  (test-results))
