grammar Tql; options { output=AST; } tokens { ON_COMPOSITE; ON_IDENTIFIER; } @parser::header { package com.compoundtheory.antlr; /** * Transfer Query Language Parser * * @author Mark Mandel */ } @lexer::header { package com.compoundtheory.antlr; /** * Transfer Query Language Lexer * * @author Mark Mandel */ } @lexer::members { private static final int STRING_MODE = 1; private static final int PROPERTY_IDENTIFIER_MODE = 2; private static final int CLASS_IDENTIFIER_MODE = 3; private static final int ALIAS_MODE = 4; private int currentMode = STRING_MODE; private int pastMode; private RecognitionException recognitionException; private String errorMessage; private void setMode(int mode) { pastMode = currentMode; currentMode = mode; } public void displayRecognitionError(String[] tokenNames, RecognitionException e) { if(!hasError()) { setRecognitionException(e); setErrorMessage(getErrorMessage(e, tokenNames)); } } public RecognitionException getRecognitionException() { return recognitionException; } private void setRecognitionException(RecognitionException e) { recognitionException = e; } public boolean hasError() { return (getRecognitionException() != null); } public String getErrorMessage() { return errorMessage; } private void setErrorMessage(String error) { errorMessage = error; } } @parser::members { private RecognitionException recognitionException; private String errorMessage; public void displayRecognitionError(String[] tokenNames, RecognitionException e) { if(!hasError()) { setRecognitionException(e); setErrorMessage(getErrorMessage(e, tokenNames)); } } public RecognitionException getRecognitionException() { return recognitionException; } private void setRecognitionException(RecognitionException e) { recognitionException = e; } public boolean hasError() { return (getRecognitionException() != null); } public String getErrorMessage() { return errorMessage; } private void setErrorMessage(String error) { errorMessage = error; } } /*----------------------------- NOTES ------------------------------- Need to do specific join statements, either by composition, or by manual operation Full outer join -------------------------------*/ /*----------------------------- Parser rules -------------------------------*/ selectStatement : ( fromExpression^ | selectExpression^ ) orderByStatement? ; fromExpression : fromStatement^ whereStatement? ; selectExpression : selectHeader^ fromStatement whereStatement? ; selectHeader : SELECT^ propertyStatement (COMMA propertyStatement)* ; propertyStatement : propertyClause | ASTERISK ; propertyClause : PROPERTY_IDENTIFIER^ (AS ALIAS)? ; fromStatement : FROM^ classClause (joinClause)* ; classClause : CLASS_IDENTIFIER^ (AS ALIAS)? ; joinClause : (outerClause)? JOIN^ classClause (onClause)? ; outerClause : (LEFT | RIGHT)? OUTER^ ; onClause : onComposite | onCondition ; onComposite : ON a=PROPERTY_IDENTIFIER (BOOLEAN_LOGIC b=PROPERTY_IDENTIFIER)* -> ^(ON_COMPOSITE ON $a (BOOLEAN_LOGIC $b)*) ; onCondition : ON conditionStatement -> ^(ON_IDENTIFIER ON conditionStatement) ; whereStatement : WHERE^ conditionStatement ; conditionStatement : ( condition ) ; condition : ( conditionBasicClause | conditionParen ) ( BOOLEAN_LOGIC ( conditionBasicClause | conditionParen ) )* ; conditionParen : LEFT_PAREN conditionStatement RIGHT_PAREN ; conditionBasicClause : PROPERTY_IDENTIFIER ( operatorClause | isNullClause | inClause ) ; operatorClause : OPERATOR (MAPPED_PARAM | PROPERTY_IDENTIFIER) ; isNullClause : IS NOT? NULL ; inClause : NOT? IN LEFT_PAREN ( MAPPED_PARAM | selectExpression ) RIGHT_PAREN ; orderByStatement : ORDER^ BY orderByClause (COMMA orderByClause)* ; orderByClause : PROPERTY_IDENTIFIER (ASC_DESC)? ; /*----------------------------- Lexer tokens -------------------------------*/ SELECT : { currentMode == STRING_MODE }?=> 'select' { setMode(PROPERTY_IDENTIFIER_MODE); } ; FROM : { currentMode == STRING_MODE }?=> 'from' { setMode(CLASS_IDENTIFIER_MODE); } ; WHERE : { currentMode == STRING_MODE }?=> 'where' { setMode(PROPERTY_IDENTIFIER_MODE); } ; COMMA : ',' { setMode(pastMode); } ; AS : //special case, not worrying about past mode { currentMode == STRING_MODE }?=> 'as' { currentMode = ALIAS_MODE; } ; ALIAS : { currentMode == ALIAS_MODE }?=> LETTER(LETTER|DIGIT|'_')* { currentMode = STRING_MODE; } ; OPERATOR : ('='|'>'|'<'|'!='|'<>'|'>='|'<='|'like') { setMode(PROPERTY_IDENTIFIER_MODE); } ; BOOLEAN_LOGIC : { currentMode == STRING_MODE }?=> ( 'and' | 'or' ) { setMode(PROPERTY_IDENTIFIER_MODE); } ; IS : { currentMode == STRING_MODE }?=> 'is' ; NOT : { currentMode == STRING_MODE }?=> 'not' ; NULL : { currentMode == STRING_MODE }?=> 'null' ; IN : { currentMode == STRING_MODE }?=> 'in' ; LEFT : { currentMode == STRING_MODE }?=> 'left' ; RIGHT : { currentMode == STRING_MODE }?=> 'right' ; OUTER : { currentMode == STRING_MODE }?=> 'outer' ; JOIN : { currentMode == STRING_MODE }?=> 'join' { setMode(CLASS_IDENTIFIER_MODE); } ; ON : { currentMode == STRING_MODE }?=> 'on' { setMode(PROPERTY_IDENTIFIER_MODE); } ; ORDER : { currentMode == STRING_MODE }?=> 'order' ; BY : { currentMode == STRING_MODE }?=> 'by' { setMode(PROPERTY_IDENTIFIER_MODE); } ; ASC_DESC : { currentMode == STRING_MODE }?=> 'asc' | 'desc' ; LEFT_PAREN : '(' ; RIGHT_PAREN : ')' ; MAPPED_PARAM : ':'(LETTER|DIGIT|'_')+ { setMode(STRING_MODE); } ; CLASS_IDENTIFIER : { currentMode == CLASS_IDENTIFIER_MODE }?=> IDENTIFIER { setMode(STRING_MODE); } ; PROPERTY_IDENTIFIER : { currentMode == PROPERTY_IDENTIFIER_MODE }?=> IDENTIFIER { setMode(STRING_MODE); } ; ASTERISK : '*' { setMode(STRING_MODE); } ; WS : (' '|'\r'|'\t'|'\u000C'|'\n') { $channel=HIDDEN; } ; fragment IDENTIFIER : LETTER(LETTER|DIGIT|'.'|'_')+ ; fragment DIGIT : '0'..'9' ; fragment LETTER : 'a'..'z' ;