/**************************************************************** | |
* Licensed to the Apache Software Foundation (ASF) under one * | |
* or more contributor license agreements. See the NOTICE file * | |
* distributed with this work for additional information * | |
* regarding copyright ownership. The ASF licenses this file * | |
* to you under the Apache License, Version 2.0 (the * | |
* "License"); you may not use this file except in compliance * | |
* with the License. You may obtain a copy of the License at * | |
* * | |
* http://www.apache.org/licenses/LICENSE-2.0 * | |
* * | |
* Unless required by applicable law or agreed to in writing, * | |
* software distributed under the License is distributed on an * | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * | |
* KIND, either express or implied. See the License for the * | |
* specific language governing permissions and limitations * | |
* under the License. * | |
****************************************************************/ | |
/** | |
* RFC2822 address list parser. | |
* | |
* Created 9/17/2004 | |
* by Joe Cheng <code@joecheng.com> | |
*/ | |
options { | |
STATIC=false; | |
LOOKAHEAD=1; | |
JDK_VERSION = "1.5"; | |
VISITOR=true; | |
MULTI=true; | |
NODE_SCOPE_HOOK=true; | |
NODE_EXTENDS="org.apache.james.mime4j.field.address.BaseNode"; | |
//DEBUG_PARSER=true; | |
//DEBUG_TOKEN_MANAGER=true; | |
} | |
PARSER_BEGIN(AddressListParser) | |
/**************************************************************** | |
* Licensed to the Apache Software Foundation (ASF) under one * | |
* or more contributor license agreements. See the NOTICE file * | |
* distributed with this work for additional information * | |
* regarding copyright ownership. The ASF licenses this file * | |
* to you under the Apache License, Version 2.0 (the * | |
* "License"); you may not use this file except in compliance * | |
* with the License. You may obtain a copy of the License at * | |
* * | |
* http://www.apache.org/licenses/LICENSE-2.0 * | |
* * | |
* Unless required by applicable law or agreed to in writing, * | |
* software distributed under the License is distributed on an * | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * | |
* KIND, either express or implied. See the License for the * | |
* specific language governing permissions and limitations * | |
* under the License. * | |
****************************************************************/ | |
package org.apache.james.mime4j.field.address; | |
public class AddressListParser { | |
public static void main(String args[]) throws ParseException { | |
while (true) { | |
try { | |
AddressListParser parser = new AddressListParser(System.in); | |
parser.parseLine(); | |
((SimpleNode) parser.jjtree.rootNode()).dump("> "); | |
} catch (Exception x) { | |
x.printStackTrace(); | |
return; | |
} | |
} | |
} | |
public ASTaddress_list parseAddressList() throws ParseException { | |
try { | |
parseAddressList0(); | |
return (ASTaddress_list) jjtree.rootNode(); | |
} catch (TokenMgrError tme) { | |
throw new ParseException(tme); | |
} | |
} | |
public ASTaddress parseAddress() throws ParseException { | |
try { | |
parseAddress0(); | |
return (ASTaddress) jjtree.rootNode(); | |
} catch (TokenMgrError tme) { | |
throw new ParseException(tme); | |
} | |
} | |
public ASTmailbox parseMailbox() throws ParseException { | |
try { | |
parseMailbox0(); | |
return (ASTmailbox) jjtree.rootNode(); | |
} catch (TokenMgrError tme) { | |
throw new ParseException(tme); | |
} | |
} | |
void jjtreeOpenNodeScope(Node n) { | |
((SimpleNode) n).firstToken = getToken(1); | |
} | |
void jjtreeCloseNodeScope(Node n) { | |
((SimpleNode) n).lastToken = getToken(0); | |
} | |
} | |
PARSER_END(AddressListParser) | |
void parseLine() #void : | |
{} | |
{ | |
address_list() ["\r"] "\n" | |
} | |
void parseAddressList0() #void : | |
{} | |
{ | |
address_list() <EOF> | |
} | |
void parseAddress0() #void : | |
{} | |
{ | |
address() <EOF> | |
} | |
void parseMailbox0() #void : | |
{} | |
{ | |
mailbox() <EOF> | |
} | |
void address_list() : | |
{} | |
{ | |
[ address() ] | |
( | |
"," | |
[ address() ] | |
)* | |
} | |
void address() : | |
{} | |
{ | |
LOOKAHEAD(2147483647) | |
addr_spec() | |
| angle_addr() | |
| ( phrase() (group_body() | angle_addr()) ) | |
} | |
void mailbox() : | |
{} | |
{ | |
LOOKAHEAD(2147483647) | |
addr_spec() | |
| angle_addr() | |
| name_addr() | |
} | |
void name_addr() : | |
{} | |
{ | |
phrase() angle_addr() | |
} | |
void group_body() : | |
{} | |
{ | |
":" | |
[ mailbox() ] | |
( | |
"," | |
[ mailbox() ] | |
)* | |
";" | |
} | |
void angle_addr() : | |
{} | |
{ | |
"<" [ route() ] addr_spec() ">" | |
} | |
void route() : | |
{} | |
{ | |
"@" domain() ( (",")* "@" domain() )* ":" | |
} | |
void phrase() : | |
{} | |
{ | |
( <DOTATOM> | |
| <QUOTEDSTRING> | |
)+ | |
} | |
void addr_spec() : | |
{} | |
{ | |
( local_part() "@" domain() ) | |
} | |
void local_part() : | |
{ Token t; } | |
{ | |
( t=<DOTATOM> | t=<QUOTEDSTRING> ) | |
( [t="."] | |
{ | |
if ( t.kind == AddressListParserConstants.QUOTEDSTRING || t.image.charAt(t.image.length() - 1) != '.') | |
throw new ParseException("Words in local part must be separated by '.'"); | |
} | |
( t=<DOTATOM> | t=<QUOTEDSTRING> ) | |
)* | |
} | |
void domain() : | |
{ Token t; } | |
{ | |
( t=<DOTATOM> | |
( [t="."] | |
{ | |
if (t.image.charAt(t.image.length() - 1) != '.') | |
throw new ParseException("Atoms in domain names must be separated by '.'"); | |
} | |
t=<DOTATOM> | |
)* | |
) | |
| <DOMAINLITERAL> | |
} | |
SPECIAL_TOKEN : | |
{ | |
< WS: ( [" ", "\t"] )+ > | |
} | |
TOKEN : | |
{ | |
< #ALPHA: ["a" - "z", "A" - "Z"] > | |
| < #DIGIT: ["0" - "9"] > | |
| < #ATEXT: ( <ALPHA> | <DIGIT> | |
| "!" | "#" | "$" | "%" | |
| "&" | "'" | "*" | "+" | |
| "-" | "/" | "=" | "?" | |
| "^" | "_" | "`" | "{" | |
| "|" | "}" | "~" | |
)> | |
| < DOTATOM: <ATEXT> ( <ATEXT> | "." )* > | |
} | |
TOKEN_MGR_DECLS : | |
{ | |
// Keeps track of how many levels of comment nesting | |
// we've encountered. This is only used when the 2nd | |
// level is reached, for example ((this)), not (this). | |
// This is because the outermost level must be treated | |
// specially anyway, because the outermost ")" has a | |
// different token type than inner ")" instances. | |
static int commentNest; | |
} | |
MORE : | |
{ | |
// domain literal | |
"[" : INDOMAINLITERAL | |
} | |
<INDOMAINLITERAL> | |
MORE : | |
{ | |
< <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); } | |
| < ~["[", "]", "\\"] > | |
} | |
<INDOMAINLITERAL> | |
TOKEN : | |
{ | |
< DOMAINLITERAL: "]" > { matchedToken.image = image.toString(); }: DEFAULT | |
} | |
MORE : | |
{ | |
// starts a comment | |
"(" : INCOMMENT | |
} | |
<INCOMMENT> | |
SKIP : | |
{ | |
// ends a comment | |
< COMMENT: ")" > : DEFAULT | |
// if this is ever changed to not be a SKIP, need | |
// to make sure matchedToken.token = token.toString() | |
// is called. | |
} | |
<INCOMMENT> | |
MORE : | |
{ | |
< <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); } | |
| "(" { commentNest = 1; } : NESTED_COMMENT | |
| < <ANY>> | |
} | |
<NESTED_COMMENT> | |
MORE : | |
{ | |
< <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); } | |
| "(" { ++commentNest; } | |
| ")" { --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); } | |
| < <ANY>> | |
} | |
// QUOTED STRINGS | |
MORE : | |
{ | |
"\"" { image.deleteCharAt(image.length() - 1); } : INQUOTEDSTRING | |
} | |
<INQUOTEDSTRING> | |
MORE : | |
{ | |
< <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); } | |
| < (~["\"", "\\"])+ > | |
} | |
<INQUOTEDSTRING> | |
TOKEN : | |
{ | |
< QUOTEDSTRING: "\"" > { matchedToken.image = image.substring(0, image.length() - 1); } : DEFAULT | |
} | |
// GLOBALS | |
<*> | |
TOKEN : | |
{ | |
< #QUOTEDPAIR: "\\" <ANY> > | |
| < #ANY: ~[] > | |
} | |
// ERROR! | |
/* | |
<*> | |
TOKEN : | |
{ | |
< UNEXPECTED_CHAR: <ANY> > | |
} | |
*/ |