- Allow keywords to be used unquoted as search terms.
[cql-java-moved-to-github.git] / src / org / z3950 / zing / cql / CQLParser.java
index 59cedf8..eadedef 100644 (file)
@@ -1,4 +1,4 @@
-// $Id: CQLParser.java,v 1.15 2002-11-06 00:05:58 mike Exp $
+// $Id: CQLParser.java,v 1.20 2002-11-14 22:04:16 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.15 2002-11-06 00:05:58 mike Exp $
+ * @version    $Id: CQLParser.java,v 1.20 2002-11-14 22:04:16 mike Exp $
  * @see                <A href="http://zing.z3950.org/cql/index.html"
  *                     >http://zing.z3950.org/cql/index.html</A>
  */
@@ -45,38 +45,38 @@ public class CQLParser {
        lexer = new CQLLexer(cql, LEXDEBUG);
 
        lexer.nextToken();
-       debug("about to parse_query()");
-       CQLNode root = parse_query("srw.serverChoice", new CQLRelation("="));
+       debug("about to parseQuery()");
+       CQLNode root = parseQuery("srw.serverChoice", new CQLRelation("scr"));
        if (lexer.ttype != lexer.TT_EOF)
            throw new CQLParseException("junk after end: " + lexer.render());
 
        return root;
     }
 
-    private CQLNode parse_query(String qualifier, CQLRelation relation)
+    private CQLNode parseQuery(String qualifier, CQLRelation relation)
        throws CQLParseException, IOException {
-       debug("in parse_query()");
+       debug("in parseQuery()");
 
-       CQLNode term = parse_term(qualifier, relation);
+       CQLNode term = parseTerm(qualifier, relation);
        while (lexer.ttype != lexer.TT_EOF &&
               lexer.ttype != ')') {
            if (lexer.ttype == lexer.TT_AND) {
                match(lexer.TT_AND);
-               CQLNode term2 = parse_term(qualifier, relation);
+               CQLNode term2 = parseTerm(qualifier, relation);
                term = new CQLAndNode(term, term2);
            } else if (lexer.ttype == lexer.TT_OR) {
                match(lexer.TT_OR);
-               CQLNode term2 = parse_term(qualifier, relation);
+               CQLNode term2 = parseTerm(qualifier, relation);
                term = new CQLOrNode(term, term2);
            } else if (lexer.ttype == lexer.TT_NOT) {
                match(lexer.TT_NOT);
-               CQLNode term2 = parse_term(qualifier, relation);
+               CQLNode term2 = parseTerm(qualifier, 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 = parse_term(qualifier, relation);
+               CQLNode term2 = parseTerm(qualifier, relation);
                proxnode.addSecondSubterm(term2);
                term = (CQLNode) proxnode;
            } else {
@@ -89,32 +89,25 @@ public class CQLParser {
        return term;
     }
 
-    private CQLNode parse_term(String qualifier, CQLRelation relation)
+    private CQLNode parseTerm(String qualifier, CQLRelation relation)
        throws CQLParseException, IOException {
-       debug("in parse_term()");
+       debug("in parseTerm()");
 
        String word;
        while (true) {
            if (lexer.ttype == '(') {
                debug("parenthesised term");
                match('(');
-               CQLNode expr = parse_query(qualifier, relation);
+               CQLNode expr = parseQuery(qualifier, relation);
                match(')');
                return expr;
-           } else if (lexer.ttype != lexer.TT_WORD &&
-                      lexer.ttype != lexer.TT_NUMBER &&
-                      lexer.ttype != '"') {
-               throw new CQLParseException("expected qualifier or term, " +
-                                           "got " + lexer.render());
+           } else if (lexer.ttype == '>') {
+               match('>');
+               return parsePrefix(qualifier, relation);
            }
 
            debug("non-parenthesised term");
-           if (lexer.ttype == lexer.TT_NUMBER) {
-               word = lexer.render();
-           } else {
-               word = lexer.sval;
-           }
-           match(lexer.ttype);
+           word = matchSymbol("qualifier or term");
            if (!isBaseRelation())
                break;
 
@@ -129,7 +122,7 @@ public class CQLParser {
                    lexer.ttype != lexer.TT_STEM)
                    throw new CQLParseException("expected relation modifier, "
                                                + "got " + lexer.render());
-               relation.addModifier(lexer.sval);
+               relation.addModifier(lexer.sval.toLowerCase());
                match(lexer.ttype);
            }
 
@@ -142,6 +135,21 @@ public class CQLParser {
        return node;
     }
 
+    private CQLNode parsePrefix(String qualifier, CQLRelation relation)
+       throws CQLParseException, IOException {
+       debug("prefix mapping");
+
+       String name = null;
+       String identifier = matchSymbol("prefix-name");
+       if (lexer.ttype == '=') {
+           match('=');
+           name = identifier;
+           identifier = matchSymbol("prefix-identifer");
+       }
+       CQLNode term = parseTerm(qualifier, relation);
+       return new CQLPrefixNode(name, identifier, term);
+    }
+
     private void gatherProxParameters(CQLProxNode node)
        throws CQLParseException, IOException {
        for (int i = 0; i < 4; i++) {
@@ -211,7 +219,8 @@ public class CQLParser {
        return (isProxRelation() ||
                lexer.ttype == lexer.TT_ANY ||
                lexer.ttype == lexer.TT_ALL ||
-               lexer.ttype == lexer.TT_EXACT);
+               lexer.ttype == lexer.TT_EXACT ||
+               lexer.ttype == lexer.TT_SCR);
     }
 
     private boolean isProxRelation() {
@@ -238,6 +247,43 @@ public class CQLParser {
              " (tmp=" + tmp + ")");
     }
 
+    private String matchSymbol(String expected)
+       throws CQLParseException, IOException {
+
+       debug("in matchSymbol()");
+       if (lexer.ttype == lexer.TT_WORD ||
+           lexer.ttype == lexer.TT_NUMBER ||
+           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.
+           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) {
+           String symbol = (lexer.ttype == lexer.TT_NUMBER) ?
+               lexer.render() : lexer.sval;
+           match(lexer.ttype);
+           return symbol;
+       }
+
+       throw new CQLParseException("expected " + expected + ", " +
+                                   "got " + lexer.render());
+    }
+
 
     /**
      * Simple test-harness for the CQLParser class.
@@ -328,7 +374,7 @@ public class CQLParser {
            try {
                // Read in the whole of standard input in one go
                int nbytes = System.in.read(bytes);
-           } catch (java.io.IOException ex) {
+           } catch (IOException ex) {
                System.err.println("Can't read query: " + ex.getMessage());
                System.exit(2);
            }
@@ -342,7 +388,7 @@ public class CQLParser {
        } catch (CQLParseException ex) {
            System.err.println("Syntax error: " + ex.getMessage());
            System.exit(3);
-       } catch (java.io.IOException ex) {
+       } catch (IOException ex) {
            System.err.println("Can't compile query: " + ex.getMessage());
            System.exit(4);
        }
@@ -362,9 +408,25 @@ public class CQLParser {
            } else {
                System.out.print(root.toXCQL(0));
            }
-       } catch (java.io.IOException ex) {
+       } 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());
+           System.exit(6);
+       } catch (UnknownRelationException ex) {
+           System.err.println("Unknown relation: " + ex.getMessage());
+           System.exit(7);
+       } catch (UnknownRelationModifierException ex) {
+           System.err.println("Unknown relation modifier: " +
+                              ex.getMessage());
+           System.exit(8);
+       } catch (UnknownPositionException ex) {
+           System.err.println("Unknown position: " + ex.getMessage());
+           System.exit(9);
+       } catch (PQFTranslationException ex) {
+           // We catch all of this class's subclasses, so --
+           throw new Error("can't get a PQFTranslationException");
        }
     }
 }