#lang typed/racket/base (provide regexp-match! ;; (-> Pattern String Any * (U #f (List String *N+1))) ;; Match the regular expression pattern against a string. ;; If the pattern is determined statically, result will be either #f ;; or a list of N+1 strings, where N is the number of groups specified ;; the pattern. ;; ;; Will raise a compile-time exception if the pattern contains unmatched groups. ) (require (for-syntax racket/base syntax/parse racket/syntax)) ;; ============================================================================= (define-syntax regexp-match! (syntax-parser [(f pat-stx arg* ...) #:with num-groups (count-groups (format "~a" (syntax-e #'pat-stx)) #:src #'f) #:with ((index* . group-id*) ...) #`#,(for/list ([i (in-range (syntax-e #'num-groups))]) (cons i (format-id #'f "group-~a" i))) ;; Chaining list-ref? #'(let ([m (regexp-match pat-stx arg* ...)]) (if m (let ([group-id* (or (list-ref m index*) (error 'regexp-match! "Internal error, try Racket's `regexp-match`"))] ...) (list (car m) group-id* ...)) m))] [(f arg* ...) (syntax/loc #'f (regexp-match arg* ...))])) (define-for-syntax (count-groups v #:src stx) (cond [(string? v) (count-groups/string v #:src stx)] ;[(regexp? v) (count-groups/regexp v #:src stx)] ;[(pregexp? v) (count-groups/pregexp v #:src stx)] [else (error 'regexp-match! "Internal error on input" v)])) ;; Count the number of matched parentheses in a regexp pattern. ;; Raise an exception if there are unmatched parens. (define-for-syntax (count-groups/string str #:src stx) (define last-index (- (string-length str) 1)) (let loop ([i 0] [in-paren #f] [num-groups 0]) (if (> i last-index) (if in-paren (group-error str (format "'(' at index ~a" in-paren)) num-groups) (case (string-ref str i) [(#\() (loop (+ i 1) i num-groups)] [(#\)) (unless in-paren (group-error str (format "')' at index ~a" i))) (loop (+ i 1) #f (+ 1 num-groups))] [(#\\) (if (and (< i last-index) (eq? #\\ (string-ref str (+ i 1)))) (loop (+ i 3) in-paren num-groups) (loop (+ i 2) in-paren num-groups))] [else (loop (+ i 1) in-paren num-groups)])))) (define-for-syntax (count-groups/regexp rxp #:src stx) (error 'regexp-match! "Not implemented")) (define-for-syntax (count-groups/regexp pxp #:src stx) (error 'regexp-match! "Not implemented")) (define-for-syntax (group-error str reason) (raise-argument-error 'regexp-match! (format "Valid regexp pattern (contains unmatched ~a)" reason) str))