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); + } }