#lang racket

(require redex
         "grammar.rkt"
         "meta.rkt"
         "reduce.rkt")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Abbreviations:

;; The classic (let ([v (call/cc call/cc)]) 
;;              ((call/cc call/cc) v))
(define call/cc-loop
  `(<>
    () []
    (% 0
       ((λ (v) ((call/cc (λ (x) (call/cc x 0)) 0) v))
        (call/cc (λ (x) (call/cc x 0)) 0))
       (λ (x) x))))

(define (show prog)
  (stepper :-> prog))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Tests
(define (test desc expr result)
  (let ([r (car (apply-reduction-relation* :-> expr))])
    (unless (equal? r result)
      (printf "~s:\n~s\n" desc expr)
      (printf "=> ~s\n\n" r)
      (error 'test "expected ~s" result)))
  (set! tests-passed (add1 tests-passed)))

(define-syntax (test-subst stx)
  (syntax-case stx ()
    [(_ in out)
     #`(test-subst/proc #,(syntax-line stx) (term in) (term out))]))
(define (test-subst/proc line in out)
  (unless (equal? in out)
    (error 'test-subst "test on line ~a failed:\n  ~s\n  ~s"
           line in out))
  (set! tests-passed (+ tests-passed 1)))

(define tests-passed 0)

;; Subst ----------------------------------------

(define (substitution-tests)
  (test-subst (subst x x 1) 1)
  (test-subst (subst x y 1) 1)
  (test-subst (subst x y (x x)) (y y))
  (test-subst (subst x y (λ (x) x)) (λ (x) x))
  (test-subst (subst x y (λ (z x p) x)) (λ (z x p) x))
  (test-subst (subst x y (λ (y) x)) (λ (y1) y))
  (test-subst (subst x y (if x x x)) (if y y y))
  (test-subst (subst* ((a 1) (b 2) (c 3)) (if a b c)) (if 1 2 3))
  (test-subst (subst* ((a 1) (b 2) (c 3)) (dw q a b c)) (dw q 1 2 3))
  (test-subst (subst* ((a 1) (b 2) (c 3)) (list (λ (p) a) (λ (q) b) (λ (r) c)))
              (list (λ (p) 1) (λ (q) 2) (λ (r) 3)))
  (test-subst (subst* ((a 1) (b 2) (c 3)) (begin a (set! z (% a b c))))
              (begin 1 (set! z (% 1 2 3))))
  (test-subst (subst* ((a 1) (b 2) (c 3)) (wcm (((λ (x) a) (λ (x) b))) c))
              (wcm (((λ (x) 1) (λ (x) 2))) 3))
  (test-subst (subst* ((a 1) (b 2)) (cont (λ (x) b) hole))
              (cont (λ (x) 2) hole)))

;; Basic ----------------------------------------

(define (basic-tests)
  (test "(λx.e)[y←v] ≠ λy.(e[x←y][y←v])"
        '(<>
          ([k 4]) []
          (((λ (k1) (λ (k) k))
            (λ () k))
           0))
        '(<> ([k 4]) [] 0))
  (test "(λx.e)[y←v] ≠ λz.(e[x←z][y←v]) if z ∈ FV(e)"
        '(<>
          ([k2 5])
          ()
          (((λ (k1) (λ (k) k2))
            (λ () k))
           0))
        '(<> ([k2 5]) [] 5))
  (test "basic dw"
        '(<>
          () []
          (dynamic-wind (λ () (print 1))
                        (λ () (print 2))
                        (λ () (print 3))))
        '(<> () (1 2 3) #f))
  (test "call/cc dw"
        '(<>
          () []
          (%
           0
           (call/cc
            (λ (k)
              (dynamic-wind (λ () (print 1))
                            (λ () (k 0))
                            (λ () (print 3))))
            0)
           (λ (x) x)))
        '(<> () (1 3) 0))
  (test "abort"
        '(<>
          () []
          (%
           0             
           (+ 10 (abort 0 7))
           (λ (x) (+ x 1))))
        '(<>
          () []
          8))
  (test "abort inner"
        '(<>
          () []
          (%
           0             
           (+ 10 
              (%
               1         
               (abort 1 7)
               (λ (x) (+ x 1))))
           (λ (x) (+ x 2))))
        '(<>
          () []
          18))
  (test "abort outer"
        '(<>
          () []
          (%
           0             
           (+ 10 
              (%
               1
               (abort 0 7)
               (λ (x) (+ x 1))))
           (λ (x) (+ x 2))))
        '(<>
          () []
          9))
  (test "abort inner with same tag"
        '(<>
          () []
          (%
           0             
           (+ 10 
              (%
               0
               (abort 0 7)
               (λ (x) (+ x 1))))
           (λ (x) (+ x 2))))
        '(<>
          () []
          18))
  (test "abort handler in tail position"
        '(<>
          () []
          (%
           0
           (call/cm
            100 1
            (λ ()
              (%
               1
               (abort 1 (λ ()
                          (call/cm
                           100 2
                           (λ ()
                             (current-marks 100 0)))))
               (λ (f)
                 (f)))))
           (λ (x) x)))
        '(<>
          () []
          (list 2)))
  (test "abort dw"
        '(<>
          () []
          (%
           0
           (call/cc
            (λ (k)
              (dynamic-wind (λ () (print 1))
                            (λ () (abort 0 7))
                            (λ () (print 3))))
            0)
           (λ (x) (+ x 1))))
        '(<> () (1 3) 8))
  (test "abort tag eval"
        '(<>
          () []
          (% (print 1) 2 3))
        '(<> () [1] 2))
  (test "abort handler eval"
        '(<>
          () []
          (% 1 2 (print 3)))
        '(<> () [3] 2))
  (test "call/cc 2 levels dw"
        '(<>
          ()
          []
          (%
           0
           (call/cc
            (λ (k)
              (dynamic-wind
               (λ () (print 1))
               (λ ()
                 (dynamic-wind
                  (λ () (print 2))
                  (λ ()
                    (k 10))
                  (λ () (print 3))))
               (λ () (print 4))))
            0)
           (λ (x) x)))
        '(<> () [1 2 3 4] 10))
  (test "abort 2 levels dw"
        '(<>
          ()
          []
          (%
           0
           (dynamic-wind
            (λ () (print 1))
            (λ ()
              (dynamic-wind
               (λ () (print 2))
               (λ ()
                 (abort 0 10))
               (λ () (print 3))))
            (λ () (print 4)))
           (λ (x) (+ x 1))))
        '(<> () [1 2 3 4] 11))
  (test "in thunk isn't really in"
        '(<>
          () []
          (%
           0
           (call/cc
            (λ (k)
              (dynamic-wind
               (λ () (begin
                       (print 1)
                       (k 11)))
               (λ () (print 2))
               (λ () (print 3))))
            0)
           (λ (x) x)))
        '(<> () [1] 11))
  (test "in thunk isn't really in, but it's in surrounding"
        '(<>
          () []
          (%
           0
           (call/cc
            (λ (k)
              (dynamic-wind
               (λ () (print -1))
               (λ ()
                 (dynamic-wind
                  (λ () (begin
                          (print 1)
                          (k 11)))
                  (λ () (print 2))
                  (λ () (print 3))))
               (λ () (print -2))))
            0)
           (λ (x) x)))
        '(<> () [-1 1 -2] 11))
  (test "dw shared during jump"
        '(<>
          () []
          (% 0 
             (dynamic-wind 
              (λ () (print 0))
              (λ () ((call/cc (λ (f) f) 0) (λ (x) 10)))
              (λ () (print 1)))
             (λ (x) x)))
        '(<> () [0 1] 10))
  (test "dw not shared during jump"
        '(<>
          () []
          (% 0 
             ((dynamic-wind 
               (λ () (print 0))
               (λ () (call/cc (λ (f) f) 0))
               (λ () (print 1)))
              (λ (x) 10))
             (λ (x) x)))
        '(<> () [0 1 0 1] 10))
  (test "composable captures continuation marks"
        '(<>
          () []
          (%
           100
           ((λ (k) (k (λ (v) (current-marks 0 100)))) 
            (% 0 
               (call/cm 0 100 
                        (λ ()
                          ((call/comp (λ (k) (λ (v) k)) 0)
                           99)) )
               (λ (x) x)))
           (λ (x) x)))
        '(<> () [] (list 100)))
  (test "continuation marks wrapping % not captured"
        '(<>
          () []
          (%
           101
           ((λ (k) (k (λ (v) (current-marks 0 101)))) 
            (call/cm 0 100 
                     (λ ()
                       (% 0 
                          ((call/comp (λ (k) (λ (v) k)) 0)
                           99)
                          (λ (x) x)))))
           (λ (x) x)))
        '(<> () [] (list)))
  (test "visible marks restricted by prompt tag"
        '(<>
          () []
          (% 101
             (call/cm 0 100
                      (λ ()
                        (% 102
                           (call/cm 0 99
                                    (λ () 
                                      (current-marks 0 102)))
                           (λ (x) x))))
             (λ (x) x)))
        '(<> () [] (list 99)))
  (test "visible marks not restricted by other prompt tags"
        '(<>
          () []
          (% 101
             (call/cm 0 100
                      (λ ()
                        (% 102
                           (call/cm 0 99
                                    (λ () 
                                      (current-marks 0 101)))
                           (λ (x) x))))
             (λ (x) x)))
        '(<> () [] (list 99 100)))
  (test "getting marks fails if there's no prompt with the given tag"
        '(<>
          () []
          (% 101
             (call/cm 0 100
                      (λ ()
                        (current-marks 0 102)))
             (λ (x) x)))
        '(<> () [] (% 101 (wcm ((0 100)) (current-marks 0 102)) (λ (x) x))))
  (test "pre and post thunks in a composable continuation"
        '(<> 
          () []
          ((λ (f)
             (f (λ (v) 10)))
           (%
            0
            (dynamic-wind
             (λ () (print 1))
             (λ ()
               (call/comp (λ (k) k) 0))
             (λ () (print 2)))
            (λ (x) x))))
        '(<>
          ()
          [1 2 1 2]
          (λ (v) 10)))
  (test "prompt enclosing prompt-tag expression"
        '(<> () [] 
             (% 0
                (% (abort 0 1) 2 3)
                (λ (x) x)))
        '(<> () [] 1))
  (test "prompt enclosing prompt-handler expression"
        '(<> () []
             (% 0 
                (begin 
                  (% 0 1 (abort 0 2))
                  (print 3))
                (λ (x) x)))
        '(<> () [] 2))
  (test "prompt-tag position in continuation-marks context"
        '(<> () []
             (% 0 
                (call/cm 
                 1 2
                 (λ ()
                   (% (abort 0 (current-marks 1 0))
                      3
                      4)))
                (λ (x) x)))
        '(<> () [] (list 2)))
  (test "prompt-handler position in continuation-marks context"
        '(<> () []
             (% 0 
                (call/cm 
                 1 2
                 (λ ()
                   (call/cm
                    1 3
                    (% 0
                       4
                       (abort 0 (current-marks 1 0))))))
                (λ (x) x)))
        '(<> () [] (list 2)))
  (test "if-test position in continuation-marks context"
        '(<> ()
             []
             (% 0
                (call/cm 
                 1 2 
                 (λ () (if (abort 0 (current-marks 1 0)) 3 4)))
                (λ (x) x)))
        '(<> () [] (list 2)))
  (test "dw in continuation-marks context"
        '(<> ()
             []
             (% 0
                (call/cm 1 2 
                         (λ () 
                           (dynamic-wind
                            (λ () #f)
                            (λ () (current-marks 1 0))
                            (λ () #t))))
                (λ (x) x)))
        '(<> () [] (list 2)))
  (test "wcm without key in continuation-marks context"
        '(<> ()
             []
             (% 0
                (wcm ([1 2])
                     ((λ (x) x)
                      (wcm ([3 4])
                           (current-marks 3 0))))
                (λ (x) x)))
        '(<> () [] (list 4))))

;; R6RS dynamic-wind ----------------------------------------

(define (r6rs-dw-tests)
  (test "out thunk is really out"
        '(<>
          ([n 0]
           [do-jump? #t]
           [k-out #f])
          []
          (%
           0
           (begin
             (begin
               (call/cc
                (λ (k)
                  (dynamic-wind
                   (λ () (set! n (+ n 1)))
                   (λ () (k 99))
                   (λ ()
                     (begin
                       (set! n (+ n 1))
                       (call/cc (λ (k) (set! k-out k)) 0)))))
                0)
               (if do-jump?
                   (begin
                     (set! do-jump? #f)
                     (k-out 0))
                   11))
             (begin
               (set! k-out #f)
               n))
           (λ (x) x)))
        '(<> ([n 2] [do-jump? #f] [k-out #f]) [] 2))
  (test "out thunk is really out during trimming"
        '(<>
          ([n 0]
           [do-jump? #t]
           [k-out #f])
          []
          (%
           0
           (begin
             (call/cc
              (λ (k)
                (dynamic-wind
                 (λ () (set! n (+ n 1)))
                 (λ () (k 100))
                 (λ ()
                   (begin
                     (set! n (+ n 1))
                     (call/cc (λ (k) (set! k-out k)) 0)))))
              0)
             (begin
               (if do-jump?
                   (begin
                     (set! do-jump? #f)
                     (k-out 0))
                   11)
               (begin
                 (set! k-out #f)
                 n)))
           (λ (x) x)))
        '(<> ([n 2] [do-jump? #f] [k-out #f]) () 2))
  (test "jumping during the results of trimming, pre-thunk"
        '(<> 
          ([pre-count 0]
           [pre-jump? #f]
           [after-jump? #t]
           [grab? #t]
           [the-k #f])
          []
          (%
           0
           (begin
             (dynamic-wind
              (λ ()
                (begin
                  (set! pre-count (+ pre-count 1))
                  (if pre-jump?
                      (begin
                        (set! pre-jump? #f)
                        (begin
                          (set! after-jump? #f)
                          (the-k 999)))
                      999)))
              (λ () 
                (if grab?
                    (call/cc
                     (λ (k)
                       (begin
                         (set! grab? #f)
                         (set! the-k k)))
                     0)
                    999))
              (λ () (+ 0 10)))
             (begin
               (if after-jump?
                   (begin
                     (set! pre-jump? #t)
                     (the-k 999))
                   999)
               (begin
                 (set! the-k #f) ;; just to make testing simpler
                 pre-count)))
           (λ (x) x)))
        '(<>
          ([pre-count 3]
           [pre-jump? #f]
           [after-jump? #f]
           [grab? #f]
           [the-k #f])
          ()
          3))
  (test "jumping during the results of trimming, post-thunk"
        '(<>
          ([post-count 0] 
           [post-jump? #t]
           [jump-main? #t]
           [grab? #t]
           [the-k #f])
          []
          (%
           0
           (begin
             (begin
               (if grab?
                   (call/cc
                    (λ (k)
                      (begin
                        (set! grab? #f)
                        (set! the-k k)))
                    0)
                   999)
               (dynamic-wind
                (λ () (+ 0 1))
                (λ () 
                  (if jump-main?
                      (begin
                        (set! jump-main? #f)
                        (the-k 999))
                      999))
                (λ () 
                  (begin
                    (set! post-count (+ post-count 1))
                    (if post-jump?
                        (begin
                          (set! post-jump? #f)
                          (the-k 999))
                        999)))))
             (begin
               (set! the-k #f) ;; just to make testing simpler
               post-count))
           (λ (x) x)))
        '(<>
          ([post-count 2]
           [post-jump? #f]
           [jump-main? #f]
           [grab? #f]
           [the-k #f]) 
          []
          2))
  (test "hop out one level"
        '(<>
          ()
          []
          
          (%
           0
           ((dynamic-wind (λ () (print 0))
                          (λ () (call/cc (λ (k) k) 0))
                          (λ () (print 1)))
            (λ (y) (print 7)))
           (λ (x) x)))
        '(<>
          ()
          [0 1 0 1 7]
          #f))
  (test "hop out two levels"
        '(<> ()
             []
             (%
              0
              ((dynamic-wind 
                (λ () (print 1))
                (λ () 
                  (dynamic-wind
                   (λ () (print 2))
                   (λ () (call/cc (λ (k) k) 0))
                   (λ () (print 3))))
                (λ () (print 4)))
               (λ (y) (print 8)))
              (λ (x) x)))
        '(<>
          ()
          [1 2 3 4 1 2 3 4 8]
          #f))
  (test "don't duplicate tail"
        '(<>
          ()
          []
          (%
           0
           (dynamic-wind 
            (λ () (print 1))
            (λ () 
              ((dynamic-wind (λ () (print 2))
                             (λ () (call/cc (λ (k) k) 0))
                             (λ () (print 3)))
               (λ (y) (print 9))))
            (λ () (print 4)))
           (λ (x) x)))
        '(<>
          ()
          [1 2 3 2 3 9 4]
          #f))
  (test "don't duplicate tail, 2 deep"
        '(<>
          ()
          []
          
          (%
           0
           (dynamic-wind 
            (λ () (print 1))
            (λ () 
              (dynamic-wind 
               (λ () (print 2))
               (λ () 
                 ((dynamic-wind (λ () (print 3))
                                (λ () (call/cc (λ (k) k) 0))
                                (λ () (print 4)))
                  (λ (y) (print 9))))
               (λ () (print 5))))
            (λ () (print 6)))
           (λ (x) x)))
        '(<>
          ()
          [1 2 3 4 3 4 9 5 6]
          #f))
  (test "hop out and back into another one"
        '(<>
          ()
          []
          (%
           0
           ((λ (ok)
              (dynamic-wind (λ () (print 1))
                            (λ () (ok (λ (y) (print 9))))
                            (λ () (print 2))))
            (dynamic-wind (λ () (print 3))
                          (λ () (call/cc (λ (k) k) 0))
                          (λ () (print 4))))
           (λ (x) x)))
        '(<>
          ()
          [3 4 1 2 3 4 1 9 2]
          #f))
  (test "hop out one level and back in two levels"
        '(<>
          ()
          []
          (%
           0
           ((λ (ok)
              (dynamic-wind
               (λ () (print 1))
               (λ ()
                 (dynamic-wind
                  (λ () (print 2))
                  (λ () (ok (λ (y) (print 9))))
                  (λ () (print 3))))
               (λ () (print 4))))
            (call/cc (λ (k) k) 0))
           (λ (x) x)))
        '(<>
          ()
          [1 2 3 4 1 2 9 3 4]
          #f))
  (test "hop out two levels and back in two levels"
        '(<>
          ()
          []
          (%
           0
           ((λ (ok)
              (dynamic-wind
               (λ () (print 1))
               (λ () 
                 (dynamic-wind
                  (λ () (print 2))
                  (λ () (ok (λ (y) (print 9))))
                  (λ () (print 3))))
               (λ () (print 4))))
            (dynamic-wind
             (λ () (print 5))
             (λ () 
               (dynamic-wind
                (λ () (print 6))
                (λ () (call/cc (λ (k) k) 0))
                (λ () (print 7))))
             (λ () (print 8))))
           (λ (x) x)))
        '(<>
          ()
          [5 6 7 8 1 2 3 4 5 6 7 8 1 2 9 3 4]
          #f)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; composability

(define (cont-tests)
  (test "captured under new %"
        '(<>
          ([retry #f])
          []
          (begin
            (%
             0
             (+ 18
                (call/cc
                 (λ (k)
                   (begin
                     (set! retry k)
                     17))
                 0))
             (λ (x) x))
            ((λ (y)
               (begin
                 (set! retry #f)
                 y))
             (+ 1 (%
                   0
                   (retry 10)
                   (λ (x) x))))))
        '(<>
          ([retry #f])
          []
          29))               
  (test "catch in composed"
        '(<>
          () []
          (%
           0
           ((λ (k)
              ((λ (k2)
                 (%
                  0
                  (k2 (list 89))
                  (λ (x) x)))
               (%
                0
                (k (λ ()
                     (first (call/cc (λ (k2)
                                       (cons k2 (list)))
                                     0))))
                (λ (x) x))))
            (%
             0
             ((call/cc (λ (k) (λ () k))
                       0))
             (λ (x) x)))
           (λ (x) x)))
        '(<>
          () []
          89))
  (test "simple composable"
        '(<>
          [] ()
          ((λ (k)
             (k (λ () 8)))
           (%
            0
            ((call/comp
              (λ (k) (λ () k))
              0))
            (λ (x) x))))
        '(<> [] () 8))
  (test "composable doesn't introduce %"
        '(<>
          [] ()
          (%
           0
           ((λ (k)
              ((λ (k2)
                 (if (first (rest k2))
                     ((first k2) (list 10 #f))
                     (first k2)))
               (k (λ ()
                    (call/cc (λ (k2) 
                               (cons k2 (list #t)))
                             0)))))
            (%
             0
             ((call/comp
               (λ (k) (λ () k))
               0))
             (λ (x) x)))
           (λ (x) x)))
        '(<>
          [] ()
          10))
  (test "post thunk runs current continuation as composable"
        '(<>
          ([a #f]
           [do-a? #t])
          []
          (%
           0
           (dynamic-wind
            (λ () (print 1))
            (λ ()
              (begin
                (dynamic-wind
                 (λ () (print 2))
                 (λ () 
                   ((call/cc (λ (k)
                               (begin
                                 (set! a k)
                                 (λ () 12)))
                             0)))
                 (λ () (print 3)))
                (dynamic-wind
                 (λ () (print 4))
                 (λ ()
                   (if do-a?
                       (begin
                         (set! do-a? #f)
                         (a (λ () 11)))
                       (begin
                         (set! a #f)
                         12)))
                 (λ ()
                   (begin
                     (print 5)
                     (call/comp
                      (λ (k)
                        (k 10))
                      0))))))
            (λ () (print 6)))
           (λ (x) x)))
        '(<>
          ([a #f][do-a? #f])
          [1 2 3 4 5 1 6 2 3 4 5 1 6 6]
          12))
  (test "post thunk runs current continuation as composable under %"
        '(<>
          ([a #f]
           [do-a? #t])
          []
          (%
           0
           (dynamic-wind
            (λ () (print 1))
            (λ ()
              (begin
                (dynamic-wind
                 (λ () (print 2))
                 (λ () 
                   ((call/cc (λ (k)
                               (begin
                                 (set! a k)
                                 (λ () 12)))
                             0)))
                 (λ () (print 3)))
                (dynamic-wind
                 (λ () (print 4))
                 (λ ()
                   (if do-a?
                       (begin
                         (set! do-a? #f)
                         (a (λ () 11)))
                       (begin
                         (set! a #f)
                         12)))
                 (λ ()
                   (begin
                     (print 5)
                     (call/comp
                      (λ (k)
                        (%
                         0
                         (k 10)
                         (λ (x) x)))
                      0))))))
            (λ () (print 6)))
           (λ (x) x)))
        '(<>
          ([a #f] [do-a? #f])
          [1 2 3 4 5 1 2 3 4 5 1 6 6 2 3 4 5 1 6 6]
          12))
  (test "post think trims dws to run on exit"
        '(<>
          ([output (list)]
           [exit-k #f]
           [done? #f])
          []
          (%
           0
           (begin
             ;; Capture a continuation w.r.t. the default prompt tag:
             (call/cc
              (λ (esc)
                (dynamic-wind
                 (λ () 0)
                 (λ () 
                   ;; Set a prompt for tag p1:
                   (%
                    1
                    
                    (dynamic-wind
                     (λ () 0)
                     ;; inside d-w, jump out:
                     (λ () (esc 100))
                     (λ ()
                       (begin
                         ;; As we jump out, capture a continuation 
                         ;; w.r.t. p1:
                         ((call/cc
                           (λ (k) 
                             (begin
                               (set! exit-k k)
                               (λ () 10)))
                           1))
                         (set! output (cons 99 output)))))
                    (λ (x) x)))
                 (λ ()
                   ;; This post thunk is not in the
                   ;;  delimited continuation captured
                   ;; via tag p1:
                   (set! output (cons 101 output)))))
              0)
             (if done?
                 (begin
                   (set! exit-k #f)
                   output)
                 (begin
                   (set! done? #t)
                   ;; Now invoke the delimited continuation, which must
                   ;; somehow continue the jump to `esc':
                   (%
                    1
                    (exit-k (λ () 10))
                    (λ (x) x)))))
           (λ (x) (x))))
        '(<> 
          ([output (list 99 101 99)]
           [exit-k #f]
           [done? #t])
          ()
          (list 99 101 99)))
  (test "post thunk captures continuation that is invoked without target % (gets stuck)"
        '(<>
          ([output (list)]
           [exit-k #f])
          ()
          (%
           0
           ((λ (k)
              (abort 0
                     (λ ()
                       (%
                        1
                        (exit-k (λ () (set! exit-k #f)))
                        (λ (x) x)))))
            (call/cc 
             (λ (esc)
               (%
                1
                (dynamic-wind
                 (λ () 0)
                 (λ () (esc 100))
                 (λ ()
                   (begin
                     ((call/cc
                       (λ (k) 
                         (begin
                           (set! exit-k k)
                           (λ () 10)))
                       1))
                     (set! output (cons 101 output)))))
                (λ (x) x)))
             0))
           (λ (f) (f))))
        (term (<>
               ((output (list 101 101))
                (exit-k #f))
               ()
               (%
                1
                ((cont 0
                       ((λ (k)
                          (abort
                           0
                           (λ ()
                             (%
                              1
                              (exit-k (λ () (set! exit-k #f)))
                              (λ (x) x)))))
                        hole))
                 100)
                (λ (x1) x1)))))
  (test "similar way to get stuck, but using the pre thunk"
        '(<>
          ([output (list)]
           [exit-k #f])
          ()
          (%
           0
           ((λ (k)
              (abort 0
                     (λ ()
                       (%
                        1
                        (exit-k (λ () (set! exit-k #f)))
                        (λ (x) x)))))
            (call/cc 
             (λ (esc)
               (%
                1
                (dynamic-wind
                 (λ ()
                   (begin
                     ((call/cc
                       (λ (k) 
                         (begin
                           (set! exit-k k)
                           (λ () 10)))
                       1))
                     (set! output (cons 101 output))))
                 (λ () (esc 100))
                 (λ () 0))
                (λ (x) x)))
             0))
           (λ (f) (f))))
        (term (<>
               ((output (list 101 101))
                (exit-k #f))
               ()
               (%
                1
                (dw
                 x_1 ; <--- beware: this is a fresh variable. Will it always be x_1?
                 (begin
                   ((call/cc
                     (λ (k1)
                       (begin
                         (set! exit-k k1)
                         (λ () 10)))
                     1))
                   (set! output (cons 101 output)))
                 ((cont
                   0
                   ((λ (k)
                      (abort
                       0
                       (λ ()
                         (%
                          1
                          (exit-k (λ () (set! exit-k #f)))
                          (λ (x)
                            x)))))
                    hole))
                  100)
                 0)
                (λ (x1) x1)))))
  (test "loop"
        '(<>
          ([counter (list 4 3 2 1 0)])
          []
          (%
           0
           ((λ (k)
              ((λ (k2)
                 (if (first (rest k2))
                     ((first k2) (λ () 
                                   (if (zero? (first counter))
                                       (list 10 #f)
                                       (begin
                                         (set! counter (rest counter))
                                         ((call/cc (λ (k) (λ () 
                                                            (cons k (list #t))))
                                                   0))))))
                     (first k2)))
               (%
                1
                (k (λ ()
                     ((call/cc (λ (k) (λ () 
                                        (cons k (list #t))))
                               0))))
                (λ (x) x))))
            (%
             1
             ((call/cc (λ (k) (λ () k))
                       1))
             (λ (x) x)))
           (λ (x) x)))
        '(<>
          ([counter (list 0)])
          []
          10))
  (void))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Chain

(define chain-defns
  `([make
     (λ (pre post)
       (%
        0
        (dynamic-wind
         (λ () (print pre))
         (λ ()
           ((call/comp
             (λ (k) (λ () k))
             0)))
         (λ () (print post)))
        (λ (x) x)))]
    [chain
     (λ (E_1 E_2)
       (%
        0
        (E_1 (λ ()
               (E_2 (λ ()
                      ((call/comp
                        (λ (k) (λ () k))
                        0))))))
        (λ (x) x)))]
    [composable->replacing
     (λ (E)
       (%
        0
        (E (λ ()
             ((call/cc
               (λ (k) (λ () k))
               0))))
        (λ (x) x)))]))

(define (with-chain-bindings e)
  `((λ (one-two three-four)
      ((λ (one-three-four-two one-NINE-three-four-two )
         ((λ (one-two* three-four* one-three-four-two* one-NINE-three-four-two*)
            (%
             0
             ,e
             (λ (x) x)))
          (composable->replacing one-two)
          (composable->replacing three-four)
          (composable->replacing one-three-four-two)
          (composable->replacing one-NINE-three-four-two)))
       (chain one-two three-four)
       (chain one-two (λ (x) ((λ (z)
                                (begin (print 9) z))
                              (three-four x))))))
    (make 1 2)
    (make 3 4)))

(define chain-output '(1 2 3 4 1 3 4 2 1 3 4 9 2 1 2 3 4 1 3 4 2 1 3 4 9 2))

(define (chain-tests)
  (test "check chain setup"
        `(<>
          (,@chain-defns)
          []
          ,(with-chain-bindings 10))
        `(<>
          (,@chain-defns)
          [,@chain-output]
          10))
  (test "chain sharing"
        `(<>
          (,@chain-defns)
          []
          ,(with-chain-bindings 
            `(one-three-four-two* (λ ()
                                    (one-two* (λ () 0))))))
        `(<>
          (,@chain-defns)
          [,@chain-output 1 3 4 2]
          0))
  (test "chain non-sharing"
        `(<>
          (,@chain-defns)
          []
          ,(with-chain-bindings 
            `(one-three-four-two* (λ ()
                                    (three-four* (λ () 0))))))
        `(<>
          (,@chain-defns)
          [,@chain-output 1 3 4 2 3 4]
          0))
  (test "chain sharing with spliced frame"
        `(<>
          (,@chain-defns)
          []
          ,(with-chain-bindings 
            `(one-three-four-two* (λ ()
                                    (one-NINE-three-four-two* (λ () 0))))))
        `(<>
          (,@chain-defns)
          [,@chain-output 1 3 4 9 2]
          0))
  (test "chain sharing with spliced frame (skipped)"
        `(<>
          (,@chain-defns)
          []
          ,(with-chain-bindings 
            `(one-NINE-three-four-two* (λ ()
                                         (one-three-four-two* (λ () 0))))))
        `(<>
          (,@chain-defns)
          [,@chain-output 1 3 4 2]
          0))
  (test "substitution"
        `(<>
          ((S #t))
          ()
          (((λ (o S) o)
            (λ () S)
            #f)))
        `(<> ((S #t)) () #t)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Run

(begin
  (substitution-tests)
  (basic-tests)
  (r6rs-dw-tests)
  (cont-tests)
  (chain-tests)
  (printf "All ~s tests passed.\n" tests-passed))
