LOTS of changes. Biggies include proper support for relations, including
[cql-java-moved-to-github.git] / src / org / z3950 / zing / cql / CQLParser.java
1 // $Id: CQLParser.java,v 1.10 2002-10-30 09:19:26 mike Exp $
2
3 package org.z3950.zing.cql;
4 import java.io.IOException;
5
6
7 /**
8  * Compiles a CQL string into a parse tree.
9  * ###
10  *
11  * @version     $Id: CQLParser.java,v 1.10 2002-10-30 09:19:26 mike Exp $
12  * @see         <A href="http://zing.z3950.org/cql/index.html"
13  *                      >http://zing.z3950.org/cql/index.html</A>
14  */
15 public class CQLParser {
16     private CQLLexer lexer;
17     static private boolean DEBUG = false;
18     static private boolean LEXDEBUG = false;
19
20     private static void debug(String str) {
21         if (DEBUG)
22             System.err.println("PARSEDEBUG: " + str);
23     }
24
25     public CQLNode parse(String cql)
26         throws CQLParseException, IOException {
27         lexer = new CQLLexer(cql, LEXDEBUG);
28
29         lexer.nextToken();
30         debug("about to parse_query()");
31         CQLNode root = parse_query("srw.serverChoice", new CQLRelation("="));
32         if (lexer.ttype != lexer.TT_EOF)
33             throw new CQLParseException("junk after end: " + lexer.render());
34
35         return root;
36     }
37
38     private CQLNode parse_query(String qualifier, CQLRelation relation)
39         throws CQLParseException, IOException {
40         debug("in parse_query()");
41
42         CQLNode term = parse_term(qualifier, relation);
43         while (lexer.ttype != lexer.TT_EOF &&
44                lexer.ttype != ')') {
45             if (lexer.ttype == lexer.TT_AND) {
46                 match(lexer.TT_AND);
47                 CQLNode term2 = parse_term(qualifier, relation);
48                 term = new CQLAndNode(term, term2);
49             } else if (lexer.ttype == lexer.TT_OR) {
50                 match(lexer.TT_OR);
51                 CQLNode term2 = parse_term(qualifier, relation);
52                 term = new CQLOrNode(term, term2);
53             } else if (lexer.ttype == lexer.TT_NOT) {
54                 match(lexer.TT_NOT);
55                 CQLNode term2 = parse_term(qualifier, relation);
56                 term = new CQLNotNode(term, term2);
57             } else if (lexer.ttype == lexer.TT_PROX) {
58                 // ### Handle "prox"
59             } else {
60                 throw new CQLParseException("expected boolean, got " +
61                                             lexer.render());
62             }
63         }
64
65         debug("no more ops");
66         return term;
67     }
68
69     private CQLNode parse_term(String qualifier, CQLRelation relation)
70         throws CQLParseException, IOException {
71         debug("in parse_term()");
72
73         String word;
74         while (true) {
75             if (lexer.ttype == '(') {
76                 debug("parenthesised term");
77                 match('(');
78                 CQLNode expr = parse_query(qualifier, relation);
79                 match(')');
80                 return expr;
81             } else if (lexer.ttype != lexer.TT_WORD && lexer.ttype != '"') {
82                 throw new CQLParseException("expected qualifier or term, " +
83                                             "got " + lexer.render());
84             }
85
86             debug("non-parenthesised term");
87             word = lexer.sval;
88             match(lexer.ttype);
89             if (!isBaseRelation())
90                 break;
91
92             qualifier = word;
93             relation = new CQLRelation(lexer.render(lexer.ttype, false));
94             match(lexer.ttype);
95
96             while (lexer.ttype == '/') {
97                 match('/');
98                 // ### could insist on known modifiers only
99                 if (lexer.ttype != lexer.TT_WORD)
100                     throw new CQLParseException("expected relation modifier, "
101                                                 + "got " + lexer.render());
102                 relation.addModifier(lexer.sval);
103                 match(lexer.TT_WORD);
104             }
105
106             debug("qualifier='" + qualifier + ", " +
107                   "relation='" + relation.toCQL() + "'");
108         }
109
110         CQLTermNode node = new CQLTermNode(qualifier, relation, word);
111         debug("made term node " + node.toCQL());
112         return node;
113     }
114
115     boolean isBaseRelation() {
116         debug("isBaseRelation: checking ttype=" + lexer.ttype +
117               " (" + lexer.render() + ")");
118         return (lexer.ttype == '<' ||
119                 lexer.ttype == '>' ||
120                 lexer.ttype == '=' ||
121                 lexer.ttype == lexer.TT_LE ||
122                 lexer.ttype == lexer.TT_GE ||
123                 lexer.ttype == lexer.TT_NE ||
124                 lexer.ttype == lexer.TT_ANY ||
125                 lexer.ttype == lexer.TT_ALL ||
126                 lexer.ttype == lexer.TT_EXACT);
127     }
128
129     private void match(int token)
130         throws CQLParseException, IOException {
131         debug("in match(" + lexer.render(token, true) + ")");
132         if (lexer.ttype != token)
133             throw new CQLParseException("expected " +
134                                         lexer.render(token, true) +
135                                         ", " + "got " + lexer.render());
136         int tmp = lexer.nextToken();
137         debug("match() got token=" + lexer.ttype + ", " +
138               "nval=" + lexer.nval + ", sval='" + lexer.sval + "'" +
139               " (tmp=" + tmp + ")");
140     }
141
142
143     // Test harness.
144     //
145     // e.g. echo '(au=Kerninghan or au=Ritchie) and ti=Unix' |
146     //                          java org.z3950.zing.cql.CQLParser
147     // yields:
148     //  <triple>
149     //    <boolean>and</boolean>
150     //    <triple>
151     //      <boolean>or</boolean>
152     //      <searchClause>
153     //        <index>au<index>
154     //        <relation>=<relation>
155     //        <term>Kerninghan<term>
156     //      </searchClause>
157     //      <searchClause>
158     //        <index>au<index>
159     //        <relation>=<relation>
160     //        <term>Ritchie<term>
161     //      </searchClause>
162     //    </triple>
163     //    <searchClause>
164     //      <index>ti<index>
165     //      <relation>=<relation>
166     //      <term>Unix<term>
167     //    </searchClause>
168     //  </triple>
169     //
170     public static void main (String[] args) {
171         if (args.length != 0) {
172             System.err.println("Usage: " + args[0]);
173             System.exit(1);
174         }
175
176         byte[] bytes = new byte[10000];
177         try {
178             // Read in the whole of standard input in one go
179             int nbytes = System.in.read(bytes);
180         } catch (java.io.IOException ex) {
181             System.err.println("Can't read query: " + ex.getMessage());
182             System.exit(2);
183         }
184         String cql = new String(bytes);
185         CQLParser parser = new CQLParser();
186         CQLNode root;
187         try {
188             root = parser.parse(cql);
189             debug("root='" + root + "'");
190             System.out.println(root.toXCQL(0));
191         } catch (CQLParseException ex) {
192             System.err.println("Syntax error: " + ex.getMessage());
193             System.exit(3);
194         } catch (java.io.IOException ex) {
195             System.err.println("Can't compile query: " + ex.getMessage());
196             System.exit(4);
197         }
198     }
199 }