//============================================================================== // 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 = "