/** * Copyright (c) 2012-2016 André Bargull * Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms. * * <https://github.com/anba/es6draft> */ package com.github.anba.es6draft.parser; import static com.github.anba.es6draft.runtime.types.Null.NULL; import static com.github.anba.es6draft.runtime.types.builtins.ArrayObject.ArrayCreate; import static com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject.ObjectCreate; import com.github.anba.es6draft.parser.ParserException.ExceptionType; import com.github.anba.es6draft.runtime.ExecutionContext; import com.github.anba.es6draft.runtime.internal.Messages; import com.github.anba.es6draft.runtime.types.Intrinsics; import com.github.anba.es6draft.runtime.types.PropertyDescriptor; import com.github.anba.es6draft.runtime.types.builtins.ArrayObject; import com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject; /** * <h1>24 Structured Data</h1><br> * <h2>24.3 The JSON Object</h2><br> * <h3>24.3.1 The JSON Grammar</h3> * <ul> * <li>24.3.1.2 The JSON Syntactic Grammar * </ul> */ public final class JSONParser { private static final boolean DEBUG = false; private boolean parseCalled = false; private final JSONTokenStream ts; private final ExecutionContext cx; private final String sourceName; public JSONParser(ExecutionContext cx, String source) { this.cx = cx; this.sourceName = "<json>"; ts = new JSONTokenStream(this, new TokenStreamInput(source)); } private static int toLine(long sourcePosition) { return (int) sourcePosition; } private static int toColumn(long sourcePosition) { return (int) (sourcePosition >>> 32); } String getSourceName() { return sourceName; } /** * Reports a mismatched token error from tokenstream's current position. * * @param expected * the expected token * @param actual * the actual token in the token stream * @return the parser exception */ private ParserException reportTokenMismatch(Token expected, Token actual) { if (actual == Token.EOF) { throw reportEofError(Messages.Key.UnexpectedEndOfFile, expected.toString()); } if (actual == Token.ERROR) { throw reportSyntaxError(Messages.Key.UnexpectedCharacter, String.valueOf(ts.lastChar()), expected.toString()); } throw reportSyntaxError(Messages.Key.UnexpectedToken, actual.toString(), expected.toString()); } /** * Reports a parser eof-error from tokenstream's current position. * * @param messageKey * the error message key * @param args * the error message arguments * @return the parser exception */ private ParserEOFException reportEofError(Messages.Key messageKey, String... args) { long sourcePosition = ts.sourcePosition(); int line = toLine(sourcePosition), column = toColumn(sourcePosition); throw new ParserEOFException(getSourceName(), line, column, messageKey, args); } /** * Reports a syntax error from tokenstream's current position. * * @param messageKey * the error message key * @param args * the error message arguments * @return the parser exception */ private ParserException reportSyntaxError(Messages.Key messageKey, String... args) { long sourcePosition = ts.sourcePosition(); int line = toLine(sourcePosition), column = toColumn(sourcePosition); throw new ParserException(ExceptionType.SyntaxError, getSourceName(), line, column, messageKey, args); } /** * Returns the current token in the token-stream. * * @return the current token */ private Token token() { return ts.currentToken(); } /** * Consumes the current token in the token-stream and advances the stream to the next token. * * @param tok * the token to consume */ private void consume(Token tok) { if (tok != token()) reportTokenMismatch(tok, token()); Token next = ts.nextToken(); if (DEBUG) System.out.printf("consume(%s) -> %s\n", tok, next); } /** * Parses the input source string as a JSON text and returns its value. Throws a * {@link ParserException} if the source string is not a valid JSON text. * * @return the value of the parsed JSON text * @throws ParserException * if the input source is not a valid JSON text */ public Object parse() throws ParserException { if (parseCalled) throw new IllegalStateException(); parseCalled = true; return jsonText(); } private <DOCUMENT, OBJECT, ARRAY, VALUE> DOCUMENT parse( JSONBuilder<DOCUMENT, OBJECT, ARRAY, VALUE> builder) throws ParserException { if (parseCalled) throw new IllegalStateException(); parseCalled = true; return jsonText(builder); } /** * Parses the input source string as a JSON text and returns its value. Throws a * {@link ParserException} if the source string is not a valid JSON text. * * @param cx * the execution context * @param source * the source string * @return the value of the parsed JSON text * @throws ParserException * if the input source is not a valid JSON text */ public static Object parse(ExecutionContext cx, String source) throws ParserException { return new JSONParser(cx, source).parse(); } /** * Parses the input source string as a JSON text. Throws a {@link ParserException} if the source * string is not a valid JSON text. * * @param <DOCUMENT> * the document type * @param <OBJECT> * the object type * @param <ARRAY> * the array type * @param <VALUE> * the value type * @param source * the source string * @param builder * the builder object * @return the value of the parsed JSON text * @throws ParserException * if the input source is not a valid JSON text */ public static <DOCUMENT, OBJECT, ARRAY, VALUE> DOCUMENT parse(String source, JSONBuilder<DOCUMENT, OBJECT, ARRAY, VALUE> builder) throws ParserException { return new JSONParser(null, source).parse(builder); } /* ***************************************************************************************** */ /** * <pre> * JSONText : * JSONValue * </pre> * * @return the value of JSON text */ private Object jsonText() { Object value = jsonValue(); consume(Token.EOF); return value; } private <DOCUMENT, OBJECT, ARRAY, VALUE> DOCUMENT jsonText( JSONBuilder<DOCUMENT, OBJECT, ARRAY, VALUE> builder) { VALUE value = jsonValue(builder); consume(Token.EOF); return builder.createDocument(value); } /** * <pre> * JSONValue : * JSONNullLiteral * JSONBooleanLiteral * JSONObject * JSONArray * JSONString * JSONNumber * </pre> * * @return the parsed JSON value */ private Object jsonValue() { Token tok = token(); switch (tok) { case NULL: consume(tok); return NULL; case FALSE: consume(tok); return Boolean.FALSE; case TRUE: consume(tok); return Boolean.TRUE; case STRING: consume(tok); return ts.getString(); case NUMBER: consume(tok); return ts.getNumber(); case LC: return jsonObject(); case LB: return jsonArray(); default: reportSyntaxError(Messages.Key.InvalidToken, tok.toString()); return null; } } private <DOCUMENT, OBJECT, ARRAY, VALUE> VALUE jsonValue( JSONBuilder<DOCUMENT, OBJECT, ARRAY, VALUE> builder) { Token tok = token(); switch (tok) { case NULL: consume(tok); return builder.newNull(); case FALSE: consume(tok); return builder.newBoolean(false); case TRUE: consume(tok); return builder.newBoolean(true); case STRING: { String string = ts.getString(); String rawValue = ts.getRaw(); consume(tok); return builder.newString(string, rawValue); } case NUMBER: { double number = ts.getNumber(); String rawValue = ts.getRaw(); consume(tok); return builder.newNumber(number, rawValue); } case LC: return jsonObject(builder); case LB: return jsonArray(builder); default: throw reportSyntaxError(Messages.Key.InvalidToken, tok.toString()); } } /** * <pre> * JSONObject : * { } * { JSONMemberList } * JSONMemberList : * JSONMember * JSONMemberList , JSONMember * JSONMember : * JSONString : JSONValue * </pre> * * @return the script object represented by the JSON object */ private OrdinaryObject jsonObject() { OrdinaryObject object = ObjectCreate(cx, Intrinsics.ObjectPrototype); consume(Token.LC); if (token() != Token.RC) { for (;;) { consume(Token.STRING); String name = ts.getString(); consume(Token.COLON); Object value = jsonValue(); object.defineOwnProperty(cx, name, new PropertyDescriptor(value, true, true, true)); if (token() == Token.RC) { break; } consume(Token.COMMA); } } consume(Token.RC); return object; } private <DOCUMENT, OBJECT, ARRAY, VALUE> VALUE jsonObject( JSONBuilder<DOCUMENT, OBJECT, ARRAY, VALUE> builder) { consume(Token.LC); OBJECT object = builder.newObject(); if (token() != Token.RC) { for (long index = 0;; ++index) { String name = ts.getString(); String rawName = ts.getRaw(); consume(Token.STRING); builder.newProperty(object, name, rawName, index); consume(Token.COLON); VALUE value = jsonValue(builder); builder.finishProperty(object, name, rawName, index, value); if (token() == Token.RC) { break; } consume(Token.COMMA); } } consume(Token.RC); return builder.finishObject(object); } /** * <pre> * JSONArray : * [ ] * [ JSONElementList ] * JSONElementList : * JSONValue * JSONElementList , JSONValue * </pre> * * @return the script object represented by the JSON array */ private ArrayObject jsonArray() { ArrayObject array = ArrayCreate(cx, 0); consume(Token.LB); if (token() != Token.RB) { for (long index = 0;;) { Object value = jsonValue(); array.defineOwnProperty(cx, index++, new PropertyDescriptor(value, true, true, true)); if (token() == Token.RB) { break; } consume(Token.COMMA); } } consume(Token.RB); return array; } private <DOCUMENT, OBJECT, ARRAY, VALUE> VALUE jsonArray( JSONBuilder<DOCUMENT, OBJECT, ARRAY, VALUE> builder) { consume(Token.LB); ARRAY array = builder.newArray(); if (token() != Token.RB) { for (long index = 0;; ++index) { builder.newElement(array, index); VALUE value = jsonValue(builder); builder.finishElement(array, index, value); if (token() == Token.RB) { break; } consume(Token.COMMA); } } consume(Token.RB); return builder.finishArray(array); } }