Removed ModifierSet, since base class now has this.
[cql-java-moved-to-github.git] / src / org / z3950 / zing / cql / CQLParser.java
index eadedef..94e8b87 100644 (file)
@@ -1,4 +1,4 @@
-// $Id: CQLParser.java,v 1.20 2002-11-14 22:04:16 mike Exp $
+// $Id: CQLParser.java,v 1.30 2007-06-28 00:24:48 mike Exp $
 
 package org.z3950.zing.cql;
 import java.io.IOException;
@@ -12,7 +12,7 @@ import java.io.FileNotFoundException;
 /**
  * Compiles CQL strings into parse trees of CQLNode subtypes.
  *
- * @version    $Id: CQLParser.java,v 1.20 2002-11-14 22:04:16 mike Exp $
+ * @version    $Id: CQLParser.java,v 1.30 2007-06-28 00:24:48 mike Exp $
  * @see                <A href="http://zing.z3950.org/cql/index.html"
  *                     >http://zing.z3950.org/cql/index.html</A>
  */
@@ -34,8 +34,10 @@ public class CQLParser {
      * data structure) or, more often, simply rendered out in the
      * desired form using one of the back-ends.  <TT>toCQL()</TT>
      * returns a decompiled CQL query equivalent to the one that was
-     * compiled in the first place; and <TT>toXCQL()</TT> returns an
-     * XML snippet representing the query.
+     * compiled in the first place; <TT>toXCQL()</TT> returns an
+     * XML snippet representing the query; and <TT>toPQF()</TT>
+     * returns the query rendered in Index Data's Prefix Query
+     * Format.
      *
      * @param cql      The query
      * @return         A CQLNode object which is the root of a parse
@@ -53,30 +55,30 @@ public class CQLParser {
        return root;
     }
 
-    private CQLNode parseQuery(String qualifier, CQLRelation relation)
+    private CQLNode parseQuery(String index, CQLRelation relation)
        throws CQLParseException, IOException {
        debug("in parseQuery()");
 
-       CQLNode term = parseTerm(qualifier, relation);
+       CQLNode term = parseTerm(index, relation);
        while (lexer.ttype != lexer.TT_EOF &&
               lexer.ttype != ')') {
            if (lexer.ttype == lexer.TT_AND) {
                match(lexer.TT_AND);
-               CQLNode term2 = parseTerm(qualifier, relation);
+               CQLNode term2 = parseTerm(index, relation);
                term = new CQLAndNode(term, term2);
            } else if (lexer.ttype == lexer.TT_OR) {
                match(lexer.TT_OR);
-               CQLNode term2 = parseTerm(qualifier, relation);
+               CQLNode term2 = parseTerm(index, relation);
                term = new CQLOrNode(term, term2);
            } else if (lexer.ttype == lexer.TT_NOT) {
                match(lexer.TT_NOT);
-               CQLNode term2 = parseTerm(qualifier, relation);
+               CQLNode term2 = parseTerm(index, relation);
                term = new CQLNotNode(term, term2);
            } else if (lexer.ttype == lexer.TT_PROX) {
                match(lexer.TT_PROX);
                CQLProxNode proxnode = new CQLProxNode(term);
                gatherProxParameters(proxnode);
-               CQLNode term2 = parseTerm(qualifier, relation);
+               CQLNode term2 = parseTerm(index, relation);
                proxnode.addSecondSubterm(term2);
                term = (CQLNode) proxnode;
            } else {
@@ -89,7 +91,7 @@ public class CQLParser {
        return term;
     }
 
-    private CQLNode parseTerm(String qualifier, CQLRelation relation)
+    private CQLNode parseTerm(String index, CQLRelation relation)
        throws CQLParseException, IOException {
        debug("in parseTerm()");
 
@@ -98,44 +100,61 @@ public class CQLParser {
            if (lexer.ttype == '(') {
                debug("parenthesised term");
                match('(');
-               CQLNode expr = parseQuery(qualifier, relation);
+               CQLNode expr = parseQuery(index, relation);
                match(')');
                return expr;
            } else if (lexer.ttype == '>') {
                match('>');
-               return parsePrefix(qualifier, relation);
+               return parsePrefix(index, relation);
            }
 
            debug("non-parenthesised term");
-           word = matchSymbol("qualifier or term");
-           if (!isBaseRelation())
+           word = matchSymbol("index or term");
+           if (!isRelation() && lexer.ttype != lexer.TT_WORD)
                break;
 
-           qualifier = word;
-           relation = new CQLRelation(lexer.render(lexer.ttype, false));
+           index = word;
+           relation = new CQLRelation(lexer.ttype == lexer.TT_WORD ?
+                                      lexer.sval :
+                                      lexer.render(lexer.ttype, false));
            match(lexer.ttype);
 
            while (lexer.ttype == '/') {
                match('/');
-               if (lexer.ttype != lexer.TT_RELEVANT &&
-                   lexer.ttype != lexer.TT_FUZZY &&
-                   lexer.ttype != lexer.TT_STEM)
+               if (lexer.ttype != lexer.TT_WORD)
                    throw new CQLParseException("expected relation modifier, "
                                                + "got " + lexer.render());
-               relation.addModifier(lexer.sval.toLowerCase());
+               String type = lexer.sval.toLowerCase();
                match(lexer.ttype);
+               if (!isRelation()) {
+                   // It's a simple modifier consisting of type only
+                   relation.addModifier(type);
+               } else {
+                   // It's a complex modifier of the form type=value
+                   String comparision = lexer.render(lexer.ttype, false);
+                   match(lexer.ttype);
+
+                   // Yuck
+                   String value = lexer.ttype == lexer.TT_WORD ? lexer.sval :
+                       (double) lexer.nval == (int) lexer.nval ?
+                       new Integer((int) lexer.nval).toString() :
+                       new Double((double) lexer.nval).toString();
+
+                   matchSymbol("relation-modifier value");
+                   relation.addModifier(type, comparision, value);
+               }
            }
 
-           debug("qualifier='" + qualifier + ", " +
+           debug("index='" + index + ", " +
                  "relation='" + relation.toCQL() + "'");
        }
 
-       CQLTermNode node = new CQLTermNode(qualifier, relation, word);
+       CQLTermNode node = new CQLTermNode(index, relation, word);
        debug("made term node " + node.toCQL());
        return node;
     }
 
-    private CQLNode parsePrefix(String qualifier, CQLRelation relation)
+    private CQLNode parsePrefix(String index, CQLRelation relation)
        throws CQLParseException, IOException {
        debug("prefix mapping");
 
@@ -146,7 +165,7 @@ public class CQLParser {
            name = identifier;
            identifier = matchSymbol("prefix-identifer");
        }
-       CQLNode term = parseTerm(qualifier, relation);
+       CQLNode term = parseQuery(index, relation);
        return new CQLPrefixNode(name, identifier, term);
     }
 
@@ -173,10 +192,10 @@ public class CQLParser {
 
     private void gatherProxRelation(CQLProxNode node)
        throws CQLParseException, IOException {
-       if (!isProxRelation())
+       if (!isRelation())
            throw new CQLParseException("expected proximity relation, got " +
                                        lexer.render());
-       node.addModifier("relation", lexer.render(lexer.ttype, false));
+       node.addModifier("relation", null, lexer.render(lexer.ttype, false));
        match(lexer.ttype);
        debug("gPR matched " + lexer.render(lexer.ttype, false));
     }
@@ -186,7 +205,7 @@ public class CQLParser {
        if (lexer.ttype != lexer.TT_NUMBER)
            throw new CQLParseException("expected proximity distance, got " +
                                        lexer.render());
-       node.addModifier("distance", lexer.render(lexer.ttype, false));
+       node.addModifier("distance", null, lexer.render(lexer.ttype, false));
        match(lexer.ttype);
        debug("gPD matched " + lexer.render(lexer.ttype, false));
     }
@@ -199,7 +218,7 @@ public class CQLParser {
            lexer.ttype != lexer.TT_ELEMENT)
            throw new CQLParseException("expected proximity unit, got " +
                                        lexer.render());
-       node.addModifier("unit", lexer.render());
+       node.addModifier("unit", null, lexer.render());
        match(lexer.ttype);
     }
 
@@ -209,22 +228,13 @@ public class CQLParser {
            lexer.ttype != lexer.TT_UNORDERED)
            throw new CQLParseException("expected proximity ordering, got " +
                                        lexer.render());
-       node.addModifier("ordering", lexer.render());
+       node.addModifier("ordering", null, lexer.render());
        match(lexer.ttype);
     }
 
-    private boolean isBaseRelation() {
-       debug("isBaseRelation: checking ttype=" + lexer.ttype +
-             " (" + lexer.render() + ")");
-       return (isProxRelation() ||
-               lexer.ttype == lexer.TT_ANY ||
-               lexer.ttype == lexer.TT_ALL ||
-               lexer.ttype == lexer.TT_EXACT ||
-               lexer.ttype == lexer.TT_SCR);
-    }
-
-    private boolean isProxRelation() {
-       debug("isProxRelation: checking ttype=" + lexer.ttype +
+    // Checks for a relation that may be used inside a prox operator
+    private boolean isRelation() {
+       debug("isRelation: checking ttype=" + lexer.ttype +
              " (" + lexer.render() + ")");
        return (lexer.ttype == '<' ||
                lexer.ttype == '>' ||
@@ -256,24 +266,19 @@ public class CQLParser {
            lexer.ttype == '"' ||
            // The following is a complete list of keywords.  Because
            // they're listed here, they can be used unquoted as
-           // qualifiers, terms, prefix names and prefix identifiers.
+           // indexes, terms, prefix names and prefix identifiers.
+           // ### Instead, we should ask the lexer whether what we
+           // have is a keyword, and let the knowledge reside there.
            lexer.ttype == lexer.TT_AND ||
            lexer.ttype == lexer.TT_OR ||
            lexer.ttype == lexer.TT_NOT ||
            lexer.ttype == lexer.TT_PROX ||
-           lexer.ttype == lexer.TT_ANY ||
-           lexer.ttype == lexer.TT_ALL ||
-           lexer.ttype == lexer.TT_EXACT ||
            lexer.ttype == lexer.TT_pWORD ||
            lexer.ttype == lexer.TT_SENTENCE ||
            lexer.ttype == lexer.TT_PARAGRAPH ||
            lexer.ttype == lexer.TT_ELEMENT ||
            lexer.ttype == lexer.TT_ORDERED ||
-           lexer.ttype == lexer.TT_UNORDERED ||
-           lexer.ttype == lexer.TT_RELEVANT ||
-           lexer.ttype == lexer.TT_FUZZY ||
-           lexer.ttype == lexer.TT_STEM ||
-           lexer.ttype == lexer.TT_SCR) {
+           lexer.ttype == lexer.TT_UNORDERED) {
            String symbol = (lexer.ttype == lexer.TT_NUMBER) ?
                lexer.render() : lexer.sval;
            match(lexer.ttype);
@@ -344,11 +349,16 @@ public class CQLParser {
        char mode = 'x';        // x=XCQL, c=CQL, p=PQF
        String pfile = null;
 
-       Vector argv = new Vector();
+       Vector<String> argv = new Vector<String>();
        for (int i = 0; i < args.length; i++) {
            argv.add(args[i]);
        }
 
+       if (argv.size() > 0 && argv.get(0).equals("-d")) {
+           DEBUG = true;
+           argv.remove(0);
+       }
+
        if (argv.size() > 0 && argv.get(0).equals("-c")) {
            mode = 'c';
            argv.remove(0);
@@ -360,8 +370,7 @@ public class CQLParser {
        }
 
        if (argv.size() > 1) {
-           System.err.println(
-               "Usage: CQLParser [-c] [-p <pqf-properties> [<CQL-query>]");
+           System.err.println("Usage: CQLParser [-d] [-c] [-p <pqf-properties> [<CQL-query>]");
            System.err.println("If unspecified, query is read from stdin");
            System.exit(1);
        }
@@ -411,8 +420,8 @@ public class CQLParser {
        } catch (IOException ex) {
            System.err.println("Can't render query: " + ex.getMessage());
            System.exit(5);
-       } catch (UnknownQualifierException ex) {
-           System.err.println("Unknown qualifier: " + ex.getMessage());
+       } catch (UnknownIndexException ex) {
+           System.err.println("Unknown index: " + ex.getMessage());
            System.exit(6);
        } catch (UnknownRelationException ex) {
            System.err.println("Unknown relation: " + ex.getMessage());