1 /* 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.parser; 27 28 import static jdk.nashorn.internal.parser.TokenType.COLON; 29 import static jdk.nashorn.internal.parser.TokenType.COMMARIGHT; 30 import static jdk.nashorn.internal.parser.TokenType.EOF; 31 import static jdk.nashorn.internal.parser.TokenType.ESCSTRING; 32 import static jdk.nashorn.internal.parser.TokenType.RBRACE; 33 import static jdk.nashorn.internal.parser.TokenType.RBRACKET; 34 import static jdk.nashorn.internal.parser.TokenType.STRING; 35 import java.util.ArrayList; 36 import java.util.List; 37 import jdk.nashorn.internal.ir.Expression; 38 import jdk.nashorn.internal.ir.LiteralNode; 39 import jdk.nashorn.internal.ir.Node; 40 import jdk.nashorn.internal.ir.ObjectNode; 41 import jdk.nashorn.internal.ir.PropertyNode; 42 import jdk.nashorn.internal.ir.UnaryNode; 43 import jdk.nashorn.internal.runtime.ErrorManager; 44 import jdk.nashorn.internal.runtime.Source; 45 46 /** 47 * Parses JSON text and returns the corresponding IR node. This is derived from the objectLiteral production of the main parser. 48 * 49 * See: 15.12.1.2 The JSON Syntactic Grammar 50 */ 51 public class JSONParser extends AbstractParser { 52 53 /** 54 * Constructor 55 * @param source the source 56 * @param errors the error manager 57 */ 58 public JSONParser(final Source source, final ErrorManager errors) { 59 super(source, errors, false, 0); 60 } 61 62 /** 63 * Implementation of the Quote(value) operation as defined in the ECMA script spec 64 * It wraps a String value in double quotes and escapes characters within in 65 * 66 * @param value string to quote 67 * 68 * @return quoted and escaped string 69 */ 70 public static String quote(final String value) { 71 72 final StringBuilder product = new StringBuilder(); 73 74 product.append("\""); 75 76 for (final char ch : value.toCharArray()) { 77 // TODO: should use a table? 78 switch (ch) { 79 case '\\': 97 case '\t': 98 product.append("\\t"); 99 break; 100 default: 101 if (ch < ' ') { 102 product.append(Lexer.unicodeEscape(ch)); 103 break; 104 } 105 106 product.append(ch); 107 break; 108 } 109 } 110 111 product.append("\""); 112 113 return product.toString(); 114 } 115 116 /** 117 * Public parsed method - start lexing a new token stream for 118 * a JSON script 119 * 120 * @return the JSON literal 121 */ 122 public Node parse() { 123 stream = new TokenStream(); 124 125 lexer = new Lexer(source, stream) { 126 127 @Override 128 protected boolean skipComments() { 129 return false; 130 } 131 132 @Override 133 protected boolean isStringDelimiter(final char ch) { 134 return ch == '\"'; 135 } 136 137 // ECMA 15.12.1.1 The JSON Lexical Grammar - JSONWhiteSpace 138 @Override 139 protected boolean isWhitespace(final char ch) { 140 return Lexer.isJsonWhitespace(ch); 141 } 142 143 @Override 144 protected boolean isEOL(final char ch) { 145 return Lexer.isJsonEOL(ch); 146 } 147 148 // ECMA 15.12.1.1 The JSON Lexical Grammar - JSONNumber 149 @Override 150 protected void scanNumber() { 151 // Record beginning of number. 152 final int startPosition = position; 153 // Assume value is a decimal. 154 TokenType valueType = TokenType.DECIMAL; 155 156 // floating point can't start with a "." with no leading digit before 157 if (ch0 == '.') { 158 error(Lexer.message("json.invalid.number"), STRING, position, limit); 159 } 160 161 // First digit of number. 162 final int digit = convertDigit(ch0, 10); 163 164 // skip first digit 165 skip(1); 166 167 if (digit != 0) { 168 // Skip over remaining digits. 169 while (convertDigit(ch0, 10) != -1) { 170 skip(1); 171 } 172 } 173 174 if (ch0 == '.' || ch0 == 'E' || ch0 == 'e') { 175 // Must be a double. 176 if (ch0 == '.') { 177 // Skip period. 178 skip(1); 179 180 boolean mantissa = false; 181 // Skip mantissa. 182 while (convertDigit(ch0, 10) != -1) { 183 mantissa = true; 184 skip(1); 185 } 186 187 if (! mantissa) { 188 // no digit after "." 189 error(Lexer.message("json.invalid.number"), STRING, position, limit); 190 } 191 } 192 193 // Detect exponent. 194 if (ch0 == 'E' || ch0 == 'e') { 195 // Skip E. 196 skip(1); 197 // Detect and skip exponent sign. 198 if (ch0 == '+' || ch0 == '-') { 199 skip(1); 200 } 201 boolean exponent = false; 202 // Skip exponent. 203 while (convertDigit(ch0, 10) != -1) { 204 exponent = true; 205 skip(1); 206 } 207 208 if (! exponent) { 209 // no digit after "E" 210 error(Lexer.message("json.invalid.number"), STRING, position, limit); 211 } 212 } 213 214 valueType = TokenType.FLOATING; 215 } 216 217 // Add number token. 218 add(valueType, startPosition); 219 } 220 221 // ECMA 15.12.1.1 The JSON Lexical Grammar - JSONEscapeCharacter 222 @Override 223 protected boolean isEscapeCharacter(final char ch) { 224 switch (ch) { 225 case '"': 226 case '/': 227 case '\\': 228 case 'b': 229 case 'f': 230 case 'n': 231 case 'r': 232 case 't': 233 // could be unicode escape 234 case 'u': 235 return true; 236 default: 237 return false; 238 } 239 } 240 }; 241 242 k = -1; 243 244 next(); 245 246 final Node resultNode = jsonLiteral(); 247 expect(EOF); 248 249 return resultNode; 250 } 251 252 @SuppressWarnings("fallthrough") 253 private LiteralNode<?> getStringLiteral() { 254 final LiteralNode<?> literal = getLiteral(); 255 final String str = (String)literal.getValue(); 256 257 for (int i = 0; i < str.length(); i++) { 258 final char ch = str.charAt(i); 259 switch (ch) { 260 default: 261 if (ch > 0x001f) { 262 break; 263 } 264 case '"': 265 case '\\': 266 throw error(AbstractParser.message("unexpected.token", str)); 267 } 268 } 269 270 return literal; 271 } 272 273 /** 274 * Parse a JSON literal from the token stream 275 * @return the JSON literal as a Node 276 */ 277 private Expression jsonLiteral() { 278 final long literalToken = token; 279 280 switch (type) { 281 case STRING: 282 return getStringLiteral(); 283 case ESCSTRING: 284 case DECIMAL: 285 case FLOATING: 286 return getLiteral(); 287 case FALSE: 288 next(); 289 return LiteralNode.newInstance(literalToken, finish, false); 290 case TRUE: 291 next(); 292 return LiteralNode.newInstance(literalToken, finish, true); 293 case NULL: 294 next(); 295 return LiteralNode.newInstance(literalToken, finish); 296 case LBRACKET: 297 return arrayLiteral(); 298 case LBRACE: 299 return objectLiteral(); 300 /* 301 * A.8.1 JSON Lexical Grammar 302 * 303 * JSONNumber :: See 15.12.1.1 304 * -opt DecimalIntegerLiteral JSONFractionopt ExponentPartopt 305 */ 306 case SUB: 307 next(); 308 309 final long realToken = token; 310 final Object value = getValue(); 311 312 if (value instanceof Number) { 313 next(); 314 return new UnaryNode(literalToken, LiteralNode.newInstance(realToken, finish, (Number)value)); 315 } 316 317 throw error(AbstractParser.message("expected", "number", type.getNameOrType())); 318 default: 319 break; 320 } 321 322 throw error(AbstractParser.message("expected", "json literal", type.getNameOrType())); 323 } 324 325 /** 326 * Parse an array literal from the token stream 327 * @return the array literal as a Node 328 */ 329 private LiteralNode<Expression[]> arrayLiteral() { 330 // Unlike JavaScript array literals, elison is not permitted in JSON. 331 332 // Capture LBRACKET token. 333 final long arrayToken = token; 334 // LBRACKET tested in caller. 335 next(); 336 337 LiteralNode<Expression[]> result = null; 338 // Prepare to accummulating elements. 339 final List<Expression> elements = new ArrayList<>(); 340 341 loop: 342 while (true) { 343 switch (type) { 344 case RBRACKET: 345 next(); 346 result = LiteralNode.newInstance(arrayToken, finish, elements); 347 break loop; 348 349 case COMMARIGHT: 350 next(); 351 // check for trailing comma - not allowed in JSON 352 if (type == RBRACKET) { 353 throw error(AbstractParser.message("trailing.comma.in.json", type.getNameOrType())); 354 } 355 break; 356 357 default: 358 // Add expression element. 359 elements.add(jsonLiteral()); 360 // Comma between array elements is mandatory in JSON. 361 if (type != COMMARIGHT && type != RBRACKET) { 362 throw error(AbstractParser.message("expected", ", or ]", type.getNameOrType())); 363 } 364 break; 365 } 366 } 367 368 return result; 369 } 370 371 /** 372 * Parse an object literal from the token stream 373 * @return the object literal as a Node 374 */ 375 private ObjectNode objectLiteral() { 376 // Capture LBRACE token. 377 final long objectToken = token; 378 // LBRACE tested in caller. 379 next(); 380 381 // Prepare to accumulate elements. 382 final List<PropertyNode> elements = new ArrayList<>(); 383 384 // Create a block for the object literal. 385 loop: 386 while (true) { 387 switch (type) { 388 case RBRACE: 389 next(); 390 break loop; 391 392 case COMMARIGHT: 393 next(); 394 // check for trailing comma - not allowed in JSON 395 if (type == RBRACE) { 396 throw error(AbstractParser.message("trailing.comma.in.json", type.getNameOrType())); 397 } 398 break; 399 400 default: 401 // Get and add the next property. 402 final PropertyNode property = propertyAssignment(); 403 elements.add(property); 404 405 // Comma between property assigments is mandatory in JSON. 406 if (type != RBRACE && type != COMMARIGHT) { 407 throw error(AbstractParser.message("expected", ", or }", type.getNameOrType())); 408 } 409 break; 410 } 411 } 412 413 // Construct new object literal. 414 return new ObjectNode(objectToken, finish, elements); 415 } 416 417 /** 418 * Parse a property assignment from the token stream 419 * @return the property assignment as a Node 420 */ 421 private PropertyNode propertyAssignment() { 422 // Capture firstToken. 423 final long propertyToken = token; 424 LiteralNode<?> name = null; 425 426 if (type == STRING) { 427 name = getStringLiteral(); 428 } else if (type == ESCSTRING) { 429 name = getLiteral(); 430 } 431 432 if (name != null) { 433 expect(COLON); 434 final Expression value = jsonLiteral(); 435 return new PropertyNode(propertyToken, value.getFinish(), name, value, null, null); 436 } 437 438 // Raise an error. 439 throw error(AbstractParser.message("expected", "string", type.getNameOrType())); 440 } 441 442 } | 1 /* 2 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.parser; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 import jdk.nashorn.internal.codegen.ObjectClassGenerator; 31 import jdk.nashorn.internal.objects.Global; 32 import jdk.nashorn.internal.runtime.ECMAErrors; 33 import jdk.nashorn.internal.runtime.ErrorManager; 34 import jdk.nashorn.internal.runtime.JSErrorType; 35 import jdk.nashorn.internal.runtime.JSType; 36 import jdk.nashorn.internal.runtime.ParserException; 37 import jdk.nashorn.internal.runtime.Property; 38 import jdk.nashorn.internal.runtime.PropertyMap; 39 import jdk.nashorn.internal.runtime.ScriptObject; 40 import jdk.nashorn.internal.runtime.Source; 41 import jdk.nashorn.internal.runtime.SpillProperty; 42 import jdk.nashorn.internal.runtime.arrays.ArrayData; 43 import jdk.nashorn.internal.runtime.arrays.ArrayIndex; 44 import jdk.nashorn.internal.scripts.JO; 45 46 import static jdk.nashorn.internal.parser.TokenType.STRING; 47 48 /** 49 * Parses JSON text and returns the corresponding IR node. This is derived from the objectLiteral production of the main parser. 50 * 51 * See: 15.12.1.2 The JSON Syntactic Grammar 52 */ 53 public class JSONParser { 54 55 final private String source; 56 final private Global global; 57 final int length; 58 int pos = 0; 59 60 private static PropertyMap EMPTY_MAP = PropertyMap.newMap(); 61 62 private static final int EOF = -1; 63 64 private static final String TRUE = "true"; 65 private static final String FALSE = "false"; 66 private static final String NULL = "null"; 67 68 private static final int STATE_EMPTY = 0; 69 private static final int STATE_ELEMENT_PARSED = 1; 70 private static final int STATE_COMMA_PARSED = 2; 71 72 /** 73 * Constructor 74 * @param source the source 75 * @param global the global object 76 */ 77 public JSONParser(final String source, final Global global ) { 78 this.source = source; 79 this.global = global; 80 this.length = source.length(); 81 } 82 83 /** 84 * Implementation of the Quote(value) operation as defined in the ECMA script spec 85 * It wraps a String value in double quotes and escapes characters within in 86 * 87 * @param value string to quote 88 * 89 * @return quoted and escaped string 90 */ 91 public static String quote(final String value) { 92 93 final StringBuilder product = new StringBuilder(); 94 95 product.append("\""); 96 97 for (final char ch : value.toCharArray()) { 98 // TODO: should use a table? 99 switch (ch) { 100 case '\\': 118 case '\t': 119 product.append("\\t"); 120 break; 121 default: 122 if (ch < ' ') { 123 product.append(Lexer.unicodeEscape(ch)); 124 break; 125 } 126 127 product.append(ch); 128 break; 129 } 130 } 131 132 product.append("\""); 133 134 return product.toString(); 135 } 136 137 /** 138 * Public parse method. Parse a string into a JSON object. 139 * 140 * @return the parsed JSON Object 141 */ 142 public Object parse() { 143 final Object value = parseLiteral(); 144 skipWhiteSpace(); 145 if (pos < length) { 146 throw expectedError(pos, "eof", toString(peek())); 147 } 148 return value; 149 } 150 151 private Object parseLiteral() { 152 skipWhiteSpace(); 153 154 final int c = peek(); 155 if (c == EOF) { 156 throw expectedError(pos, "json literal", "eof"); 157 } 158 switch (c) { 159 case '{': 160 return parseObject(); 161 case '[': 162 return parseArray(); 163 case '"': 164 return parseString(); 165 case 'f': 166 return parseKeyword(FALSE, Boolean.FALSE); 167 case 't': 168 return parseKeyword(TRUE, Boolean.TRUE); 169 case 'n': 170 return parseKeyword(NULL, null); 171 default: 172 if (isDigit(c) || c == '-') { 173 return parseNumber(); 174 } else if (c == '.') { 175 throw numberError(pos); 176 } else { 177 throw expectedError(pos, "json literal", toString(c)); 178 } 179 } 180 } 181 182 private Object parseObject() { 183 PropertyMap propertyMap = EMPTY_MAP; 184 ArrayData arrayData = ArrayData.EMPTY_ARRAY; 185 final ArrayList<Object> values = new ArrayList<>(); 186 int state = STATE_EMPTY; 187 188 assert peek() == '{'; 189 pos++; 190 191 while (pos < length) { 192 skipWhiteSpace(); 193 final int c = peek(); 194 195 switch (c) { 196 case '"': 197 if (state == STATE_ELEMENT_PARSED) { 198 throw expectedError(pos - 1, ", or }", toString(c)); 199 } 200 final String id = parseString(); 201 expectColon(); 202 final Object value = parseLiteral(); 203 final int index = ArrayIndex.getArrayIndex(id); 204 if (ArrayIndex.isValidArrayIndex(index)) { 205 arrayData = addArrayElement(arrayData, index, value); 206 } else { 207 propertyMap = addObjectProperty(propertyMap, values, id, value); 208 } 209 state = STATE_ELEMENT_PARSED; 210 break; 211 case ',': 212 if (state != STATE_ELEMENT_PARSED) { 213 throw error(AbstractParser.message("trailing.comma.in.json"), pos); 214 } 215 state = STATE_COMMA_PARSED; 216 pos++; 217 break; 218 case '}': 219 if (state == STATE_COMMA_PARSED) { 220 throw error(AbstractParser.message("trailing.comma.in.json"), pos); 221 } 222 pos++; 223 return createObject(propertyMap, values, arrayData); 224 default: 225 throw expectedError(pos, ", or }", toString(c)); 226 } 227 } 228 throw expectedError(pos, ", or }", "eof"); 229 } 230 231 private static ArrayData addArrayElement(final ArrayData arrayData, final int index, final Object value) { 232 final long oldLength = arrayData.length(); 233 final long longIndex = ArrayIndex.toLongIndex(index); 234 ArrayData newArrayData = arrayData; 235 if (longIndex > oldLength) { 236 if (arrayData.canDelete(oldLength, longIndex - 1, false)) { 237 newArrayData = newArrayData.delete(oldLength, longIndex - 1); 238 } 239 } 240 return newArrayData.ensure(longIndex).set(index, value, false); 241 } 242 243 private static PropertyMap addObjectProperty(final PropertyMap propertyMap, final List<Object> values, 244 final String id, final Object value) { 245 final Property oldProperty = propertyMap.findProperty(id); 246 final Property newProperty; 247 final PropertyMap newMap; 248 final Class<?> type = ObjectClassGenerator.OBJECT_FIELDS_ONLY ? Object.class : getType(value); 249 250 if (oldProperty != null) { 251 values.set(oldProperty.getSlot(), value); 252 newProperty = new SpillProperty(id, 0, oldProperty.getSlot()); 253 newProperty.setType(type); 254 newMap = propertyMap.replaceProperty(oldProperty, newProperty);; 255 } else { 256 values.add(value); 257 newProperty = new SpillProperty(id, 0, propertyMap.size()); 258 newProperty.setType(type); 259 newMap = propertyMap.addProperty(newProperty); 260 } 261 262 return newMap; 263 } 264 265 private Object createObject(final PropertyMap propertyMap, final List<Object> values, final ArrayData arrayData) { 266 final long[] primitiveSpill = new long[values.size()]; 267 final Object[] objectSpill = new Object[values.size()]; 268 269 for (final Property property : propertyMap.getProperties()) { 270 if (property.getType() == Object.class) { 271 objectSpill[property.getSlot()] = values.get(property.getSlot()); 272 } else { 273 primitiveSpill[property.getSlot()] = ObjectClassGenerator.pack((Number) values.get(property.getSlot())); 274 } 275 } 276 277 final ScriptObject object = new JO(propertyMap, primitiveSpill, objectSpill); 278 object.setInitialProto(global.getObjectPrototype()); 279 object.setArray(arrayData); 280 return object; 281 } 282 283 private static Class<?> getType(final Object value) { 284 if (value instanceof Integer) { 285 return int.class; 286 } else if (value instanceof Long) { 287 return long.class; 288 } else if (value instanceof Double) { 289 return double.class; 290 } else { 291 return Object.class; 292 } 293 } 294 295 private void expectColon() { 296 skipWhiteSpace(); 297 final int n = next(); 298 if (n != ':') { 299 throw expectedError(pos - 1, ":", toString(n)); 300 } 301 } 302 303 private Object parseArray() { 304 ArrayData arrayData = ArrayData.EMPTY_ARRAY; 305 int state = STATE_EMPTY; 306 307 assert peek() == '['; 308 pos++; 309 310 while (pos < length) { 311 skipWhiteSpace(); 312 final int c = peek(); 313 314 switch (c) { 315 case ',': 316 if (state != STATE_ELEMENT_PARSED) { 317 throw error(AbstractParser.message("trailing.comma.in.json"), pos); 318 } 319 state = STATE_COMMA_PARSED; 320 pos++; 321 break; 322 case ']': 323 if (state == STATE_COMMA_PARSED) { 324 throw error(AbstractParser.message("trailing.comma.in.json"), pos); 325 } 326 pos++; 327 return global.wrapAsObject(arrayData); 328 default: 329 if (state == STATE_ELEMENT_PARSED) { 330 throw expectedError(pos, ", or ]", toString(c)); 331 } 332 final long index = arrayData.length(); 333 arrayData = arrayData.ensure(index).set((int) index, parseLiteral(), true); 334 state = STATE_ELEMENT_PARSED; 335 break; 336 } 337 } 338 339 throw expectedError(pos, ", or ]", "eof"); 340 } 341 342 private String parseString() { 343 // String buffer is only instantiated if string contains escape sequences. 344 int start = ++pos; 345 StringBuilder sb = null; 346 347 while (pos < length) { 348 final int c = next(); 349 if (c <= 0x1f) { 350 // Characters < 0x1f are not allowed in JSON strings. 351 throw syntaxError(pos, "String contains control character"); 352 353 } else if (c == '\\') { 354 if (sb == null) { 355 sb = new StringBuilder(pos - start + 16); 356 } 357 sb.append(source, start, pos - 1); 358 sb.append(parseEscapeSequence()); 359 start = pos; 360 361 } else if (c == '"') { 362 if (sb != null) { 363 sb.append(source, start, pos - 1); 364 return sb.toString(); 365 } 366 return source.substring(start, pos - 1); 367 } 368 } 369 370 throw error(Lexer.message("missing.close.quote"), pos, length); 371 } 372 373 private char parseEscapeSequence() { 374 final int c = next(); 375 switch (c) { 376 case '"': 377 return '"'; 378 case '\\': 379 return '\\'; 380 case '/': 381 return '/'; 382 case 'b': 383 return '\b'; 384 case 'f': 385 return '\f'; 386 case 'n': 387 return '\n'; 388 case 'r': 389 return '\r'; 390 case 't': 391 return '\t'; 392 case 'u': 393 return parseUnicodeEscape(); 394 default: 395 throw error(Lexer.message("invalid.escape.char"), pos - 1, length); 396 } 397 } 398 399 private char parseUnicodeEscape() { 400 return (char) (parseHexDigit() << 12 | parseHexDigit() << 8 | parseHexDigit() << 4 | parseHexDigit()); 401 } 402 403 private int parseHexDigit() { 404 final int c = next(); 405 if (c >= '0' && c <= '9') { 406 return c - '0'; 407 } else if (c >= 'A' && c <= 'F') { 408 return c + 10 - 'A'; 409 } else if (c >= 'a' && c <= 'f') { 410 return c + 10 - 'a'; 411 } 412 throw error(Lexer.message("invalid.hex"), pos - 1, length); 413 } 414 415 private boolean isDigit(final int c) { 416 return c >= '0' && c <= '9'; 417 } 418 419 private void skipDigits() { 420 while (pos < length) { 421 final int c = peek(); 422 if (!isDigit(c)) { 423 break; 424 } 425 pos++; 426 } 427 } 428 429 private Number parseNumber() { 430 final int start = pos; 431 int c = next(); 432 433 if (c == '-') { 434 c = next(); 435 } 436 if (!isDigit(c)) { 437 throw numberError(start); 438 } 439 // no more digits allowed after 0 440 if (c != '0') { 441 skipDigits(); 442 } 443 444 // fraction 445 if (peek() == '.') { 446 pos++; 447 if (!isDigit(next())) { 448 throw numberError(pos - 1); 449 } 450 skipDigits(); 451 } 452 453 // exponent 454 c = peek(); 455 if (c == 'e' || c == 'E') { 456 pos++; 457 c = next(); 458 if (c == '-' || c == '+') { 459 c = next(); 460 } 461 if (!isDigit(c)) { 462 throw numberError(pos - 1); 463 } 464 skipDigits(); 465 } 466 467 final double d = Double.parseDouble(source.substring(start, pos)); 468 if (JSType.isRepresentableAsInt(d)) { 469 return (int) d; 470 } else if (JSType.isRepresentableAsLong(d)) { 471 return (long) d; 472 } 473 return d; 474 } 475 476 private Object parseKeyword(final String keyword, final Object value) { 477 if (!source.regionMatches(pos, keyword, 0, keyword.length())) { 478 throw expectedError(pos, "json literal", "ident"); 479 } 480 pos += keyword.length(); 481 return value; 482 } 483 484 private int peek() { 485 if (pos >= length) { 486 return -1; 487 } 488 return source.charAt(pos); 489 } 490 491 private int next() { 492 final int next = peek(); 493 pos++; 494 return next; 495 } 496 497 private void skipWhiteSpace() { 498 while (pos < length) { 499 switch (peek()) { 500 case '\t': 501 case '\r': 502 case '\n': 503 case ' ': 504 pos++; 505 break; 506 default: 507 return; 508 } 509 } 510 } 511 512 private static String toString(final int c) { 513 return c == EOF ? "eof" : String.valueOf((char) c); 514 } 515 516 ParserException error(final String message, final int start, final int length) throws ParserException { 517 final long token = Token.toDesc(STRING, start, length); 518 final int pos = Token.descPosition(token); 519 final Source src = Source.sourceFor("<json>", source); 520 final int lineNum = src.getLine(pos); 521 final int columnNum = src.getColumn(pos); 522 final String formatted = ErrorManager.format(message, src, lineNum, columnNum, token); 523 return new ParserException(JSErrorType.SYNTAX_ERROR, formatted, src, lineNum, columnNum, token); 524 } 525 526 private ParserException error(final String message, final int start) { 527 return error(message, start, length); 528 } 529 530 private ParserException numberError(final int start) { 531 return error(Lexer.message("json.invalid.number"), start); 532 } 533 534 private ParserException expectedError(final int start, final String expected, final String found) { 535 return error(AbstractParser.message("expected", expected, found), start); 536 } 537 538 private ParserException syntaxError(final int start, final String reason) { 539 final String message = ECMAErrors.getMessage("syntax.error.invalid.json", reason); 540 return error(message, start); 541 } 542 } |