#lang scheme/base
(require (planet schematics/schemeunit:3) "pa3.ss")
(require (planet schematics/schemeunit:3/text-ui))
;; When first you run this, it will fetch and install schemeunit automatically.

;; For use in tests. True if the correct error message is raised; false otherwise.
(define read-and-parse-string (compose parse read open-input-string))
(define read-and-eval-string (compose interp read-and-parse-string))
(define (try-to-parse input-string)
  (lambda () (read-and-parse-string input-string)))
(define (try-to-eval input-string)
  (lambda () (read-and-eval-string input-string)))

;; For use in tests. True if the correct error message is raised; false otherwise.
(define (expect-error msg)
  (lambda (exn) (string=? (exn-message exn) msg)))

(define my-value-is-7
  "{with {{x 2} {y 3}}
         {with {{z {+ x y}}}
               {+ x z}}}")

(define static-scope
  "{with {{f {with {{y 1}} {fun {x} {+ x y}}}}}
     {with {{y 2}} {f y}}}")

(define factorial-4
  "{with {{fact {fun {f n} {if0 n 1 {* n {f f {- n 1}}}}}}}
     {fact fact 4}}")

(define pa3-tests
  (test-suite
   "pa3"
   ;; Tests that should produce errors.
   (check-exn (expect-error "parse: unexpected keyword")
              (try-to-parse "with"))
   (check-exn (expect-error "parse: unexpected empty list")
              (try-to-parse "{}"))
   (check-exn (expect-error "parse: malformed with expression")
              (try-to-parse "{with}"))
   (check-exn (expect-error "lookup: no binding for identifier: foo")
              (try-to-eval "{foo 1 2}"))
   (check-exn (expect-error "parse: wrong number of arguments to binary operator")
              (try-to-parse "{+ 1}"))
   (check-exn (expect-error "parse: invalid datatype in input")
              (try-to-parse "#t"))
   (check-exn (expect-error "parse: expected a list of bindings")
              (try-to-parse "{with x x}"))
   (check-exn (expect-error "parse: expected list in binding of with expression")
              (try-to-parse "{with {x} x}"))
   (check-exn (expect-error "parse: expected (id val) in with expression")
              (try-to-parse "{with {{x}} x}"))
   (check-exn (expect-error "parse: identifiers must be symbols")
              (try-to-parse "{with {{1 2}} x}"))
   (check-exn (expect-error "parse: cannot bind a value to a reserved symbol")
              (try-to-parse "{with {{with 2}} x}"))
   (check-exn (expect-error "parse: duplicate bindings for identifier")
              (try-to-parse "{with {{x 1} {x 2}} x}"))
   (check-exn (expect-error "lookup: no binding for identifier: x")
              (try-to-eval "x"))
   (check-exn (expect-error "parse: malformed if0 expression")
              (try-to-parse "{if0}"))
   (check-exn (expect-error "interp: expected a number")
              (try-to-eval "{if0 {fun {} 0} 1 2}"))
   (check-exn (expect-error "parse: malformed fun expression")
              (try-to-parse "{fun}"))
   (check-exn (expect-error "parse: expected a list of formals")
              (try-to-parse "{fun x x}"))
   (check-exn (expect-error "parse: identifiers must be symbols")
              (try-to-parse "{fun {1} 1}"))
   (check-exn (expect-error "parse: cannot bind a value to a reserved symbol")
              (try-to-parse "{fun {with} 1}"))
   (check-exn (expect-error "parse: duplicate formal parameter name")
              (try-to-parse "{fun {x x} 1}"))
   (check-exn (expect-error "lookup: no binding for identifier: f")
              (try-to-eval "{f}"))
   (check-exn (expect-error "interp: function expects 0 argument(s), given 1")
              (try-to-eval "{{fun {} 1} 1}"))
   (check-exn (expect-error "interp: cannot apply non-function value")
              (try-to-eval "{1 2}"))

   ;; Tests that should succeed. You should write a lot more of these!
   (check-equal? (read-and-eval-string my-value-is-7) (numV 7))
   (check-equal? (read-and-eval-string static-scope) (numV 3))
   (check-equal? (read-and-eval-string factorial-4) (numV 24))
   (check-equal? (interp (parse '{* 6 x}) (aSub 'x (numV 7) (mtSub))) (numV 42))
   ))

(run-tests pa3-tests)
