//============================================================================== // QueryParser.java //============================================================================== package tribble.parse.sql; // System imports import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; import java.lang.Character; import java.lang.String; import java.lang.System; import java.text.ParseException; import java.util.HashMap; // Local imports import tribble.io.ParserI; import tribble.parse.sql.ExprLexer; import tribble.parse.sql.QueryExpr; /******************************************************************************* * Contains methods for parsing query expressions. * *
* This class contains methods for parsing an SQL-like query expression string * (i.e., an expression similar to an SQL 'SELECT WHERE' * clause) and constructing an expression tree from it. Such a tree can then * evaluated against a given set of value objects. * * *
* A query (search) criteria string, composed of operators and operands, * is used to construct a query control object: * *
* QueryParser parser; // Query expression parser
* String crit; // Query expression
* QueryExpr query; // Query control object
*
* parser = new {@link #QueryParser() QueryParser}();
* crit = "date >= '2001-08-01' and rec.name like 'report%.pdf'";
* query = {@link #parse parse}(crit);
*
* * See also the description of Expression Trees in the {@link QueryExpr} class. * * *
* The following expression syntax is recognized: * *
* expr:
* or_expr
*
* or_expr:
* and_expr
* and_expr 'OR' or_expr OP_OR
*
* and_expr:
* not_expr
* not_expr 'AND' and_expr OP_AND
*
* not_expr:
* cmp_expr
* 'NOT' not_expr OP_NOT
*
* cmp_expr:
* add_expr
* add_expr 'IS' ['NOT'] 'NULL' OP_IS
* add_expr ['NOT'] '=' add_expr OP_EQ
* add_expr ['NOT'] '<>' add_expr OP_NE
* add_expr ['NOT'] '<' add_expr OP_LT
* add_expr ['NOT'] '<=' add_expr OP_LE
* add_expr ['NOT'] '>' add_expr OP_GT
* add_expr ['NOT'] '>=' add_expr OP_GE
* add_expr ['NOT'] 'CONTAINS' add_expr OP_CONTAINS
* add_expr ['NOT'] 'LIKE' add_expr OP_LIKE
* add_expr ['NOT'] 'LIKEFILE' add_expr OP_LIKEFILE
* add_expr ['NOT'] 'BETWEEN' add_expr 'AND' add_expr OP_BETWEEN
* add_expr ['NOT'] 'IN' '(' expr_list ')' OP_IN
*
* expr_list:
* add_expr
* add_expr [','] expr_list OP_LIST
*
* add_expr:
* mul_expr
* mul_expr '+' add_expr OP_ADD
* mul_expr '-' add_expr OP_SUB
* mul_expr '||' add_expr OP_CONCAT
*
* mul_expr:
* unary_expr
* unary_expr '*' mul_expr OP_MUL
* unary_expr '/' mul_expr OP_DIV
* unary_expr 'MOD' mul_expr OP_MOD
*
* unary_expr
* expo_expr
* '+' unary_expr OP_POS
* '-' unary_expr OP_NEG
*
* expo_expr:
* operand
* operand '**' unary_expr OP_EXPO
*
* operand:
* '(' or_expr ')'
* name_expr
* number String
* 'NULL' String
*
* name_expr:
* name String
* string String
* name_expr '.' name OP_MEMBER
* name_expr '.' string OP_MEMBER
* name_expr '[' add_expr ']' OP_SUBSCR
*
*
*
* @version $Revision: 1.11 $ $Date: 2007/08/01 03:33:10 $
* @since 2001-03-24
* @author David R. Tribble (david@tribble.com).
*
* Copyright ©2001 by David R. Tribble, all rights reserved.
* Permission is granted to any person or entity except those designated by
* by the United States Department of State as a terrorist, or terrorist
* government or agency, to use and distribute this source code provided
* that the original copyright notice remains present and unaltered.
*
* @see QueryExpr
*/
public class QueryParser
//implements tribble.io.ParserI
{
// Identification
/** Revision information. */
static final String REV =
"@(#)tribble/parse/sql/QueryParser.java $Revision: 1.11 $ $Date: 2007/08/01 03:33:10 $\n";
public static final int SERIES = 200;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Private constants
private static final HashMap s_operators;
static
{
// Initialize
s_operators = new HashMap(41);
s_operators.put("not", QueryExpr.OP_NOT);
s_operators.put("and", QueryExpr.OP_AND);
s_operators.put("or", QueryExpr.OP_OR);
s_operators.put("&", QueryExpr.OP_AND);
s_operators.put("|", QueryExpr.OP_OR);
s_operators.put("(", QueryExpr.OP_LP);
s_operators.put(")", QueryExpr.OP_RP);
s_operators.put(",", QueryExpr.OP_LIST);
s_operators.put("is", QueryExpr.OP_IS);
s_operators.put("=", QueryExpr.OP_EQ);
s_operators.put("!=", QueryExpr.OP_NE);
s_operators.put("<>", QueryExpr.OP_NE);
s_operators.put("<", QueryExpr.OP_LT);
s_operators.put("<=", QueryExpr.OP_LE);
s_operators.put(">", QueryExpr.OP_GT);
s_operators.put(">=", QueryExpr.OP_GE);
s_operators.put("**", QueryExpr.OP_EXPO);
s_operators.put("*", QueryExpr.OP_MUL);
s_operators.put("/", QueryExpr.OP_DIV);
s_operators.put("mod", QueryExpr.OP_MOD);
s_operators.put("+", QueryExpr.OP_ADD);
s_operators.put("-", QueryExpr.OP_SUB);
s_operators.put("||", QueryExpr.OP_CONCAT);
s_operators.put(".", QueryExpr.OP_MEMBER);
s_operators.put("[", QueryExpr.OP_SUBSCR);
s_operators.put("]", QueryExpr.OP_SUBSCR2);
s_operators.put("contains", QueryExpr.OP_CONTAINS);
s_operators.put("like", QueryExpr.OP_LIKE);
s_operators.put("likefile", QueryExpr.OP_LIKEFILE);
s_operators.put("in", QueryExpr.OP_IN);
s_operators.put("between", QueryExpr.OP_BETWEEN);
s_operators.put("null", QueryExpr.OP_NULL);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Public static methods
/***************************************************************************
* Test driver.
*
* @since 1.1, 2001-03-11
*/
public static void main(String[] args)
throws Exception
{
PrintWriter sysout;
QueryParser parser;
boolean opt_simplify = false;
boolean opt_xml = false;
int i;
// Get command line options
for (i = 0; i < args.length && args[i].charAt(0) == '-'; i++)
{
if (args[i].equals("-x"))
opt_xml = true;
else if (args[i].equals("-s"))
opt_simplify = true;
else
throw new Exception("Unknown option: '" + args[i] + "'");
}
// Check args
if (i >= args.length)
{
System.out.println("Parse an SQL-like expression.");
System.out.println();
System.out.println("usage: java " + QueryParser.class.getName()
+ " [-option...] \"query-expr\"...");
System.out.println();
System.out.println("Options:");
System.out.println(" -s "
+ "Simplify the parse tree.");
System.out.println(" -x "
+ "Display the parse tree as XML.");
System.exit(255);
}
// Parse one or more query expressions
sysout = new PrintWriter(System.out, true);
parser = new QueryParser();
for ( ; i < args.length; i++)
{
try
{
QueryExpr expr;
// Parse the query expression
System.out.println((i+1) + ". " + args[i]);
System.out.println();
expr = parser.parse(args[i]);
// Display the resulting parse tree
System.out.println("Parse tree:");
expr.dump(sysout);
System.out.println();
// Simplify the parse tree
if (opt_simplify)
{
System.out.println("Simplified:");
expr.simplify();
expr.dump(sysout);
System.out.println();
}
// Display the parse tree reconstructed as a string
System.out.println("=> " + expr.toString());
System.out.println();
// Display the parse tree as XML
if (opt_xml)
{
System.out.println("XML:");
expr.writeAsXml(new OutputStreamWriter(System.out));
System.out.println();
}
}
catch (ParseException ex)
{
// Parsing error
sysout.println("*** ParseException ***");
ex.printStackTrace(sysout);
System.out.println();
}
sysout.flush();
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Public constructors
/***************************************************************************
* Default constructor.
*
* @since 1.1, 2001-03-13
*/
public QueryParser()
{
// Do nothing
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Public methods
/***************************************************************************
* Parse a query expression, converting it into an expression tree.
*
* @param expr
* A query expression. This is parsed and converted into an expression tree.
*
* @throws ParseException
* Thrown if the query expression expr is malformed.
*
* @return
* An expression tree resulting from the parse.
*
* @throws ParseException
* Thrown if the query expression expr is malformed.
*
* @since 1.1, 2001-03-13
*/
public QueryExpr parse(String expr)
throws ParseException
{
Object tree;
QueryExpr res;
// Split the search criteria expression into tokens
m_toks = ExprLexer.getTokens(expr);
// Parse the expression tokens
m_toki = 0;
tree = parse_or_expr();
if (tree instanceof String)
{
res = new QueryExpr();
res.m_op = QueryExpr.OP_VALUE;
res.m_arg1 = tree;
}
else
res = (QueryExpr) tree;
// Check for the end of the expression
if (m_toki < m_toks.length)
throw new ParseException("Malformed expression at: '"
+ m_toks[m_toki] + "'", m_toki);
return (res);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Private variables
/** Expression tokens. */
private String[] m_toks;
/** Next token index. */
private int m_toki;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Private methods
/***************************************************************************
* Parse a query subexpression, converting it into an expression subtree.
*
*
* This {@link QueryExpr} object consitutes a partial expression subtree * (or node), which is filled in with the results of the parse. * * *
* Syntax *
*
* or_expr:
* and_expr
* and_expr 'OR' or_expr op: OP_OR
*
*
*
* @param toks
* A sequence of tokens forming a query expression.
*
* @param n
* Index of the first token in toks[] to start parsing at.
*
* @return
* A query expression tree.
*
* @throws ParseException
* Thrown if the query expression expr is malformed.
*
* @since 1.1, 2001-03-23
*/
private Object parse_or_expr()
throws ParseException
{
Object res;
// or_expr: and_expr ['OR' and_expr]...
res = parse_and_expr();
for (;;)
{
String tok;
String keyword;
// Check for end of expression
if (m_toki >= m_toks.length)
return (res);
// Continue parsing
tok = m_toks[m_toki++];
keyword = (String) s_operators.get(tok.toLowerCase());
if (keyword == null)
throw new ParseException("Bad operator: '" + tok + "'",
m_toki-1);
// Parse a mul-op
if (keyword == QueryExpr.OP_OR)
{
QueryExpr expr;
// or_expr: and_expr 'OR' and_expr
expr = new QueryExpr();
expr.m_op = keyword;
expr.m_arg1 = res;
expr.m_arg2 = parse_and_expr();
res = expr;
}
else
{
m_toki--;
return (res);
}
}
}
/***************************************************************************
* Parse a query subexpression, converting it into an expression subtree.
* See {@link #parse_or_expr} for further details.
*
*
* * Syntax *
*
* and_expr:
* not_expr
* not_expr 'AND' and_expr op: OP_AND
*
*
* @since 1.1, 2001-03-23
*/
private Object parse_and_expr()
throws ParseException
{
Object res;
// and_expr: not_expr ['AND' not_expr]...
res = parse_not_expr();
for (;;)
{
String tok;
String keyword;
// Check for end of expression
if (m_toki >= m_toks.length)
return (res);
// Continue parsing
tok = m_toks[m_toki++];
keyword = (String) s_operators.get(tok.toLowerCase());
if (keyword == null)
throw new ParseException("Bad operator: '" + tok + "'",
m_toki-1);
// Parse a mul-op
if (keyword == QueryExpr.OP_AND)
{
QueryExpr expr;
// and_expr: not_expr 'AND' not_expr
expr = new QueryExpr();
expr.m_op = keyword;
expr.m_arg1 = res;
expr.m_arg2 = parse_not_expr();
res = expr;
}
else
{
m_toki--;
return (res);
}
}
}
/***************************************************************************
* Parse a query subexpression, converting it into an expression subtree.
* See {@link #parse_or_expr} for further details.
*
*
* * Syntax *
*
* not_expr:
* cmp_expr
* 'NOT' not_expr op: OP_NOT
*
*
* @since 1.6, 2001-04-08
*/
private Object parse_not_expr()
throws ParseException
{
Object res;
String tok;
String keyword;
// Check for end of expression
if (m_toki >= m_toks.length)
return (null);
// Continue parsing
tok = m_toks[m_toki++];
keyword = (String) s_operators.get(tok.toLowerCase());
if (keyword == QueryExpr.OP_NOT)
{
QueryExpr expr;
// not_expr: 'NOT' cmp_expr
expr = new QueryExpr();
expr.m_op = keyword;
expr.m_arg1 = parse_not_expr();
res = expr;
}
else
{
// not_expr: cmp_expr
m_toki--;
res = parse_cmp_expr();
}
return (res);
}
/***************************************************************************
* Parse a query subexpression, converting it into an expression subtree.
* See {@link #parse_or_expr} for further details.
*
*
* * Syntax *
*
* cmp_expr:
* add_expr
* add_expr 'IS' ['NOT'] 'NULL' op: OP_IS
* add_expr ['NOT'] '=' add_expr op: OP_EQ
* add_expr ['NOT'] '<>' add_expr op: OP_NE
* add_expr ['NOT'] '<' add_expr op: OP_LT
* add_expr ['NOT'] '<=' add_expr op: OP_LE
* add_expr ['NOT'] '>' add_expr op: OP_GT
* add_expr ['NOT'] '>=' add_expr op: OP_GE
* add_expr ['NOT'] 'CONTAINS' add_expr op: OP_CONTAINS
* add_expr ['NOT'] 'LIKE' add_expr op: OP_LIKE
* add_expr ['NOT'] 'LIKEFILE' add_expr op: OP_LIKEFILE
* add_expr ['NOT'] 'BETWEEN' add_expr 'AND' add_expr op: OP_BETWEEN
* add_expr ['NOT'] 'IN' '(' expr_list ')' op: OP_IN
*
*
* @since 1.1, 2001-03-24
*/
private Object parse_cmp_expr()
throws ParseException
{
Object res;
String tok;
String keyword;
boolean isCompl;
// Parse 'add_expr'
res = parse_add_expr();
// Check for end of expression
if (m_toki >= m_toks.length)
return (res);
// Continue parsing
tok = m_toks[m_toki++];
keyword = (String) s_operators.get(tok.toLowerCase());
if (keyword == null)
{
// Invalid operator
throw new ParseException("Bad operator: '" + tok + "'", m_toki-1);
}
// Parse 'IS [NOT] NULL'
if (keyword == QueryExpr.OP_IS)
{
QueryExpr expr;
// Parse '[NOT] NULL'
if (m_toki < m_toks.length)
tok = m_toks[m_toki++];
else
throw new ParseException("Missing operand following: '"
+ tok + "'", m_toki-1);
keyword = (String) s_operators.get(tok.toLowerCase());
expr = new QueryExpr();
expr.m_op = QueryExpr.OP_IS;
expr.m_arg1 = res;
expr.m_arg2 = QueryExpr.OP_NULL;
res = expr;
// Parse an optional 'NOT'
if (keyword == QueryExpr.OP_NOT)
{
// Consume a 'NOT'
if (m_toki < m_toks.length)
tok = m_toks[m_toki++];
else
throw new ParseException("Missing 'NULL' following: '"
+ tok + "'", m_toki-1);
keyword = (String) s_operators.get(tok.toLowerCase());
// cmp_expr: add_expr 'IS' ['NOT'] 'NULL'
expr = new QueryExpr();
expr.m_op = QueryExpr.OP_NOT;
expr.m_arg1 = res;
res = expr;
}
// Parse 'NULL'
if (keyword != QueryExpr.OP_NULL)
throw new ParseException("Missing 'NULL' at: '"
+ tok + "'", m_toki-1);
return (res);
}
// Parse an optional 'NOT'
isCompl = false;
if (keyword == QueryExpr.OP_NOT)
{
// Consume a 'NOT'
if (m_toki < m_toks.length)
tok = m_toks[m_toki++];
else
throw new ParseException("Missing operator following: '"
+ tok + "'", m_toki-1);
isCompl = true;
keyword = (String) s_operators.get(tok.toLowerCase());
}
// Parse a compare-op
if (keyword == QueryExpr.OP_EQ ||
keyword == QueryExpr.OP_NE ||
keyword == QueryExpr.OP_LT ||
keyword == QueryExpr.OP_LE ||
keyword == QueryExpr.OP_GT ||
keyword == QueryExpr.OP_GE ||
keyword == QueryExpr.OP_CONTAINS ||
keyword == QueryExpr.OP_LIKE ||
keyword == QueryExpr.OP_LIKEFILE)
{
QueryExpr expr;
// cmp_expr: add_expr ['NOT'] compare-op add_expr
expr = new QueryExpr();
expr.m_op = keyword;
expr.m_arg1 = res;
expr.m_arg2 = parse_add_expr();
res = expr;
}
else if (keyword == QueryExpr.OP_BETWEEN)
{
QueryExpr expr;
QueryExpr sub;
Object lo;
Object hi;
// cmp_expr: add_expr1 ['NOT'] 'BETWEEN' add_expr2 'AND' add_expr3
expr = new QueryExpr();
expr.m_op = QueryExpr.OP_BETWEEN;
expr.m_arg1 = res;
lo = parse_add_expr();
// Consume an 'AND'
tok = "* Syntax *
*
* expr_list:
* add_expr lookahead: ')'
* add_expr [','] expr_list lookahead: ')', op: OP_LIST
*
*
* @since 1.1, 2001-03-24
*/
private Object parse_expr_list()
throws ParseException
{
Object res;
String tok;
String keyword;
QueryExpr expr;
// Parse 'add_expr'
res = parse_add_expr();
// Check for premature end of expression
if (m_toki >= m_toks.length)
throw new ParseException("Missing closing ')'", m_toki-1);
// Continue parsing
tok = m_toks[m_toki++];
keyword = (String) s_operators.get(tok.toLowerCase());
expr = new QueryExpr();
expr.m_op = QueryExpr.OP_LIST;
expr.m_arg1 = res;
expr.m_arg2 = null;
res = expr;
// Look for end of expr_list, closing ')'
if (keyword == QueryExpr.OP_RP)
{
// expr_list: add_expr
// End of operand list
m_toki--;
}
else
{
// Parse an optional ','
if (keyword == QueryExpr.OP_LIST)
{
// Consume a ','
if (m_toki >= m_toks.length)
throw new ParseException("Missing 'IN' list or ')' at: '"
+ tok + "'", m_toki-1);
}
else
m_toki--;
// expr_list: add_expr [','] expr_list
// '(a, b, c)' -> {list a {list b c}}
expr.m_arg2 = parse_expr_list();
res = expr;
}
return (res);
}
/***************************************************************************
* Parse a query subexpression, converting it into an expression subtree.
* See {@link #parse_or_expr} for further details.
*
*
* * Syntax *
*
* add_expr:
* mul_expr
* mul_expr '+' add_expr op: OP_ADD
* mul_expr '-' add_expr op: OP_SUB
* mul_expr '||' add_expr op: OP_CONCAT
*
*
* @since 1.10, 2007-07-30
*/
private Object parse_add_expr()
throws ParseException
{
Object res;
// add_expr: mul_expr [add-op mul_expr]...
res = parse_mul_expr();
for (;;)
{
String tok;
String keyword;
// Check for end of expression
if (m_toki >= m_toks.length)
return (res);
// Continue parsing
tok = m_toks[m_toki++];
keyword = (String) s_operators.get(tok.toLowerCase());
if (keyword == null)
throw new ParseException("Bad operator: '" + tok + "'",
m_toki-1);
// Parse an add-op
if (keyword == QueryExpr.OP_ADD ||
keyword == QueryExpr.OP_SUB ||
keyword == QueryExpr.OP_CONCAT)
{
QueryExpr expr;
// add_expr: mul_expr add-op mul_expr
expr = new QueryExpr();
expr.m_op = keyword;
expr.m_arg1 = res;
expr.m_arg2 = parse_mul_expr();
res = expr;
}
else
{
m_toki--;
return (res);
}
}
}
/***************************************************************************
* Parse a query subexpression, converting it into an expression subtree.
* See {@link #parse_or_expr} for further details.
*
*
* * Syntax *
*
* mul_expr:
* unary_expr
* unary_expr '*' mul_expr op: OP_MUL
* unary_expr '/' mul_expr op: OP_DIV
* unary_expr 'MOD' mul_expr op: OP_MOD
*
*
* @since 1.10, 2007-07-30
*/
private Object parse_mul_expr()
throws ParseException
{
Object res;
// mul_expr: unary_expr [mul-op unary_expr]...
res = parse_unary_expr();
for (;;)
{
String tok;
String keyword;
// Check for end of expression
if (m_toki >= m_toks.length)
return (res);
// Continue parsing
tok = m_toks[m_toki++];
keyword = (String) s_operators.get(tok.toLowerCase());
if (keyword == null)
throw new ParseException("Bad operator: '" + tok + "'",
m_toki-1);
// Parse a mul-op
if (keyword == QueryExpr.OP_MUL ||
keyword == QueryExpr.OP_DIV ||
keyword == QueryExpr.OP_MOD)
{
QueryExpr expr;
// mul_expr: unary_expr mul-op unary_expr
expr = new QueryExpr();
expr.m_op = keyword;
expr.m_arg1 = res;
expr.m_arg2 = parse_unary_expr();
res = expr;
}
else
{
m_toki--;
return (res);
}
}
}
/***************************************************************************
* Parse a query subexpression, converting it into an expression subtree.
* See {@link #parse_or_expr} for further details.
*
*
* * Syntax *
*
* unary_expr:
* expo_expr
* '+' unary_expr op: OP_POS
* '-' unary_expr op: OP_NEG
*
*
* @since 1.10, 2007-07-30
*/
private Object parse_unary_expr()
throws ParseException
{
Object res;
String tok;
String keyword;
// Check for end of expression
if (m_toki >= m_toks.length)
throw new ParseException("Missing operand/operator", m_toki-1);
// Parse unary-op
tok = m_toks[m_toki++];
keyword = (String) s_operators.get(tok.toLowerCase());
// Parse a unary-op
if (keyword == QueryExpr.OP_ADD ||
keyword == QueryExpr.OP_SUB)
{
QueryExpr expr;
// unary_expr: unary-op unary_expr
// Note: unary-op is right-associative, '--a' = '-(-a)'
expr = new QueryExpr();
if (keyword == QueryExpr.OP_ADD)
expr.m_op = QueryExpr.OP_POS;
else
expr.m_op = QueryExpr.OP_NEG;
expr.m_arg1 = parse_unary_expr();
res = expr;
}
else
{
// unary_expr: expo_expr
m_toki--;
res = parse_expo_expr();
}
return (res);
}
/***************************************************************************
* Parse a query subexpression, converting it into an expression subtree.
* See {@link #parse_or_expr} for further details.
*
*
* * Syntax *
*
* expo_expr:
* operand
* operand '**' unary_expr op: OP_EXPO
*
*
* @since 1.10, 2007-07-30
*/
private Object parse_expo_expr()
throws ParseException
{
Object res;
String tok;
String keyword;
// Parse 'operand'
res = parse_operand();
// Check for end of expression
if (m_toki >= m_toks.length)
return (res);
// Continue parsing
tok = m_toks[m_toki++];
keyword = (String) s_operators.get(tok.toLowerCase());
if (keyword == null)
{
// Invalid operator
throw new ParseException("Bad operator: '" + tok + "'", m_toki-1);
}
// Parse an expo-op
if (keyword == QueryExpr.OP_EXPO)
{
QueryExpr expr;
// expo_expr: operand expo-op unary_expr
// Note: expo-op is right-associative, 'a**b**c' = 'a**(b**c)'
expr = new QueryExpr();
expr.m_op = keyword;
expr.m_arg1 = res;
expr.m_arg2 = parse_unary_expr();
res = expr;
}
else
m_toki--;
return (res);
}
/***************************************************************************
* Parse a query subexpression, converting it into an expression subtree.
* See {@link #parse_or_expr} for further details.
*
*
* * Syntax *
*
* operand:
* '(' or_expr ')'
* name_expr
* number return: String
* 'NULL' return: String
*
*
* @since 1.1, 2001-03-23
*/
private Object parse_operand()
throws ParseException
{
Object res;
String tok;
String keyword;
// Check for end of expression
if (m_toki >= m_toks.length)
return (null);
// Continue parsing
tok = m_toks[m_toki++];
keyword = (String) s_operators.get(tok.toLowerCase());
if (keyword == null)
{
char ch;
// operand: name_expr | number | string
ch = tok.charAt(0);
if (ch == '"' || ch == '\'' ||
Character.isJavaIdentifierPart(ch))
{
// operand: name_expr
m_toki--;
return (parse_name_expr());
}
else
{
// operand: number
return (tok);
}
}
else if (keyword == QueryExpr.OP_NULL)
{
// operand: 'NULL'
return (tok);
}
else if (keyword == QueryExpr.OP_LP)
{
QueryExpr expr;
// operand: '(' or_expr ')'
res = parse_or_expr();
// Consume a closing ')'
tok = "* Syntax *
*
* name_expr:
* name return: String
* string return: String
* name name_tail...
* string name_tail...
*
* name_tail:
* '.' name op: OP_MEMBER
* '.' string op: OP_MEMBER
* '[' add_expr ']' op: OP_SUBSCR
*
*
* @since 1.1, 2001-03-24
*/
private Object parse_name_expr()
throws ParseException
{
Object res;
String tok;
String keyword;
char ch;
// Check for end of expression
if (m_toki >= m_toks.length)
return (null);
// Continue parsing
tok = m_toks[m_toki++];
keyword = (String) s_operators.get(tok.toLowerCase());
ch = tok.charAt(0);
if (keyword != null ||
(ch != '"' && ch != '\'' &&
!Character.isJavaIdentifierPart(ch)))
throw new ParseException("Missing name or string at: '" + tok + "'",
m_toki-1);
res = tok;
// Parse 'name_tail...'
for (;;)
{
// Check for end of expression
if (m_toki >= m_toks.length)
return (res);
// Parse 'name_tail'
tok = m_toks[m_toki++];
keyword = (String) s_operators.get(tok.toLowerCase());
// Strip enclosing quotes if necessary
if (keyword == QueryExpr.OP_MEMBER ||
keyword == QueryExpr.OP_SUBSCR)
{
// Strip enclosing quotes from the operand token
if (res instanceof String)
{
tok = (String) res;
ch = tok.charAt(0);
if (ch == '"' || ch == '\'')
res = tok.substring(1, tok.length()-1);
}
}
// Parse 'name_tail'
if (keyword == QueryExpr.OP_MEMBER)
{
QueryExpr expr;
// name_tail: '.' name|string
tok = "