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 '\\': 101 product.append("\\\\"); 102 break; 103 case '"': 104 product.append("\\\""); 105 break; 106 case '\b': 107 product.append("\\b"); 108 break; 109 case '\f': 110 product.append("\\f"); 111 break; 112 case '\n': 113 product.append("\\n"); 114 break; 115 case '\r': 116 product.append("\\r"); 117 break; 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 newArrayData = newArrayData.ensure(longIndex); 237 if (longIndex > oldLength) { 238 newArrayData = newArrayData.delete(oldLength, longIndex - 1); 239 } 240 } 241 return newArrayData.set(index, value, false); 242 } 243 244 private static PropertyMap addObjectProperty(final PropertyMap propertyMap, final List<Object> values, 245 final String id, final Object value) { 246 final Property oldProperty = propertyMap.findProperty(id); 247 final Property newProperty; 248 final PropertyMap newMap; 249 final Class<?> type = ObjectClassGenerator.OBJECT_FIELDS_ONLY ? Object.class : getType(value); 250 251 if (oldProperty != null) { 252 values.set(oldProperty.getSlot(), value); 253 newMap = propertyMap.replaceProperty(oldProperty, new SpillProperty(id, 0, oldProperty.getSlot(), type));; 254 } else { 255 values.add(value); 256 newMap = propertyMap.addProperty(new SpillProperty(id, 0, propertyMap.size(), type)); 257 } 258 259 return newMap; 260 } 261 262 private Object createObject(final PropertyMap propertyMap, final List<Object> values, final ArrayData arrayData) { 263 final long[] primitiveSpill = new long[values.size()]; 264 final Object[] objectSpill = new Object[values.size()]; 265 266 for (final Property property : propertyMap.getProperties()) { 267 if (property.getType() == Object.class) { 268 objectSpill[property.getSlot()] = values.get(property.getSlot()); 269 } else { 270 primitiveSpill[property.getSlot()] = ObjectClassGenerator.pack((Number) values.get(property.getSlot())); 271 } 272 } 273 274 final ScriptObject object = new JO(propertyMap, primitiveSpill, objectSpill); 275 object.setInitialProto(global.getObjectPrototype()); 276 object.setArray(arrayData); 277 return object; 278 } 279 280 private static Class<?> getType(final Object value) { 281 if (value instanceof Integer) { 282 return int.class; 283 } else if (value instanceof Long) { 284 return long.class; 285 } else if (value instanceof Double) { 286 return double.class; 287 } else { 288 return Object.class; 289 } 290 } 291 292 private void expectColon() { 293 skipWhiteSpace(); 294 final int n = next(); 295 if (n != ':') { 296 throw expectedError(pos - 1, ":", toString(n)); 297 } 298 } 299 300 private Object parseArray() { 301 ArrayData arrayData = ArrayData.EMPTY_ARRAY; 302 int state = STATE_EMPTY; 303 304 assert peek() == '['; 305 pos++; 306 307 while (pos < length) { 308 skipWhiteSpace(); 309 final int c = peek(); 310 311 switch (c) { 312 case ',': 313 if (state != STATE_ELEMENT_PARSED) { 314 throw error(AbstractParser.message("trailing.comma.in.json"), pos); 315 } 316 state = STATE_COMMA_PARSED; 317 pos++; 318 break; 319 case ']': 320 if (state == STATE_COMMA_PARSED) { 321 throw error(AbstractParser.message("trailing.comma.in.json"), pos); 322 } 323 pos++; 324 return global.wrapAsObject(arrayData); 325 default: 326 if (state == STATE_ELEMENT_PARSED) { 327 throw expectedError(pos, ", or ]", toString(c)); 328 } 329 final long index = arrayData.length(); 330 arrayData = arrayData.ensure(index).set((int) index, parseLiteral(), true); 331 state = STATE_ELEMENT_PARSED; 332 break; 333 } 334 } 335 336 throw expectedError(pos, ", or ]", "eof"); 337 } 338 339 private String parseString() { 340 // String buffer is only instantiated if string contains escape sequences. 341 int start = ++pos; 342 StringBuilder sb = null; 343 344 while (pos < length) { 345 final int c = next(); 346 if (c <= 0x1f) { 347 // Characters < 0x1f are not allowed in JSON strings. 348 throw syntaxError(pos, "String contains control character"); 349 350 } else if (c == '\\') { 351 if (sb == null) { 352 sb = new StringBuilder(pos - start + 16); 353 } 354 sb.append(source, start, pos - 1); 355 sb.append(parseEscapeSequence()); 356 start = pos; 357 358 } else if (c == '"') { 359 if (sb != null) { 360 sb.append(source, start, pos - 1); 361 return sb.toString(); 362 } 363 return source.substring(start, pos - 1); 364 } 365 } 366 367 throw error(Lexer.message("missing.close.quote"), pos, length); 368 } 369 370 private char parseEscapeSequence() { 371 final int c = next(); 372 switch (c) { 373 case '"': 374 return '"'; 375 case '\\': 376 return '\\'; 377 case '/': 378 return '/'; 379 case 'b': 380 return '\b'; 381 case 'f': 382 return '\f'; 383 case 'n': 384 return '\n'; 385 case 'r': 386 return '\r'; 387 case 't': 388 return '\t'; 389 case 'u': 390 return parseUnicodeEscape(); 391 default: 392 throw error(Lexer.message("invalid.escape.char"), pos - 1, length); 393 } 394 } 395 396 private char parseUnicodeEscape() { 397 return (char) (parseHexDigit() << 12 | parseHexDigit() << 8 | parseHexDigit() << 4 | parseHexDigit()); 398 } 399 400 private int parseHexDigit() { 401 final int c = next(); 402 if (c >= '0' && c <= '9') { 403 return c - '0'; 404 } else if (c >= 'A' && c <= 'F') { 405 return c + 10 - 'A'; 406 } else if (c >= 'a' && c <= 'f') { 407 return c + 10 - 'a'; 408 } 409 throw error(Lexer.message("invalid.hex"), pos - 1, length); 410 } 411 412 private boolean isDigit(final int c) { 413 return c >= '0' && c <= '9'; 414 } 415 416 private void skipDigits() { 417 while (pos < length) { 418 final int c = peek(); 419 if (!isDigit(c)) { 420 break; 421 } 422 pos++; 423 } 424 } 425 426 private Number parseNumber() { 427 final int start = pos; 428 int c = next(); 429 430 if (c == '-') { 431 c = next(); 432 } 433 if (!isDigit(c)) { 434 throw numberError(start); 435 } 436 // no more digits allowed after 0 437 if (c != '0') { 438 skipDigits(); 439 } 440 441 // fraction 442 if (peek() == '.') { 443 pos++; 444 if (!isDigit(next())) { 445 throw numberError(pos - 1); 446 } 447 skipDigits(); 448 } 449 450 // exponent 451 c = peek(); 452 if (c == 'e' || c == 'E') { 453 pos++; 454 c = next(); 455 if (c == '-' || c == '+') { 456 c = next(); 457 } 458 if (!isDigit(c)) { 459 throw numberError(pos - 1); 460 } 461 skipDigits(); 462 } 463 464 final double d = Double.parseDouble(source.substring(start, pos)); 465 if (JSType.isRepresentableAsInt(d)) { 466 return (int) d; 467 } else if (JSType.isRepresentableAsLong(d)) { 468 return (long) d; 469 } 470 return d; 471 } 472 473 private Object parseKeyword(final String keyword, final Object value) { 474 if (!source.regionMatches(pos, keyword, 0, keyword.length())) { 475 throw expectedError(pos, "json literal", "ident"); 476 } 477 pos += keyword.length(); 478 return value; 479 } 480 481 private int peek() { 482 if (pos >= length) { 483 return -1; 484 } 485 return source.charAt(pos); 486 } 487 488 private int next() { 489 final int next = peek(); 490 pos++; 491 return next; 492 } 493 494 private void skipWhiteSpace() { 495 while (pos < length) { 496 switch (peek()) { 497 case '\t': 498 case '\r': 499 case '\n': 500 case ' ': 501 pos++; 502 break; 503 default: 504 return; 505 } 506 } 507 } 508 509 private static String toString(final int c) { 510 return c == EOF ? "eof" : String.valueOf((char) c); 511 } 512 513 ParserException error(final String message, final int start, final int length) throws ParserException { 514 final long token = Token.toDesc(STRING, start, length); 515 final int pos = Token.descPosition(token); 516 final Source src = Source.sourceFor("<json>", source); 517 final int lineNum = src.getLine(pos); 518 final int columnNum = src.getColumn(pos); 519 final String formatted = ErrorManager.format(message, src, lineNum, columnNum, token); 520 return new ParserException(JSErrorType.SYNTAX_ERROR, formatted, src, lineNum, columnNum, token); 521 } 522 523 private ParserException error(final String message, final int start) { 524 return error(message, start, length); 525 } 526 527 private ParserException numberError(final int start) { 528 return error(Lexer.message("json.invalid.number"), start); 529 } 530 531 private ParserException expectedError(final int start, final String expected, final String found) { 532 return error(AbstractParser.message("expected", expected, found), start); 533 } 534 535 private ParserException syntaxError(final int start, final String reason) { 536 final String message = ECMAErrors.getMessage("syntax.error.invalid.json", reason); 537 return error(message, start); 538 } 539 } --- EOF ---