/*
* Copyright 2005 Nissim Karpenstein, Stein M. Hugubakken
*
* Licensed 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.exolab.castor.jdo.oql;
import java.util.HashMap;
/**
* Generates a parse tree for a stream of tokens representing an OQL query.
*
* @author <a href="nissim@nksystems.com">Nissim Karpenstein</a>
* @version $Revision$ $Date: 2006-01-03 17:47:48 -0700 (Tue, 03 Jan 2006) $
*/
public final class Parser {
private static final HashMap TOKEN_TYPES = new HashMap();
static {
TOKEN_TYPES.put(new Integer(TokenType.END_OF_QUERY), "END_OF_QUERY");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_SELECT), "KEYWORD_SELECT");
TOKEN_TYPES.put(new Integer(TokenType.IDENTIFIER), "IDENTIFIER");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_AS), "KEYWORD_AS");
TOKEN_TYPES.put(new Integer(TokenType.COLON), "COLON");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_FROM), "KEYWORD_FROM");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_IN), "KEYWORD_IN");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_WHERE), "KEYWORD_WHERE");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_OR), "KEYWORD_OR");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_AND), "KEYWORD_AND");
TOKEN_TYPES.put(new Integer(TokenType.EQUAL), "EQUAL");
TOKEN_TYPES.put(new Integer(TokenType.NOT_EQUAL), "NOT_EQUAL");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_LIKE), "KEYWORD_LIKE");
TOKEN_TYPES.put(new Integer(TokenType.LT), "LT");
TOKEN_TYPES.put(new Integer(TokenType.LTE), "LTE");
TOKEN_TYPES.put(new Integer(TokenType.GT), "GT");
TOKEN_TYPES.put(new Integer(TokenType.GTE), "GTE");
TOKEN_TYPES.put(new Integer(TokenType.PLUS), "PLUS");
TOKEN_TYPES.put(new Integer(TokenType.MINUS), "MINUS");
TOKEN_TYPES.put(new Integer(TokenType.CONCAT), "CONCAT");
TOKEN_TYPES.put(new Integer(TokenType.TIMES), "MULTIPLY");
TOKEN_TYPES.put(new Integer(TokenType.DIVIDE), "DIVIDE");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_MOD), "KEYWORD_MOD");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_ABS), "KEYWORD_ABS");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_NOT), "KEYWORD_NOT");
TOKEN_TYPES.put(new Integer(TokenType.LPAREN), "LEFT_PAREN");
TOKEN_TYPES.put(new Integer(TokenType.RPAREN), "RIGHT_PAREN");
TOKEN_TYPES.put(new Integer(TokenType.DOLLAR), "DOLLAR");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_NIL), "KEYWORD_NIL");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_UNDEFINED), "KEYWORD_UNDEFINED");
TOKEN_TYPES.put(new Integer(TokenType.DOT), "DOT");
TOKEN_TYPES.put(new Integer(TokenType.ARROW), "ARROW");
TOKEN_TYPES.put(new Integer(TokenType.BOOLEAN_LITERAL), "BOOLEAN_LITERAL");
TOKEN_TYPES.put(new Integer(TokenType.LONG_LITERAL), "LONG_LITERAL");
TOKEN_TYPES.put(new Integer(TokenType.DOUBLE_LITERAL), "DOUBLE_LITERAL");
TOKEN_TYPES.put(new Integer(TokenType.CHAR_LITERAL), "CHAR_LITERAL");
TOKEN_TYPES.put(new Integer(TokenType.STRING_LITERAL), "STRING_LITERAL");
TOKEN_TYPES.put(new Integer(TokenType.DATE_LITERAL), "DATE_LITERAL");
TOKEN_TYPES.put(new Integer(TokenType.TIME_LITERAL), "TIME_LITERAL");
TOKEN_TYPES.put(new Integer(TokenType.TIMESTAMP_LITERAL), "TIMESTAMP_LITERAL");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_BETWEEN), "KEYWORD_BETWEEN");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_DISTINCT), "KEYWORD_DISTINCT");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_IS_DEFINED), "KEYWORD_IS_DEFINED");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_IS_UNDEFINED), "KEYWORD_IS_UNDEFINED");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_LIST), "KEYWORD_LIST");
TOKEN_TYPES.put(new Integer(TokenType.COMMA), "COMMA");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_ORDER), "KEYWORD_ORDER");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_BY), "KEYWORD_BY");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_ASC), "KEYWORD_ASC");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_DESC), "KEYWORD_DESC");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_COUNT), "KEYWORD_COUNT");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_SUM), "KEYWORD_SUM");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_MIN), "KEYWORD_MIN");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_MAX), "KEYWORD_MAX");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_AVG), "KEYWORD_AVG");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_LIMIT), "KEYWORD_LIMIT");
TOKEN_TYPES.put(new Integer(TokenType.KEYWORD_OFFSET), "KEYWORD_OFFSET");
}
private Lexer _lexer;
private Token _curToken;
private Token _nextToken;
private ParseTreeNode _treeRoot;
/**
* Creates a parser which will generate a parse tree from a series of tokens.
*
* @param lexer Lexer instance.
* @throws InvalidCharException thrown by primeLexer.
* @throws OQLSyntaxException thrown by primeLexer.
*/
public Parser(final Lexer lexer) throws InvalidCharException {
_lexer = lexer;
primeLexer();
}
/**
* Generates the parse tree for the tokens provided by the Lexer passed in
* the constructor.
*
* @return a ParseTreeNode representing the query.
* @throws InvalidCharException thrown by match.
* @throws OQLSyntaxException thrown by match.
*/
public ParseTreeNode getParseTree() throws InvalidCharException, OQLSyntaxException {
_treeRoot = match(TokenType.KEYWORD_SELECT);
if (_curToken.getTokenType() == TokenType.KEYWORD_DISTINCT) {
_treeRoot.addChild(match(TokenType.KEYWORD_DISTINCT));
}
_treeRoot.addChild(projectionAttributes());
_treeRoot.addChild(fromClause());
if (_curToken.getTokenType() == TokenType.KEYWORD_WHERE) {
_treeRoot.addChild(whereClause());
}
if (_curToken.getTokenType() == TokenType.KEYWORD_ORDER) {
_treeRoot.addChild(orderClause());
}
if (_curToken.getTokenType() == TokenType.KEYWORD_LIMIT) {
_treeRoot.addChild(limitClause());
if (_curToken.getTokenType() == TokenType.KEYWORD_OFFSET) {
_treeRoot.addChild(offsetClause());
}
}
match(TokenType.END_OF_QUERY);
return _treeRoot;
}
/**
* Primes the _curToken and _nextToken private members by calling
* {@link Lexer#nextToken()}.
*
* @throws InvalidCharException thrown by the Lexer.
*/
private void primeLexer() throws InvalidCharException {
_curToken = _lexer.nextToken();
_nextToken = _lexer.nextToken();
}
/**
* Tests whether the current token has the same token type that was passed.
* If the test passes, a ParseTreeNode is returned containing the current
* token, and the _curToken and _nextToken private members are advanced.
* If the test fails, an exception is thrown.
*
* @param tokenType The token type to compare the current token to.
* @return A ParseTreeNode containing the current Token if the test is
* successful, otherwise throws an exception.
* @throws InvalidCharException thrown by Lexer.
* @throws OQLSyntaxException if the token types don't match.
*/
private ParseTreeNode match(final int tokenType)
throws InvalidCharException, OQLSyntaxException {
if (_curToken.getTokenType() != tokenType) {
throw new OQLSyntaxException("An incorrect token type was found near "
+ _curToken.getTokenValue() + " (found "
+ (String) TOKEN_TYPES.get(new Integer(_curToken.getTokenType()))
+ ", but expected " + (String) TOKEN_TYPES.get(new Integer(tokenType))
+ ")");
}
ParseTreeNode retNode = new ParseTreeNode(_curToken);
_curToken = _nextToken;
_nextToken = _lexer.nextToken();
return retNode;
}
/**
* Consumes tokens of projection attributes (the query targets in the select
* part of an OQL query). This method also does a transformation. In OQL,
* the following projection attributes are equivalent:
* <pre>
* x as a
*
* a : x
* </pre>
* These will both return a tree with root AS, first child x, and second
* child a.
*
* @return a parse tree containing a single identifier, or a root containing
* KEYWORD_AS, with two IDENTIFIER children.
* @throws InvalidCharException passed through from match.
* @throws OQLSyntaxException passed through from match.
*/
private ParseTreeNode projectionAttributes()
throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = null;
ParseTreeNode queryTarget = expr();
if (_curToken.getTokenType() == TokenType.KEYWORD_AS) {
retNode = match(TokenType.KEYWORD_AS);
retNode.addChild(queryTarget);
retNode.addChild(match(TokenType.IDENTIFIER));
} else if (_curToken.getTokenType() == TokenType.COLON) {
if (queryTarget.getToken().getTokenType() != TokenType.IDENTIFIER) {
throw new OQLSyntaxException("When using the ':' in projection "
+ "attributes (select part of query) the token before the ':' "
+ "must be an identifier.");
}
match(TokenType.COLON);
retNode = new ParseTreeNode(Token.KEYWORD_AS);
retNode.addChild(expr());
retNode.addChild(queryTarget);
}
if (retNode == null) { return queryTarget; }
return retNode;
}
/**
* Consumes tokens of from clause.
*
* @return a parse tree with a root containing KEYWORD_FROM, and a child
* containing a tree returned from iteratorDef.
* @throws InvalidCharException passed through from match.
* @throws OQLSyntaxException passed through from match.
*/
private ParseTreeNode fromClause() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = match(TokenType.KEYWORD_FROM);
retNode.addChild(iteratorDef());
return retNode;
}
/**
* Consumes tokens of iteratorDef (the tables in the from part of an OQL
* query). The EBNF grammar for iteratorDef looks like this:
* <pre>
* iteratorDef ::= identifier{.identifier} [ [as ] identifier ]
* | identifier in identifier{.identifier}
* </pre>
* This method also does a transformation. In OQL, the following
* iteratorDefs are equivalent:
* <pre>
* x as a
*
* x a
*
* a in x
* </pre>
* These will all return a tree with root AS, first child x, and second
* child a.
*
* @return a Parse tress containing a single identifier or a root containing
* KEYWORD_AS with two IDENTIFIER children.
* @throws InvalidCharException passed through from match.
* @throws OQLSyntaxException passed through from match.
*/
private ParseTreeNode iteratorDef() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = null;
ParseTreeNode tableIdentifier = null;
if (_nextToken.getTokenType() == TokenType.DOT) {
tableIdentifier = new ParseTreeNode(Token.DOT);
tableIdentifier.addChild(match(TokenType.IDENTIFIER));
} else {
tableIdentifier = match(TokenType.IDENTIFIER);
}
while (_curToken.getTokenType() == TokenType.DOT) {
match(TokenType.DOT);
tableIdentifier.addChild(match(TokenType.IDENTIFIER));
}
if (_curToken.getTokenType() == TokenType.KEYWORD_AS) {
retNode = match(TokenType.KEYWORD_AS);
retNode.addChild(tableIdentifier);
retNode.addChild(match(TokenType.IDENTIFIER));
} else if (_curToken.getTokenType() == TokenType.IDENTIFIER) {
retNode = new ParseTreeNode(Token.KEYWORD_AS);
retNode.addChild(tableIdentifier);
retNode.addChild(match(TokenType.IDENTIFIER));
} else if (_curToken.getTokenType() == TokenType.KEYWORD_IN) {
if (tableIdentifier.getChildCount() > 0) {
throw new OQLSyntaxException("Only the class name in the from clause "
+ "can contain dots.");
}
match(TokenType.KEYWORD_IN);
retNode = new ParseTreeNode(Token.KEYWORD_AS);
ParseTreeNode classNode = null;
if (_nextToken.getTokenType() == TokenType.DOT) {
classNode = new ParseTreeNode(Token.DOT);
classNode.addChild(match(TokenType.IDENTIFIER));
} else {
classNode = match(TokenType.IDENTIFIER);
}
while (_curToken.getTokenType() == TokenType.DOT) {
match(TokenType.DOT);
classNode.addChild(match(TokenType.IDENTIFIER));
}
retNode.addChild(classNode);
retNode.addChild(tableIdentifier);
}
if (retNode == null) { return tableIdentifier; }
return retNode;
}
/**
* Consumes tokens of where clause.
*
* @return a Parse tree with a root containing KEYWORD_WHERE, and one child
* containing a tree returned by expr.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match().
*/
private ParseTreeNode whereClause() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = match(TokenType.KEYWORD_WHERE);
retNode.addChild(expr());
return retNode;
}
/**
* Consumes tokens of expr clause.
*
* @return a parse tree containing the return value of orExpr.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match().
*/
private ParseTreeNode expr() throws InvalidCharException, OQLSyntaxException {
return orExpr();
}
/**
* Consumes tokens of orExpr clause.
*
* @return a Parse tree containing a single andExpr tree, or a root
* containing KEYWORD_OR with two andExpr tree children.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match().
*/
private ParseTreeNode orExpr() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode tmpNode = null;
ParseTreeNode leftSide = andExpr();
// consume all sequential OR's
while (_curToken.getTokenType() == TokenType.KEYWORD_OR) {
tmpNode = match(TokenType.KEYWORD_OR);
tmpNode.addChild(leftSide);
tmpNode.addChild(andExpr());
leftSide = tmpNode;
}
return leftSide;
}
/**
* Consumes tokens of andExpr clause.
*
* @return a Parse tree containing a single equalityExpr tree, or a root
* containing KEYWORD_AND with two equalityExpr tree children.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match().
*/
private ParseTreeNode andExpr() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode tmpNode = null;
ParseTreeNode leftSide = equalityExpr();
// consume all sequential AND's
while (_curToken.getTokenType() == TokenType.KEYWORD_AND) {
tmpNode = match(TokenType.KEYWORD_AND);
tmpNode.addChild(leftSide);
tmpNode.addChild(equalityExpr());
leftSide = tmpNode;
}
return leftSide;
}
/**
* Consumes tokens of equalityExpr clause.
*
* @return a Parse tree containing a single relationalExpr tree, or a root
* containing EQUAL, NOT_EQUAL, or KEYWORD_LIKE, with two relationalExpr
* tree children.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match().
*/
private ParseTreeNode equalityExpr() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = null;
ParseTreeNode leftSide = relationalExpr();
int tokenType = _curToken.getTokenType();
switch (tokenType) {
case TokenType.EQUAL:
case TokenType.NOT_EQUAL:
case TokenType.KEYWORD_LIKE:
retNode = match(tokenType);
retNode.addChild(leftSide);
retNode.addChild(relationalExpr());
default:
break;
}
if (retNode == null) { return leftSide; }
return retNode;
}
/**
* Consumes tokens of relationalExpr clause.
*
* @return a Parse tree containing a single additiveExpr tree, or a root
* containing GT, GTE, LT, or LTE, with two additiveExpr tree children.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match().
*/
private ParseTreeNode relationalExpr()
throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = null;
ParseTreeNode leftSide = additiveExpr();
int tokenType = _curToken.getTokenType();
switch (tokenType) {
case TokenType.GT:
case TokenType.GTE:
case TokenType.LT:
case TokenType.LTE:
retNode = match(tokenType);
retNode.addChild(leftSide);
retNode.addChild(additiveExpr());
break;
case TokenType.KEYWORD_BETWEEN:
retNode = match(TokenType.KEYWORD_BETWEEN);
retNode.addChild(leftSide);
retNode.addChild(additiveExpr());
match(TokenType.KEYWORD_AND);
retNode.addChild(additiveExpr());
break;
default:
break;
}
if (retNode == null) { return leftSide; }
return retNode;
}
/**
* Consumes tokens of additiveExpr clause.
*
* @return a Parse tree containing a single multiplicativeExpr tree, or a
* root containing PLUS, MINUS, or CONCAT, with two multiplicativeExpr
* tree children.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match().
*/
private ParseTreeNode additiveExpr() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = null;
ParseTreeNode leftSide = multiplicativeExpr();
int tokenType = _curToken.getTokenType();
switch (tokenType) {
case TokenType.PLUS:
case TokenType.MINUS:
case TokenType.CONCAT:
retNode = match(tokenType);
retNode.addChild(leftSide);
retNode.addChild(multiplicativeExpr());
break;
default:
break;
}
if (retNode == null) { return leftSide; }
return retNode;
}
/**
* Consumes tokens of multiplicativeExpr clause.
*
* @return a Parse tree containing a single unaryExpr tree, or a
* root containing TIMES, DIVIDE, or KEYWORD_MOD, with two unaryExpr
* tree children.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match().
*/
private ParseTreeNode multiplicativeExpr()
throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = null;
ParseTreeNode leftSide = inExpr();
int tokenType = _curToken.getTokenType();
switch (tokenType) {
case TokenType.TIMES:
case TokenType.DIVIDE:
case TokenType.KEYWORD_MOD:
retNode = match(tokenType);
retNode.addChild(leftSide);
retNode.addChild(inExpr());
break;
default:
break;
}
if (retNode == null) { return leftSide; }
return retNode;
}
/**
* Consumes tokens of inExpr clause.
*
* @return a Parse tree containing a single unaryExpr tree, or a
* root containing KEYWORD_IN, with two unaryExpr
* tree children.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match().
*/
private ParseTreeNode inExpr() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = null;
ParseTreeNode leftSide = unaryExpr();
if (_curToken.getTokenType() == TokenType.KEYWORD_IN) {
retNode = match(TokenType.KEYWORD_IN);
retNode.addChild(leftSide);
retNode.addChild(unaryExpr());
}
if (retNode == null) { return leftSide; }
return retNode;
}
/**
* Consumes tokens of unaryExpr clause.
*
* @return a Parse tree containing a single primaryExpr tree, or a unary
* operator root, with a single unaryExpr tree child.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match().
*/
private ParseTreeNode unaryExpr() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = null;
int tokenType = _curToken.getTokenType();
switch (tokenType) {
case TokenType.PLUS:
case TokenType.MINUS:
case TokenType.KEYWORD_ABS:
case TokenType.KEYWORD_NOT:
retNode = match(tokenType);
retNode.addChild(unaryExpr());
break;
default:
break;
}
if (retNode == null) { return postfixExpr(); }
return retNode;
}
/**
* Consumes tokens of postfixExpr. This method also performs a transformation
* returning a tree with DOT as the root and two IDENTIFIERS as children for
* both <code>IDENTIFIER.IDENTIFIER</code> and
* <code>IDENTIFIER->IDENTIFIER</code>
*
* @return a Parse tree containing a single primaryExpr tree, or a DOT root
* with two IDENTIFIER children.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match().
*/
private ParseTreeNode postfixExpr() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = null;
int curTokenType = _curToken.getTokenType();
int nextTokenType = 0;
if (_nextToken != null) {
nextTokenType = _nextToken.getTokenType();
}
if ((curTokenType == TokenType.IDENTIFIER)
&& ((nextTokenType == TokenType.DOT)
|| (nextTokenType == TokenType.ARROW))) {
retNode = new ParseTreeNode(Token.DOT);
while ((curTokenType == TokenType.IDENTIFIER)
&& ((nextTokenType == TokenType.DOT)
|| (nextTokenType == TokenType.ARROW))) {
retNode.addChild(match(TokenType.IDENTIFIER));
match(nextTokenType); //the dot or arrow
curTokenType = _curToken.getTokenType();
if (_nextToken != null) {
nextTokenType = _nextToken.getTokenType();
} else {
nextTokenType = 0;
}
}
retNode.addChild(match(TokenType.IDENTIFIER));
}
if (retNode == null) { return primaryExpr(); }
return retNode;
}
/**
* Consumes tokens of primaryExpr clause.
*
* @return a Parse tree containing a single primaryExpr tree, which is either
* an expr, a queryParam, an identifier, or a literal.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match(), or if an unknown
* token is encountered here.
*/
private ParseTreeNode primaryExpr() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = null;
int tokenType = _curToken.getTokenType();
switch (tokenType) {
case TokenType.LPAREN:
retNode = match(TokenType.LPAREN);
retNode.addChild(expr());
match(TokenType.RPAREN);
break;
case TokenType.KEYWORD_IS_DEFINED:
case TokenType.KEYWORD_IS_UNDEFINED:
retNode = undefinedExpr();
break;
case TokenType.KEYWORD_LIST:
retNode = collectionExpr();
break;
case TokenType.KEYWORD_COUNT:
case TokenType.KEYWORD_SUM:
case TokenType.KEYWORD_MIN:
case TokenType.KEYWORD_MAX:
case TokenType.KEYWORD_AVG:
retNode = aggregateExpr();
break;
case TokenType.DOLLAR:
retNode = queryParam();
break;
case TokenType.IDENTIFIER:
if (_nextToken.getTokenType() == TokenType.LPAREN) {
retNode = functionCall();
} else {
retNode = match(TokenType.IDENTIFIER);
}
break;
case TokenType.KEYWORD_NIL:
case TokenType.KEYWORD_UNDEFINED:
case TokenType.BOOLEAN_LITERAL:
case TokenType.LONG_LITERAL:
case TokenType.DOUBLE_LITERAL:
case TokenType.CHAR_LITERAL:
case TokenType.STRING_LITERAL:
case TokenType.DATE_LITERAL:
case TokenType.TIME_LITERAL:
case TokenType.TIMESTAMP_LITERAL:
retNode = match(tokenType);
break;
default:
throw new OQLSyntaxException("An inapropriate token ("
+ String.valueOf(tokenType) + ") was encountered in an expression.");
}
if (retNode == null) { return primaryExpr(); }
return retNode;
}
/**
* Consumes tokens of a function call.
*
* @return a Parse tree containing an identifier root which is the name of
* the function, child LPAREN with children the arguments.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match(), or if an unknown
* token is encountered here.
*/
private ParseTreeNode functionCall() throws InvalidCharException, OQLSyntaxException {
int tokenType = _curToken.getTokenType();
int nextTokenType = _nextToken.getTokenType();
if ((tokenType != TokenType.IDENTIFIER) || (nextTokenType != TokenType.LPAREN)) {
throw new OQLSyntaxException("Expected a function call and did not find "
+ "one, near " + _curToken.getTokenValue());
}
ParseTreeNode retNode = match(TokenType.IDENTIFIER);
ParseTreeNode parNode = match(TokenType.LPAREN);
parNode.addChild(unaryExpr());
while (_curToken.getTokenType() == TokenType.COMMA) {
match(TokenType.COMMA);
parNode.addChild(unaryExpr());
}
retNode.addChild(parNode);
retNode.addChild(match(TokenType.RPAREN));
return retNode;
}
/**
* Consumes tokens of collectionExpr.
*
* @return Parse Tree with the root containing the function call and
* the children containing the parameters
* @throws InvalidCharException passed through from match()
* @throws OQLSyntaxException passed through from match() or if an
* unexpected token is encountered here.
*/
private ParseTreeNode collectionExpr()
throws InvalidCharException, OQLSyntaxException {
if (_curToken.getTokenType() == TokenType.KEYWORD_LIST) {
ParseTreeNode retNode = match(TokenType.KEYWORD_LIST);
match(TokenType.LPAREN);
retNode.addChild(expr());
while (_curToken.getTokenType() == TokenType.COMMA) {
match(TokenType.COMMA);
retNode.addChild(expr());
}
match(TokenType.RPAREN);
return retNode;
}
throw new OQLSyntaxException("Expected collectionExpr and didn't find it at "
+ "or near: " + _curToken.getTokenValue());
}
/**
* Consumes tokens of aggregateExpr.
*
* @return Parse Tree with the root containing the function call and
* the child containing the parameter.
* @throws InvalidCharException passed through from match()
* @throws OQLSyntaxException passed through from match() or if an
* unexpected token is encountered here.
*/
private ParseTreeNode aggregateExpr()
throws InvalidCharException, OQLSyntaxException {
int tokenType = _curToken.getTokenType();
ParseTreeNode retNode = null;
switch (tokenType) {
case TokenType.KEYWORD_SUM:
case TokenType.KEYWORD_MIN:
case TokenType.KEYWORD_MAX:
case TokenType.KEYWORD_AVG:
retNode = match(tokenType);
match(TokenType.LPAREN);
retNode.addChild(expr());
match(TokenType.RPAREN);
break;
case TokenType.KEYWORD_COUNT:
//special case because it supports count(*)
retNode = match(TokenType.KEYWORD_COUNT);
match(TokenType.LPAREN);
if (_curToken.getTokenType() == TokenType.TIMES) {
retNode.addChild(match(TokenType.TIMES));
} else if (_curToken.getTokenType() == TokenType.KEYWORD_DISTINCT) {
retNode.addChild(match(TokenType.KEYWORD_DISTINCT));
retNode.addChild(expr());
} else {
retNode.addChild(expr());
}
match(TokenType.RPAREN);
break;
default:
throw new OQLSyntaxException("Expected aggregateExpr and didn't find it "
+ "at or near: " + _curToken.getTokenValue());
}
return retNode;
}
/**
* Consumes tokens of undefinedExpr.
*
* @return Parse Tree with the root containing the function call and
* the child containing the parameter
* @throws InvalidCharException passed through from match()
* @throws OQLSyntaxException passed through from match() or if an
* unexpected token is encountered here.
*/
private ParseTreeNode undefinedExpr()
throws InvalidCharException, OQLSyntaxException {
int tokenType = _curToken.getTokenType();
if ((tokenType == TokenType.KEYWORD_IS_DEFINED)
|| (tokenType == TokenType.KEYWORD_IS_UNDEFINED)) {
ParseTreeNode retNode = match(tokenType);
match(TokenType.LPAREN);
if (_nextToken.getTokenType() == TokenType.DOT) {
ParseTreeNode childNode = new ParseTreeNode(Token.DOT);
childNode.addChild(match(TokenType.IDENTIFIER));
while (_curToken.getTokenType() == TokenType.DOT) {
match(TokenType.DOT);
childNode.addChild(match(TokenType.IDENTIFIER));
}
retNode.addChild(childNode);
} else {
retNode.addChild(match(TokenType.IDENTIFIER));
}
match(TokenType.RPAREN);
return retNode;
}
throw new OQLSyntaxException("Expected undefinedExpr and didn't find it "
+ "at or near: " + _curToken.getTokenValue());
}
/**
* Consumes tokens of queryParam.
*
* @return a Parse tree containing DOLLAR as the root, and either one child
* which is a LONG_LITERAL, or one child which is an IDENTIFIER, and one
* child which is a LONG_LITERAL.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match(), or if an unknown
* token is encountered here.
*/
private ParseTreeNode queryParam() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = match(TokenType.DOLLAR);
int tokenType = _curToken.getTokenType();
switch (tokenType) {
case TokenType.LPAREN:
match(TokenType.LPAREN);
retNode.addChild(match(TokenType.IDENTIFIER));
match(TokenType.RPAREN);
retNode.addChild(match(TokenType.LONG_LITERAL));
break;
case TokenType.LONG_LITERAL:
retNode.addChild(match(TokenType.LONG_LITERAL));
break;
default:
throw new OQLSyntaxException("An inapropriate token was encountered in "
+ "a query parameter.");
}
return retNode;
}
/**
* Consumes tokens of orderClause.
*
* @return a Parse tree containing ORDER as the root, with children
* as order parameters.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match(), or if an
* unknown token is encountered here.
*/
private ParseTreeNode orderClause() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = match(TokenType.KEYWORD_ORDER);
match(TokenType.KEYWORD_BY);
ParseTreeNode curExpression = null;
ParseTreeNode curOrder = null;
curExpression = expr();
int tokenType = _curToken.getTokenType();
if ((tokenType == TokenType.KEYWORD_ASC)
|| (tokenType == TokenType.KEYWORD_DESC)) {
curOrder = match(tokenType);
curOrder.addChild(curExpression);
retNode.addChild(curOrder);
} else {
retNode.addChild(curExpression);
}
while (_curToken.getTokenType() == TokenType.COMMA) {
match(TokenType.COMMA);
curExpression = expr();
tokenType = _curToken.getTokenType();
if ((tokenType == TokenType.KEYWORD_ASC)
|| (tokenType == TokenType.KEYWORD_DESC)) {
curOrder = match(tokenType);
curOrder.addChild(curExpression);
retNode.addChild(curOrder);
} else {
retNode.addChild(curExpression);
}
}
return retNode;
}
/**
* Consumes tokens of limitClause.
*
* @return a Parse tree containing LIMIT as the root, with children
* as limit parameters.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match(), or if an
* unknown token is encountered here.
*/
private ParseTreeNode limitClause() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = match(TokenType.KEYWORD_LIMIT);
retNode.addChild(queryParam());
return retNode;
}
/**
* Consumes tokens of limitClause.
*
* @return a Parse tree containing LIMIT as the root, with children
* as limit parameters.
* @throws InvalidCharException passed through from match().
* @throws OQLSyntaxException passed through from match(), or if an
* unknown token is encountered here.
*/
private ParseTreeNode offsetClause() throws InvalidCharException, OQLSyntaxException {
ParseTreeNode retNode = match(TokenType.KEYWORD_OFFSET);
retNode.addChild(queryParam());
return retNode;
}
}