#lang racket/base (require lang/private/rewrite-error-message) ;; ----------------------------------------------------------------------------- ;; this module provides one-point functionality to report errors in teachpacks ;; ----------------------------------------------------------------------------- (provide check-arg check-list-list check-arity check-proc check-result check-fun-res check-color check-dependencies natural? number->ord find-non tp-exn? tp-error) ;; check-arg : sym bool str (or/c str non-negative-integer) TST -> void (define (check-arg pname condition expected arg-posn given) (unless condition (tp-error pname "expects ~a as ~a argument, given ~e" (add-article expected) (spell-out arg-posn) given))) ;; Symbol (union true String) String X -> void (define (check-list-list pname condition pred given) (when (string? condition) (tp-error pname (string-append condition (format "\nin ~e" given))))) ;; check-arity : sym num (list-of TST) -> void (define (check-arity name arg# args) (unless (= (length args) arg#) (tp-error name (argcount-error-message #f arg# (length args))))) ;; check-proc : sym (... *->* ...) num (union sym str) (union sym str) -> void (define (check-proc name f exp-arity arg# arg-err) (define arg#-text (if (number? arg#) (number->ord arg#) arg#)) (unless (procedure? f) (tp-error name "expected a function as ~a argument; given ~e" arg#-text f)) (define arity-of-f (procedure-arity f)) (unless (procedure-arity-includes? f exp-arity) (tp-error name "expected function of ~a as ~a argument; given function of ~a " arg-err arg#-text (cond [(number? arity-of-f) (if (= arity-of-f 1) (format "1 argument") (format "~s arguments" arity-of-f))] [(arity-at-least? arity-of-f) "variable number of arguments"] [else (format "multiple arities (~s)" arity-of-f)])))) ;; Symbol (_ -> Boolean) String X X *-> X (define (check-result pname pred? expected given . other-given) (if (pred? given) given (tp-error pname "is expected to return ~a, but it returned ~v" (add-article expected) (if (pair? other-given) (car other-given) given)))) ;; check-color : symbol (or/c str non-negative-integer) TST -> void (define (check-color pname arg-pos given) (check-arg pname (or (string? given) (symbol? given)) 'color arg-pos given) ;; this would be good to check, but it isn't possible, since this ;; file is not allowed to rely on mred. ;; also nice would be to allow color% objects here, but that's ;; not possible for the same reason (but that is why ;; the '[else color]' case is below in the cond. #; (let ([color (cond [(symbol? given) (send the-color-database find-color (symbol->string given))] [(string? given) (send the-color-database find-color given)] [else given])]) (unless color (tp-error pname "expected the name ~e to be a color, but did not recognize it" given)))) ;; (: check-fun-res (∀ (γ) (∀ (β α ...) (α ...α -> β)) (_ -γ-> boolean) _ -> γ)) (define (check-fun-res f pred? type) (lambda x (check-result (object-name f) pred? type (apply f x)))) ;; check-dependencies : Symbol x Boolean x FormatString x Any* -> Void (define (check-dependencies pname condition fmt . args) (unless condition (tp-error pname (apply format fmt args)))) (define-struct (tp-exn exn) ()) (define (tp-error name fmt . args) (raise (make-exn:fail:contract (string-append (format "~a: " name) (apply format fmt args)) (current-continuation-marks)))) (define (number->ord i) (if (= i 0) "zeroth" (case (modulo i 10) [(0 4 5 6 7 8 9) (format "~ath" i)] [(1) (format "~ast" i)] [(2) (format "~and" i)] [(3) (format "~ard" i)]))) ;; (_ -> Boolean) (listof X) -> (union X false) ;; (not (find-non list? '((1 2 3) (a b c)))) ;; (symbol? (find-non number? '(1 2 3 a))) ;; (symbol? (find-non list? '((1 2 3) a (b c)))) (define (find-non pred? l) (let ([r (filter (compose not pred?) l)]) (if (null? r) #f (car r)))) (define (natural? w) (and (number? w) (integer? w) (>= w 0))) ;; add-article : anything -> string ;; (add-article 'color) should be "a color" ;; (add-article 'acronym) should be "an acronym" (define (add-article thing) (let ((s (format "~a" thing))) (string-append (if (starts-with-vowel? s) "an " "a ") s))) ;; starts-with-vowel? : string -> boolean (define (starts-with-vowel? s) (and (not (string=? s "")) (member (string-ref s 0) (list #\a #\e #\i #\o #\u)))) ;; spell-out : number-or-string -> string (define (spell-out arg-posn) (cond [(string? arg-posn) arg-posn] [(number? arg-posn) (case arg-posn [(1) "first"] [(2) "second"] [(3) "third"] [(4) "fourth"] [(5) "fifth"] [(6) "sixth"] [(7) "seventh"] [(8) "eighth"] [(9) "ninth"] [(10) "tenth"] [else (number->ord arg-posn)])]))