/* * 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. */ //$Revision$ $Date$ header { package org.apache.commons.flatfile.dsl; import java.io.IOException; import java.util.Map; import org.apache.commons.flatfile.*; } class EntityParser extends Parser; options { k = 2; defaultErrorHandler = false; buildAST = true; exportVocab = Entity; // codeGenBitsetTestThreshold=999; } tokens { ROOT; CHECK; VALUE; ALL; FIELD; LENGTH; ARRAY; IMMUTABLE; MAP; PROTOTYPE; OPTIONS; MIN; MAX; } /* public */ parse : ( optionBlock )* ( explicitAssign ( lengthcheck )? )+ EOF! { ## = #([ROOT, "root"], ##); } ; protected optionBlock : o:OPTION_BLOCK^ { #o.setType(OPTIONS); } ( entityOption )+ SEMI! ; protected explicitAssign : IDENT entity { ## = #([ASSIGN, "="], ##); } ; protected anonymousAssign : ANON entity { ## = #([ASSIGN, "="], ##); } ; protected lengthcheck : c:COLON { #c.setType(CHECK); } length ; protected entity { String junk = null; } : valueOnlyEntity | junk=typeRefEntity | fieldOrMapEntity ; protected valueOnlyEntity { AST ast = null; } : explicitValue { ## = #([FIELD, "field"], ##); ast = ##; } ( immutable )? ( a:array[ast]! { ## = #a; } )? ; protected typeRefEntity returns [String name] { AST ast = null; } : ( t:TYPEREF^ { name = t.getText(); } ( entityOptions )? ( value )? ) { ast = ##; } ( a:array[ast]! { ## = #a; } )? ; protected fieldOrMapEntity { AST ast = null; } : ( field | map ) { ast = ##; } ( a:array[ast]! { ## = #a; } )? ; protected array[AST p] : a:LBRACK^ { #a.setType(ARRAY); } ( arrayLength )? RBRACK! { ##.addChild(#([PROTOTYPE, "prototype"], p)); } ( o:entityOptions! { ##.addChild(#o); } )? ( v:value! { ##.addChild(#v); } )? ; protected value : ( fillValue | explicitValue ) ( immutable )? ; protected fillValue : c:CHAR_LITERAL STAR! { #c.setType(ALL); } ; protected explicitValue : c:CHAR_LITERAL { #c.setType(VALUE); } | s:STRING_LITERAL { #s.setType(VALUE); } ; protected immutable : b:BANG { #b.setType(IMMUTABLE); } ; protected field : LPAREN! ( arrayLength | unboundedDynamicField ) RPAREN! ( entityOptions )? ( value )? { ## = #([FIELD, "field"], ##); } ; protected map : m:LCURLY^ { #m.setType(MAP); } mapChild ( ( COMMA! )? mapChild )* RCURLY! ( entityOptions )? ( value )? ; protected mapChild : explicitAssign | anonymousAssign | implicitTypeAssign ; protected implicitTypeAssign { String name = null; } : name=typeRefEntity { ## = #([ASSIGN], [IDENT, name], ##); } ; protected arrayLength : n:NUMBER { #n.setType(MIN); } RANGE^ ( n2:NUMBER { #n2.setType(MAX); } )? | RANGE^ n3:NUMBER { #n3.setType(MAX); } | length ; protected unboundedDynamicField : s:STAR^ { #s.setType(RANGE); } ; protected length : n:NUMBER { #n.setType(LENGTH); } ; protected entityOptions : ( entityOption )+ { ## = #([OPTIONS, "options"], ##); } ; protected entityOption : ( IDENT | STRING_LITERAL | CHAR_LITERAL | NUMBER ) ASSIGN ^ ( IDENT | STRING_LITERAL | CHAR_LITERAL | NUMBER ) ; //------------------------------------------------------- class EntityLexer extends Lexer; options { k = 2; charVocabulary='\u0003'..'\uFFFF'; // allow ascii } SEMI : ';' ; COMMA : ','; LPAREN : '(' ; RPAREN : ')' ; LCURLY : '{' ; RCURLY : '}' ; LBRACK : '[' ; RBRACK : ']' ; STAR : '*' ; ANON : '?' ; COLON : ':' ; BANG : '!' ; ASSIGN : '='; RANGE : ".."; WS: ( ' ' | '\t' | '\f' | NL ) {$setType(Token.SKIP);} ; // Single-line comments SL_COMMENT : "//" (~('\n'|'\r'))* (NL)? {$setType(Token.SKIP);} ; // multiple-line comments ML_COMMENT : "/*" ( /* '\r' '\n' can be matched in one alternative or by matching '\r' in one iteration and '\n' in another. I am trying to handle any flavor of newline that comes in, but the language that allows both "\r\n" and "\r" and "\n" to all be valid newline is ambiguous. Consequently, the resulting grammar must be ambiguous. I'm shutting this warning off. */ options { generateAmbigWarnings=false; } : { LA(2)!='/' }? '*' | NL | ~('*'|'\n'|'\r') )* "*/" {$setType(Token.SKIP);} ; protected NL : ( {LA(2) == '\n'}? '\r' '\n' | '\r' | '\n' ) { newline(); } ; // an identifier. Note that testLiterals is set to true! This means // that after we match the rule, we look in the literals table to see // if it's a literal or really an identifer IDENT : PTIDENT ; protected PTIDENT options { testLiterals = true; } : ('a'..'z'|'A'..'Z') ('a'..'z'|'A'..'Z'|'_'|'0'..'9'|'$'|'/'|'*')* ; TYPEREF : '$' i:PTIDENT { $setText(i.getText()); } ; OPTION_BLOCK : '@' i:PTIDENT { $setText(i.getText()); } ; NUMBER : ('0'..'9')+ ; // character literals CHAR_LITERAL : '\'' c:CHAR_BODY '\'' { $setText(c.getText()); } ; protected CHAR_BODY : ESC | ~('\''|'\n'|'\r'|'\\') ; // string literals STRING_LITERAL : '"' s:STRING_BODY '"' { $setText(s.getText()); } ; protected STRING_BODY { StringBuffer sb = new StringBuffer(); } : (e:ESC { sb.append(e.getText()); } | o:~('"'|'\\'|'\n'|'\r') { sb.append(o); } )* { $setText(sb.toString()); } ; // escape sequence -- note that this is protected; it can only be called // from another lexer rule -- it will not ever directly return a token to // the parser // There are various ambiguities hushed in this rule. The optional // '0'...'9' digit matches should be matched here rather than letting // them go back to STRING_LITERAL to be matched. ANTLR does the // right thing by matching immediately; hence, it's ok to shut off // the FOLLOW ambig warnings. protected ESC { char c = 0; } : '\\' ( 'n' { $setText("\n"); } | 'r' { $setText("\r"); } | 't' { $setText("\t"); } | 'b' { $setText("\b"); } | 'f' { $setText("\f"); } | '"' { $setText("\""); } | '\'' { $setText("\'"); } | '\\' { $setText("\\"); } | c=UNICODE { $setText(Character.toString(c)); } | c=OCTAL { $setText(Character.toString(c)); } ) ; protected UNICODE returns [char c = 0;] : ('u')+ HEX_DIGIT { c <<= 4; c |= Character.digit(LA(0), 16); } HEX_DIGIT { c <<= 4; c |= Character.digit(LA(0), 16); } HEX_DIGIT { c <<= 4; c |= Character.digit(LA(0), 16); } HEX_DIGIT { c <<= 4; c |= Character.digit(LA(0), 16); } ; protected OCTAL returns [char c = 0;] : '0'..'3' { c <<= 3; c |= Character.digit(LA(0), 8); } ( options { warnWhenFollowAmbig = false; } : '0'..'7' { c <<= 3; c |= Character.digit(LA(0), 8); } ( options { warnWhenFollowAmbig = false; } : '0'..'7' { c <<= 3; c |= Character.digit(LA(0), 8); } )? )? | '4'..'7' { c <<= 3; c |= Character.digit(LA(0), 8); } ( options { warnWhenFollowAmbig = false; } : '0'..'7' { c <<= 3; c |= Character.digit(LA(0), 8); } )? ; // hexadecimal digit (again, note it's protected!) protected HEX_DIGIT returns [int i = 0;] : ('0'..'9'|'A'..'F'|'a'..'f') { i = Character.digit(LA(0), 16); } ;