Make classes public.
[cql-java-moved-to-github.git] / src / org / z3950 / zing / cql / CQLParser.java
1 // $Id: CQLParser.java,v 1.5 2002-10-25 16:01:26 mike Exp $
2
3 package org.z3950.zing.cql;
4 import java.util.Properties;
5 import java.io.InputStream;
6 import java.io.FileNotFoundException;
7 import java.io.IOException;
8 import java.io.StringReader;
9 import java.io.StreamTokenizer;
10
11
12 /**
13  * Compiles a CQL string into a parse tree ...
14  * ###
15  *
16  * @version     $Id: CQLParser.java,v 1.5 2002-10-25 16:01:26 mike Exp $
17  * @see         <A href="http://zing.z3950.org/cql/index.html"
18  *                      >http://zing.z3950.org/cql/index.html</A>
19  */
20 public class CQLParser {
21     private String cql;
22     private StreamTokenizer st;
23
24     private class CQLParseException extends Exception {
25         CQLParseException(String s) { super(s); }
26     }
27
28     public CQLParser() {
29         // Nothing to do: do we need this constructor, then?
30     }
31
32     public CQLNode parse(String cql)
33         throws FileNotFoundException, IOException {
34         this.cql = cql;
35         st = new StreamTokenizer(new StringReader(cql));
36         // ### these settings are wrong
37         st.wordChars('/', '/');
38         st.wordChars('0', '9'); // ### but 1 is still recognised as TT_NUM
39         st.wordChars('.', '.');
40         st.wordChars('-', '-');
41         st.ordinaryChar('=');
42         st.ordinaryChar(',');
43         st.ordinaryChar('(');
44         st.ordinaryChar(')');
45
46 //      int token;
47 //      while ((token = st.nextToken()) != st.TT_EOF) {
48 //          System.out.println("token=" + token + ", " +
49 //                             "nval=" + st.nval + ", " +
50 //                             "sval=" + st.sval);
51 //      }
52
53         st.nextToken();
54         CQLNode root;
55         try {
56             root = parse_expression();
57         } catch (CQLParseException ex) {
58             System.err.println("### Oops: " + ex);
59             return null;
60         }
61
62         if (st.ttype != st.TT_EOF) {
63             System.err.println("### Extra bits: " + render(st));
64             return null;
65         }
66
67         return root;
68     }
69
70     private CQLNode parse_expression()
71         throws CQLParseException, IOException {
72         CQLNode term = parse_term();
73
74         while (st.ttype == st.TT_WORD) {
75             String op = st.sval.toLowerCase();
76             if (st.sval.equals("and")) {
77                 match(st.TT_WORD);
78                 CQLNode term2 = parse_term();
79                 term = new CQLAndNode(term, term2);
80             } else if (st.sval.equals("or")) {
81                 match(st.TT_WORD);
82                 CQLNode term2 = parse_term();
83                 term = new CQLOrNode(term, term2);
84             } else if (st.sval.equals("not")) {
85                 match(st.TT_WORD);
86                 CQLNode term2 = parse_term();
87                 term = new CQLNotNode(term, term2);
88             }
89         }
90
91         return term;
92     }
93
94     private CQLNode parse_term()
95         throws CQLParseException, IOException {
96         if (st.ttype == '(') {
97             match('(');
98             CQLNode expr = parse_expression();
99             match(')');
100             return expr;
101         }
102
103         String word = st.sval;
104         return new CQLTermNode("x", "=", word);
105     }
106
107     private void match(int token)
108         throws CQLParseException, IOException {
109         if (st.ttype != token)
110             throw new CQLParseException("expected " + render(st, token, null) +
111                                         ", " + "got " + render(st));
112         st.nextToken();
113     }
114
115     // ### This utility should surely be a method of the StreamTokenizer class
116     private static String render(StreamTokenizer st) {
117         return render(st, st.ttype, null);
118     }
119
120     private static String render(StreamTokenizer st, int token, String str) {
121         String ret;
122
123         if (token == st.TT_EOF) {
124             return "EOF";
125         } else if (token == st.TT_EOL) {
126             return "EOL";
127         } else if (token == st.TT_NUMBER) {
128             return "number";
129         } else if (token == st.TT_WORD) {
130             return "word";
131         } else if (token == '"' && token == '\'') {
132             return "string";
133         }
134
135         return "'" + String.valueOf((char) token) + "'";
136     }
137
138
139     // Test harness.
140     //
141     // e.g. echo '(au=Kerninghan or au=Ritchie) and ti=Unix' |
142     //                          java org.z3950.zing.cql.CQLParser
143     // yields:
144     //  ###
145     //
146     public static void main (String[] args) {
147         if (args.length != 0) {
148             System.err.println("Usage: " + args[0]);
149             System.exit(1);
150         }
151
152         byte[] bytes = new byte[1000];
153         try {
154             int nbytes = System.in.read(bytes);
155         } catch (java.io.IOException ex) {
156             System.err.println("Can't read query: " + ex);
157             System.exit(2);
158         }
159         String cql = String(bytes);
160         CQLParser parser = new CQLParser();
161         CQLNode root = parser.parse(cql);
162         System.out.println(root.toXCQL());
163     }
164 }