//============================================================================== // 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. * * *

* * Usage * *

* 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. * * *

* * Syntax * *

* 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 = ""; keyword = null; if (m_toki < m_toks.length) { tok = m_toks[m_toki++]; keyword = (String) s_operators.get(tok.toLowerCase()); } if (keyword != QueryExpr.OP_AND) throw new ParseException("Missing expected 'AND' at: '" + tok + "'", m_toki-1); hi = parse_add_expr(); // Build an expr subtree: {BETWEEN op1 {AND op2 op3}} sub = new QueryExpr(); sub.m_op = QueryExpr.OP_AND; sub.m_arg1 = lo; sub.m_arg2 = hi; expr.m_arg2 = sub; res = expr; } else if (keyword == QueryExpr.OP_IN) { QueryExpr expr; // cmp_expr: add_expr ['NOT'] 'IN' '(' expr_list ')' expr = new QueryExpr(); expr.m_op = QueryExpr.OP_IN; expr.m_arg1 = res; // Consume a '(' tok = ""; keyword = null; if (m_toki < m_toks.length) { tok = m_toks[m_toki++]; keyword = (String) s_operators.get(tok.toLowerCase()); } if (keyword != QueryExpr.OP_LP) throw new ParseException("Missing expected '(' at: '" + tok + "'", m_toki-1); expr.m_arg2 = parse_expr_list(); // Consume a ')' tok = ""; keyword = null; if (m_toki < m_toks.length) { tok = m_toks[m_toki++]; keyword = (String) s_operators.get(tok.toLowerCase()); } if (keyword != QueryExpr.OP_RP) throw new ParseException("Missing expected ')' at: '" + tok + "'", m_toki-1); res = expr; } else m_toki--; // Handle a 'NOT' operator modifier if (isCompl) { QueryExpr expr; // cmp_expr: add_expr 'NOT' compare-op add_expr expr = new QueryExpr(); expr.m_op = QueryExpr.OP_NOT; expr.m_arg1 = res; res = expr; } return (res); } /*************************************************************************** * Parse a query subexpression, converting it into an expression subtree. * See {@link #parse_or_expr} for further details. * * *

* 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 = ""; keyword = null; if (m_toki < m_toks.length) { tok = m_toks[m_toki++]; keyword = (String) s_operators.get(tok.toLowerCase()); } if (keyword != QueryExpr.OP_RP) throw new ParseException("Missing closing ')' at: '" + tok + "'", m_toki-1); } else { throw new ParseException("Bad operand: '" + tok + "'", m_toki-1); } return (res); } /*************************************************************************** * Parse a query subexpression, converting it into an expression subtree. * See {@link #parse_or_expr} for further details. * * *

* 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 = ""; if (m_toki < m_toks.length) { tok = m_toks[m_toki++]; keyword = (String) s_operators.get(tok.toLowerCase()); } ch = tok.charAt(0); if (ch == '"' || ch == '\'') tok = tok.substring(1, tok.length()-1); else if (keyword != null || !Character.isJavaIdentifierPart(ch)) throw new ParseException("Missing name or string at: '" + tok + "'", m_toki-1); expr = new QueryExpr(); expr.m_op = QueryExpr.OP_MEMBER; expr.m_arg1 = res; expr.m_arg2 = tok; res = expr; } else if (keyword == QueryExpr.OP_SUBSCR) { QueryExpr expr; // name_tail: '[' add_expr ']' expr = new QueryExpr(); expr.m_op = QueryExpr.OP_SUBSCR; expr.m_arg1 = res; expr.m_arg2 = parse_add_expr(); res = expr; // Consume a closing ']' tok = ""; keyword = null; if (m_toki < m_toks.length) { tok = m_toks[m_toki++]; keyword = (String) s_operators.get(tok.toLowerCase()); } if (keyword != QueryExpr.OP_SUBSCR2) throw new ParseException("Missing closing ']' at: '" + tok + "'", m_toki-1); } else { m_toki--; break; } } return (res); } /*+++REMOVE (1.10, 2007-07-30) /*************************************************************************** * Convert a string (name or quoted literal) operand into a subexpression * (with operator OP_VALUE). * * @param expr * An expression operand object. * * @return * A QueryExpr object that is equivalent to the given operand. * * @since 1.7, 2001-04-11 *\/ private QueryExpr asQueryExpr(Object expr) { if (expr instanceof String) { QueryExpr sub; // Convert the string operand into an OP_VALUE subexpression sub = new QueryExpr(); sub.m_op = QueryExpr.OP_VALUE; sub.m_arg1 = (String) expr; return (sub); } else return ((QueryExpr) expr); } +++*/ } // End QueryParser.java