Fri, 22 Jan 2016 16:18:19 +0100
8144020: Remove long as an internal numeric type
Reviewed-by: attila, mhaupt
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 */
26 package jdk.nashorn.internal.parser;
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.JD;
45 import jdk.nashorn.internal.scripts.JO;
47 import static jdk.nashorn.internal.parser.TokenType.STRING;
49 /**
50 * Parses JSON text and returns the corresponding IR node. This is derived from
51 * the objectLiteral production of the main parser.
52 *
53 * See: 15.12.1.2 The JSON Syntactic Grammar
54 */
55 public class JSONParser {
57 final private String source;
58 final private Global global;
59 final private boolean dualFields;
60 final int length;
61 int pos = 0;
63 private static final int EOF = -1;
65 private static final String TRUE = "true";
66 private static final String FALSE = "false";
67 private static final String NULL = "null";
69 private static final int STATE_EMPTY = 0;
70 private static final int STATE_ELEMENT_PARSED = 1;
71 private static final int STATE_COMMA_PARSED = 2;
73 /**
74 * Constructor.
75 *
76 * @param source the source
77 * @param global the global object
78 * @param dualFields whether the parser should regard dual field representation
79 */
80 public JSONParser(final String source, final Global global, final boolean dualFields) {
81 this.source = source;
82 this.global = global;
83 this.length = source.length();
84 this.dualFields = dualFields;
85 }
87 /**
88 * Implementation of the Quote(value) operation as defined in the ECMAscript
89 * spec. It wraps a String value in double quotes and escapes characters
90 * within.
91 *
92 * @param value string to quote
93 *
94 * @return quoted and escaped string
95 */
96 public static String quote(final String value) {
98 final StringBuilder product = new StringBuilder();
100 product.append("\"");
102 for (final char ch : value.toCharArray()) {
103 // TODO: should use a table?
104 switch (ch) {
105 case '\\':
106 product.append("\\\\");
107 break;
108 case '"':
109 product.append("\\\"");
110 break;
111 case '\b':
112 product.append("\\b");
113 break;
114 case '\f':
115 product.append("\\f");
116 break;
117 case '\n':
118 product.append("\\n");
119 break;
120 case '\r':
121 product.append("\\r");
122 break;
123 case '\t':
124 product.append("\\t");
125 break;
126 default:
127 if (ch < ' ') {
128 product.append(Lexer.unicodeEscape(ch));
129 break;
130 }
132 product.append(ch);
133 break;
134 }
135 }
137 product.append("\"");
139 return product.toString();
140 }
142 /**
143 * Public parse method. Parse a string into a JSON object.
144 *
145 * @return the parsed JSON Object
146 */
147 public Object parse() {
148 final Object value = parseLiteral();
149 skipWhiteSpace();
150 if (pos < length) {
151 throw expectedError(pos, "eof", toString(peek()));
152 }
153 return value;
154 }
156 private Object parseLiteral() {
157 skipWhiteSpace();
159 final int c = peek();
160 if (c == EOF) {
161 throw expectedError(pos, "json literal", "eof");
162 }
163 switch (c) {
164 case '{':
165 return parseObject();
166 case '[':
167 return parseArray();
168 case '"':
169 return parseString();
170 case 'f':
171 return parseKeyword(FALSE, Boolean.FALSE);
172 case 't':
173 return parseKeyword(TRUE, Boolean.TRUE);
174 case 'n':
175 return parseKeyword(NULL, null);
176 default:
177 if (isDigit(c) || c == '-') {
178 return parseNumber();
179 } else if (c == '.') {
180 throw numberError(pos);
181 } else {
182 throw expectedError(pos, "json literal", toString(c));
183 }
184 }
185 }
187 private Object parseObject() {
188 PropertyMap propertyMap = dualFields ? JD.getInitialMap() : JO.getInitialMap();
189 ArrayData arrayData = ArrayData.EMPTY_ARRAY;
190 final ArrayList<Object> values = new ArrayList<>();
191 int state = STATE_EMPTY;
193 assert peek() == '{';
194 pos++;
196 while (pos < length) {
197 skipWhiteSpace();
198 final int c = peek();
200 switch (c) {
201 case '"':
202 if (state == STATE_ELEMENT_PARSED) {
203 throw expectedError(pos - 1, ", or }", toString(c));
204 }
205 final String id = parseString();
206 expectColon();
207 final Object value = parseLiteral();
208 final int index = ArrayIndex.getArrayIndex(id);
209 if (ArrayIndex.isValidArrayIndex(index)) {
210 arrayData = addArrayElement(arrayData, index, value);
211 } else {
212 propertyMap = addObjectProperty(propertyMap, values, id, value);
213 }
214 state = STATE_ELEMENT_PARSED;
215 break;
216 case ',':
217 if (state != STATE_ELEMENT_PARSED) {
218 throw error(AbstractParser.message("trailing.comma.in.json"), pos);
219 }
220 state = STATE_COMMA_PARSED;
221 pos++;
222 break;
223 case '}':
224 if (state == STATE_COMMA_PARSED) {
225 throw error(AbstractParser.message("trailing.comma.in.json"), pos);
226 }
227 pos++;
228 return createObject(propertyMap, values, arrayData);
229 default:
230 throw expectedError(pos, ", or }", toString(c));
231 }
232 }
233 throw expectedError(pos, ", or }", "eof");
234 }
236 private static ArrayData addArrayElement(final ArrayData arrayData, final int index, final Object value) {
237 final long oldLength = arrayData.length();
238 final long longIndex = ArrayIndex.toLongIndex(index);
239 ArrayData newArrayData = arrayData;
240 if (longIndex >= oldLength) {
241 newArrayData = newArrayData.ensure(longIndex);
242 if (longIndex > oldLength) {
243 newArrayData = newArrayData.delete(oldLength, longIndex - 1);
244 }
245 }
246 return newArrayData.set(index, value, false);
247 }
249 private PropertyMap addObjectProperty(final PropertyMap propertyMap, final List<Object> values,
250 final String id, final Object value) {
251 final Property oldProperty = propertyMap.findProperty(id);
252 final PropertyMap newMap;
253 final Class<?> type;
254 final int flags;
255 if (dualFields) {
256 type = getType(value);
257 flags = Property.DUAL_FIELDS;
258 } else {
259 type = Object.class;
260 flags = 0;
261 }
263 if (oldProperty != null) {
264 values.set(oldProperty.getSlot(), value);
265 newMap = propertyMap.replaceProperty(oldProperty, new SpillProperty(id, flags, oldProperty.getSlot(), type));;
266 } else {
267 values.add(value);
268 newMap = propertyMap.addProperty(new SpillProperty(id, flags, propertyMap.size(), type));
269 }
271 return newMap;
272 }
274 private Object createObject(final PropertyMap propertyMap, final List<Object> values, final ArrayData arrayData) {
275 final long[] primitiveSpill = dualFields ? new long[values.size()] : null;
276 final Object[] objectSpill = new Object[values.size()];
278 for (final Property property : propertyMap.getProperties()) {
279 if (!dualFields || property.getType() == Object.class) {
280 objectSpill[property.getSlot()] = values.get(property.getSlot());
281 } else {
282 primitiveSpill[property.getSlot()] = ObjectClassGenerator.pack((Number) values.get(property.getSlot()));
283 }
284 }
286 final ScriptObject object = dualFields ?
287 new JD(propertyMap, primitiveSpill, objectSpill) : new JO(propertyMap, null, objectSpill);
288 object.setInitialProto(global.getObjectPrototype());
289 object.setArray(arrayData);
290 return object;
291 }
293 private static Class<?> getType(final Object value) {
294 if (value instanceof Integer) {
295 return int.class;
296 } else if (value instanceof Double) {
297 return double.class;
298 } else {
299 return Object.class;
300 }
301 }
303 private void expectColon() {
304 skipWhiteSpace();
305 final int n = next();
306 if (n != ':') {
307 throw expectedError(pos - 1, ":", toString(n));
308 }
309 }
311 private Object parseArray() {
312 ArrayData arrayData = ArrayData.EMPTY_ARRAY;
313 int state = STATE_EMPTY;
315 assert peek() == '[';
316 pos++;
318 while (pos < length) {
319 skipWhiteSpace();
320 final int c = peek();
322 switch (c) {
323 case ',':
324 if (state != STATE_ELEMENT_PARSED) {
325 throw error(AbstractParser.message("trailing.comma.in.json"), pos);
326 }
327 state = STATE_COMMA_PARSED;
328 pos++;
329 break;
330 case ']':
331 if (state == STATE_COMMA_PARSED) {
332 throw error(AbstractParser.message("trailing.comma.in.json"), pos);
333 }
334 pos++;
335 return global.wrapAsObject(arrayData);
336 default:
337 if (state == STATE_ELEMENT_PARSED) {
338 throw expectedError(pos, ", or ]", toString(c));
339 }
340 final long index = arrayData.length();
341 arrayData = arrayData.ensure(index).set((int) index, parseLiteral(), true);
342 state = STATE_ELEMENT_PARSED;
343 break;
344 }
345 }
347 throw expectedError(pos, ", or ]", "eof");
348 }
350 private String parseString() {
351 // String buffer is only instantiated if string contains escape sequences.
352 int start = ++pos;
353 StringBuilder sb = null;
355 while (pos < length) {
356 final int c = next();
357 if (c <= 0x1f) {
358 // Characters < 0x1f are not allowed in JSON strings.
359 throw syntaxError(pos, "String contains control character");
361 } else if (c == '\\') {
362 if (sb == null) {
363 sb = new StringBuilder(pos - start + 16);
364 }
365 sb.append(source, start, pos - 1);
366 sb.append(parseEscapeSequence());
367 start = pos;
369 } else if (c == '"') {
370 if (sb != null) {
371 sb.append(source, start, pos - 1);
372 return sb.toString();
373 }
374 return source.substring(start, pos - 1);
375 }
376 }
378 throw error(Lexer.message("missing.close.quote"), pos, length);
379 }
381 private char parseEscapeSequence() {
382 final int c = next();
383 switch (c) {
384 case '"':
385 return '"';
386 case '\\':
387 return '\\';
388 case '/':
389 return '/';
390 case 'b':
391 return '\b';
392 case 'f':
393 return '\f';
394 case 'n':
395 return '\n';
396 case 'r':
397 return '\r';
398 case 't':
399 return '\t';
400 case 'u':
401 return parseUnicodeEscape();
402 default:
403 throw error(Lexer.message("invalid.escape.char"), pos - 1, length);
404 }
405 }
407 private char parseUnicodeEscape() {
408 return (char) (parseHexDigit() << 12 | parseHexDigit() << 8 | parseHexDigit() << 4 | parseHexDigit());
409 }
411 private int parseHexDigit() {
412 final int c = next();
413 if (c >= '0' && c <= '9') {
414 return c - '0';
415 } else if (c >= 'A' && c <= 'F') {
416 return c + 10 - 'A';
417 } else if (c >= 'a' && c <= 'f') {
418 return c + 10 - 'a';
419 }
420 throw error(Lexer.message("invalid.hex"), pos - 1, length);
421 }
423 private boolean isDigit(final int c) {
424 return c >= '0' && c <= '9';
425 }
427 private void skipDigits() {
428 while (pos < length) {
429 final int c = peek();
430 if (!isDigit(c)) {
431 break;
432 }
433 pos++;
434 }
435 }
437 private Number parseNumber() {
438 final int start = pos;
439 int c = next();
441 if (c == '-') {
442 c = next();
443 }
444 if (!isDigit(c)) {
445 throw numberError(start);
446 }
447 // no more digits allowed after 0
448 if (c != '0') {
449 skipDigits();
450 }
452 // fraction
453 if (peek() == '.') {
454 pos++;
455 if (!isDigit(next())) {
456 throw numberError(pos - 1);
457 }
458 skipDigits();
459 }
461 // exponent
462 c = peek();
463 if (c == 'e' || c == 'E') {
464 pos++;
465 c = next();
466 if (c == '-' || c == '+') {
467 c = next();
468 }
469 if (!isDigit(c)) {
470 throw numberError(pos - 1);
471 }
472 skipDigits();
473 }
475 final double d = Double.parseDouble(source.substring(start, pos));
476 if (JSType.isRepresentableAsInt(d)) {
477 return (int) d;
478 }
479 return d;
480 }
482 private Object parseKeyword(final String keyword, final Object value) {
483 if (!source.regionMatches(pos, keyword, 0, keyword.length())) {
484 throw expectedError(pos, "json literal", "ident");
485 }
486 pos += keyword.length();
487 return value;
488 }
490 private int peek() {
491 if (pos >= length) {
492 return -1;
493 }
494 return source.charAt(pos);
495 }
497 private int next() {
498 final int next = peek();
499 pos++;
500 return next;
501 }
503 private void skipWhiteSpace() {
504 while (pos < length) {
505 switch (peek()) {
506 case '\t':
507 case '\r':
508 case '\n':
509 case ' ':
510 pos++;
511 break;
512 default:
513 return;
514 }
515 }
516 }
518 private static String toString(final int c) {
519 return c == EOF ? "eof" : String.valueOf((char) c);
520 }
522 ParserException error(final String message, final int start, final int length) throws ParserException {
523 final long token = Token.toDesc(STRING, start, length);
524 final int pos = Token.descPosition(token);
525 final Source src = Source.sourceFor("<json>", source);
526 final int lineNum = src.getLine(pos);
527 final int columnNum = src.getColumn(pos);
528 final String formatted = ErrorManager.format(message, src, lineNum, columnNum, token);
529 return new ParserException(JSErrorType.SYNTAX_ERROR, formatted, src, lineNum, columnNum, token);
530 }
532 private ParserException error(final String message, final int start) {
533 return error(message, start, length);
534 }
536 private ParserException numberError(final int start) {
537 return error(Lexer.message("json.invalid.number"), start);
538 }
540 private ParserException expectedError(final int start, final String expected, final String found) {
541 return error(AbstractParser.message("expected", expected, found), start);
542 }
544 private ParserException syntaxError(final int start, final String reason) {
545 final String message = ECMAErrors.getMessage("syntax.error.invalid.json", reason);
546 return error(message, start);
547 }
548 }