cs019/rewrite-error-message.rkt
#lang scheme/base

(require mzlib/etc
         mzlib/list
         (for-syntax "firstorder.rkt"
                     scheme/base))

(provide rewrite-contract-error-message
         reraise-rewriten-lookup-error-message
         get-rewriten-error-message
         plural
         raise-not-bound-error
         argcount-error-message)

(define (reraise-rewriten-lookup-error-message e id was-in-app-position)
  (let ([var-or-function (if was-in-app-position "function" "variable")])
    (raise-syntax-error
     #f
     (format "this ~a is not defined" var-or-function)
     id)))

(define (exn-needs-rewriting? exn)
  (exn:fail:contract? exn))

(define (ensure-number n-or-str)
  (if (string? n-or-str) (string->number n-or-str) n-or-str))

(define (plural n)
  (if (> (ensure-number n) 1) "s" ""))

(define (raise-not-bound-error id)
  (if (syntax-property id 'was-in-app-position)
      (raise-syntax-error
       #f
       "this function is not defined"
       id)
      (raise-syntax-error
       #f
       "this variable is not defined"
       id)))

(define (argcount-error-message arity found [at-least #f])
  (define arity:n (ensure-number arity))
  (define found:n (ensure-number found))
  (define fn-is-large (> arity:n found:n))
  (format "expects ~a~a~a argument~a, but found ~a~a"
          (if at-least "at least " "")
          (if (or (= arity:n 0) fn-is-large) "" "only ")
          (if (= arity:n 0) "no" arity:n) (plural arity:n)
          (if (and (not (= found:n 0)) fn-is-large) "only " "")
          (if (= found:n 0) "none" found:n)))

(define (rewrite-contract-error-message msg)
  (define replacements
    (list (list #rx"procedure application: expected procedure, given: (.*) \\(no arguments\\)"
                (lambda (all one) 
                  (format "function call: expected a function after the open parenthesis, but received ~a" one)))
          (list #rx"procedure application: expected procedure, given: (.*); arguments were:.*"
                (lambda (all one)  
                  (format "function call: expected a function after the open parenthesis, but received ~a" one)))
          (list #rx"expects argument of type (<([^>]+)>)"
                (lambda (all one two) (format "expects a ~a" two)))
          (list #rx"expected argument of type (<([^>]+)>)"
                (lambda (all one two) (format "expects a ~a" two)))
          (list #rx"expects type (<([^>]+)>)"
                (lambda (all one two) (format "expects a ~a" two)))
          (list #px"expects at least (\\d+) argument.?, given (\\d+)(: .*)?"
                (lambda (all one two three) (argcount-error-message one two #t)))
          (list #px"expects (\\d+) argument.?, given (\\d+)(: .*)?"
                (lambda (all one two three) (argcount-error-message one two)))
          (list #rx"^procedure "
                (lambda (all) ""))
          (list #rx", given: "
                (lambda (all) ", given "))
          (list #rx"; other arguments were:.*"
                (lambda (all) ""))
          (list #rx"expects a (struct:)"
                (lambda (all one) "expects a "))
          (list #rx"list or cyclic list"
                (lambda (all) "list"))
          (list (regexp-quote "given #(struct:object:image% ...)")
                (lambda (all) "given an image"))
          (list (regexp-quote "given #(struct:object:image-snip% ...)")
                (lambda (all) "given an image"))
          (list (regexp-quote "given #(struct:object:cache-image-snip% ...)")
                (lambda (all) "given an image"))
          (list (regexp-quote "#(struct:object:image% ...)")
                (lambda (all) "(image)"))
          (list (regexp-quote "#(struct:object:image-snip% ...)")
                (lambda (all) "(image)"))
          (list (regexp-quote "#(struct:object:cache-image-snip% ...)")
                (lambda (all) "(image)"))))
  (for/fold ([msg msg]) ([repl. replacements])
    (regexp-replace* (first repl.) msg (second repl.))))

(define (get-rewriten-error-message exn)
  (if (exn-needs-rewriting? exn)
      (rewrite-contract-error-message (exn-message exn))
      (exn-message exn)))