1 // $Id: CQLParser.java,v 1.13 2002-11-02 01:24:14 mike Exp $
3 package org.z3950.zing.cql;
4 import java.io.IOException;
5 import java.util.Vector;
9 * Compiles a CQL string into a parse tree.
12 * @version $Id: CQLParser.java,v 1.13 2002-11-02 01:24:14 mike Exp $
13 * @see <A href="http://zing.z3950.org/cql/index.html"
14 * >http://zing.z3950.org/cql/index.html</A>
16 public class CQLParser {
17 private CQLLexer lexer;
18 static private boolean DEBUG = false;
19 static private boolean LEXDEBUG = false;
21 private static void debug(String str) {
23 System.err.println("PARSEDEBUG: " + str);
26 public CQLNode parse(String cql)
27 throws CQLParseException, IOException {
28 lexer = new CQLLexer(cql, LEXDEBUG);
31 debug("about to parse_query()");
32 CQLNode root = parse_query("srw.serverChoice", new CQLRelation("="));
33 if (lexer.ttype != lexer.TT_EOF)
34 throw new CQLParseException("junk after end: " + lexer.render());
39 private CQLNode parse_query(String qualifier, CQLRelation relation)
40 throws CQLParseException, IOException {
41 debug("in parse_query()");
43 CQLNode term = parse_term(qualifier, relation);
44 while (lexer.ttype != lexer.TT_EOF &&
46 if (lexer.ttype == lexer.TT_AND) {
48 CQLNode term2 = parse_term(qualifier, relation);
49 term = new CQLAndNode(term, term2);
50 } else if (lexer.ttype == lexer.TT_OR) {
52 CQLNode term2 = parse_term(qualifier, relation);
53 term = new CQLOrNode(term, term2);
54 } else if (lexer.ttype == lexer.TT_NOT) {
56 CQLNode term2 = parse_term(qualifier, relation);
57 term = new CQLNotNode(term, term2);
58 } else if (lexer.ttype == lexer.TT_PROX) {
60 CQLProxNode proxnode = new CQLProxNode(term);
61 gatherProxParameters(proxnode);
62 CQLNode term2 = parse_term(qualifier, relation);
63 proxnode.addSecondSubterm(term2);
64 term = (CQLNode) proxnode;
66 throw new CQLParseException("expected boolean, got " +
75 private CQLNode parse_term(String qualifier, CQLRelation relation)
76 throws CQLParseException, IOException {
77 debug("in parse_term()");
81 if (lexer.ttype == '(') {
82 debug("parenthesised term");
84 CQLNode expr = parse_query(qualifier, relation);
87 } else if (lexer.ttype != lexer.TT_WORD &&
88 lexer.ttype != lexer.TT_NUMBER &&
90 throw new CQLParseException("expected qualifier or term, " +
91 "got " + lexer.render());
94 debug("non-parenthesised term");
95 if (lexer.ttype == lexer.TT_NUMBER) {
96 word = lexer.render();
101 if (!isBaseRelation())
105 relation = new CQLRelation(lexer.render(lexer.ttype, false));
108 while (lexer.ttype == '/') {
110 if (lexer.ttype != lexer.TT_RELEVANT &&
111 lexer.ttype != lexer.TT_FUZZY &&
112 lexer.ttype != lexer.TT_STEM)
113 throw new CQLParseException("expected relation modifier, "
114 + "got " + lexer.render());
115 relation.addModifier(lexer.sval);
119 debug("qualifier='" + qualifier + ", " +
120 "relation='" + relation.toCQL() + "'");
123 CQLTermNode node = new CQLTermNode(qualifier, relation, word);
124 debug("made term node " + node.toCQL());
128 private void gatherProxParameters(CQLProxNode node)
129 throws CQLParseException, IOException {
130 for (int i = 0; i < 4; i++) {
131 if (lexer.ttype != '/')
132 return; // end of proximity parameters
135 if (lexer.ttype != '/') {
136 // not an omitted default
138 // Order should be: relation/distance/unit/ordering
139 // For now, use MA's: unit/relation/distance/ordering
140 case 1: gatherProxRelation(node); break;
141 case 2: gatherProxDistance(node); break;
142 case 0: gatherProxUnit(node); break;
143 case 3: gatherProxOrdering(node); break;
149 private void gatherProxRelation(CQLProxNode node)
150 throws CQLParseException, IOException {
151 if (!isProxRelation())
152 throw new CQLParseException("expected proximity relation, got " +
154 node.addModifier("relation", lexer.render(lexer.ttype, false));
156 debug("gPR matched " + lexer.render(lexer.ttype, false));
159 private void gatherProxDistance(CQLProxNode node)
160 throws CQLParseException, IOException {
161 if (lexer.ttype != lexer.TT_NUMBER)
162 throw new CQLParseException("expected proximity distance, got " +
164 node.addModifier("distance", lexer.render(lexer.ttype, false));
166 debug("gPD matched " + lexer.render(lexer.ttype, false));
169 private void gatherProxUnit(CQLProxNode node)
170 throws CQLParseException, IOException {
171 if (lexer.ttype != lexer.TT_pWORD &&
172 lexer.ttype != lexer.TT_SENTENCE &&
173 lexer.ttype != lexer.TT_PARAGRAPH &&
174 lexer.ttype != lexer.TT_ELEMENT)
175 throw new CQLParseException("expected proximity unit, got " +
177 node.addModifier("unit", lexer.render());
181 private void gatherProxOrdering(CQLProxNode node)
182 throws CQLParseException, IOException {
183 if (lexer.ttype != lexer.TT_ORDERED &&
184 lexer.ttype != lexer.TT_UNORDERED)
185 throw new CQLParseException("expected proximity ordering, got " +
187 node.addModifier("ordering", lexer.render());
191 boolean isBaseRelation() {
192 debug("isBaseRelation: checking ttype=" + lexer.ttype +
193 " (" + lexer.render() + ")");
194 return (isProxRelation() ||
195 lexer.ttype == lexer.TT_ANY ||
196 lexer.ttype == lexer.TT_ALL ||
197 lexer.ttype == lexer.TT_EXACT);
200 boolean isProxRelation() {
201 debug("isProxRelation: checking ttype=" + lexer.ttype +
202 " (" + lexer.render() + ")");
203 return (lexer.ttype == '<' ||
204 lexer.ttype == '>' ||
205 lexer.ttype == '=' ||
206 lexer.ttype == lexer.TT_LE ||
207 lexer.ttype == lexer.TT_GE ||
208 lexer.ttype == lexer.TT_NE);
211 private void match(int token)
212 throws CQLParseException, IOException {
213 debug("in match(" + lexer.render(token, true) + ")");
214 if (lexer.ttype != token)
215 throw new CQLParseException("expected " +
216 lexer.render(token, true) +
217 ", " + "got " + lexer.render());
218 int tmp = lexer.nextToken();
219 debug("match() got token=" + lexer.ttype + ", " +
220 "nval=" + lexer.nval + ", sval='" + lexer.sval + "'" +
221 " (tmp=" + tmp + ")");
227 // e.g. echo '(au=Kerninghan or au=Ritchie) and ti=Unix' |
228 // java org.z3950.zing.cql.CQLParser
231 // <boolean>and</boolean>
233 // <boolean>or</boolean>
236 // <relation>=<relation>
237 // <term>Kerninghan<term>
241 // <relation>=<relation>
242 // <term>Ritchie<term>
247 // <relation>=<relation>
252 public static void main (String[] args) {
253 boolean canonicalise = false;
254 Vector argv = new Vector();
255 for (int i = 0; i < args.length; i++) {
259 if (argv.size() > 0 && argv.get(0).equals("-c")) {
264 if (argv.size() > 1) {
265 System.err.println("Usage: CQLParser [-c] [<CQL-query>]");
266 System.err.println("If unspecified, query is read from stdin");
271 if (argv.size() == 1) {
272 cql = (String) argv.get(0);
274 byte[] bytes = new byte[10000];
276 // Read in the whole of standard input in one go
277 int nbytes = System.in.read(bytes);
278 } catch (java.io.IOException ex) {
279 System.err.println("Can't read query: " + ex.getMessage());
282 cql = new String(bytes);
285 CQLParser parser = new CQLParser();
288 root = parser.parse(cql);
289 debug("root='" + root + "'");
291 System.out.println(root.toCQL());
293 System.out.print(root.toXCQL(0));
295 } catch (CQLParseException ex) {
296 System.err.println("Syntax error: " + ex.getMessage());
298 } catch (java.io.IOException ex) {
299 System.err.println("Can't compile query: " + ex.getMessage());