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