2 * Copyright (C) 2014, United States Government, as represented by the
3 * Administrator of the National Aeronautics and Space Administration.
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
10 * http://www.apache.org/licenses/LICENSE-2.0.
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.
18 package gov.nasa.jpf.util.json;
20 import gov.nasa.jpf.JPFException;
22 import java.io.IOException;
23 import java.io.Reader;
24 import java.io.StringReader;
26 * Lexical analyzer that reads stream and return JSON tokens.
27 * @author Ivan Mushketik
29 public class JSONLexer {
31 // JSON document reader
32 private Reader reader;
33 // number of symbol in text
37 // number of symbol in line
38 int symbolNumberInLine;
40 // If parser backtracked to previous symbol
42 // Last read character
45 private final int STREAM_END = -1;
47 public JSONLexer(Reader reader) {
52 public JSONLexer(String JSONStr) {
53 this(new StringReader(JSONStr));
57 * Read next token from input stream.
58 * @return new read token
60 public Token getNextToken() {
66 } while(isSkipChar(c));
68 if (c == STREAM_END) {
69 return new Token(Token.Type.DocumentEnd, null);
73 return new Token(Token.Type.ObjectStart, "{");
77 return new Token(Token.Type.ObjectEnd, "}");
81 return new Token(Token.Type.ArrayStart, "[");
85 return new Token(Token.Type.ArrayEnd, "]");
89 return new Token(Token.Type.KeyValueSeparator, ":");
93 return new Token(Token.Type.Comma, ",");
97 return new Token(Token.Type.CGCallParamsStart, "(");
101 return new Token(Token.Type.CGCallParamsEnd, ")");
104 if (c == '\"' || c == '\'') {
105 return parseString(c);
108 if (Character.isDigit(c) || c == '-') {
110 return parseNumber();
113 if (isIdentifierStartSymbol(c)) {
115 return parseIdentifier();
118 // No sutable symbols found
119 error("Unexpected sybmol");
124 * Method checks if parser has more input to read
125 * @return true if scanner has more tokens to read
127 public boolean hasMore() {
128 return currentChar != STREAM_END;
132 * Read next symbol from input stream
133 * @return new read symbol
142 currentChar = reader.read();
145 symbolNumberInLine++;
146 if (currentChar == '\n') {
148 symbolNumberInLine = 0;
152 } catch (IOException ex) {
153 throw new JPFException("IOException during tokenizing JSON", ex);
158 * Backtrack to previous symbol
160 private void back() {
162 throw new JPFException("Tried to return twice. Posibly an error. Please report");
167 // Scaner doesn't backtrack before call this method
168 private Token parseString(int delimiter) {
169 StringBuilder result = new StringBuilder();
172 while((c = next()) != delimiter) {
174 result.append((char) readEscapedSymbol());
176 result.append((char) c);
180 return new Token(Token.Type.String, result.toString());
183 private int readEscapedSymbol() {
184 int escaped = next();
215 // Extract hexadecimal Unicode symbol (\\uXXXX)
221 while (hexadecimalChar(c = next()) && i < 4) {
226 // Unicode escape consists of 4 hexadecimal symbols
228 error("Escaped Unicode symbol should consist of 4 hexadecimal digits");
233 res = Integer.parseInt(r, 16);
238 error("Illegal excape");
245 private Token parseNumber() {
246 StringBuilder sb = new StringBuilder();
249 // '-' symbol is not obligatory
253 // We read unnecessary symbol, need to bactrack
259 // Integer part of digit is either '0' or '1'..'9' and digits
264 sb.append(readDigits());
272 sb.append(readDigits());
279 if (c == 'e' || c == 'E') {
282 if (c == '+' || c == '-') {
288 sb.append(readDigits());
293 return new Token(Token.Type.Number, sb.toString());
297 * Read at least one digit
298 * @return String that represents read number
300 private String readDigits() {
301 StringBuilder sb = new StringBuilder();
304 while (Character.isDigit(c = next())) {
310 error("Expected not empty sequence of digits");
314 return sb.toString();
317 private Token parseIdentifier() {
318 StringBuilder result = new StringBuilder();
322 while (Character.isJavaIdentifierPart(c)) {
323 result.append((char) c);
330 return new Token(Token.Type.Identificator, result.toString());
333 private boolean isIdentifierStartSymbol(int c) {
334 return Character.isJavaIdentifierStart(c);
337 private boolean isSkipChar(int currentChar) {
338 return Character.isSpaceChar(currentChar);
341 private void error(String string) {
342 throw new JPFException(string + " '" + (char) currentChar + "' charCode = " + currentChar +
343 "; in line " + lineNumber + " pos " + symbolNumberInLine);
346 private boolean hexadecimalChar(int i) {
347 return Character.isDigit(i) || (i <= 'F' && i >= 'A') || (i <= 'f' && i >= 'a');
350 int getLineNumber() {
354 int getCurrentPos() {
355 return symbolNumberInLine;