#lang scheme
(require "pa6.ss" (planet "test.ss" ("schematics" "schemeunit.plt" 2)))
(require (planet "text-ui.ss" ("schematics" "schemeunit.plt" 2)))

;; 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 (try-to-parse input-string)
  (lambda () (parse input-string)))
(define (try-to-type input-string)
  (lambda () (type-of (parse 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 zomg-double
  '{with {f {fun {n : number} : number {+ n n}}}
     {with {g {fun {h : (number -> number)} : (number -> number) f}}
       {g f}}})

(define rec-double
  '{rec {mult : (number -> (number -> number))
          {fun {x : number} : (number -> number)
            {fun {y : number} : number
              {if {is-zero y} 0 {+ x {{mult x} {+ y -1}}}}}}}
     {mult 2}})

(define pa6-tests
  (test-suite
   "pa6"
   ;; Your PA6 solution should pass PA5 tests as well.
   ;; The following tests are adapted from the PA5 tests.
   ;; Sorry for the changes to the messages!
   ;; You should also reuse your own PA5 tests (modified as necessary).

   ;; Adapted PA5 tests that should produce errors.
   (test-exn "parse"
             (expect-error "parse: wrong number of operands to +")
             (try-to-parse '{+ 1}))
   (test-exn "parse"
             (expect-error "parse: malformed fun expression")
             (try-to-parse '{fun {x} x}))
   (test-exn "parse"
             (expect-error "parse: expected a typed formal parameter")
             (try-to-parse '{fun x : number x}))
   (test-exn "parse"
             (expect-error "parse: malformed typed formal parameter")
             (try-to-parse '{fun {x} : number x}))
   (test-exn "parse"
             (expect-error "parse: expected an identifier, given: 1")
             (try-to-parse '{fun {1 : number} : number x}))
   (test-exn "parse-type"
             (expect-error "parse-type: expected a type expression")
             (try-to-type '{fun {x : 1} : 1 x}))
   (test-exn "parse-type"
             (expect-error "parse-type: malformed type expression")
             (try-to-type '{fun {x : {number}} : number {x 1}}))
   (test-exn "parse-type"
             (expect-error "parse-type: unknown type: foo")
             (try-to-type '{fun {x : foo} : foo x}))
   (test-exn "type-of"
             (expect-error "free identifier: x")
             (try-to-type 'x))
   (test-exn "type-of"
             (expect-error "type mismatch: operands must be numbers")
             (try-to-type '{+ 1 {fun {x : number} : number x}}))
   (test-exn "type-of"
             (expect-error "type mismatch: function body has wrong type")
             (try-to-type '{fun {x : number} : (number -> number) x}))
   (test-exn "type-of"
             (expect-error "type mismatch: actual parameter has wrong type")
             (try-to-type '{{fun {f : (number -> number)} : number 0} 0}))
   (test-exn "type-of"
             (expect-error "expected a function")
             (try-to-type '{1 2}))

   ;; New PA6 tests that should produce errors.
   (test-exn "type-of"
             (expect-error "type mismatch: actual parameter has wrong type")
             (try-to-type '{is-zero true}))
   (test-exn "parse"
             (expect-error "parse: malformed if expression")
             (try-to-parse '{if}))
   (test-exn "type-of"
             (expect-error "type mismatch: test expression must have type boolean")
             (try-to-type '{if 0 0 1}))
   (test-exn "type-of"
             (expect-error "type mismatch: then and else expressions must have same type")
             (try-to-type '{if true true 0}))
   (test-exn "parse"
             (expect-error "parse: malformed rec expression")
             (try-to-parse '{rec}))
   (test-exn "parse"
             (expect-error "parse: expected a typed cyclic binding")
             (try-to-parse '{rec f x}))
   (test-exn "parse"
             (expect-error "parse: malformed typed cyclic binding")
             (try-to-parse '{rec {f} x}))
   (test-exn "parse"
             (expect-error "parse: expected an identifier, given: 0")
             (try-to-parse '{rec {0 : (number -> number) 1} 2}))
   (test-exn "parse-type"
             (expect-error "parse-type: expected a type expression")
             (try-to-parse '{rec {f : 0 {fun {x : number} : number x}} 1}))
   (test-exn "parse-type"
             (expect-error "parse-type: malformed type expression")
             (try-to-type '{rec {x : (number ->) x} 0}))
   (test-exn "parse-type"
             (expect-error "parse-type: malformed type expression")
             (try-to-type '{rec {x : (number to number) x} 0}))
   (test-exn "parse-type"
             (expect-error "parse-type: unknown type: string")
             (try-to-type '{rec {x : (string -> string) x} 0}))
   (test-exn "parse"
             (expect-error "parse: expected a colon, given: hastype")
             (try-to-parse '{rec {f hastype (number -> number) 1} 2}))
   (test-exn "parse"
             (expect-error "parse: cyclic binding must have a function type")
             (try-to-parse '{rec {f : boolean true} 0}))
   (test-exn "type-of"
             (expect-error "type mismatch: named expression must have declared type")
             (try-to-type '{rec {f : (number -> number) is-zero} 0}))

   ;; PA6 tests that should succeed. You should write a lot more of these!
   (test-equal? "type-of" (type-of (parse 'true)) (t-bool))
   (test-equal? "type-of" (type-of (parse '{if {is-zero 0} true false})) (t-bool))
   (test-equal? "type-of" (type-of (parse rec-double)) (t-fun (t-num) (t-num)))
   ))

(test/text-ui pa6-tests)
