#lang scheme
(require "depend.ss"
"phrase.ss"
)
(define-struct email-address (name domain angle? display route) #:prefab)
(define-struct mail-list (display addresses) #:prefab)
(define p:atext (choice alphanumeric
(char-in '(#\! #\#
#\$ #\%
#\& #\'
#\* #\+
#\- #\/
#\- #\?
#\^ #\_
#\` #\{
#\| #\}
#\~))))
(define p:atom (seq v <- (one-many p:atext) (return (list->string v))))
(define p:dot-atom (seq a <- p:atom
rest <- (zero-many (seq #\. a <- p:atom
(return (list "." a))))
(return (apply string-append (flatten (cons a rest))))))
(define p:WSP (char-in '(#\space #\tab #\vtab)))
(define p:NO-WS-CTL (choice (char-between (integer->char 1) (integer->char 8))
(char-in (list (integer->char 11)
(integer->char 12)
(integer->char 127)))
(char-between (integer->char 14) (integer->char 31))))
(define p:dtext (choice p:NO-WS-CTL
(char-between (integer->char 33) (integer->char 90))
(char-between (integer->char 94) (integer->char 126))))
(define p:quoted-pair (char-when (lambda (c)
(and (char-ascii? c)
(not (char=? c #\return))
(not (char=? c #\newline))))))
(define p:dcontent (choice p:quoted-pair p:dtext))
(define p:domain-literal (seq #\[ v <- (one-many p:dcontent) #\]
(return (list->string v))))
(define p:domain (choice p:dot-atom p:domain-literal))
(define p:local-part (choice p:dot-atom quoted-string))
(define p:addr-spec (seq login <- p:local-part
#\@
host <- p:domain
(return (make-email-address login host #f #f #f))))
(define p:name-addr (tokens v <- (zero-one p:phrase #f)
addr <- p:angle-addr
(return (make-email-address (email-address-name addr)
(email-address-domain addr)
(email-address-angle? addr)
v
(email-address-route addr)
))))
(define p:route (seq #\@ d <- p:domain (return d)))
(define p:route-list (tokens v <- (delimited p:route #\,)
#\:
(return v)))
(define p:angle-addr (tokens #\<
route <- (zero-one p:route-list #f)
addr <- p:addr-spec
#\>
(return (make-email-address (email-address-name addr)
(email-address-domain addr)
#t
(email-address-display addr)
route))))
(define p:mailbox (choice p:name-addr p:addr-spec))
(define p:mailbox-list (delimited p:mailbox (choice #\,)))
(define p:mailbox-group (tokens p <- p:phrase
#\:
boxes <- p:mailbox-list
#\;
(return (make-mail-list p boxes))))
(define p:address (choice p:mailbox-group p:mailbox))
(define p:address-list (delimited p:address (char-in '(#\,))))
(define read-email-address-list (make-reader p:address-list))
(define read-email-address (make-reader p:address))
(define read-angle-address (make-reader p:angle-addr))
(define (email-address->string e (display? #t))
(cond ((and display? (email-address-display e))
(format "~a <~a@~a>" (encode-phrase (email-address-display e))
(email-address-name e)
(email-address-domain e)))
((email-address-angle? e)
(format "<~a@~a>" (email-address-name e)
(email-address-domain e)))
(else
(format "~a@~a" (email-address-name e)
(email-address-domain e)))))
(define (mail-list->string ml)
(format"~a:~a;" (encode-phrase (mail-list-display ml))
(email-address-list->string (mail-list-addresses ml))
))
(define (email-address-list->string addresses)
(if (mail-list? addresses)
(mail-list->string addresses)
(string-join (map email-address->string addresses) ", ")))
(provide/contract
(read-email-address Reader/c)
(read-email-address-list Reader/c)
(read-angle-address Reader/c)
(p:atom Parser/c)
(email-address-list->string (-> (or/c mail-list? (listof email-address?)) string?))
(mail-list->string (-> mail-list? string?))
(email-address->string (->* (email-address?)
(boolean?)
string?))
(struct email-address ((name string?)
(domain string?)
(angle? boolean?)
(display (or/c false/c string?))
(route (or/c false/c (listof string?)))))
(struct mail-list ((display (or/c false/c string?))
(addresses (listof email-address?))))
)