Properly render string tokens
[cql-java-moved-to-github.git] / src / main / java / org / z3950 / zing / cql / CQLLexer.java
1 /*
2  * Copyright (c) 1995-2014, Index Datassss
3  * All rights reserved.
4  * See the file LICENSE for details.
5  */
6 package org.z3950.zing.cql;
7
8 import java.io.BufferedReader;
9 import java.io.IOException;
10 import java.io.InputStreamReader;
11
12 /**
13  * Implementation of the CQL lexical syntax analyzer
14  * @author jakub
15  */
16 public class CQLLexer implements CQLTokenizer {
17   private String qs;
18   private int qi;
19   private int ql;
20   private int what = TT_NOTHING;
21   private String val;
22   private String lval;
23   private StringBuilder buf = new StringBuilder();
24
25   public CQLLexer(String cql, boolean debug) {
26     qs = cql;
27     ql = cql.length();
28   }
29
30   @Override
31   public void move() {
32     //eat whitespace
33     while (qi < ql && strchr(" \t\r\n", qs.charAt(qi)))
34       qi++;
35     //eof
36     if (qi == ql) {
37       what = TT_EOF;
38       return;
39     }
40     //current char
41     char c = qs.charAt(qi);
42     //separators
43     if (strchr("()/", c)) {
44       what = c;
45       qi++;
46     //comparitor
47     } else if (strchr("<>=", c)) {
48       what = c;
49       qi++;
50       //two-char comparitor
51       if (qi < ql) {
52         char d = qs.charAt(qi);
53         String comp = String.valueOf((char) c) + String.valueOf((char) d);
54         if (comp.equals("==")) {
55           what = TT_EQEQ;
56           qi++;
57         }
58         else if (comp.equals("<=")) {
59           what = TT_LE;
60           qi++;
61         }
62         else if (comp.equals(">=")) {
63           what = TT_GE;
64           qi++;
65         }
66         else if (comp.equals("<>")) {
67           what = TT_NE;
68           qi++;
69         }
70       }
71     //quoted string
72     } else if (strchr("\"", c)) { //no single-quotes
73       what = TT_STRING;
74       //remember quote char
75       char mark = c;
76       qi++;
77       boolean escaped = false;
78       buf.setLength(0); //reset buffer
79       while (qi < ql) {
80         if (!escaped && qs.charAt(qi) == mark) //terminator
81           break;
82         if (escaped && strchr("*?^\\", qs.charAt(qi))) //no escaping for d-quote
83           buf.append("\\");
84         if (!escaped && qs.charAt(qi) == '\\') { //escape-char
85           escaped = true;
86           qi++;
87           continue;
88         }
89         escaped = false; //reset escape
90         buf.append(qs.charAt(qi));
91         qi++;
92       }
93       val = buf.toString();
94       lval = val.toLowerCase();
95       if (qi < ql)
96         qi++;
97       else //unterminated
98         what = TT_EOF; //notify error
99       //unquoted string
100     } else {
101       what = TT_WORD;
102       buf.setLength(0); //reset buffer
103       while (qi < ql
104         && !strchr("()/<>= \t\r\n", qs.charAt(qi))) {
105         buf.append(qs.charAt(qi));
106         qi++;
107       }
108       val = buf.toString();
109       lval = val.toLowerCase();
110       if (lval.equals("or")) what = TT_OR;
111       else if (lval.equals("and")) what = TT_AND;
112       else if (lval.equals("not")) what = TT_NOT;
113       else if (lval.equals("prox")) what = TT_PROX;
114       else if (lval.equals("sortby")) what = TT_SORTBY;
115     }
116   }
117
118   private boolean strchr(String s, char ch) {
119     return s.indexOf(ch) >= 0;
120   }
121
122   @Override
123   public String value() {
124     return val;
125   }
126
127   @Override
128   public int what() {
129     return what;
130   }
131
132   @Override
133   public String render() {
134     return render(what, true);
135   }
136
137   @Override
138   public String render(int token, boolean quoteChars) {
139     switch (token) {
140       case TT_EOF:
141         return "EOF";
142       case TT_WORD:
143         return "word: '" + val + "'";
144       case TT_STRING:
145         return "string: \"" + val + "\"";
146       case TT_LE:
147         return "<=";
148       case TT_GE:
149         return ">=";
150       case TT_NE:
151         return "<>";
152       case TT_EQEQ:
153         return "==";
154       case TT_AND:
155         return "and";
156       case TT_NOT:
157         return "not";
158       case TT_OR:
159         return "or";
160       case TT_PROX:
161         return "prox";
162       case TT_SORTBY:
163         return "sortby";
164       default:
165         //a single character, such as '(' or '/' or relation
166         String res = String.valueOf((char) token);
167         if (quoteChars)
168           res = "'" + res + "'";
169         return res;
170     }
171   }
172
173   @Override
174   public int pos() {
175     return qi;
176   }
177   
178   public static void main(String[] args) throws Exception {
179     if (args.length > 1) {
180       System.err.println("Usage: CQLLexer [<CQL-query>]");
181       System.err.println("If unspecified, query is read from stdin");
182       System.exit(1);
183     }
184
185     String cql;
186     if (args.length == 1) {
187       cql = args[0];
188     } else {
189       BufferedReader buff = new BufferedReader(new InputStreamReader(System.in));
190       try {
191         // read a single line of input
192         cql = buff.readLine();
193         if (cql == null) {
194           System.err.println("Can't read query from stdin");
195           System.exit(2);
196           return;
197         }
198       } catch (IOException ex) {
199         System.err.println("Can't read query: " + ex.getMessage());
200         System.exit(2);
201         return;
202       }
203     }
204
205     CQLTokenizer lexer = new CQLLexer(cql, true);
206     while ((lexer.what()) != TT_EOF) {
207       lexer.move();
208       System.out.println(lexer.render());
209     }
210   }
211 }