Change obsolete collections, remove redundant imports
[cql-java-moved-to-github.git] / src / main / java / org / z3950 / zing / cql / CQLLexer.java
1 // $Id: CQLLexer.java,v 1.14 2007-07-03 13:30:42 mike Exp $
2
3 package org.z3950.zing.cql;
4 import java.io.StreamTokenizer;
5 import java.io.StringReader;
6
7
8 // This is a semi-trivial subclass for java.io.StreamTokenizer that:
9 //      * Has a halfDecentPushBack() method that actually works
10 //      * Includes a render() method
11 //      * Knows about the multi-character tokens "<=", ">=" and "<>"
12 //      * Recognises a set of keywords as tokens in their own right
13 //      * Includes some primitive debugging-output facilities
14 // It's used only by CQLParser.
15 //
16 class CQLLexer extends StreamTokenizer {
17     // New publicly visible token-types
18     public final static int TT_LE        = 1000;        // The "<=" relation
19     public final static int TT_GE        = 1001;        // The ">=" relation
20     public final static int TT_NE        = 1002;        // The "<>" relation
21     public final static int TT_EQEQ      = 1003;        // The "==" relation
22     public final static int TT_AND       = 1004;        // The "and" boolean
23     public final static int TT_OR        = 1005;        // The "or" boolean
24     public final static int TT_NOT       = 1006;        // The "not" boolean
25     public final static int TT_PROX      = 1007;        // The "prox" boolean
26     public final static int TT_SORTBY     = 1008;       // The "sortby" operator
27
28     // Support for keywords.  It would be nice to compile this linear
29     // list into a Hashtable, but it's hard to store ints as hash
30     // values, and next to impossible to use them as hash keys.  So
31     // we'll just scan the (very short) list every time we need to do
32     // a lookup.
33     private class Keyword {
34         int token;
35         String keyword;
36         Keyword(int token, String keyword) {
37             this.token = token;
38             this.keyword = keyword;
39         }
40     }
41     // This should logically be static, but Java won't allow it  :-P
42     private Keyword[] keywords = {
43         new Keyword(TT_AND, "and"),
44         new Keyword(TT_OR,  "or"),
45         new Keyword(TT_NOT, "not"),
46         new Keyword(TT_PROX, "prox"),
47         new Keyword(TT_SORTBY, "sortby"),
48     };
49
50     // For halfDecentPushBack() and the code at the top of nextToken()
51     private static int TT_UNDEFINED = -1000;
52     private int saved_ttype = TT_UNDEFINED;
53     private double saved_nval;
54     private String saved_sval;
55
56     // Controls debugging output
57     private static boolean DEBUG;
58
59     CQLLexer(String cql, boolean lexdebug) {
60         super(new StringReader(cql));
61         wordChars('!', '?');    // ASCII-dependency!
62         wordChars('[', '`');    // ASCII-dependency!
63         quoteChar('"');
64         ordinaryChar('=');
65         ordinaryChar('<');
66         ordinaryChar('>');
67         ordinaryChar('/');
68         ordinaryChar('(');
69         ordinaryChar(')');
70         wordChars('\'', '\''); // prevent this from introducing strings
71         //parseNumbers();
72         ordinaryChar('-');
73         wordChars('-', '-');
74         ordinaryChars('0', '9');
75         wordChars('0', '9');
76         DEBUG = lexdebug;
77     }
78
79     private static void debug(String str) {
80         if (DEBUG)
81             System.err.println("LEXDEBUG: " + str);
82     }
83
84     // I don't honestly understand why we need this, but the
85     // documentation for java.io.StreamTokenizer.pushBack() is pretty
86     // vague about its semantics, and it seems to me that they could
87     // be summed up as "it doesn't work".  This version has the very
88     // clear semantics "pretend I didn't call nextToken() just then".
89     //
90     private void halfDecentPushBack() {
91         saved_ttype = ttype;
92         saved_nval = nval;
93         saved_sval = sval;
94     }
95
96     public int nextToken() throws java.io.IOException {
97         if (saved_ttype != TT_UNDEFINED) {
98             ttype = saved_ttype;
99             nval = saved_nval;
100             sval = saved_sval;
101             saved_ttype = TT_UNDEFINED;
102             debug("using saved ttype=" + ttype + ", " +
103                   "nval=" + nval + ", sval='" + sval + "'");
104             return ttype;
105         }
106
107         underlyingNextToken();
108         if (ttype == '<') {
109             debug("token starts with '<' ...");
110             underlyingNextToken();
111             if (ttype == '=') {
112                 debug("token continues with '=' - it's '<='");
113                 ttype = TT_LE;
114             } else if (ttype == '>') {
115                 debug("token continues with '>' - it's '<>'");
116                 ttype = TT_NE;
117             } else {
118                 debug("next token is " + render() + " (pushed back)");
119                 halfDecentPushBack();
120                 ttype = '<';
121                 debug("AFTER: ttype is now " + ttype + " - " + render());
122             }
123         } else if (ttype == '>') {
124             debug("token starts with '>' ...");
125             underlyingNextToken();
126             if (ttype == '=') {
127                 debug("token continues with '=' - it's '>='");
128                 ttype = TT_GE;
129             } else {
130                 debug("next token is " + render() + " (pushed back)");
131                 halfDecentPushBack();
132                 ttype = '>';
133                 debug("AFTER: ttype is now " + ttype + " - " + render());
134             }
135         } else if (ttype == '=') {
136             debug("token starts with '=' ...");
137             underlyingNextToken();
138             if (ttype == '=') {
139                 debug("token continues with '=' - it's '=='");
140                 ttype = TT_EQEQ;
141             } else {
142                 debug("next token is " + render() + " (pushed back)");
143                 halfDecentPushBack();
144                 ttype = '=';
145                 debug("AFTER: ttype is now " + ttype + " - " + render());
146             }
147         }
148
149         debug("done nextToken(): ttype=" + ttype + ", " +
150               "nval=" + nval + ", " + "sval='" + sval + "'" +
151               " (" + render() + ")");
152
153         return ttype;
154     }
155
156     // It's important to do keyword recognition here at the lowest
157     // level, otherwise when one of these words follows "<" or ">"
158     // (which can be the beginning of multi-character tokens) it gets
159     // pushed back as a string, and its keywordiness is not
160     // recognised.
161     //
162     public int underlyingNextToken() throws java.io.IOException {
163         super.nextToken();
164         if (ttype == TT_WORD)
165             for (int i = 0; i < keywords.length; i++)
166                 if (sval.equalsIgnoreCase(keywords[i].keyword))
167                     ttype = keywords[i].token;
168
169         return ttype;
170     }
171
172     // Simpler interface for the usual case: current token with quoting
173     String render() {
174         return render(ttype, true);
175     }
176
177     String render(int token, boolean quoteChars) {
178         if (token == TT_EOF) {
179             return "EOF";
180         } else if (token == TT_NUMBER) {
181             if ((double) nval == (int) nval) {
182                 return new Integer((int) nval).toString();
183             } else {
184                 return new Double((double) nval).toString();
185             }
186         } else if (token == TT_WORD) {
187             return "word: " + sval;
188         } else if (token == '"') {
189             return "string: \"" + sval + "\"";
190         } else if (token == TT_LE) {
191             return "<=";
192         } else if (token == TT_GE) {
193             return ">=";
194         } else if (token == TT_NE) {
195             return "<>";
196         } else if (token == TT_EQEQ) {
197             return "==";
198         }
199
200         // Check whether its associated with one of the keywords
201         for (int i = 0; i < keywords.length; i++)
202             if (token == keywords[i].token)
203                 return keywords[i].keyword;
204
205         // Otherwise it must be a single character, such as '(' or '/'.
206         String res = String.valueOf((char) token);
207         if (quoteChars) res = "'" + res + "'";
208         return res;
209     }
210
211     public static void main(String[] args) throws Exception {
212         if (args.length > 1) {
213             System.err.println("Usage: CQLLexer [<CQL-query>]");
214             System.err.println("If unspecified, query is read from stdin");
215             System.exit(1);
216         }
217
218         String cql;
219         if (args.length == 1) {
220             cql = args[0];
221         } else {
222             byte[] bytes = new byte[10000];
223             try {
224                 // Read in the whole of standard input in one go
225                 int nbytes = System.in.read(bytes);
226             } catch (java.io.IOException ex) {
227                 System.err.println("Can't read query: " + ex.getMessage());
228                 System.exit(2);
229             }
230             cql = new String(bytes);
231         }
232
233         CQLLexer lexer = new CQLLexer(cql, true);
234         int token;
235         while ((token = lexer.nextToken()) != TT_EOF) {
236             // Nothing to do: debug() statements render tokens for us
237         }
238     }
239 }