src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/JSONParser.java
Print this page
*** 1,7 ****
/*
! * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
--- 1,7 ----
/*
! * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
*** 23,64 ****
* questions.
*/
package jdk.nashorn.internal.parser;
- import static jdk.nashorn.internal.parser.TokenType.COLON;
- import static jdk.nashorn.internal.parser.TokenType.COMMARIGHT;
- import static jdk.nashorn.internal.parser.TokenType.EOF;
- import static jdk.nashorn.internal.parser.TokenType.ESCSTRING;
- import static jdk.nashorn.internal.parser.TokenType.RBRACE;
- import static jdk.nashorn.internal.parser.TokenType.RBRACKET;
- import static jdk.nashorn.internal.parser.TokenType.STRING;
import java.util.ArrayList;
import java.util.List;
! import jdk.nashorn.internal.ir.Expression;
! import jdk.nashorn.internal.ir.LiteralNode;
! import jdk.nashorn.internal.ir.Node;
! import jdk.nashorn.internal.ir.ObjectNode;
! import jdk.nashorn.internal.ir.PropertyNode;
! import jdk.nashorn.internal.ir.UnaryNode;
import jdk.nashorn.internal.runtime.ErrorManager;
import jdk.nashorn.internal.runtime.Source;
/**
* Parses JSON text and returns the corresponding IR node. This is derived from the objectLiteral production of the main parser.
*
* See: 15.12.1.2 The JSON Syntactic Grammar
*/
! public class JSONParser extends AbstractParser {
/**
* Constructor
* @param source the source
- * @param errors the error manager
*/
! public JSONParser(final Source source, final ErrorManager errors) {
! super(source, errors, false, 0);
}
/**
* Implementation of the Quote(value) operation as defined in the ECMA script spec
* It wraps a String value in double quotes and escapes characters within in
--- 23,73 ----
* questions.
*/
package jdk.nashorn.internal.parser;
import java.util.ArrayList;
+ import java.util.LinkedHashMap;
import java.util.List;
! import java.util.Map;
! import jdk.nashorn.internal.runtime.ECMAErrors;
import jdk.nashorn.internal.runtime.ErrorManager;
+ import jdk.nashorn.internal.runtime.JSErrorType;
+ import jdk.nashorn.internal.runtime.JSType;
+ import jdk.nashorn.internal.runtime.ParserException;
import jdk.nashorn.internal.runtime.Source;
+ import static jdk.nashorn.internal.parser.TokenType.STRING;
+
/**
* Parses JSON text and returns the corresponding IR node. This is derived from the objectLiteral production of the main parser.
*
* See: 15.12.1.2 The JSON Syntactic Grammar
*/
! public class JSONParser {
!
! final private String source;
! final int length;
! int pos = 0;
!
! private static final int EOF = -1;
!
! private static final String TRUE = "true";
! private static final String FALSE = "false";
! private static final String NULL = "null";
!
! private static final int STATE_EMPTY = 0;
! private static final int STATE_ELEMENT_PARSED = 1;
! private static final int STATE_COMMA_PARSED = 2;
/**
* Constructor
* @param source the source
*/
! public JSONParser(final String source) {
! this.source = source;
! this.length = source.length();
}
/**
* Implementation of the Quote(value) operation as defined in the ECMA script spec
* It wraps a String value in double quotes and escapes characters within in
*** 112,442 ****
return product.toString();
}
/**
! * Public parsed method - start lexing a new token stream for
! * a JSON script
*
! * @return the JSON literal
*/
! public Node parse() {
! stream = new TokenStream();
! lexer = new Lexer(source, stream) {
! @Override
! protected boolean skipComments() {
! return false;
! }
! @Override
! protected boolean isStringDelimiter(final char ch) {
! return ch == '\"';
! }
! // ECMA 15.12.1.1 The JSON Lexical Grammar - JSONWhiteSpace
! @Override
! protected boolean isWhitespace(final char ch) {
! return Lexer.isJsonWhitespace(ch);
}
!
! @Override
! protected boolean isEOL(final char ch) {
! return Lexer.isJsonEOL(ch);
}
- // ECMA 15.12.1.1 The JSON Lexical Grammar - JSONNumber
- @Override
- protected void scanNumber() {
- // Record beginning of number.
- final int startPosition = position;
- // Assume value is a decimal.
- TokenType valueType = TokenType.DECIMAL;
! // floating point can't start with a "." with no leading digit before
! if (ch0 == '.') {
! error(Lexer.message("json.invalid.number"), STRING, position, limit);
! }
! // First digit of number.
! final int digit = convertDigit(ch0, 10);
! // skip first digit
! skip(1);
! if (digit != 0) {
! // Skip over remaining digits.
! while (convertDigit(ch0, 10) != -1) {
! skip(1);
}
}
!
! if (ch0 == '.' || ch0 == 'E' || ch0 == 'e') {
! // Must be a double.
! if (ch0 == '.') {
! // Skip period.
! skip(1);
!
! boolean mantissa = false;
! // Skip mantissa.
! while (convertDigit(ch0, 10) != -1) {
! mantissa = true;
! skip(1);
}
!
! if (! mantissa) {
! // no digit after "."
! error(Lexer.message("json.invalid.number"), STRING, position, limit);
}
}
! // Detect exponent.
! if (ch0 == 'E' || ch0 == 'e') {
! // Skip E.
! skip(1);
! // Detect and skip exponent sign.
! if (ch0 == '+' || ch0 == '-') {
! skip(1);
}
! boolean exponent = false;
! // Skip exponent.
! while (convertDigit(ch0, 10) != -1) {
! exponent = true;
! skip(1);
}
! if (! exponent) {
! // no digit after "E"
! error(Lexer.message("json.invalid.number"), STRING, position, limit);
}
}
-
- valueType = TokenType.FLOATING;
}
! // Add number token.
! add(valueType, startPosition);
}
! // ECMA 15.12.1.1 The JSON Lexical Grammar - JSONEscapeCharacter
! @Override
! protected boolean isEscapeCharacter(final char ch) {
! switch (ch) {
case '"':
! case '/':
case '\\':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
! // could be unicode escape
case 'u':
! return true;
default:
! return false;
}
}
- };
! k = -1;
!
! next();
!
! final Node resultNode = jsonLiteral();
! expect(EOF);
! return resultNode;
}
! @SuppressWarnings("fallthrough")
! private LiteralNode<?> getStringLiteral() {
! final LiteralNode<?> literal = getLiteral();
! final String str = (String)literal.getValue();
! for (int i = 0; i < str.length(); i++) {
! final char ch = str.charAt(i);
! switch (ch) {
! default:
! if (ch > 0x001f) {
break;
}
! case '"':
! case '\\':
! throw error(AbstractParser.message("unexpected.token", str));
! }
}
-
- return literal;
}
! /**
! * Parse a JSON literal from the token stream
! * @return the JSON literal as a Node
! */
! private Expression jsonLiteral() {
! final long literalToken = token;
!
! switch (type) {
! case STRING:
! return getStringLiteral();
! case ESCSTRING:
! case DECIMAL:
! case FLOATING:
! return getLiteral();
! case FALSE:
! next();
! return LiteralNode.newInstance(literalToken, finish, false);
! case TRUE:
! next();
! return LiteralNode.newInstance(literalToken, finish, true);
! case NULL:
! next();
! return LiteralNode.newInstance(literalToken, finish);
! case LBRACKET:
! return arrayLiteral();
! case LBRACE:
! return objectLiteral();
! /*
! * A.8.1 JSON Lexical Grammar
! *
! * JSONNumber :: See 15.12.1.1
! * -opt DecimalIntegerLiteral JSONFractionopt ExponentPartopt
! */
! case SUB:
! next();
! final long realToken = token;
! final Object value = getValue();
!
! if (value instanceof Number) {
! next();
! return new UnaryNode(literalToken, LiteralNode.newInstance(realToken, finish, (Number)value));
}
!
! throw error(AbstractParser.message("expected", "number", type.getNameOrType()));
! default:
! break;
}
!
! throw error(AbstractParser.message("expected", "json literal", type.getNameOrType()));
}
! /**
! * Parse an array literal from the token stream
! * @return the array literal as a Node
! */
! private LiteralNode<Expression[]> arrayLiteral() {
! // Unlike JavaScript array literals, elison is not permitted in JSON.
!
! // Capture LBRACKET token.
! final long arrayToken = token;
! // LBRACKET tested in caller.
! next();
!
! LiteralNode<Expression[]> result = null;
! // Prepare to accummulating elements.
! final List<Expression> elements = new ArrayList<>();
!
! loop:
! while (true) {
! switch (type) {
! case RBRACKET:
! next();
! result = LiteralNode.newInstance(arrayToken, finish, elements);
! break loop;
!
! case COMMARIGHT:
! next();
! // check for trailing comma - not allowed in JSON
! if (type == RBRACKET) {
! throw error(AbstractParser.message("trailing.comma.in.json", type.getNameOrType()));
}
- break;
! default:
! // Add expression element.
! elements.add(jsonLiteral());
! // Comma between array elements is mandatory in JSON.
! if (type != COMMARIGHT && type != RBRACKET) {
! throw error(AbstractParser.message("expected", ", or ]", type.getNameOrType()));
}
! break;
}
}
! return result;
}
! /**
! * Parse an object literal from the token stream
! * @return the object literal as a Node
! */
! private ObjectNode objectLiteral() {
! // Capture LBRACE token.
! final long objectToken = token;
! // LBRACE tested in caller.
! next();
!
! // Prepare to accumulate elements.
! final List<PropertyNode> elements = new ArrayList<>();
!
! // Create a block for the object literal.
! loop:
! while (true) {
! switch (type) {
! case RBRACE:
! next();
! break loop;
!
! case COMMARIGHT:
! next();
! // check for trailing comma - not allowed in JSON
! if (type == RBRACE) {
! throw error(AbstractParser.message("trailing.comma.in.json", type.getNameOrType()));
}
- break;
! default:
! // Get and add the next property.
! final PropertyNode property = propertyAssignment();
! elements.add(property);
! // Comma between property assigments is mandatory in JSON.
! if (type != RBRACE && type != COMMARIGHT) {
! throw error(AbstractParser.message("expected", ", or }", type.getNameOrType()));
}
break;
}
}
! // Construct new object literal.
! return new ObjectNode(objectToken, finish, elements);
}
! /**
! * Parse a property assignment from the token stream
! * @return the property assignment as a Node
! */
! private PropertyNode propertyAssignment() {
! // Capture firstToken.
! final long propertyToken = token;
! LiteralNode<?> name = null;
! if (type == STRING) {
! name = getStringLiteral();
! } else if (type == ESCSTRING) {
! name = getLiteral();
}
! if (name != null) {
! expect(COLON);
! final Expression value = jsonLiteral();
! return new PropertyNode(propertyToken, value.getFinish(), name, value, null, null);
}
! // Raise an error.
! throw error(AbstractParser.message("expected", "string", type.getNameOrType()));
}
}
--- 121,455 ----
return product.toString();
}
/**
! * Public parse method. Parse a string into a JSON object.
*
! * @return the parsed JSON Object
*/
! public Object parse() {
! final Object value = parseLiteral();
! skipWhiteSpace();
! if (pos < length) {
! throw expectedError(pos, "eof", toString(peek()));
! }
! return value;
! }
!
! private Object parseLiteral() {
! skipWhiteSpace();
!
! final int c = peek();
! if (c == EOF) {
! throw expectedError(pos, "json literal", "eof");
! }
! switch (c) {
! case '{':
! return parseObject();
! case '[':
! return parseArray();
! case '"':
! return parseString();
! case 'f':
! return parseKeyword(FALSE, Boolean.FALSE);
! case 't':
! return parseKeyword(TRUE, Boolean.TRUE);
! case 'n':
! return parseKeyword(NULL, null);
! default:
! if (isDigit(c) || c == '-') {
! return parseNumber();
! } else if (c == '.') {
! throw numberError(pos);
! } else {
! throw expectedError(pos, "json literal", toString(c));
! }
! }
! }
! private Object parseObject() {
! final Map<String, Object> map = new LinkedHashMap<>();
! int state = STATE_EMPTY;
! assert peek() == '{';
! pos++;
! while (pos < length) {
! skipWhiteSpace();
! final int c = peek();
! switch (c) {
! case '"':
! if (state == STATE_ELEMENT_PARSED) {
! throw expectedError(pos - 1, ", or }", toString(c));
}
! final String id = parseString();
! skipWhiteSpace();
! final int n = next();
! if (n != ':') {
! throw expectedError(pos - 1, ":", toString(n));
! }
! final Object value = parseLiteral();
! map.put(id, value);
! state = STATE_ELEMENT_PARSED;
! break;
! case ',':
! if (state != STATE_ELEMENT_PARSED) {
! throw error(AbstractParser.message("trailing.comma.in.json"), pos);
! }
! state = STATE_COMMA_PARSED;
! pos++;
! break;
! case '}':
! if (state == STATE_COMMA_PARSED) {
! throw error(AbstractParser.message("trailing.comma.in.json"), pos);
! }
! pos++;
! return map;
! default:
! throw expectedError(pos, ", or }", toString(c));
! }
! }
! throw expectedError(pos, ", or }", "eof");
}
! private Object parseArray() {
! final List<Object> list = new ArrayList<>();
! int state = STATE_EMPTY;
! assert peek() == '[';
! pos++;
! while (pos < length) {
! skipWhiteSpace();
! final int c = peek();
! switch (c) {
! case ',':
! if (state != STATE_ELEMENT_PARSED) {
! throw error(AbstractParser.message("trailing.comma.in.json"), pos);
}
+ state = STATE_COMMA_PARSED;
+ pos++;
+ break;
+ case ']':
+ if (state == STATE_COMMA_PARSED) {
+ throw error(AbstractParser.message("trailing.comma.in.json"), pos);
}
! pos++;
! return list;
! default:
! if (state == STATE_ELEMENT_PARSED) {
! throw expectedError(pos, ", or ]", toString(c));
}
! list.add(parseLiteral());
! state = STATE_ELEMENT_PARSED;
! break;
}
}
! throw expectedError(pos, ", or ]", "eof");
}
!
! private String parseString() {
! // String buffer is only instantiated if string contains escape sequences.
! int start = ++pos;
! StringBuilder sb = null;
!
! while (pos < length) {
! final int c = next();
! if (c <= 0x1f) {
! // Characters < 0x1f are not allowed in JSON strings.
! throw syntaxError(pos, "String contains control character");
!
! } else if (c == '\\') {
! if (sb == null) {
! sb = new StringBuilder(Math.max(32, (pos - start) * 2));
}
+ sb.append(source, start, pos - 1);
+ sb.append(parseEscapeSequence());
+ start = pos;
! } else if (c == '"') {
! if (sb != null) {
! sb.append(source, start, pos - 1);
! return sb.toString();
}
+ return source.substring(start, pos - 1);
}
}
! throw error(Lexer.message("missing.close.quote"), pos, length);
}
! private char parseEscapeSequence() {
! final int c = next();
! switch (c) {
case '"':
! return '"';
case '\\':
+ return '\\';
+ case '/':
+ return '/';
case 'b':
+ return '\b';
case 'f':
+ return '\f';
case 'n':
+ return '\n';
case 'r':
+ return '\r';
case 't':
! return '\t';
case 'u':
! return parseUnicodeEscape();
default:
! throw error(Lexer.message("invalid.escape.char"), pos - 1, length);
}
}
! private char parseUnicodeEscape() {
! return (char) (parseHexDigit() << 12 | parseHexDigit() << 8 | parseHexDigit() << 4 | parseHexDigit());
! }
! private int parseHexDigit() {
! final int c = next();
! if (c >= '0' && c <= '9') {
! return c - '0';
! } else if (c >= 'A' && c <= 'F') {
! return c + 10 - 'A';
! } else if (c >= 'a' && c <= 'f') {
! return c + 10 - 'a';
! }
! throw error(Lexer.message("invalid.hex"), pos - 1, length);
}
! private boolean isDigit(final int c) {
! return c >= '0' && c <= '9';
! }
! private void skipDigits() {
! while (pos < length) {
! final int c = peek();
! if (!isDigit(c)) {
break;
}
! pos++;
}
}
! private Number parseNumber() {
! final int start = pos;
! int c = next();
! if (c == '-') {
! c = next();
}
! if (!isDigit(c)) {
! throw numberError(start);
}
! // no more digits allowed after 0
! if (c != '0') {
! skipDigits();
}
! // fraction
! if (peek() == '.') {
! pos++;
! if (!isDigit(next())) {
! throw numberError(pos - 1);
! }
! skipDigits();
}
! // exponent
! c = peek();
! if (c == 'e' || c == 'E') {
! pos++;
! c = next();
! if (c == '-' || c == '+') {
! c = next();
}
! if (!isDigit(c)) {
! throw numberError(pos - 1);
}
+ skipDigits();
}
! final double d = Double.parseDouble(source.substring(start, pos));
! if (JSType.isRepresentableAsInt(d)) {
! return (int) d;
! } else if (JSType.isRepresentableAsLong(d)) {
! return (long) d;
! }
! return d;
}
! private Object parseKeyword(final String keyword, final Object value) {
! if (!source.regionMatches(pos, keyword, 0, keyword.length())) {
! throw expectedError(pos, "json literal", "ident");
! }
! pos += keyword.length();
! return value;
}
! private int peek() {
! if (pos >= length) {
! return -1;
! }
! return source.charAt(pos);
! }
! private int next() {
! final int next = peek();
! pos++;
! return next;
}
+
+ private void skipWhiteSpace() {
+ while (pos < length) {
+ switch (peek()) {
+ case '\t':
+ case '\r':
+ case '\n':
+ case ' ':
+ pos++;
break;
+ default:
+ return;
+ }
}
}
! private static String toString(final int c) {
! return c == EOF ? "eof" : String.valueOf((char) c);
}
! ParserException error(final String message, final int start, final int length) throws ParserException {
! final long token = Token.toDesc(STRING, start, length);
! final int pos = Token.descPosition(token);
! final Source src = Source.sourceFor("<json>", source);
! final int lineNum = src.getLine(pos);
! final int columnNum = src.getColumn(pos);
! final String formatted = ErrorManager.format(message, src, lineNum, columnNum, token);
! return new ParserException(JSErrorType.SYNTAX_ERROR, formatted, src, lineNum, columnNum, token);
! }
! private ParserException error(final String message, final int start) {
! return error(message, start, length);
}
! private ParserException numberError(final int start) {
! return error(Lexer.message("json.invalid.number"), start);
}
! private ParserException expectedError(final int start, final String expected, final String found) {
! return error(AbstractParser.message("expected", expected, found), start);
}
+ private ParserException syntaxError(final int start, final String reason) {
+ final String message = ECMAErrors.getMessage("syntax.error.invalid.json", reason);
+ return error(message, start);
+ }
}