header { package com.fr.third.org.hibernate.hql.internal.antlr; import com.fr.third.org.hibernate.hql.internal.ast.*; import com.fr.third.org.hibernate.hql.internal.ast.util.*; } /** * Hibernate Query Language Grammar *
* This grammar parses the query language for Hibernate (an Open Source, Object-Relational * mapping library). A partial BNF grammar description is available for reference here: * http://www.hibernate.org/Documentation/HQLBNF * * Text from the original reference BNF is prefixed with '//##'. * @author Joshua Davis (pgmjsd@sourceforge.net) */ class HqlBaseParser extends Parser; options { exportVocab=Hql; buildAST=true; k=3; // For 'not like', 'not in', etc. } tokens { // -- HQL Keyword tokens -- ALL="all"; ANY="any"; AND="and"; AS="as"; ASCENDING="asc"; AVG="avg"; BETWEEN="between"; CLASS="class"; COUNT="count"; DELETE="delete"; DESCENDING="desc"; DOT; DISTINCT="distinct"; ELEMENTS="elements"; ESCAPE="escape"; EXISTS="exists"; FALSE="false"; FETCH="fetch"; FROM="from"; FULL="full"; GROUP="group"; HAVING="having"; IN="in"; INDICES="indices"; INNER="inner"; INSERT="insert"; INTO="into"; IS="is"; JOIN="join"; LEFT="left"; LIKE="like"; MAX="max"; MIN="min"; NEW="new"; NOT="not"; NULL="null"; OR="or"; ORDER="order"; OUTER="outer"; PROPERTIES="properties"; RIGHT="right"; SELECT="select"; SET="set"; SOME="some"; SUM="sum"; TRUE="true"; UPDATE="update"; VERSIONED="versioned"; WHERE="where"; NULLS="nulls"; FIRST; LAST; // -- SQL tokens -- // These aren't part of HQL, but the SQL fragment parser uses the HQL lexer, so they need to be declared here. CASE="case"; // a "searched case statement", whereas CASE2 represents a "simple case statement" END="end"; ELSE="else"; THEN="then"; WHEN="when"; ON="on"; WITH="with"; // -- JPAQL tokens -- BOTH="both"; EMPTY="empty"; LEADING="leading"; MEMBER="member"; OBJECT="object"; OF="of"; TRAILING="trailing"; KEY; VALUE; ENTRY; // -- Synthetic token types -- AGGREGATE; // One of the aggregate functions (e.g. min, max, avg) ALIAS; CONSTRUCTOR; CASE2; // a "simple case statement", whereas CASE represents a "searched case statement" CAST; EXPR_LIST; FILTER_ENTITY; // FROM element injected because of a filter expression (happens during compilation phase 2) IN_LIST; INDEX_OP; IS_NOT_NULL; IS_NULL; // Unary 'is null' operator. METHOD_CALL; NOT_BETWEEN; NOT_IN; NOT_LIKE; ORDER_ELEMENT; QUERY; RANGE; ROW_STAR; SELECT_FROM; UNARY_MINUS; UNARY_PLUS; VECTOR_EXPR; // ( x, y, z ) WEIRD_IDENT; // Identifiers that were keywords when they came in. // Literal tokens. CONSTANT; NUM_DOUBLE; NUM_FLOAT; NUM_LONG; NUM_BIG_INTEGER; NUM_BIG_DECIMAL; JAVA_CONSTANT; } { /** True if this is a filter query (allow no FROM clause). **/ private boolean filter = false; /** * Sets the filter flag. * @param f True for a filter query, false for a normal query. */ public void setFilter(boolean f) { filter = f; } /** * Returns true if this is a filter query, false if not. * @return true if this is a filter query, false if not. */ public boolean isFilter() { return filter; } /** * This method is overriden in the sub class in order to provide the * 'keyword as identifier' hack. * @param token The token to retry as an identifier. * @param ex The exception to throw if it cannot be retried as an identifier. */ public AST handleIdentifierError(Token token,RecognitionException ex) throws RecognitionException, TokenStreamException { // Base implementation: Just re-throw the exception. throw ex; } /** * This method looks ahead and converts . into . IDENT when * appropriate. */ public void handleDotIdent() throws TokenStreamException { } /** * Returns the negated equivalent of the expression. * @param x The expression to negate. */ public AST negateNode(AST x) { // Just create a 'not' parent for the default behavior. return ASTUtil.createParent(astFactory, NOT, "not", x); } /** * Returns the 'cleaned up' version of a comparison operator sub-tree. * @param x The comparison operator to clean up. */ public AST processEqualityExpression(AST x) throws RecognitionException { return x; } public void weakKeywords() throws TokenStreamException { } /** * Called after we have recognized ':'. The expectation is to handle converting * any non-IDENT token where possibleID == true into an IDENT */ public void expectNamedParameterName() throws TokenStreamException { } public void processMemberOf(Token n,AST p,ASTPair currentAST) { } protected boolean validateSoftKeyword(String text) throws TokenStreamException { return validateLookAheadText(1, text); } protected boolean validateLookAheadText(int lookAheadPosition, String text) throws TokenStreamException { String text2Validate = retrieveLookAheadText( lookAheadPosition ); return text2Validate == null ? false : text2Validate.equalsIgnoreCase( text ); } protected String retrieveLookAheadText(int lookAheadPosition) throws TokenStreamException { Token token = LT(lookAheadPosition); return token == null ? null : token.getText(); } protected String unquote(String text) { return text.substring( 1, text.length() - 1 ); } protected void registerTreat(AST pathToTreat, AST treatAs) { } } statement : ( updateStatement | deleteStatement | selectStatement | insertStatement ) ; updateStatement : UPDATE^ (VERSIONED)? optionalFromTokenFromClause setClause (whereClause)? ; setClause : (SET^ assignment (COMMA! assignment)*) ; assignment : stateField EQ^ newValue ; // "state_field" is the term used in the EJB3 sample grammar; used here for easy reference. // it is basically a property ref stateField : path ; // this still needs to be defined in the ejb3 spec; additiveExpression is currently just a best guess, // although it is highly likely I would think that the spec may limit this even more tightly. newValue : concatenation ; deleteStatement : DELETE^ (optionalFromTokenFromClause) (whereClause)? ; optionalFromTokenFromClause! : (FROM!)? f:path (a:asAlias)? { AST #range = #([RANGE, "RANGE"], #f, #a); #optionalFromTokenFromClause = #([FROM, "FROM"], #range); } ; selectStatement : queryRule { #selectStatement = #([QUERY,"query"], #selectStatement); } ; insertStatement // Would be nice if we could abstract the FromClause/FromElement logic // out such that it could be reused here; something analogous to // a "table" rule in sql-grammars : INSERT^ intoClause selectStatement ; intoClause : INTO^ path { weakKeywords(); } insertablePropertySpec ; insertablePropertySpec : OPEN! primaryExpression ( COMMA! primaryExpression )* CLOSE! { // Just need *something* to distinguish this on the hql-sql.g side #insertablePropertySpec = #([RANGE, "column-spec"], #insertablePropertySpec); } ; //## query: //## [selectClause] fromClause [whereClause] [groupByClause] [havingClause] [orderByClause]; queryRule : selectFrom (whereClause)? (groupByClause)? (orderByClause)? ; selectFrom! : (s:selectClause)? (f:fromClause)? { // If there was no FROM clause and this is a filter query, create a from clause. Otherwise, throw // an exception because non-filter queries must have a FROM clause. if (#f == null) { if (filter) { #f = #([FROM,"{filter-implied FROM}"]); } else throw new SemanticException("FROM expected (non-filter queries must contain a FROM clause)"); } // Create an artificial token so the 'FROM' can be placed // before the SELECT in the tree to make tree processing // simpler. #selectFrom = #([SELECT_FROM,"SELECT_FROM"],f,s); } ; //## selectClause: //## SELECT DISTINCT? selectedPropertiesList | ( NEW className OPEN selectedPropertiesList CLOSE ); selectClause : SELECT^ // NOTE: The '^' after a token causes the corresponding AST node to be the root of the sub-tree. { weakKeywords(); } // Weak keywords can appear immediately after a SELECT token. (DISTINCT)? ( selectedPropertiesList | newExpression | selectObject ) ; newExpression : (NEW! path) op:OPEN^ {#op.setType(CONSTRUCTOR);} selectedPropertiesList CLOSE! ; selectObject : OBJECT^ OPEN! identifier CLOSE! ; //## fromClause: //## FROM className AS? identifier ( ( COMMA className AS? identifier ) | ( joinType path AS? identifier ) )*; // NOTE: This *must* begin with the "FROM" token, otherwise the sub-query rule will be ambiguous // with the expression rule. // Also note: after a comma weak keywords are allowed and should be treated as identifiers. fromClause : FROM^ { weakKeywords(); } fromRange ( fromJoin | COMMA! { weakKeywords(); } fromRange )* ; //## joinType: //## ( ( 'left'|'right' 'outer'? ) | 'full' | 'inner' )? JOIN FETCH?; fromJoin : ( ( ( LEFT | RIGHT ) (OUTER)? ) | FULL | INNER )? JOIN^ (FETCH)? joinPath (asAlias)? (propertyFetch)? (withClause)? ; joinPath : { validateSoftKeyword("treat") && LA(2) == OPEN }? castedJoinPath | path ; /** * Represents the JPA 2.1 TREAT construct when applied to a join. Hibernate already handles subclass * property references implicitly, so we simply "eat" all tokens of the TREAT construct and just return the * join path itself. * * Uses a validating semantic predicate to make sure the text of the matched first IDENT is the TREAT keyword */ castedJoinPath : i:IDENT! OPEN! p:path AS! a:path! CLOSE! {i.getText().equalsIgnoreCase("treat") }? { registerTreat( #p, #a ); } ; withClause : WITH^ logicalExpression // JPA 2.1 support for an ON clause that isn't really an ON clause... | ON! le:logicalExpression { // it's really just a WITH clause, so treat it as such... #withClause = #( [WITH, "with"], #le ); } ; fromRange : fromClassOrOuterQueryPath | inClassDeclaration | inCollectionDeclaration | inCollectionElementsDeclaration ; fromClassOrOuterQueryPath! : c:path { weakKeywords(); } (a:asAlias)? (p:propertyFetch)? { #fromClassOrOuterQueryPath = #([RANGE, "RANGE"], #c, #a, #p); } ; inClassDeclaration! : a:alias IN! CLASS! c:path { #inClassDeclaration = #([RANGE, "RANGE"], #c, #a); } ; inCollectionDeclaration! : IN! OPEN! p:path CLOSE! a:asAlias { #inCollectionDeclaration = #([JOIN, "join"], [INNER, "inner"], #p, #a); } ; inCollectionElementsDeclaration! : a:alias IN! ELEMENTS! OPEN! p:path CLOSE! { #inCollectionElementsDeclaration = #([JOIN, "join"], [INNER, "inner"], #p, #a); } ; // Alias rule - Parses the optional 'as' token and forces an AST identifier node. asAlias : (AS!)? alias ; alias : a:identifier { #a.setType(ALIAS); } ; propertyFetch : FETCH ALL! PROPERTIES! ; //## groupByClause: //## GROUP_BY path ( COMMA path )*; groupByClause : GROUP^ "by"! expression ( COMMA! expression )* (havingClause)? ; //## orderByClause: //## ORDER_BY selectedPropertiesList; orderByClause : ORDER^ "by"! orderElement ( COMMA! orderElement )* ; orderElement : expression ( ascendingOrDescending )? ( nullOrdering )? ; ascendingOrDescending : ( "asc" | "ascending" ) { #ascendingOrDescending.setType(ASCENDING); } | ( "desc" | "descending") { #ascendingOrDescending.setType(DESCENDING); } ; nullOrdering : NULLS nullPrecedence ; nullPrecedence : IDENT { if ( "first".equalsIgnoreCase( #nullPrecedence.getText() ) ) { #nullPrecedence.setType( FIRST ); } else if ( "last".equalsIgnoreCase( #nullPrecedence.getText() ) ) { #nullPrecedence.setType( LAST ); } else { throw new SemanticException( "Expecting 'first' or 'last', but found '" + #nullPrecedence.getText() + "' as null ordering precedence." ); } } ; //## havingClause: //## HAVING logicalExpression; havingClause : HAVING^ logicalExpression ; //## whereClause: //## WHERE logicalExpression; whereClause : WHERE^ logicalExpression ; //## selectedPropertiesList: //## ( path | aggregate ) ( COMMA path | aggregate )*; selectedPropertiesList : aliasedExpression ( COMMA! aliasedExpression )* ; aliasedExpression : expression ( AS^ identifier )? ; // expressions // Note that most of these expressions follow the pattern // thisLevelExpression : // nextHigherPrecedenceExpression // (OPERATOR nextHigherPrecedenceExpression)* // which is a standard recursive definition for a parsing an expression. // // Operator precedence in HQL // lowest --> ( 7) OR // ( 6) AND, NOT // ( 5) equality: ==, <>, !=, is // ( 4) relational: <, <=, >, >=, // LIKE, NOT LIKE, BETWEEN, NOT BETWEEN, IN, NOT IN // ( 3) addition and subtraction: +(binary) -(binary) // ( 2) multiplication: * / %, concatenate: || // highest --> ( 1) +(unary) -(unary) // [] () (method call) . (dot -- identifier qualification) // aggregate function // () (explicit parenthesis) // // Note that the above precedence levels map to the rules below... // Once you have a precedence chart, writing the appropriate rules as below // is usually very straightfoward logicalExpression : expression ; // Main expression rule expression : logicalOrExpression ; // level 7 - OR logicalOrExpression : logicalAndExpression ( OR^ logicalAndExpression )* ; // level 6 - AND, NOT logicalAndExpression : negatedExpression ( AND^ negatedExpression )* ; // NOT nodes aren't generated. Instead, the operator in the sub-tree will be // negated, if possible. Expressions without a NOT parent are passed through. negatedExpression! { weakKeywords(); } // Weak keywords can appear in an expression, so look ahead. : NOT^ x:negatedExpression { #negatedExpression = negateNode(#x); } | y:equalityExpression { #negatedExpression = #y; } ; //## OP: EQ | LT | GT | LE | GE | NE | SQL_NE | LIKE; // level 5 - EQ, NE equalityExpression : x:relationalExpression ( ( EQ^ | is:IS^ { #is.setType(EQ); } (NOT! { #is.setType(NE); } )? | NE^ | ne:SQL_NE^ { #ne.setType(NE); } ) y:relationalExpression)* { // Post process the equality expression to clean up 'is null', etc. #equalityExpression = processEqualityExpression(#equalityExpression); } ; // level 4 - LT, GT, LE, GE, LIKE, NOT LIKE, BETWEEN, NOT BETWEEN // NOTE: The NOT prefix for LIKE and BETWEEN will be represented in the // token type. When traversing the AST, use the token type, and not the // token text to interpret the semantics of these nodes. relationalExpression : concatenation ( ( ( ( LT^ | GT^ | LE^ | GE^ ) additiveExpression )* ) // Disable node production for the optional 'not'. | (n:NOT!)? ( // Represent the optional NOT prefix using the token type by // testing 'n' and setting the token type accordingly. (i:IN^ { #i.setType( (n == null) ? IN : NOT_IN); #i.setText( (n == null) ? "in" : "not in"); } inList) | (b:BETWEEN^ { #b.setType( (n == null) ? BETWEEN : NOT_BETWEEN); #b.setText( (n == null) ? "between" : "not between"); } betweenList ) | (l:LIKE^ { #l.setType( (n == null) ? LIKE : NOT_LIKE); #l.setText( (n == null) ? "like" : "not like"); } concatenation likeEscape) | (MEMBER! (OF!)? p:memberOfPath! { processMemberOf(n,#p,currentAST); } ) ) ) ; likeEscape : (ESCAPE^ concatenation)? ; inList : x:compoundExpr { #inList = #([IN_LIST,"inList"], #inList); } ; betweenList : concatenation AND! concatenation ; memberOfPath // JPA says this is a `collection_valued_path_expression` which is essentially either: // 1) a treated path followed by a collection-valued attribute reference // 2) a path : { validateSoftKeyword("treat") && LA(2) == OPEN }? i:IDENT! OPEN! p:path AS! a:path! CLOSE! ( DOT^ path ) | path ; //level 4 - string concatenation concatenation : additiveExpression ( c:CONCAT^ { #c.setType(EXPR_LIST); #c.setText("concatList"); } additiveExpression ( CONCAT! additiveExpression )* { #concatenation = #([METHOD_CALL, "||"], #([IDENT, "concat"]), #c ); } )? ; // level 3 - binary plus and minus additiveExpression : multiplyExpression ( ( PLUS^ | MINUS^ ) multiplyExpression )* ; // level 2 - binary multiply and divide multiplyExpression : unaryExpression ( ( STAR^ | DIV^ | MOD^ ) unaryExpression )* ; // level 1 - unary minus, unary plus, not unaryExpression : MINUS^ {#MINUS.setType(UNARY_MINUS);} unaryExpression | PLUS^ {#PLUS.setType(UNARY_PLUS);} unaryExpression | caseExpression | quantifiedExpression | atom ; caseExpression // NOTE : the unaryExpression rule contains the subQuery rule : simpleCaseStatement | searchedCaseStatement ; simpleCaseStatement : CASE^ unaryExpression (simpleCaseWhenClause)+ (elseClause)? END! { #simpleCaseStatement.setType(CASE2); } ; simpleCaseWhenClause : (WHEN^ unaryExpression THEN! unaryExpression) ; elseClause : (ELSE^ unaryExpression) ; searchedCaseStatement : CASE^ (searchedCaseWhenClause)+ (elseClause)? END! ; searchedCaseWhenClause : (WHEN^ logicalExpression THEN! unaryExpression) ; quantifiedExpression : ( SOME^ | EXISTS^ | ALL^ | ANY^ ) ( identifier | collectionExpr | (OPEN! ( subQuery ) CLOSE!) ) ; // level 0 - expression atom // * ident qualifier ('.' ident ) // * array index ( [ expr ] ) // * method call ( '.' ident '(' exprList ') ) // * function : differentiated from method call via explicit keyword atom : primaryExpression ( DOT^ identifier ( options { greedy=true; } : ( op:OPEN^ {#op.setType(METHOD_CALL);} exprList CLOSE! ) )? | lb:OPEN_BRACKET^ {#lb.setType(INDEX_OP);} expression CLOSE_BRACKET! )* ; // level 0 - the basic element of an expression primaryExpression : { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax | { validateSoftKeyword("cast") && LA(2) == OPEN }? castFunction | identPrimary ( options {greedy=true;} : DOT^ "class" )? | constant | parameter | OPEN! (expressionOrVector | subQuery) CLOSE! ; jpaFunctionSyntax! : i:IDENT OPEN n:QUOTED_STRING COMMA a:exprList CLOSE { final String functionName = unquote( #n.getText() ); if ( functionName.equalsIgnoreCase( "cast" ) ) { #i.setType( CAST ); #i.setText( #i.getText() + " (" + functionName + ")" ); final AST expression = #a.getFirstChild(); final AST type = expression.getNextSibling(); #jpaFunctionSyntax = #( #i, expression, type ); } else { #i.setType( METHOD_CALL ); #i.setText( #i.getText() + " (" + functionName + ")" ); #jpaFunctionSyntax = #( #i, [IDENT, unquote( #n.getText() )], #a ); } } ; castFunction! : c:IDENT OPEN e:expression (AS)? t:castTargetType CLOSE { #c.setType( CAST ); #castFunction = #( #c, #e, #t ); } ; castTargetType // the cast target type is Hibernate type name which is either: // 1) a simple identifier // 2) a simple identifier-(dot-identifier)* sequence : identifier { handleDotIdent(); } ( options { greedy=true; } : DOT^ identifier )* ; parameter : COLON^ { expectNamedParameterName(); } IDENT | PARAM^ (NUM_INT)? ; // This parses normal expression and a list of expressions separated by commas. If a comma is encountered // a parent VECTOR_EXPR node will be created for the list. expressionOrVector! : e:expression ( v:vectorExpr )? { // If this is a vector expression, create a parent node for it. if (#v != null) #expressionOrVector = #([VECTOR_EXPR,"{vector}"], #e, #v); else #expressionOrVector = #e; } ; vectorExpr : COMMA! expression (COMMA! expression)* ; // identifier, followed by member refs (dot ident), or method calls. // NOTE: handleDotIdent() is called immediately after the first IDENT is recognized because // the method looks a head to find keywords after DOT and turns them into identifiers. identPrimary : i:identPrimaryBase { handleDotIdent(); } ( options { greedy=true; } : DOT^ ( identifier | ELEMENTS | o:OBJECT { #o.setType(IDENT); } ) )* ( options { greedy=true; } : ( op:OPEN^ { #op.setType(METHOD_CALL);} e:exprList CLOSE! ) { AST path = #e.getFirstChild(); if ( #i.getText().equalsIgnoreCase( "key" ) ) { #identPrimary = #( [KEY], path ); } else if ( #i.getText().equalsIgnoreCase( "value" ) ) { #identPrimary = #( [VALUE], path ); } else if ( #i.getText().equalsIgnoreCase( "entry" ) ) { #identPrimary = #( [ENTRY], path ); } } )? // Also allow special 'aggregate functions' such as count(), avg(), etc. | aggregate ; identPrimaryBase : { validateSoftKeyword("treat") && LA(2) == OPEN }? castedIdentPrimaryBase | i:identifier ; castedIdentPrimaryBase : i:IDENT! OPEN! p:path AS! a:path! CLOSE! { i.getText().equals("treat") }? { registerTreat( #p, #a ); } ; aggregate : ( SUM^ | AVG^ | MAX^ | MIN^ ) OPEN! additiveExpression CLOSE! { #aggregate.setType(AGGREGATE); } // Special case for count - It's 'parameters' can be keywords. | COUNT^ OPEN! ( STAR { #STAR.setType(ROW_STAR); } | ( ( DISTINCT | ALL )? ( path | collectionExpr | NUM_INT | caseExpression ) ) ) CLOSE! | collectionExpr ; //## collection: ( OPEN query CLOSE ) | ( 'elements'|'indices' OPEN path CLOSE ); collectionExpr : (ELEMENTS^ | INDICES^) OPEN! path CLOSE! ; // NOTE: compoundExpr can be a 'path' where the last token in the path is '.elements' or '.indicies' compoundExpr : collectionExpr | path | { LA(1) == OPEN && LA(2) == CLOSE }? OPEN! CLOSE! | (OPEN! ( (expression (COMMA! expression)*) | subQuery ) CLOSE!) | parameter ; subQuery : queryRule { #subQuery = #([QUERY,"query"], #subQuery); } ; exprList { AST trimSpec = null; } : (t:TRAILING {#trimSpec = #t;} | l:LEADING {#trimSpec = #l;} | b:BOTH {#trimSpec = #b;})? { if(#trimSpec != null) #trimSpec.setType(IDENT); } ( expression ( (COMMA! expression)+ | FROM { #FROM.setType(IDENT); } expression | AS! identifier )? | FROM { #FROM.setType(IDENT); } expression )? { #exprList = #([EXPR_LIST,"exprList"], #exprList); } ; constant : NUM_INT | NUM_FLOAT | NUM_LONG | NUM_DOUBLE | NUM_BIG_INTEGER | NUM_BIG_DECIMAL | QUOTED_STRING | NULL | TRUE | FALSE | EMPTY ; //## quantifiedExpression: 'exists' | ( expression 'in' ) | ( expression OP 'any' | 'some' ) collection; //## compoundPath: path ( OPEN_BRACKET expression CLOSE_BRACKET ( '.' path )? )*; //## path: identifier ( '.' identifier )*; path : identifier ( DOT^ { weakKeywords(); } identifier )* ; // Wraps the IDENT token from the lexer, in order to provide // 'keyword as identifier' trickery. identifier : IDENT exception catch [RecognitionException ex] { identifier_AST = handleIdentifierError(LT(1),ex); } ; // **** LEXER ****************************************************************** /** * Hibernate Query Language Lexer *
* This lexer provides the HQL parser with tokens. * @author Joshua Davis (pgmjsd@sourceforge.net) */ class HqlBaseLexer extends Lexer; options { exportVocab=Hql; // call the vocabulary "Hql" testLiterals = false; k=2; // needed for newline, and to distinguish '>' from '>='. // HHH-241 : Quoted strings don't allow unicode chars - This should fix it. charVocabulary='\u0000'..'\uFFFE'; // Allow any char but \uFFFF (16 bit -1, ANTLR's EOF character) caseSensitive = false; caseSensitiveLiterals = false; } // -- Declarations -- { // NOTE: The real implementations are in the subclass. protected void setPossibleID(boolean possibleID) {} } // -- Keywords -- EQ: '='; LT: '<'; GT: '>'; SQL_NE: "<>"; NE: "!=" | "^="; LE: "<="; GE: ">="; COMMA: ','; OPEN: '('; CLOSE: ')'; OPEN_BRACKET: '['; CLOSE_BRACKET: ']'; CONCAT: "||"; PLUS: '+'; MINUS: '-'; STAR: '*'; DIV: '/'; MOD: '%'; COLON: ':'; PARAM: '?'; IDENT options { testLiterals=true; } : ID_START_LETTER ( ID_LETTER )* { // Setting this flag allows the grammar to use keywords as identifiers, if necessary. setPossibleID(true); } ; protected ID_START_LETTER : '_' | '$' | 'a'..'z' | '\u0080'..'\ufffe' // HHH-558 : Allow unicode chars in identifiers ; protected ID_LETTER : ID_START_LETTER | '0'..'9' ; QUOTED_STRING : '\'' ( (ESCqs)=> ESCqs | ~'\'' )* '\'' ; protected ESCqs : '\'' '\'' ; WS : ( ' ' | '\t' | '\r' '\n' { newline(); } | '\n' { newline(); } | '\r' { newline(); } ) {$setType(Token.SKIP);} //ignore this token ; //--- From the Java example grammar --- // a numeric literal NUM_INT {boolean isDecimal=false; Token t=null;} : '.' {_ttype = DOT;} ( ('0'..'9')+ (EXPONENT)? (f1:FLOAT_SUFFIX {t=f1;})? { if ( t != null && t.getText().toUpperCase().indexOf("BD")>=0) { _ttype = NUM_BIG_DECIMAL; } else if (t != null && t.getText().toUpperCase().indexOf('F')>=0) { _ttype = NUM_FLOAT; } else { _ttype = NUM_DOUBLE; // assume double } } )? | ( '0' {isDecimal = true;} // special case for just '0' ( ('x') ( // hex // the 'e'|'E' and float suffix stuff look // like hex digits, hence the (...)+ doesn't // know when to stop: ambig. ANTLR resolves // it correctly by matching immediately. It // is therefore ok to hush warning. options { warnWhenFollowAmbig=false; } : HEX_DIGIT )+ | ('0'..'7')+ // octal )? | ('1'..'9') ('0'..'9')* {isDecimal=true;} // non-zero decimal ) ( ('l') { _ttype = NUM_LONG; } | ('b''i') { _ttype = NUM_BIG_INTEGER; } // only check to see if it's a float if looks like decimal so far | {isDecimal}? ( '.' ('0'..'9')* (EXPONENT)? (f2:FLOAT_SUFFIX {t=f2;})? | EXPONENT (f3:FLOAT_SUFFIX {t=f3;})? | f4:FLOAT_SUFFIX {t=f4;} ) { if ( t != null && t.getText().toUpperCase().indexOf("BD")>=0) { _ttype = NUM_BIG_DECIMAL; } else if (t != null && t.getText().toUpperCase() .indexOf('F') >= 0) { _ttype = NUM_FLOAT; } else { _ttype = NUM_DOUBLE; // assume double } } )? ; // hexadecimal digit (again, note it's protected!) protected HEX_DIGIT : ('0'..'9'|'a'..'f') ; // a couple protected methods to assist in matching floating point numbers protected EXPONENT : ('e') ('+'|'-')? ('0'..'9')+ ; protected FLOAT_SUFFIX : 'f'|'d'|'b''d' ;