#lang racket

(require redex/reduction-semantics
         "reduction.rkt")

;                                                                                             
;                                                                                             
;  ;;; ;;;                        ;;;                                ;                        
;   ;; ;;          ;             ;                           ;                                
;   ;; ;;   ;;;   ;;;;;   ;;;   ;;;;; ;;  ;; ;; ;;    ;;;;  ;;;;;  ;;;     ;;;  ;; ;;    ;;;; 
;   ; ; ;  ;   ;   ;     ;   ;   ;     ;   ;  ;;  ;  ;   ;   ;       ;    ;   ;  ;;  ;  ;   ; 
;   ; ; ;  ;;;;;   ;      ;;;;   ;     ;   ;  ;   ;  ;       ;       ;    ;   ;  ;   ;   ;;;  
;   ;   ;  ;       ;     ;   ;   ;     ;   ;  ;   ;  ;       ;       ;    ;   ;  ;   ;      ; 
;   ;   ;  ;       ;   ; ;   ;   ;     ;  ;;  ;   ;  ;   ;   ;   ;   ;    ;   ;  ;   ;  ;   ; 
;  ;;; ;;;  ;;;;    ;;;   ;;;;; ;;;;;   ;; ;;;;; ;;;  ;;;     ;;;  ;;;;;   ;;;  ;;; ;;; ;;;;  
;                                                                                             
;                                                                                             
;                                                                                             
;                                                                                             


;; heap-ref
(test-equal (term (heap-ref (box y) ((w 0) (x 1) (y 2) (z 3)))) 2)

;; heap-set
(test-equal (term (heap-set 7 (box y) ((w 0) (x 1) (y 2) (z 3))))
            (term ((w 0) (x 1) (y 7) (z 3))))

;; push
(test-equal (term (push (1 2 3) (4 5 ε)))
            (term (1 2 3 4 5 ε)))

;; push-uninit
(test-equal (term (push-uninit 2 (1 2 (3 uninit 4 ε))))
            (term (uninit uninit 1 2 (3 uninit 4 ε))))

;; stack-ref
(test-equal (term (stack-ref 1 (1 2 3 (ε)))) 2)
(test-equal (term (stack-ref 3 (1 2 (3 4 (ε))))) 4)

;; stack-set
(test-equal (term (stack-set 2 1 (1 uninit uninit 4 ε)))
            (term (1 2 uninit 4 ε)))
(test-equal (term (stack-set 4 3 (1 2 (3 uninit 5 ε))))
            (term (1 2 (3 4 5 ε))))

;; load

(define-syntax test-load
  (syntax-rules ()
    [(_ e (e* h t))
     (test-predicate
      (redex-match runtime (uninit (((ε))) h t (e*)))
      (term (load e ())))]))

(test-load
 (proc-const (val ref) 'body)
 ((clos x_1) ((x_1 ((clos 2 () x_2)))) ((x_2 'body))))

(test-load
 (application 
  (proc-const (val ref) 'body1)
  (proc-const (val) 'body2))
 ((application (clos x_1) (clos x_2)) 
  ((x_1 ((clos 2 () x_3)))
   (x_2 ((clos 1 () x_4))))
  ((x_3 'body1) (x_4 'body2))))

(test-load
 (seq 
  (proc-const (val ref) 'body1)
  (proc-const (val) 'body2))
 ((seq (clos x_1) (clos x_2)) 
  ((x_1 ((clos 2 () x_3)))
   (x_2 ((clos 1 () x_4))))
  ((x_3 'body1) (x_4 'body2))))

(test-load
 (let-rec ((lam () (1) 'body1)
           (lam (val) (0) 'body2))
          (lam (val val) (0) 'body3))
 ((let-rec ((lam 0 (1) x_1)
            (lam 1 (0) x_2))
           (lam 2 (0) x_3))
  ()
  ((x_3 'body3) (x_1 'body1) (x_2 'body2))))

(test-load
 (let-one 
  (proc-const (val ref) 'body1)
  (proc-const (val) 'body2))
 ((let-one (clos x_1) (clos x_2)) 
  ((x_1 ((clos 2 () x_3)))
   (x_2 ((clos 1 () x_4))))
  ((x_3 'body1) (x_4 'body2))))

(test-load
 (let-void 0 (proc-const (val ref) 'body))
 ((let-void 0 (clos x_1)) 
  ((x_1 ((clos 2 () x_2))))
  ((x_2 'body))))

(test-load
 (boxenv 0 (proc-const (val ref) 'body))
 ((boxenv 0 (clos x_1)) 
  ((x_1 ((clos 2 () x_2))))
  ((x_2 'body))))

(test-load
 (install-value 0 
                (proc-const (val ref) 'body1)
                (proc-const (val) 'body2))
 ((install-value 0 (clos x_1) (clos x_2)) 
  ((x_1 ((clos 2 () x_3)))
   (x_2 ((clos 1 () x_4))))
  ((x_3 'body1) (x_4 'body2))))

(test-load
 (branch
  (proc-const (val ref) 'body1)
  (proc-const (val) 'body2)
  (proc-const () 'body3))
 ((branch (clos x_1) (clos x_2) (clos x_3)) 
  ((x_1 ((clos 2 () x_4)))
   (x_2 ((clos 1 () x_5)))
   (x_3 ((clos 0 () x_6))))
  ((x_4 'body1) (x_5 'body2) (x_6 'body3))))

(test-load
 (let-void 1 (let-rec ((lam () (0) (application (loc-noclr 0)))) 'x))
 ((let-void 1 (let-rec ((lam 0 (0) x_1)) 'x))
  ()
  ((x_1 (self-app x_1 (loc-noclr 0))))))

(test-load
 (let-void 1 (let-rec ((lam (val) (0) (application (loc-noclr 1) 'x))) 'y))
 ((let-void 1 (let-rec ((lam 1 (0) x_1)) 'y))
  ()
  ((x_1 (self-app x_1 (loc-noclr 1) 'x)))))

(test-load
 (let-void 1 (let-rec ((lam () (0) (application (loc-noclr 0) 'x))) 'y))
 ((let-void 1 (let-rec ((lam 0 (0) x_1)) 'y))
  ()
  ((x_1 (application (loc-noclr 0) 'x)))))

(test-load
 (let-void 1 (let-rec ((lam () (0) (boxenv 0 (application (loc-box-noclr 0))))) 'x))
 ((let-void 1 (let-rec ((lam 0 (0) x_1)) 'x))
  ()
  ((x_1 (boxenv 0 (application (loc-box-noclr 0)))))))

(test-load
 (let-void 1 (let-rec ((lam (val) (0) (application (loc-noclr 0)))) 'x))
 ((let-void 1 (let-rec ((lam 1 (0) x_1)) 'x))
  ()
  ((x_1 (application (loc-noclr 0))))))

(test-load
 (let-one 'x (let-void 1 (let-rec ((lam () (1 0) (application (loc-noclr 1)))) 42)))
 ((let-one 'x (let-void 1 (let-rec ((lam 0 (1 0) x_1)) 42)))
  ()
  ((x_1 (self-app x_1 (loc-noclr 1))))))

(test-load
 (let-one 'x (let-void 1 (let-rec ((lam () (1) (application (loc-noclr 0)))) 42)))
 ((let-one 'x (let-void 1 (let-rec ((lam 0 (1) x_1)) 42)))
  ()
  ((x_1 (application (loc-noclr 0))))))

(test-load
 (let-one 'x (let-void 1 (let-rec ((lam () (1) (application (loc-noclr 0)))) 42)))
 ((let-one 'x (let-void 1 (let-rec ((lam 0 (1) x_1)) 42)))
  ()
  ((x_1 (application (loc-noclr 0))))))

(test-load
 (let-void 1 (let-rec ((lam () (0) (lam () (0) (application (loc-noclr 0))))) 'x))
 ((let-void 1 (let-rec ((lam 0 (0) x_1)) 'x))
  ()
  ((x_1 (lam 0 (0) x_2))
   (x_2 (application (loc-noclr 0))))))

(test-load
 (let-void 1 (let-rec ((lam () (0 0) (application (loc-noclr 0)))) 'y))
 ((let-void 1 (let-rec ((lam 0 (0 0) x_1)) 'y))
  ()
  ((x_1 (application (loc-noclr 0))))))

(test-load
 (let-void 1 (let-rec ((lam () (0 0) (application (loc-noclr 1)))) 'y))
 ((let-void 1 (let-rec ((lam 0 (0 0) x_1)) 'y))
  ()
  ((x_1 (self-app x_1 (loc-noclr 1))))))

(test-load
 (let-void 1 (let-rec ((lam () (0) (let-one 'x (boxenv 0 (application (loc-noclr 1)))))) 'y))
 ((let-void 1 (let-rec ((lam 0 (0) x_1)) 'y))
  ()
  ((x_1 (let-one 'x (boxenv 0 (self-app x_1 (loc-noclr 1))))))))

(test-load
 (let-void 1 (let-rec ((lam () (0) (let-one (application (loc-noclr 1)) 'x))) 'y))
 ((let-void 1 (let-rec ((lam 0 (0) x_1)) 'y))
  ()
  ((x_1 (let-one (application (loc-noclr 1)) 'x)))))

(test-load
 (let-void 1 (let-rec ((lam () (0) (application (application (loc-noclr 0))))) 'x))
 ((let-void 1 (let-rec ((lam 0 (0) x_1)) 'x))
  ()
  ((x_1 (application (application (loc-noclr 0)))))))

(test-load
 (let-void 1 (let-rec ((lam () (0) (let-rec () (application (loc-noclr 0))))) 'x))
 ((let-void 1 (let-rec ((lam 0 (0) x_1)) 'x))
  ()
  ((x_1 (let-rec () (self-app x_1 (loc-noclr 0)))))))

(test-load
 (let-void 1 (let-rec ((lam () (0) (let-void 1 (install-value 1 'x (application (loc-noclr 1)))))) 'y))
 ((let-void 1 (let-rec ((lam 0 (0) x_1)) 'y))
  ()
  ((x_1 (let-void 1 (install-value 1 'x (self-app x_1 (loc-noclr 1))))))))

(test-load
 (let-void 1 (let-rec ((lam () (0) (let-void 1 (install-value 1 (application (loc-noclr 1)) 'x)))) 'y))
 ((let-void 1 (let-rec ((lam 0 (0) x_1)) 'y))
  ()
  ((x_1 (let-void 1 (install-value 1 (application (loc-noclr 1)) 'x))))))

(test-load
 (let-void 1 (let-rec ((lam () (0) (seq 'x (application (loc-noclr 0))))) 'y))
 ((let-void 1 (let-rec ((lam 0 (0) x_1)) 'y))
  ()
  ((x_1 (seq 'x (self-app x_1 (loc-noclr 0)))))))

(test-load
 (let-void 1 (let-rec ((lam () (0) (seq (application (loc-noclr 0)) 'x))) 'y))
 ((let-void 1 (let-rec ((lam 0 (0) x_1)) 'y))
  ()
  ((x_1 (seq (application (loc-noclr 0)) 'x)))))

(test-load
 (case-lam (lam (val) () (lam (val) (0) 'x)) (lam (val val) () 'y))
 ((case-lam (lam 1 () x_1) (lam 2 () x_3))
  ()
  ((x_1 (lam 1 (0) x_2)) (x_2 'x) (x_3 'y))))

;                                                                 
;                                                                 
;  ;;;;;             ;;                         ;                 
;   ;   ;             ;                 ;                         
;   ;   ;   ;;;    ;; ; ;;  ;;   ;;;;  ;;;;;  ;;;     ;;;  ;; ;;  
;   ;   ;  ;   ;  ;  ;;  ;   ;  ;   ;   ;       ;    ;   ;  ;;  ; 
;   ;;;;   ;;;;;  ;   ;  ;   ;  ;       ;       ;    ;   ;  ;   ; 
;   ;  ;   ;      ;   ;  ;   ;  ;       ;       ;    ;   ;  ;   ; 
;   ;   ;  ;      ;   ;  ;  ;;  ;   ;   ;   ;   ;    ;   ;  ;   ; 
;  ;;;   ;  ;;;;   ;;;;;  ;; ;;  ;;;     ;;;  ;;;;;   ;;;  ;;; ;;;
;                                                                 
;                                                                 
;                                                                 
;                                                                 

(define step (compose car (curry apply-reduction-relation ->)))

;; application
(test-->
 ->
 (term (uninit (((ε))) () () ((application 1 2 3))))
 (term (uninit (uninit uninit ((ε))) () () ((reorder (call 2) (1 ?) (2 0) (3 1))))))

;; self-app
(test-->
 ->
 (term
  ((clos x)
   ('a ((clos x) ('b ε)))
   ((x ((clos 0 ((clos x)) x1))))
   ((x1 (self-app x1 (loc-noclr 1) 'c)))
   ((self-app x1 (loc-noclr 1) 'c))))
 (term
  ((clos x)
   ('a ((clos x) ('b ε)))
   ((x ((clos 0 ((clos x)) x1))))
   ((x1 (self-app x1 (loc-noclr 1) 'c)))
   ((application (loc-noclr 1) 'c))))
 (term
  ((clos x)
   (uninit 'a ((clos x) ('b ε)))
   ((x ((clos 0 ((clos x)) x1))))
   ((x1 (self-app x1 (loc-noclr 1) 'c)))
   ((reorder (self-call x1) ('c 0))))))

;; reorder
(test-->
 ->
 (term (uninit (((ε))) () () ((reorder (call 1) ('x 0) ((loc-noclr 0) ?)))))
 (term (uninit (((ε))) () () (framepush 'x framepop (set 0)
                                        framepush (loc-noclr 0) framepop
                                        (call 1)))))
(test-->
 ->
 (term (uninit (((ε))) () () ((reorder (call 1) ((loc-noclr 0) ?) ('x 0)))))
 (term (uninit (((ε))) () () ((reorder (call 1) ('x 0) ((loc-noclr 0) ?)))))
 (term (uninit (((ε))) () () (framepush (loc-noclr 0) framepop (set 0)
                                        framepush 'x framepop
                                        (swap 0) (call 1)))))

(test-->
 ->
 (term (uninit (((ε))) () ((x 'q)) ((reorder (self-call x) ('x 0) ((loc-noclr 0) 1)))))
 (term (uninit (((ε))) () ((x 'q)) (framepush 'x framepop (set 0)
                                              framepush (loc-noclr 0) framepop (set 1)
                                              (self-call x)))))
(test-->
 ->
 (term (uninit (((ε))) () ((x 'q)) ((reorder (self-call x) ((loc-noclr 0) 1) ('x 0)))))
 (term (uninit (((ε))) () ((x 'q)) ((reorder (self-call x) ('x 0) ((loc-noclr 0) 1)))))
 (term (uninit (((ε))) () ((x 'q)) (framepush (loc-noclr 0) framepop (set 1)
                                              framepush 'x framepop (set 0)
                                              (self-call x)))))

(test-equal
 (sort
  (map
   car
   (apply-reduction-relation/tag-with-names
    ->
    (term 
     (uninit
      (uninit uninit 'a 'b 'c ε)
      ()
      ()
      ((reorder (call 2) ((loc-noclr 0) ?) ((loc-noclr 1) 0) ((loc-noclr 2) 1)))))))
  string<=?)
 '("finalize-app-not-last" "reorder" "reorder"))

;; swap
(test-->
 ->
 (term ('z ('a 'b 'c ε) () () ((swap 1))))
 (term ('b ('a 'z 'c ε) () () ())))

;; call
(test-->
 ->
 (term ((clos x1) ('x 'y 'z ('q ('r ε))) ((x1 ((clos 2 ('w) x2)))) ((x2 'z)) ((call 2))))
 (term ((clos x1) (('w ('x 'y ε))) ((x1 ((clos 2 ('w) x2)))) ((x2 'z)) ('z))))

(test-->
 ->
 (term ((clos x1) 
        ('x 'y 'z ('q ('r ε)))
        ((x1 ((clos 1 () x2) (clos 2 ('w) x3) (clos 2 ('w) x4))))
        ((x2 'a) (x3 'b) (x4 'c))
        ((call 2))))
 (term ((clos x1)
        (('w ('x 'y ε)))
        ((x1 ((clos 1 () x2) (clos 2 ('w) x3) (clos 2 ('w) x4))))
        ((x2 'a) (x3 'b) (x4 'c))
        ('b))))

(test-equal
 (caar
  (apply-reduction-relation*
   ->
   (term (load (application (lam (val) () (loc-noclr 0)) (application (lam (val) () (loc-noclr 0)) 'x)) ()))))
 ''x)

;; self-call
(test-->
 ->
 (term
  ('c
   ('d 'b ((clos x) ('c ε)))
   ((x ((clos 0 ((clos x)) x1))))
   ((x1 (self-app x1 (loc-noclr 1) 'c)))
   ((self-call x1))))
 (term
  ('c
   (((clos x) ('d ε)))
   ((x ((clos 0 ((clos x)) x1))))
   ((x1 (self-app x1 (loc-noclr 1) 'c)))
   ((self-app x1 (loc-noclr 1) 'c)))))

(test-equal
 (map 
  car
  (apply-reduction-relation* 
   ->
   (term (load (let-void 1 (let-rec ((lam (val val) (0) 
                                          (branch (loc-noclr 1)
                                                  (loc-noclr 2)
                                                  (application (loc-noclr 2) #t (loc-noclr 3)))))
                                    (application (loc-noclr 2) #f #t))) ()))))
 '(#f))

;; arity
(test-equal
 (step (term ((clos x1) ('y 'q ε) ((x1 ((clos 2 ('w) x2)))) ((x2 'z)) ((call 1)))))
 (term error))

(test-->>
 ->
 (term (uninit (((ε))) () ((x 'x)) ((let-one 7 (boxenv 0 (application (lam 0 (1) x) 'y))))))
 (term error))

(test-->
 ->
 (term
  ((clos x170017)
   (19 ((ε)))
   ((x170017
     ((clos 4 () x170018)))
    (x170015
     ((clos 9 () x170016))))
   ((x170018 (clos x170015))
    (x170016 void))
   ((call 1))))
 (term error))

;; non-closure
(test-equal
 (step (term ('f ('x 'y 'q ε) () () ((call 2)))))
 (term error))

;; localref
(test-->
 ->
 (term (uninit (1 ε) () () ((loc 0))))
 (term (1 (1 ε) () () ())))

;; loc-box
(test-->
 ->
 (term (uninit ((box x) ε) ((x 1)) () ((loc-box 0))))
 (term (1 ((box x) ε) ((x 1)) () ())))

;; loc-clr
(test-equal
 (step (term (uninit (1 ε) () () ((loc-clr 0)))))
 (term (1 (uninit ε) () () ())))

;; loc-box-clr
(test-equal
 (step (term (uninit ((box x) ε) ((x 1)) () ((loc-box-clr 0)))))
 (term (1 (uninit ε) ((x 1)) () ())))

;; value
(test-equal
 (step (term (uninit (((ε))) () () (3))))
 (term (3 (((ε))) () () ())))

;; close-lam
(test-equal
 (step (term (uninit ('x 'y ('z ε)) () ((x 'q)) ((lam 3 (0 2) x)))))
 (term ((clos x1) ('x 'y ('z ε)) ((x1 ((clos 3 ('x 'z) x)))) ((x 'q)) ())))

;; close-case-lam
(test-equal
 (step (term (uninit ('x 'y ('z ε)) ((x1 'a)) ((x2 'b) (x3 'c)) ((case-lam (lam 3 (0 2) x2) (lam 2 (1 0) x3))))))
 (term ((clos x4) ('x 'y ('z ε)) ((x4 ((clos 3 ('x 'z) x2) (clos 2 ('y 'x) x3))) (x1 'a)) ((x2 'b) (x3 'c)) ())))

;; let-one
(test-equal
 (step (term (uninit ('x ε) () () ((let-one 'y 'z)))))
 (term (uninit (uninit 'x ε) () () (framepush 'y framepop (set 0) 'z))))

;; framepop
(test-equal
 (step (term (uninit ('u ('w ('x 'y ('z ε)))) () () (framepop))))
 (term (uninit ('z ε) () () ())))

;; framepush
(test-equal
 (step (term (7 ('x uninit ('y ε)) () () (framepush))))
 (term (7 (((('x uninit ('y ε))))) () () ())))

;; set
(test-equal
 (step (term ('z ('x ('y ε)) () () ((set 1)))))
 (term ('z ('x ('z ε)) () () ())))

;; set-box
(test-equal
 (step (term ('x ((box y) ε) ((y 'z)) () ((set-box 0)))))
 (term ('x ((box y) ε) ((y 'x)) () ())))

;; boxenv
(test-equal
 (step (term (uninit (9 ε) ((y 8)) () ((boxenv 0 'z)))))
 (term (uninit ((box x) ε) ((x 9) (y 8)) () ('z))))

;; let-void
(test-equal
 (step (term (uninit ('x 'y ε) () () ((let-void 3 'z)))))
 (term (uninit (uninit uninit uninit 'x 'y ε) () () ('z))))

;; let-void-box
(test-predicate
 (redex-match
  runtime
  (uninit 
   ((box variable_1) (box variable_2) (box x) ε)
   ((variable_1 undefined) (variable_2 undefined) (x 'y))
   ()
   ('z)))
 (step (term (uninit ((box x) ε) ((x 'y)) () ((let-void-box 2 'z))))))

;; install-value
(test-->
 ->
 (term (uninit (uninit ε) () () ((install-value 0 'r 'b) 'q)))
 (term (uninit (uninit ε) () () (framepush 'r framepop (set 0) 'b 'q))))
(test-->
 ->
 (term (uninit (uninit ε) () () ((install-value-box 0 'r 'b) 'q)))
 (term (uninit (uninit ε) () () (framepush 'r framepop (set-box 0) 'b 'q))))

;; seq-many
(test-equal
 (step (term (uninit (((ε))) () () ((seq 'x 'y 'z) 'w))))
 (term (uninit (((ε))) () () (framepush 'x framepop (seq 'y 'z) 'w))))

;; seq-one
(test-equal
 (step (term (uninit ((((ε)))) () () ((seq 'x 'y) 'z))))
 (term (uninit ((((ε)))) () () (framepush 'x framepop 'y 'z))))

;; branch
(let ([test-branch (λ (cond res)
                     (test-->>
                      ->
                      `(uninit 
                        ('t 'f ε)
                        ()
                        ()
                        ((branch (let-one 'q ,cond)
                                 (loc 0)
                                 (loc 1))))
                      `(',res ('t 'f ε) () () ())))])
  (test-branch ''not-false 't)
  (test-branch #f 'f))

;; let-rec
(test-->
 ->
 (term
  ('x
   (uninit uninit (box x1) ε)
   ((x1 'x))
   ((x2 'f) (x3 'g))
   ((let-rec
     ((lam 1 (1 2) x2)
      (lam 1 (0 2) x3))
     (loc-noclr 0)))))
 (term
  ('x
   ((clos x4) (clos x5) (box x1) ε)
   ((x1 'x)
    (x4 ((clos 1 ((clos x5) (box x1)) x2)))
    (x5 ((clos 1 ((clos x4) (box x1)) x3))))
   ((x2 'f) (x3 'g))
   ((loc-noclr 0)))))

(test-->
 ->
 (term
  (uninit
   (uninit ((ε)))
   ((x1 ((clos 0 () x2))))
   ((x2 'x) (x3 'y))
   ((let-rec ((lam 0 (0) x3)) 'z))))
 (term
  (uninit
   ((clos x4) ((ε)))
   ((x1 ((clos 0 () x2))) (x4 ((clos 0 ((clos x4)) x3))))
   ((x2 'x) (x3 'y))
   ('z))))

(test-->> 
 ->
 #:cycles-ok
 (term
  (uninit
   (((ε)))
   ()
   ((x1 (application (loc-noclr 0)))
    (x2 (application (loc-noclr 0))))
   ((let-void
     2
     (let-rec ((lam 0 (1) x1)
               (lam 0 (0) x2))
              (application (loc-noclr 1))))))))

;; indirect
(test-->> 
 ->
 #:cycles-ok
 (term
  (uninit
   (((ε)))
   ((x1 ((clos 0 () x2))))
   ((x2 (application (indirect x3)))
    (x3 (clos x1)))
   ((application (indirect x3))))))

;; loops
(test-->>
 ->
 #:cycles-ok
 (term
  (uninit
   (((ε)))
   ()
   ((x1 (application
         (loc-noclr 1)
         (loc-noclr 1)))
    (x2 (let-one
         7
         (application
          (let-one 8 (loc-noclr 3))
          (let-one 9 (loc-noclr 3))))))
   ((application (lam 1 () x1) (lam 1 () x2))))))

(test-->>
 ->
 #:cycles-ok
 (term (load (let-one (indirect x57042) (application (loc-noclr 1) (loc-noclr 1)))
             ((x57042 (proc-const (val) (application (loc-noclr 1) (loc-noclr 1))))))))

(test-->>
 ->
 #:cycles-ok
 (term (load (let-void 1 (let-rec ((lam () (0) (application (loc-noclr 0)))) (application (loc-noclr 0)))) ())))

; mutable variables
(test-->>
 ->
 `(uninit (((ε))) () 
          ()
          ((let-void
            1
            (install-value 0 777 (boxenv 0 (install-value-box 0 888 (loc-box-noclr 0)))))))
 (term (888 ((box x) ((ε))) ((x 888)) () ())))

(test-->>
 ->
 `(uninit (((ε))) () ()
          ((let-one
            (let-void
             1
             (install-value
              0
              'foo
              (boxenv
               0
               (let-one
                (install-value-box 1 7 void)
                (seq (loc-clr 0) (let-one (loc-box-noclr 2) (loc-noclr 0)))))))
            (loc-noclr 0))))
 (term (7 (7 ((ε))) ((x 7)) () ())))

;; locals pushed for seq sub-exprs should be popped
(test-->>
 ->
 (term (uninit (((ε))) () () ((seq (let-one 1 (loc 0)) (let-one 2 (loc 0)) 3))))
 (term (3 (((ε))) () () ())))

; closure-captured value is above explicit arguments
(test-equal
 (caar
  (apply-reduction-relation*
   ->
   (term (load (application
                (let-void
                 1
                 (install-value
                  0
                  777
                  (boxenv
                   0
                   (lam
                    (val val val)
                    (0)
                    (install-value-box 0 (loc-noclr 3) (loc-box-noclr 0))))))
                111
                222
                333)
               ()))))
 333)

;; ref arg
(test-->>
 ->
 #:cycles-ok
 (term (uninit (((ε))) () ((x1 (loc-box-noclr 0))) ((let-one 'x (boxenv 0 (application (lam 1 () x1) (loc-noclr 1)))))))
 (term ('x ((((box x2) ε))) ((x3 ((clos 1 () x1))) (x2 'x)) ((x1 (loc-box-noclr 0))) ())))

;; case-lam
(test-equal
 (map
  car
  (apply-reduction-relation* 
   ->
   (term 
    (load (application 
           (let-one 'a 
                    (let-one 'b
                             (let-one 'c
                                      (case-lam (lam () (2) (loc-noclr 0))
                                                (lam (val) (1) (loc-noclr 0))
                                                (lam (val) (0) (loc-noclr 0))))))
           'x)
          ()))))
 '('b))

(test-equal
 (apply-reduction-relation* 
  ->
  (term (load (application (case-lam (lam (val) () 1) (lam (val val) () 2))) ())))
 '(error))

(test-equal
 (apply-reduction-relation* 
  ->
  (term (load (application (case-lam)) ())))
 '(error))

(test-results)
