[racket-dev] match/fail*: re-inventing the wheel for better error messages with match?

From: John Clements (clements at brinckerhoff.org)
Date: Thu Sep 2 14:47:13 EDT 2010

Match is fantastic, and sometimes you want finer control over the error messages. This morning I whipped up a "match/fail*" macro, which successively matches against a sequence of patterns, and allows you to specify a separate fail message for each "layer".

It's a simple macro, and I give a use of it  (my use of it) below, followed by its simple definition.

FWIW, I *love* how check syntax shows the binding arrows from the pattern variables to the uses, outside of the defining clause.  

Is there something like this already in an existing library? If not, would this be useful to anyone?

John

p.s.: the use of syntax-parse in the definition of the macro was awesome, and produced at least one really nice error message for me.


;; first-line-checker : string -> (or/c string symbol)
;; returns a string, indicating an error message, or 'success,
;; indicating success.
(provide first-line-checker)
(define (first-line-checker first-line)
  (match/fail* 'success
    [(tokenize-string first-line) `("public" ,rest ...) "This function signature must begin with the word \"public\"."]
    [rest `(,(? type-name? ty) ,rest ...) (format "After the word public, you need a type name.")]
    [ty "int" "This function's return type must be \"int\"."]
    [rest `(,pre ... "(" ,args ... ")" ,leftover ...) "A function header must contain a pair of parentheses around the argument list."]
    [leftover `() "There shouldn't be anything after the right-paren (\")\")."]
    [pre `(,name) (if (empty? pre)
                      "There must be a function name between the type of the function and the argument list."
                      "The function name (a single word) is the only thing that comes between the type of the function and the argument list.")]
    [name "getApproxAge" "The name of the function should be \"getApproxAge\"."]
    [args `(,arg1 ... "," ,arg2 ...) "You need a comma in the argument list to separate the two arguments."]
    [arg1 `(,arg1ty ,arg1name) "The first argument should consist of a type and a name (exactly two words)."]
    [arg1ty (? type-name? _) "The first part of the first argument must be a type."]
    [arg1ty "int" "The first argument should be of type \"int\"."]
    [arg1name "birthYear" "The name of the first argument should be \"birthYear\"."]
    [arg2 `(,arg2ty ,arg2name) "The second argument should consist of a type and a name."]
    [arg2ty (? type-name? _) "The first part of the second argument must be a type."]
    [arg2ty "int" "The second argument should be of type \"int\"."]
    [arg2name "birthYear" "The name of the second argument should be \"birthYear\"."]))

;; the match/fail macro.  
;; A use of match/fail contains a single 'success' value
;; followed by a bunch of fail clauses.  Each fail clause
;; matches a value against a pattern and signals the given
;; error if it fails.  If it succeeds, it goes on to the 
;; next clause.  Note that pattern variables bound in each
;; pattern may be used in the remaining clauses.
(define-syntax (match/fail* stx)
  (define-syntax-class match/fail-clause
    #:description "match/fail clause"
    ;; pat:expr *can't* be right here...
    (pattern (val:expr pat:expr fail:expr)))
  
  (syntax-parse stx
    [(_ retval) #'retval]
    [(_ retval:expr clause:match/fail-clause more-clauses ...)
     #`(match clause.val 
         [clause.pat (match/fail* retval more-clauses ...)]
         [fail clause.fail])]))
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 4669 bytes
Desc: not available
URL: <http://lists.racket-lang.org/dev/archive/attachments/20100902/c81a5c55/attachment.p7s>

Posted on the dev mailing list.