anchor.diff:      ;
   
   multipleWords
  -    : MINUS* WORD ((MINUS)* WORD)* MINUS*
  +    : (MINUS* WORD)+ MINUS*
       | GIBBERISH
       ;
   
  
positive.code:     : (SEMICOLON|COMMA|MINUS|'\''|'"'|'\\'|';'|'='|BRACEOPEN|BLOCKOPEN)*                // Leading garbage
        (('user-agent'|'User-Agent'|'UserAgent') (COLON|COMMA|EQUALS|'\\t')*)?            // Leading garbage
        ( (SEMICOLON|COMMA|MINUS)? ( product | emailAddress | siteUrl | rootTextPart SEMICOLON) )*
        ( (SEMICOLON|COMMA|MINUS)? rootTextPart )*                                        // Capture trailing text like "like Gecko"
          (SEMICOLON|COMMA|MINUS|PLUS|'\''|'"'|'\\'|';'|'='|BRACECLOSE|BLOCKCLOSE)*       // Trailing garbage
      ;
  
  
negative.code:     ;
  
  commentEntry
      :   ( emptyWord )
      |   (
              ( commentBlock
              | commentProduct
              | keyValue
              | uuId
              | siteUrl
              | emailAddress
              | versionWord
              | base64
              | CURLYBRACEOPEN commentProduct  CURLYBRACECLOSE
              | CURLYBRACEOPEN keyValue        CURLYBRACECLOSE
              | CURLYBRACEOPEN uuId            CURLYBRACECLOSE
              | CURLYBRACEOPEN siteUrl         CURLYBRACECLOSE
              | CURLYBRACEOPEN emailAddress    CURLYBRACECLOSE
              | CURLYBRACEOPEN multipleWords   CURLYBRACECLOSE
              | CURLYBRACEOPEN versionWord     CURLYBRACECLOSE
              | CURLYBRACEOPEN base64          CURLYBRACECLOSE
              )
              (MINUS*)
          )*
          ( multipleWords
          | productNameNoVersion
          | keyWithoutValue
          | CURLYBRACEOPEN productNameNoVersion CURLYBRACECLOSE
          | CURLYBRACEOPEN keyWithoutValue CURLYBRACECLOSE
          )?
      ;
  
  productNameKeyValue
  