Initial import
[jpf-core.git] / src / main / gov / nasa / jpf / util / json / JSONLexer.java
1 /*
2  * Copyright (C) 2014, United States Government, as represented by the
3  * Administrator of the National Aeronautics and Space Administration.
4  * All rights reserved.
5  *
6  * The Java Pathfinder core (jpf-core) platform is licensed under the
7  * Apache License, Version 2.0 (the "License"); you may not use this file except
8  * in compliance with the License. You may obtain a copy of the License at
9  * 
10  *        http://www.apache.org/licenses/LICENSE-2.0. 
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and 
16  * limitations under the License.
17  */
18 package gov.nasa.jpf.util.json;
19
20 import gov.nasa.jpf.JPFException;
21
22 import java.io.IOException;
23 import java.io.Reader;
24 import java.io.StringReader;
25 /**
26  * Lexical analyzer that reads stream and return JSON tokens.
27  * @author Ivan Mushketik
28  */
29 public class JSONLexer {
30
31   // JSON document reader
32   private Reader reader;
33   // number of symbol in text
34   int symbolNumber;
35   // number line
36   int lineNumber;
37   // number of symbol in line
38   int symbolNumberInLine;
39
40   // If parser backtracked to previous symbol
41   boolean backtracked;
42   // Last read character
43   int currentChar;
44
45   private final int STREAM_END = -1;
46
47   public JSONLexer(Reader reader) {
48     this.reader = reader;
49     backtracked = false;
50   }
51
52   public JSONLexer(String JSONStr) {
53     this(new StringReader(JSONStr));
54   }
55
56   /**
57    * Read next token from input stream.
58    * @return new read token
59    */
60   public Token getNextToken() {
61
62     int c;
63     // Skip whitespaces
64     do {
65       c = next();
66     } while(isSkipChar(c));
67
68     if (c == STREAM_END) {
69       return new Token(Token.Type.DocumentEnd, null);
70     }
71
72     if (c == '{') {
73       return new Token(Token.Type.ObjectStart, "{");
74     }
75
76     if (c == '}') {
77       return new Token(Token.Type.ObjectEnd, "}");
78     }
79
80     if (c == '[') {
81       return new Token(Token.Type.ArrayStart, "[");
82     }
83
84     if (c == ']') {
85       return new Token(Token.Type.ArrayEnd, "]");
86     }
87
88     if (c == ':') {
89       return new Token(Token.Type.KeyValueSeparator, ":");
90     }
91
92     if (c == ',') {
93       return new Token(Token.Type.Comma, ",");
94     }
95
96     if (c == '(') {
97       return new Token(Token.Type.CGCallParamsStart, "(");
98     }
99
100     if (c == ')') {
101       return new Token(Token.Type.CGCallParamsEnd, ")");
102     }
103
104     if (c == '\"' || c == '\'') {
105       return parseString(c);
106     }
107
108     if (Character.isDigit(c) || c == '-') {
109       back();
110       return parseNumber();
111     }
112
113     if (isIdentifierStartSymbol(c)) {
114       back();
115       return parseIdentifier();
116     }
117
118     // No sutable symbols found
119     error("Unexpected sybmol");
120     return null;
121   }
122
123   /**
124    * Method checks if parser has more input to read
125    * @return true if scanner has more tokens to read
126    */
127   public boolean hasMore() {
128     return currentChar != STREAM_END;
129   }
130
131   /**
132    * Read next symbol from input stream
133    * @return new read symbol
134    */
135   private int next() {
136     try {
137       if (backtracked) {
138         backtracked = false;
139         return currentChar;
140       }
141
142       currentChar = reader.read();
143       
144       symbolNumber++;
145       symbolNumberInLine++;
146       if (currentChar == '\n') {
147         lineNumber++;
148         symbolNumberInLine = 0;
149       }
150
151       return currentChar;
152     } catch (IOException ex) {
153       throw new JPFException("IOException during tokenizing JSON", ex);
154     }
155   }
156
157   /**
158    * Backtrack to previous symbol
159    */
160   private void back() {
161     if (backtracked) {
162       throw new JPFException("Tried to return twice. Posibly an error. Please report");
163     }
164     backtracked = true;
165   }
166
167   // Scaner doesn't backtrack before call this method
168   private Token parseString(int delimiter) {
169     StringBuilder result = new StringBuilder();
170     int c;
171
172     while((c = next()) != delimiter) {
173       if (c == '\\') {
174           result.append((char) readEscapedSymbol());
175       } else {
176          result.append((char) c);
177       }
178     }
179
180     return new Token(Token.Type.String, result.toString());
181   }
182
183   private int readEscapedSymbol() {
184     int escaped = next();
185
186     int res = -1;
187
188     switch(escaped) {
189       case '\"':
190       case '\\':
191       case '/':
192         res = escaped;
193         break;
194
195       case 'b':
196         res = '\b';
197         break;
198
199       case 'f':
200         res = '\f';
201         break;
202
203       case 'n':
204         res = '\n';
205         break;
206
207       case 'r':
208         res = '\r';
209         break;
210
211       case 't':
212         res = '\t';
213         break;
214
215       // Extract hexadecimal Unicode symbol (\\uXXXX)
216       case 'u': {
217         String r = "";
218         int i = 0;
219         int c;
220
221         while (hexadecimalChar(c = next()) && i < 4) {
222           r += (char) c;
223           i++;
224         }
225
226         // Unicode escape consists of 4 hexadecimal symbols
227         if (i < 4) {
228           error("Escaped Unicode symbol should consist of 4 hexadecimal digits");
229         }
230         
231         back();
232
233         res = Integer.parseInt(r, 16);
234       }
235       break;
236
237       default:
238         error("Illegal excape");
239         break;
240     }
241
242     return res;
243   }
244
245   private Token parseNumber() {    
246     StringBuilder sb = new StringBuilder();
247     int c = next();
248
249     // '-' symbol is not obligatory
250     if (c == '-') {
251       sb.append('-');
252     } else {
253       // We read unnecessary symbol, need to bactrack
254       back();
255     }
256
257     c = next();
258
259     // Integer part of digit is either '0' or '1'..'9' and digits
260     if (c == '0') {
261       sb.append('0');
262     } else {
263       back();
264       sb.append(readDigits());
265     }
266
267     c = next();
268
269     // "float part"
270     if (c == '.') {
271       sb.append('.');
272       sb.append(readDigits());
273     } else {
274       back();
275     }
276
277     c = next();
278
279     if (c == 'e' || c == 'E') {
280       sb.append((char) c);
281       c = next();
282       if (c == '+' || c == '-') {
283         sb.append((char) c);
284       } else {
285         back();
286       }
287
288       sb.append(readDigits());
289     } else {
290       back();
291     }
292
293     return new Token(Token.Type.Number, sb.toString());
294   }
295
296   /**
297    * Read at least one digit
298    * @return String that represents read number
299    */
300   private String readDigits() {
301     StringBuilder sb = new StringBuilder();
302     int c;
303     int n = 0;
304     while (Character.isDigit(c = next())) {
305       sb.append((char) c);
306       n++;
307     }
308
309     if (n == 0) {
310       error("Expected not empty sequence of digits");
311     }
312
313     back();
314     return sb.toString();
315   }
316
317   private Token parseIdentifier() {
318     StringBuilder result = new StringBuilder();
319
320     int c = next();
321
322     while (Character.isJavaIdentifierPart(c)) {
323       result.append((char) c);
324
325       c = next();
326     }
327
328     back();
329
330     return new Token(Token.Type.Identificator, result.toString());
331   }
332
333   private boolean isIdentifierStartSymbol(int c) {
334     return Character.isJavaIdentifierStart(c);
335   }
336
337   private boolean isSkipChar(int currentChar) {
338     return Character.isSpaceChar(currentChar);
339   }
340
341   private void error(String string) {
342     throw new JPFException(string + " '" + (char) currentChar + "' charCode = " + currentChar +
343                            "; in line " + lineNumber + " pos " + symbolNumberInLine);
344   }
345
346   private boolean hexadecimalChar(int i) {
347     return Character.isDigit(i) || (i <= 'F' && i >= 'A') || (i <= 'f' && i >= 'a');
348   }
349
350   int getLineNumber() {
351     return lineNumber;
352   }
353
354   int getCurrentPos() {
355     return symbolNumberInLine;
356   }
357 }