module Mail

grammar RFC2822

  include RFC2822Obsolete

  rule ALPHA
    [a-zA-Z]
  end

  rule DIGIT
    [0-9]
  end

  rule DQUOTE
    '"'
  end

  rule LF
    "\n"
  end

  rule CR
    "\r"
  end

  rule CRLF
    "\r\n"
  end

  rule WSP
    [\x09\x20]
  end

  rule FWS    # Folding white space
    (WSP* CRLF WSP+) / (CRLF WSP+) / obs_FWS
  end

  rule CFWS
    (FWS* comment)* FWS?
  end

  rule NO_WS_CTL
    [\x01-\x08]   /         # US-ASCII control characters
    [\x0B-\x0C]   /         #  that do not include the
    [\x0E-\x1F]   /         #  carriage return, line feed,
    [\x7f]                  #  and white space characters
  end

  rule specials
    "(" / ")" /     # Special characters used in
    "<" / ">" /     #  other parts of the syntax
    "[" / "]" /
    ":" / ";" /
    "@" / '\' /
    "," / "." /
    DQUOTE
  end

  rule ctext
    NO_WS_CTL   /    # Non white space controls
    [\x21-\x27] /    # The rest of the US-ASCII
    [\x2a-\x5b] /    #  characters not including "(",
    [\x5d-\x7e]      #  ")", or "\"
  end

  rule ccontent
    ctext / quoted_pair / comment
  end

  rule comment
    "(" ( FWS? ccontent )* FWS? ")"
  end

  rule atext
    ALPHA / DIGIT / # Any character except controls,
    "!" / "#" /     #  SP, and specials.
    "$" / "%" /     #  Used for atoms
    "&" / "'" /
    "*" / "+" /
    "-" / "/" /
    "=" / "?" /
    "^" / "_" /
    "`" / "{" /
    "|" / "}" /
    "~"
  end

  rule mtext
    (atext / ".")+
  end

  rule atom
    CFWS? atext+ CFWS?
  end

  rule dot_atom
    CFWS? dot_atom_text CFWS?
  end

  rule local_dot_atom
    CFWS? local_dot_atom_text CFWS?
  end

  rule message_id_text
    mtext+
  end

  rule dot_atom_text
    (domain_text "."?)+
  end

  rule local_dot_atom_text
    ("."* domain_text "."*)+
  end

  rule domain_text
    (DQUOTE (FWS? quoted_domain)+ FWS? DQUOTE) / atext+
  end

  rule quoted_domain
    qdcontent / "\\" text
  end

  rule qdcontent
    NO_WS_CTL /     # Non white space controls
    [\x21] /        # The rest of the US-ASCII
    [\x23-\x45] /   # characters not including "\"
    [\x47-\x5b] /   # or the "." or the 
    [\x5d-\x7e]     # double quote character
  end

  rule phrase
    obs_phrase / word+
  end

  rule word
    atom / quoted_string
  end

  rule phrase_list
    first_phrase:phrase other_phrases:("," FWS* phrase_value:phrase)*
  end

  rule domain_literal
    CFWS? "[" (FWS? dcontent)* FWS? "]" CFWS?
  end

  rule dcontent
    dtext / quoted_pair
  end

  rule dtext
    NO_WS_CTL    /  # Non white space controls
    [\x21-\x5a]  /  # The rest of the US-ASCII characters
    [\x5e-\x7e]     #  not including "[", "]", or "\"
  end

  rule angle_addr
    CFWS? "<" addr_spec ">" CFWS? / obs_angle_addr
  end

  rule addr_spec
    (local_part "@" domain) / local_part
  end

  rule local_part
    local_dot_atom / quoted_string / obs_local_part
  end

  rule domain
    dot_atom / domain_literal / obs_domain
  end

  rule group
    group_name:display_name ":" group_list:(mailbox_list_group / CFWS)? ";" CFWS?
  end

  rule mailbox_list_group
    mailbox_list {
      def addresses
        [first_addr] + other_addr.elements.map { |o| o.addr_value }
      end
    }
  end

  rule quoted_string
    CFWS? DQUOTE quoted_content:(FWS? qcontent)+ FWS? DQUOTE CFWS?
  end

  rule qcontent
    qtext / quoted_pair
  end

  rule quoted_pair
    ("\\" text) / obs_qp
  end

  rule qtext
    NO_WS_CTL /     # Non white space controls
    [\x21] /        # The rest of the US-ASCII
    [\x23-\x5b] /   #  characters not including "\"
    [\x5d-\x7e]     #  or the quote character
  end

  rule text
    [\x01-\x09]     /       # Characters excluding CR and LF
    [\x0b-\x0c]     /
    [\x0e-\x7e]     / 
    obs_text
  end

  rule display_name
    phrase
  end

  rule name_addr
    display_name angle_addr / angle_addr
  end

  rule mailbox_list
    (first_addr:mailbox other_addr:(("," / ";") addr_value:mailbox)*) / obs_mbox_list
  end

  rule mailbox
    name_addr / addr_spec
  end

  rule address
    group {

      def dig_comments(comments, elements)
        elements.each { |elem|
          if elem.respond_to?(:comment)
            comments << elem.comment
          end
          dig_comments(comments, elem.elements) if elem.elements
         }
      end

      def comments
        comments = []
        dig_comments(comments, elements)
        comments
      end

    } /
    mailbox {

    def dig_comments(comments, elements)
      elements.each { |elem|
        if elem.respond_to?(:comment)
          comments << elem.comment
        end
        dig_comments(comments, elem.elements) if elem.elements
       }
    end

    def comments
      comments = []
      dig_comments(comments, elements)
      comments
    end

    }
  end

  rule address_list
    first_addr:address? other_addr:(FWS* ("," / ";") FWS* addr_value:address?)*
  end

  rule date_time
    ( day_of_week ",")? date FWS time CFWS?
  end

  rule day_of_week
    (FWS? day_name) / obs_day_of_week
  end

  rule day_name
    "Mon" / "Tue" / "Wed" / "Thu" /
    "Fri" / "Sat" / "Sun"
  end

  rule date
    day month year
  end

  rule year
    DIGIT DIGIT DIGIT DIGIT / obs_year
  end

  rule month
    (FWS month_name FWS) / obs_month
  end

  rule month_name
    "Jan" / "Feb" / "Mar" / "Apr" /
    "May" / "Jun" / "Jul" / "Aug" /
    "Sep" / "Oct" / "Nov" / "Dec"
  end

  rule day
    (FWS? DIGIT DIGIT?) / obs_day
  end

  rule time
    time_of_day FWS zone
  end

  rule time_of_day
    hour ":" minute ( ":" second )?
  end

  rule hour
    DIGIT DIGIT / obs_hour
  end

  rule minute
    DIGIT DIGIT / obs_minute
  end

  rule second
    DIGIT DIGIT / obs_second
  end

  rule zone
    (( "+" / "-" ) DIGIT DIGIT DIGIT DIGIT) / obs_zone
  end

  rule return
    path CRLF
  end

  rule path
    ((CFWS)? "<" ((CFWS)? / addr_spec) ">" (CFWS)?) / obs_path
  end

  rule received
    name_val_list ";" date_time CRLF
  end

  rule name_val_list
    (CFWS)? (name_val_pair (CFWS name_val_pair)*)
  end

  rule name_val_pair
    item_name CFWS item_value
  end

  rule item_name
    ALPHA (("-")? (ALPHA / DIGIT))*
  end

  rule item_value
    (angle_addr)+ / addr_spec / atom / domain / msg_id
  end

  rule message_ids
    first_msg_id:msg_id other_msg_ids:( CFWS msg_id_value:msg_id )*
  end

  rule msg_id
    (CFWS)? "<" msg_id_value ">" (CFWS)?
  end

  rule msg_id_value
    id_left "@" id_right
  end

  rule id_left
    message_id_text / no_fold_quote / obs_id_left
  end

  rule id_right
    msg_id_dot_atom_text / no_fold_literal / obs_id_right
  end

  rule msg_id_dot_atom_text
    (msg_id_domain_text "."?)+
  end

  rule msg_id_domain_text
    (DQUOTE (FWS? quoted_domain)+ FWS? DQUOTE) / msg_id_atext+
  end

  rule msg_id_atext
    ALPHA / DIGIT / # Any character except controls,
    "!" / "#" /     #  SP, and specials.
    "$" / "%" /     #  Used for atoms
    "&" / "'" /
    "*" / "+" /
    "-" / "/" /
    "=" / "?" /
    "^" / "_" /
    "`" / "{" /
    "|" / "}" /
    "~" / "@"
  end

  rule no_fold_quote
    DQUOTE (qtext / quoted_pair)+ DQUOTE
  end

  rule no_fold_literal
    "[" (dtext / quoted_pair)+ "]"
  end

end

end