#lang racket

(require "../compiler/il-structs.rkt"
         "../compiler/lexical-structs.rkt"
         "../compiler/arity-structs.rkt"
         "../simulator/simulator-structs.rkt"
         "../simulator/simulator-primitives.rkt"
         "../simulator/simulator.rkt")

(printf "test-simulator.rkt\n")


(define-syntax (test stx)
  (syntax-case stx ()
    [(_ actual exp)
     (with-syntax ([stx stx])
       (syntax/loc #'stx
         (begin
           (printf "Running ~s ..." (syntax->datum #'stx))
           (let ([results actual])
             (unless (equal? results exp)
               (raise-syntax-error #f (format "Expected ~s, got ~s" exp results)
                                   #'stx)))
           (printf "ok\n\n"))))]))


;; take n steps in evaluating the machine.
(define (step-n m n)
  (cond
    [(= n 0)
     m]
    [else
     (step! m)
     (step-n m (sub1 n))]))


;; run: machine -> machine
;; Run the machine to completion.
(define (run! m)
  (cond
    [(can-step? m)
     (step! m)
     (run! m)]
    [else
     m]))


;; Infinite loop
(let ([m (new-machine `(hello world ,(make-GotoStatement (make-Label 'hello)))
                      #f)])
  (test (machine-pc (step-n m 0)) 0)
  (test (machine-pc (step-n m 1)) 1)
  (test (machine-pc (step-n m 1)) 2)
  (test (machine-pc (step-n m 1)) 1)
  (test (machine-pc (step-n m 1)) 2)
  (test (machine-pc (step-n m 1)) 1))


;; Assigning to val
(let ([m (new-machine `(,(make-AssignImmediateStatement 'val (make-Const 42)))
                      #f)])
  (test (machine-val m) (make-undefined))
  (step! m)
  (test (machine-val m) 42))

;; Assigning to proc
(let ([m (new-machine `(,(make-AssignImmediateStatement 'proc (make-Const 42)))
                      #f)])
  (test (machine-proc m) (make-undefined))
  (step! m)
  (test (machine-proc m) 42))


;; Assigning to a environment reference
(let* ([m (new-machine `(,(make-PushEnvironment 1 #f)
                         ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f) (make-Const 42)))
                       #f)]
       [m (run! m)])
  (test (machine-env m) '(42)))

;; Assigning to a boxed environment reference
(let* ([m (new-machine `(,(make-PushEnvironment 1 #t)
                         ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #t) (make-Const 42))))]
       [m (run! m)])
  (test (machine-env m) (list (box 42))))



;; Copying boxes over
(let* ([m (new-machine `(,(make-PushEnvironment 1 #t)
                         ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #t) (make-Const 42))
                         ,(make-PushEnvironment 1 #f)
                         ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f)
                                                         (make-EnvLexicalReference 1 #t))))]
       [m (run! m)])
  (test (machine-env m) (list 42 (box 42))))

(let* ([m (new-machine `(,(make-PushEnvironment 1 #t)
                         ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #t) (make-Const 42))
                         ,(make-PushEnvironment 1 #f)))]
       [m (run! m)])
  (test (machine-env m) (list (make-undefined)
                              (box 42))))
(let* ([m (new-machine `(,(make-PushEnvironment 1 #t)
                         ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #t) (make-Const 42))
                         ,(make-PushEnvironment 1 #f)
                         ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f)
                                                         (make-EnvLexicalReference 1 #f))))]
       [m (run! m)])
  (test (machine-env m) (list (box 42)
                              (box 42))))




;; Assigning to another environment reference
(let* ([m (new-machine `(,(make-PushEnvironment 2 #f)
                         ,(make-AssignImmediateStatement (make-EnvLexicalReference 1 #f) (make-Const 42))))]
       [m (run! m)])
  (test (machine-env m) `(,(make-undefined) 42)))


;; Assigning to another environment reference
(let* ([m (new-machine `(,(make-PushEnvironment 2 #f)
                         ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f) (make-Const 42))))]
       [m (run! m)])
  (test (machine-env m) `(42 ,(make-undefined))))


;; PushEnv
(let ([m (new-machine `(,(make-PushEnvironment 20 #f)))])
  (test (machine-env (run! m)) (build-list 20 (lambda (i) (make-undefined)))))


;; PopEnv
(let ([m (new-machine `(,(make-PushEnvironment 20 #f)
                        ,(make-PopEnvironment (make-Const 20) (make-Const 0))))])
  (test (machine-env (run! m)) '()))

(let* ([m (new-machine `(,(make-PushEnvironment 3 #f)
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f) (make-Const "hewie"))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 1 #f) (make-Const "dewey"))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 2 #f) (make-Const "louie"))
                        ,(make-PopEnvironment (make-Const 1) (make-Const 0))))])
  (test (machine-env (run! m)) '("dewey" "louie")))

(let* ([m (new-machine `(,(make-PushEnvironment 3 #f)
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f) (make-Const "hewie"))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 1 #f) (make-Const "dewey"))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 2 #f) (make-Const "louie"))
                        ,(make-PopEnvironment (make-Const 1) (make-Const 1))))])
  (test (machine-env (run! m)) '("hewie" "louie")))

(let* ([m (new-machine `(,(make-PushEnvironment 3 #f)
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f) (make-Const "hewie"))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 1 #f) (make-Const "dewey"))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 2 #f) (make-Const "louie"))
                        ,(make-PopEnvironment (make-Const 1) (make-Const 2))))])
  (test (machine-env (run! m)) '("hewie" "dewey")))

(let* ([m (new-machine `(,(make-PushEnvironment 3 #f)
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f) (make-Const "hewie"))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 1 #f) (make-Const "dewey"))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 2 #f) (make-Const "louie"))
                        ,(make-PopEnvironment (make-Const 2) (make-Const 1))))])
  (test (machine-env (run! m)) '("hewie")))



;; PushControl
(let ([m (new-machine `(,(make-AssignImmediateStatement 'proc (make-Const #f))
                        foo 
                        ,(make-PushControlFrame/Call (make-LinkedLabel 'foo 'foo))
                        bar
                        ,(make-PushControlFrame/Call (make-LinkedLabel 'bar 'bar))
                        baz
                        ))])
  (test (machine-control (run! m))
        (list (make-CallFrame (make-LinkedLabel 'bar 'bar) #f (make-hasheq) (make-hasheq))
              (make-CallFrame (make-LinkedLabel 'foo 'foo) #f (make-hasheq) (make-hasheq)))))



;; PopControl
(let ([m (new-machine `(,(make-AssignImmediateStatement 'proc (make-Const #f))
                        foo 
                        ,(make-PushControlFrame/Call (make-LinkedLabel 'foo 'foo))
                        bar
                        ,(make-PushControlFrame/Call (make-LinkedLabel 'bar 'bar))
                        baz
                        ,(make-PopControlFrame)
                        ))])
  (test (machine-control (run! m))
        (list (make-CallFrame (make-LinkedLabel 'foo 'foo) #f (make-hasheq) (make-hasheq)))))

(let ([m (new-machine `(,(make-AssignImmediateStatement 'proc (make-Const #f))
                        foo 
                        ,(make-PushControlFrame/Call (make-LinkedLabel 'foo 'foo))
                        bar
                        ,(make-PushControlFrame/Call (make-LinkedLabel 'bar 'bar))
                        baz
                        ,(make-PopControlFrame)
                        ,(make-PopControlFrame)))])
  (test (machine-control (run! m))
        (list)))





;; TestAndBranch: try the true branch
(let ([m (new-machine `(,(make-AssignImmediateStatement 'val (make-Const 42))
                        ,(make-TestAndJumpStatement (make-TestFalse (make-Reg 'val)) 'on-false)
                        ,(make-AssignImmediateStatement 'val (make-Const 'ok))
                        ,(make-GotoStatement (make-Label 'end))
                        on-false
                        ,(make-AssignImmediateStatement 'val (make-Const 'not-ok))
                        end))])
  (test (machine-val (run! m))
        'ok))
;; TestAndBranch: try the false branch
(let ([m (new-machine `(,(make-AssignImmediateStatement 'val (make-Const #f))
                        ,(make-TestAndJumpStatement (make-TestFalse (make-Reg 'val)) 'on-false)
                        ,(make-AssignImmediateStatement 'val (make-Const 'not-ok))
                        ,(make-GotoStatement (make-Label 'end))
                        on-false
                        ,(make-AssignImmediateStatement 'val (make-Const 'ok))
                        end))])
  (test (machine-val (run! m))
        'ok))
;; Test for primitive procedure
(let ([m (new-machine `(,(make-AssignImmediateStatement 'val (make-Const '+))
                        ,(make-TestAndJumpStatement (make-TestPrimitiveProcedure (make-Reg 'val)) 'on-true)
                        ,(make-AssignImmediateStatement 'val (make-Const 'ok))
                        ,(make-GotoStatement (make-Label 'end))
                        on-true
                        ,(make-AssignImmediateStatement 'val (make-Const 'not-ok))
                        end))])
  (test (machine-val (run! m))
        'ok))


;; ;; Give a primitive procedure in val
;; (let ([m (new-machine `(,(make-AssignImmediateStatement 'val (make-Const (lookup-primitive '+)))
;;                         ,(make-TestAndJumpStatement (make-TestPrimitiveProcedure (make-Reg 'val)) 'on-true)
;;                         ,(make-AssignImmediateStatement 'val (make-Const 'not-ok))
;;                         ,(make-GotoStatement (make-Label 'end))
;;                         on-true
;;                         ,(make-AssignImmediateStatement 'val (make-Const 'ok))
;;                         end))])
;;   (test (machine-val (run! m))
;;         'ok))
;; ;; Give a primitive procedure in proc, but test val
;; (let ([m (new-machine `(,(make-AssignImmediateStatement 'proc (make-Const (lookup-primitive '+)))
;;                         ,(make-TestAndJumpStatement (make-TestPrimitiveProcedure (make-Reg 'val)) 'on-true)
;;                         ,(make-AssignImmediateStatement 'val (make-Const 'not-a-procedure))
;;                         ,(make-GotoStatement (make-Label 'end))
;;                         on-true
;;                         ,(make-AssignImmediateStatement 'val (make-Const 'a-procedure))
;;                         end))])
;;   (test (machine-val (run! m))
;;         'not-a-procedure))
;; ;; Give a primitive procedure in proc and test proc
;; (let ([m (new-machine `(,(make-AssignImmediateStatement 'proc (make-Const (lookup-primitive '+)))
;;                         ,(make-TestAndJumpStatement (make-TestPrimitiveProcedure (make-Reg 'proc)) 'on-true)
;;                         ,(make-AssignImmediateStatement 'val (make-Const 'not-a-procedure))
;;                         ,(make-GotoStatement (make-Label 'end))
;;                         on-true
;;                         ,(make-AssignImmediateStatement 'val (make-Const 'a-procedure))
;;                         end))])
;;   (test (machine-val (run! m))
;;         'a-procedure))





;; AssignPrimOpStatement
(let ([m (new-machine `(,(make-PerformStatement (make-ExtendEnvironment/Prefix! '(+ - * =)))))])
  (test (first (machine-env (run! m)))
        (make-toplevel '(+ - * =)
                       (list (lookup-primitive '+)
                             (lookup-primitive '-)
                             (lookup-primitive '*)
                             (lookup-primitive '=)))))

(let ([m (new-machine `(,(make-PerformStatement (make-ExtendEnvironment/Prefix! '(some-variable)))
                        ,(make-AssignImmediateStatement 'val (make-Const "Danny"))
                        ,(make-AssignImmediateStatement (make-EnvPrefixReference 0 0) (make-Reg 'val))))])
  (test (machine-env (run! m))
        (list (make-toplevel '(some-variable) (list "Danny")))))

(let ([m (new-machine `(,(make-PerformStatement (make-ExtendEnvironment/Prefix! '(some-variable another)))
                        ,(make-AssignImmediateStatement 'val (make-Const "Danny"))
                        ,(make-AssignImmediateStatement (make-EnvPrefixReference 0 1) (make-Reg 'val))))])
  (test (machine-env (run! m))
        (list (make-toplevel '(some-variable another) (list (make-undefined) "Danny")))))

(let ([m (new-machine `(,(make-PerformStatement (make-ExtendEnvironment/Prefix! '(some-variable)))
                        ,(make-AssignImmediateStatement 'val (make-Const "Danny"))
                        ,(make-PushEnvironment 5 #f)
                        ,(make-AssignImmediateStatement (make-EnvPrefixReference 5 0) (make-Reg 'val))))])
  (test (machine-env (run! m))
        (list (make-undefined) (make-undefined) (make-undefined) (make-undefined) (make-undefined)
              (make-toplevel '(some-variable) (list "Danny")))))




;; check-toplevel-bound
;; This should produce an error.
(let ([m (new-machine `(,(make-PerformStatement (make-ExtendEnvironment/Prefix! '(some-variable)))
                        ,(make-PerformStatement (make-CheckToplevelBound! 0 0))))])
  (with-handlers ((exn:fail? (lambda (exn)
                               (void))))
    
    (run! m)
    (raise "I expected an error")))

;; check-toplevel-bound shouldn't fail here.
(let ([m (new-machine `(,(make-PerformStatement (make-ExtendEnvironment/Prefix! '(some-variable)))
                        ,(make-AssignImmediateStatement 'val (make-Const "Danny"))
                        ,(make-AssignImmediateStatement (make-EnvPrefixReference 0 0) (make-Reg 'val))
                        ,(make-PerformStatement (make-CheckToplevelBound! 0 0))))])
  (void (run! m)))



;; install-closure-values
(let ([m  
       (make-machine (make-undefined) 
                     (make-closure 'procedure-entry
                                   0
                                   (list 1 2 3)
                                   'procedure-entry)
                     (make-undefined)
                     (list true false) ;; existing environment holds true, false
                     '() 
                     0 
                     (list->vector `(,(make-PerformStatement (make-InstallClosureValues!))
                                     procedure-entry))
                     (make-hash)
                     0
                     (make-hash))])
  (test (machine-env (run! m))
        ;; Check that the environment has installed the expected closure values.
        (list 1 2 3 true false)))


;; get-compiled-procedure-entry
(let ([m 
       (make-machine (make-undefined) 
                     (make-closure 'procedure-entry 0 (list 1 2 3) 'procedure-entry)
                     (make-undefined)
                     (list true false) ;; existing environment holds true, false
                     '() 
                     0 
                     (list->vector `(,(make-AssignPrimOpStatement 'val (make-GetCompiledProcedureEntry))))
                     (make-hash)
                     0
                     (make-hash))])
  (test (machine-val (run! m))
        'procedure-entry))


;; make-compiled-procedure, with empty closure set
(let ([m (new-machine `(,(make-AssignPrimOpStatement 
                          'val
                          (make-MakeCompiledProcedure 'procedure-entry 0 (list) 'procedure-entry))
                        ,(make-GotoStatement (make-Label 'end))
                        procedure-entry
                        end
                        ))])
  (test (machine-val (run! m))
        (make-closure 'procedure-entry 0 (list) 'procedure-entry)))

;; make-compiled-procedure: Capturing a few variables.
(let ([m (new-machine `(,(make-PushEnvironment 3 #f)
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f) (make-Const 'larry))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 1 #f) (make-Const 'curly))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 2 #f) (make-Const 'moe))
                        ,(make-AssignPrimOpStatement 
                          'val
                          (make-MakeCompiledProcedure 'procedure-entry 
                                                      0 
                                                      (list 0 2)
                                                      'procedure-entry))
                        ,(make-GotoStatement (make-Label 'end))
                        procedure-entry
                        end
                        ))])
  (test (machine-val (run! m))
        (make-closure 'procedure-entry 0 (list 'larry 'moe)
                      'procedure-entry)))

;; make-compiled-procedure: Capturing a toplevel.
(let ([m (new-machine `(,(make-PerformStatement (make-ExtendEnvironment/Prefix! '(x y z)))
                        ,(make-AssignImmediateStatement 'val (make-Const "x"))
                        ,(make-AssignImmediateStatement (make-EnvPrefixReference 0 0) (make-Reg 'val))
                        ,(make-AssignImmediateStatement 'val (make-Const "y"))
                        ,(make-AssignImmediateStatement (make-EnvPrefixReference 0 1) (make-Reg 'val))
                        ,(make-AssignImmediateStatement 'val (make-Const "z"))
                        ,(make-AssignImmediateStatement (make-EnvPrefixReference 0 2) (make-Reg 'val))
                        ,(make-AssignPrimOpStatement 
                          'val
                          (make-MakeCompiledProcedure 'procedure-entry 
                                                      0
                                                      (list 0)
                                                      'procedure-entry))
                        ,(make-GotoStatement (make-Label 'end))
                        procedure-entry
                        end
                        ))])
  (test (machine-val (run! m))
        (make-closure 'procedure-entry 0 (list (make-toplevel '(x y z) (list "x" "y" "z")))
                      'procedure-entry)))

;; make-compiled-procedure: Capturing both a toplevel and some lexical values
(let ([m (new-machine `(,(make-PerformStatement (make-ExtendEnvironment/Prefix! '(x y z)))
                        ,(make-AssignImmediateStatement 'val (make-Const "x"))
                        ,(make-AssignImmediateStatement (make-EnvPrefixReference 0 0) (make-Reg 'val))
                        ,(make-AssignImmediateStatement 'val (make-Const "y"))
                        ,(make-AssignImmediateStatement (make-EnvPrefixReference 0 1) (make-Reg 'val))
                        ,(make-AssignImmediateStatement 'val (make-Const "z"))
                        ,(make-AssignImmediateStatement (make-EnvPrefixReference 0 2) (make-Reg 'val))

                        ,(make-PushEnvironment 3 #f)
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f) (make-Const 'larry))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 1 #f) (make-Const 'curly))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 2 #f) (make-Const 'moe))
                        ,(make-AssignPrimOpStatement 
                          'val
                          (make-MakeCompiledProcedure 'procedure-entry 
                                                      0
                                                      (list 3 0 2)
                                                      'procedure-entry))
                        ,(make-PopEnvironment (make-Const 3) (make-Const 0))
                        ,(make-GotoStatement (make-Label 'end))
                        procedure-entry
                        end
                        ))])
  (test (machine-val (run! m))
        (make-closure 'procedure-entry 
                      0
                      (list (make-toplevel '(x y z) (list "x" "y" "z"))
                            'larry
                            'moe)
                      'procedure-entry)))


;; Test toplevel lookup
(let ([m (new-machine `(,(make-PerformStatement (make-ExtendEnvironment/Prefix! '(+)))
                        ,(make-AssignImmediateStatement 'val (make-EnvPrefixReference 0 0))))])
  (test (machine-val (run! m))
        (lookup-primitive '+)))

;; Test lexical lookup
(let ([m (new-machine `(,(make-PerformStatement (make-ExtendEnvironment/Prefix! '(+)))
                        ,(make-PushEnvironment 3 #f)
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f) (make-Const 'larry))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 1 #f) (make-Const 'curly))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 2 #f) (make-Const 'moe))
                        
                        ,(make-AssignImmediateStatement 'val (make-EnvLexicalReference 0 #f))))])
  (test (machine-val (run! m))
        'larry))
;; Another lexical lookup test
(let ([m (new-machine `(,(make-PerformStatement (make-ExtendEnvironment/Prefix! '(+)))
                        ,(make-PushEnvironment 3 #f)
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f) (make-Const 'larry))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 1 #f) (make-Const 'curly))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 2 #f) (make-Const 'moe))
                        
                        ,(make-AssignImmediateStatement 'val (make-EnvLexicalReference 1 #f))))])
  (test (machine-val (run! m))
        'curly))

;; ApplyPrimitiveProcedure
;; Adding two numbers
(let ([m (new-machine `(,(make-PerformStatement (make-ExtendEnvironment/Prefix! '(+)))
                        ,(make-AssignImmediateStatement 'proc (make-EnvPrefixReference 0 0))
                        ,(make-PushEnvironment 2 #f)
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f) (make-Const 126389))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 1 #f) (make-Const 42))
                        ,(make-AssignImmediateStatement 'argcount (make-Const 2))
                        ,(make-AssignPrimOpStatement 'val (make-ApplyPrimitiveProcedure))
                        after))])
  (test (machine-val (run! m))
        (+ 126389 42))
  
  (test (machine-env (run! m))
        (list 126389 42 (make-toplevel '(+) (list (lookup-primitive '+))))))


;; ControlStackLabel
(let ([m (new-machine `(,(make-AssignImmediateStatement 'proc (make-Const #f))
                        foo
                        ,(make-PushControlFrame/Call (make-LinkedLabel 'foo 'foo))
                        ,(make-AssignImmediateStatement 'proc (make-ControlStackLabel))))])
  (test (machine-proc (run! m))
        'foo))


;; ControlStackLabel
(let ([m (new-machine `(,(make-AssignImmediateStatement 'proc (make-Const #f))
                        ,(make-PushControlFrame/Call (make-LinkedLabel 'foo-single 'foo-multiple))
                        ,(make-AssignImmediateStatement 'proc (make-ControlStackLabel))
                        ,(make-GotoStatement (make-Reg 'proc))
                        foo-single
                        ,(make-AssignImmediateStatement 'val (make-Const "single"))
                        ,(make-GotoStatement (make-Label 'end))
                        foo-multiple
                        ,(make-AssignImmediateStatement 'val (make-Const "multiple"))
                        ,(make-GotoStatement (make-Label 'end))
                        end))])
  (test (machine-val (run! m))
        "single"))
(let ([m (new-machine `(,(make-AssignImmediateStatement 'proc (make-Const #f))
                        ,(make-PushControlFrame/Call (make-LinkedLabel 'foo-single 'foo-multiple))
                        ,(make-AssignImmediateStatement 'proc (make-ControlStackLabel/MultipleValueReturn))
                        ,(make-GotoStatement (make-Reg 'proc))
                        foo-single
                        ,(make-AssignImmediateStatement 'val (make-Const "single"))
                        ,(make-GotoStatement (make-Label 'end))
                        foo-multiple
                        ,(make-AssignImmediateStatement 'val (make-Const "multiple"))
                        ,(make-GotoStatement (make-Label 'end))
                        end))])
  (test (machine-val (run! m))
        "multiple"))


(let ([m (new-machine `(,(make-AssignImmediateStatement 'proc (make-Const #f))
                        ,(make-PushControlFrame/Prompt default-continuation-prompt-tag
                                                       (make-LinkedLabel 'foo-single 'foo-multiple))
                        ,(make-AssignImmediateStatement 'proc (make-ControlStackLabel))
                        ,(make-GotoStatement (make-Reg 'proc))
                        foo-single
                        ,(make-AssignImmediateStatement 'val (make-Const "single"))
                        ,(make-GotoStatement (make-Label 'end))
                        foo-multiple
                        ,(make-AssignImmediateStatement 'val (make-Const "multiple"))
                        ,(make-GotoStatement (make-Label 'end))
                        end))])
  (test (machine-val (run! m))
        "single"))
(let ([m (new-machine `(,(make-AssignImmediateStatement 'proc (make-Const #f))
                        ,(make-PushControlFrame/Prompt default-continuation-prompt-tag
                                                       (make-LinkedLabel 'foo-single 'foo-multiple))
                        ,(make-AssignImmediateStatement 'proc (make-ControlStackLabel/MultipleValueReturn))
                        ,(make-GotoStatement (make-Reg 'proc))
                        foo-single
                        ,(make-AssignImmediateStatement 'val (make-Const "single"))
                        ,(make-GotoStatement (make-Label 'end))
                        foo-multiple
                        ,(make-AssignImmediateStatement 'val (make-Const "multiple"))
                        ,(make-GotoStatement (make-Label 'end))
                        end))])
  (test (machine-val (run! m))
        "multiple"))



;; Splicing
(let ([m (new-machine `(,(make-PushEnvironment 1 #f)
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f)
                                                        (make-Const '(1 2 3)))
                        ,(make-AssignImmediateStatement 'argcount (make-Const 1))
                        ,(make-PerformStatement (make-SpliceListIntoStack! (make-Const 0)))))])
  (run! m)
  (test (machine-argcount m)
        3)
  (test (machine-env m)
        '(1 2 3)))
                      


(let ([m (new-machine `(,(make-PushEnvironment 3 #f)
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f)
                                                        (make-Const "hello"))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 1 #f)
                                                        (make-Const "world"))
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 2 #f)
                                                        (make-Const '(1 2 3)))
                        ,(make-AssignImmediateStatement 'argcount (make-Const 3))
                        ,(make-PerformStatement (make-SpliceListIntoStack! (make-Const 2)))))])
  (run! m)
  (test (machine-argcount m)
        5)
  (test (machine-env m)
        '("hello" "world" 1 2 3)))




;; Testing immediate pushing
(let ([m (new-machine `(,(make-PushImmediateOntoEnvironment (make-Const "this is a message")
                                                            #f)))])
  (run! m)
  (test (machine-env m)
        '("this is a message")))

(let ([m (new-machine `(,(make-PushImmediateOntoEnvironment (make-Const "this is a message")
                                                            #t)))])
  (run! m)
  (test (machine-env m)
        `(,(box "this is a message"))))


(let ([m (new-machine `(,(make-PushImmediateOntoEnvironment (make-Const "this is a message")
                                                            #f)
                        ,(make-PushImmediateOntoEnvironment (make-Const "again")
                                                            #f)
                        ))])
  (run! m)
  (test (machine-env m)
        '("again" "this is a message")))

(let ([m (new-machine `(,(make-PushImmediateOntoEnvironment (make-Const "this is a message")
                                                            #f)
                        ,(make-PushImmediateOntoEnvironment (make-Const "again")
                                                            #t)
                        ))])
  (run! m)
  (test (machine-env m)
        `(,(box "again") "this is a message")))
                           





;; testing rest splicing
(let ([m (new-machine `(,(make-PushEnvironment 1 #f)
                        ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f)
                                                        (make-Const "hello"))
                        ,(make-AssignImmediateStatement 'argcount (make-Const 1))
                        ,(make-PerformStatement (make-UnspliceRestFromStack! (make-Const 0)
                                                                             (make-Const 1)))))])
  (run! m)
  (test (machine-argcount m)
        1)
  (test (machine-env m)
        (list (make-MutablePair "hello" null))))


(let ([m (new-machine 
          `(,(make-PushEnvironment 5 #f)
            ,(make-AssignImmediateStatement (make-EnvLexicalReference 0 #f)
                                            (make-Const "hello"))
            ,(make-AssignImmediateStatement (make-EnvLexicalReference 1 #f)
                                            (make-Const "world"))
            ,(make-AssignImmediateStatement (make-EnvLexicalReference 2 #f)
                                            (make-Const 'x))
            ,(make-AssignImmediateStatement (make-EnvLexicalReference 3 #f)
                                                        (make-Const 'y))
            ,(make-AssignImmediateStatement (make-EnvLexicalReference 4 #f)
                                            (make-Const 'z))
            ,(make-AssignImmediateStatement 'argcount (make-Const 5))
            ,(make-PerformStatement (make-UnspliceRestFromStack! (make-Const 2)  (make-Const 3)))))])
  (run! m)
  (test (machine-argcount m)
        3)
  (test (machine-env m)
        (list "hello"
              "world"
              (make-MutablePair 'x (make-MutablePair 'y (make-MutablePair 'z null))))))







;; Check closure mismatch.  Make sure we're getting the right values from the test.
(let ([m (new-machine `(procedure-entry
                        ;; doesn't matter about the procedure entry...
                        ,(make-AssignPrimOpStatement 
                          'proc
                          (make-MakeCompiledProcedure 'procedure-entry 0 (list) 'procedure-entry))
                        ,(make-TestAndJumpStatement
                          (make-TestClosureArityMismatch (make-Reg 'proc) (make-Const 0))
                          'bad)
                        ,(make-AssignImmediateStatement 'val (make-Const 'ok))
                        ,(make-GotoStatement (make-Label 'end))
                        bad
                        ,(make-AssignImmediateStatement 'val (make-Const 'bad))
                        end))])
  (test (machine-val (run! m))
        'ok))

(let ([m (new-machine `(procedure-entry
                        ;; doesn't matter about the procedure entry...
                        ,(make-AssignPrimOpStatement 
                          'proc
                          (make-MakeCompiledProcedure 'procedure-entry 0 (list) 'procedure-entry))
                        ,(make-TestAndJumpStatement
                          (make-TestClosureArityMismatch (make-Reg 'proc) (make-Const 1))
                          'ok)
                        ,(make-AssignImmediateStatement 'val (make-Const 'bad))
                        ,(make-GotoStatement (make-Label 'end))
                        ok
                        ,(make-AssignImmediateStatement 'val (make-Const 'ok))
                        end))])
  (test (machine-val (run! m))
        'ok))

(let ([m (new-machine `(procedure-entry
                        ;; doesn't matter about the procedure entry...
                        ,(make-AssignPrimOpStatement 
                          'proc
                          (make-MakeCompiledProcedure 'procedure-entry (make-ArityAtLeast 2) (list) 'procedure-entry))
                        ,(make-TestAndJumpStatement
                          (make-TestClosureArityMismatch (make-Reg 'proc) (make-Const 0))
                          'ok)
                        ,(make-AssignImmediateStatement 'val (make-Const 'bad))
                        ,(make-GotoStatement (make-Label 'end))
                        ok
                        ,(make-AssignImmediateStatement 'val (make-Const 'ok))
                        end))])
  (test (machine-val (run! m))
        'ok))

(let ([m (new-machine `(procedure-entry
                        ;; doesn't matter about the procedure entry...
                        ,(make-AssignPrimOpStatement 
                          'proc
                          (make-MakeCompiledProcedure 'procedure-entry (make-ArityAtLeast 2) (list) 'procedure-entry))
                        ,(make-TestAndJumpStatement
                          (make-TestClosureArityMismatch (make-Reg 'proc) (make-Const 2))
                          'bad)
                        ,(make-AssignImmediateStatement 'val (make-Const 'ok))
                        ,(make-GotoStatement (make-Label 'end))
                        bad
                        ,(make-AssignImmediateStatement 'val (make-Const 'bad))
                        end))])
  (test (machine-val (run! m))
        'ok))

