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