1 /*
   2  * Copyright (c) 2010, 2013, 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 static jdk.nashorn.internal.parser.TokenType.COLON;
  29 import static jdk.nashorn.internal.parser.TokenType.COMMARIGHT;
  30 import static jdk.nashorn.internal.parser.TokenType.EOF;
  31 import static jdk.nashorn.internal.parser.TokenType.ESCSTRING;
  32 import static jdk.nashorn.internal.parser.TokenType.RBRACE;
  33 import static jdk.nashorn.internal.parser.TokenType.RBRACKET;
  34 import static jdk.nashorn.internal.parser.TokenType.STRING;
  35 
  36 import java.util.ArrayList;
  37 import java.util.List;
  38 import jdk.nashorn.internal.ir.LiteralNode;
  39 import jdk.nashorn.internal.ir.Node;
  40 import jdk.nashorn.internal.ir.ObjectNode;
  41 import jdk.nashorn.internal.ir.PropertyNode;
  42 import jdk.nashorn.internal.ir.UnaryNode;
  43 import jdk.nashorn.internal.runtime.ErrorManager;
  44 import jdk.nashorn.internal.runtime.Source;
  45 
  46 /**
  47  * Parses JSON text and returns the corresponding IR node. This is derived from the objectLiteral production of the main parser.
  48  *
  49  * See: 15.12.1.2 The JSON Syntactic Grammar
  50  */
  51 public class JSONParser extends AbstractParser {
  52 
  53     /**
  54      * Constructor
  55      * @param source  the source
  56      * @param errors  the error manager
  57      * @param strict  are we in strict mode
  58      */
  59     public JSONParser(final Source source, final ErrorManager errors, final boolean strict) {
  60         super(source, errors, strict);
  61     }
  62 
  63     /**
  64      * Implementation of the Quote(value) operation as defined in the ECMA script spec
  65      * It wraps a String value in double quotes and escapes characters within in
  66      *
  67      * @param value string to quote
  68      *
  69      * @return quoted and escaped string
  70      */
  71     public static String quote(final String value) {
  72 
  73         final StringBuilder product = new StringBuilder();
  74 
  75         product.append("\"");
  76 
  77         for (final char ch : value.toCharArray()) {
  78             // TODO: should use a table?
  79             switch (ch) {
  80             case '\\':
  81                 product.append("\\\\");
  82                 break;
  83             case '"':
  84                 product.append("\\\"");
  85                 break;
  86             case '\b':
  87                 product.append("\\b");
  88                 break;
  89             case '\f':
  90                 product.append("\\f");
  91                 break;
  92             case '\n':
  93                 product.append("\\n");
  94                 break;
  95             case '\r':
  96                 product.append("\\r");
  97                 break;
  98             case '\t':
  99                 product.append("\\t");
 100                 break;
 101             default:
 102                 if (ch < ' ') {
 103                     product.append(Lexer.unicodeEscape(ch));
 104                     break;
 105                 }
 106 
 107                 product.append(ch);
 108                 break;
 109             }
 110         }
 111 
 112         product.append("\"");
 113 
 114         return product.toString();
 115     }
 116 
 117     /**
 118      * Public parsed method - start lexing a new token stream for
 119      * a JSON script
 120      *
 121      * @return the JSON literal
 122      */
 123     public Node parse() {
 124         stream = new TokenStream();
 125 
 126         lexer = new Lexer(source, stream) {
 127 
 128             @Override
 129             protected boolean skipComments() {
 130                 return false;
 131             }
 132 
 133             @Override
 134             protected boolean isStringDelimiter(final char ch) {
 135                 return ch == '\"';
 136             }
 137 
 138             @Override
 139             protected boolean isWhitespace(final char ch) {
 140                 return Lexer.isJsonWhitespace(ch);
 141             }
 142 
 143             @Override
 144             protected boolean isEOL(final char ch) {
 145                 return Lexer.isJsonEOL(ch);
 146             }
 147         };
 148 
 149         k = -1;
 150 
 151         next();
 152 
 153         final Node resultNode = jsonLiteral();
 154         expect(EOF);
 155 
 156         return resultNode;
 157     }
 158 
 159     @SuppressWarnings("fallthrough")
 160     private LiteralNode<?> getStringLiteral() {
 161         final LiteralNode<?> literal = getLiteral();
 162         final String         str     = (String)literal.getValue();
 163 
 164         for (int i = 0; i < str.length(); i++) {
 165             final char ch = str.charAt(i);
 166             switch (ch) {
 167             default:
 168                 if (ch > 0x001f) {
 169                     break;
 170                 }
 171             case '"':
 172             case '\\':
 173                 throw error(AbstractParser.message("unexpected.token", str));
 174             }
 175         }
 176 
 177         return literal;
 178     }
 179 
 180     /**
 181      * Parse a JSON literal from the token stream
 182      * @return the JSON literal as a Node
 183      */
 184     private Node jsonLiteral() {
 185         final long literalToken = token;
 186 
 187         switch (type) {
 188         case STRING:
 189             return getStringLiteral();
 190         case ESCSTRING:
 191         case DECIMAL:
 192         case FLOATING:
 193             return getLiteral();
 194         case FALSE:
 195             next();
 196             return LiteralNode.newInstance(literalToken, finish, false);
 197         case TRUE:
 198             next();
 199             return LiteralNode.newInstance(literalToken, finish, true);
 200         case NULL:
 201             next();
 202             return LiteralNode.newInstance(literalToken, finish);
 203         case LBRACKET:
 204             return arrayLiteral();
 205         case LBRACE:
 206             return objectLiteral();
 207         /*
 208          * A.8.1 JSON Lexical Grammar
 209          *
 210          * JSONNumber :: See 15.12.1.1
 211          *    -opt DecimalIntegerLiteral JSONFractionopt ExponentPartopt
 212          */
 213         case SUB:
 214             next();
 215 
 216             final long realToken = token;
 217             final Object value = getValue();
 218 
 219             if (value instanceof Number) {
 220                 next();
 221                 return new UnaryNode(literalToken, LiteralNode.newInstance(realToken, finish, (Number)value));
 222             }
 223 
 224             throw error(AbstractParser.message("expected", "number", type.getNameOrType()));
 225         default:
 226             break;
 227         }
 228 
 229         throw error(AbstractParser.message("expected", "json literal", type.getNameOrType()));
 230     }
 231 
 232     /**
 233      * Parse an array literal from the token stream
 234      * @return the array literal as a Node
 235      */
 236     private Node arrayLiteral() {
 237         // Unlike JavaScript array literals, elison is not permitted in JSON.
 238 
 239         // Capture LBRACKET token.
 240         final long arrayToken = token;
 241         // LBRACKET tested in caller.
 242         next();
 243 
 244         Node result = null;
 245         // Prepare to accummulating elements.
 246         final List<Node> elements = new ArrayList<>();
 247 
 248 loop:
 249         while (true) {
 250             switch (type) {
 251             case RBRACKET:
 252                 next();
 253                 result = LiteralNode.newInstance(arrayToken, finish, elements);
 254                 break loop;
 255 
 256             case COMMARIGHT:
 257                 next();
 258                 break;
 259 
 260             default:
 261                 // Add expression element.
 262                 elements.add(jsonLiteral());
 263                 // Comma between array elements is mandatory in JSON.
 264                 if (type != COMMARIGHT && type != RBRACKET) {
 265                    throw error(AbstractParser.message("expected", ", or ]", type.getNameOrType()));
 266                 }
 267                 break;
 268             }
 269         }
 270 
 271         return result;
 272     }
 273 
 274     /**
 275      * Parse an object literal from the token stream
 276      * @return the object literal as a Node
 277      */
 278     private Node objectLiteral() {
 279         // Capture LBRACE token.
 280         final long objectToken = token;
 281         // LBRACE tested in caller.
 282         next();
 283 
 284         // Prepare to accumulate elements.
 285         final List<PropertyNode> elements = new ArrayList<>();
 286 
 287         // Create a block for the object literal.
 288 loop:
 289         while (true) {
 290             switch (type) {
 291             case RBRACE:
 292                 next();
 293                 break loop;
 294 
 295             case COMMARIGHT:
 296                 next();
 297                 break;
 298 
 299             default:
 300                 // Get and add the next property.
 301                 final PropertyNode property = propertyAssignment();
 302                 elements.add(property);
 303 
 304                 // Comma between property assigments is mandatory in JSON.
 305                 if (type != RBRACE && type != COMMARIGHT) {
 306                     throw error(AbstractParser.message("expected", ", or }", type.getNameOrType()));
 307                 }
 308                 break;
 309             }
 310         }
 311 
 312         // Construct new object literal.
 313         return new ObjectNode(objectToken, finish, elements);
 314     }
 315 
 316     /**
 317      * Parse a property assignment from the token stream
 318      * @return the property assignment as a Node
 319      */
 320     private PropertyNode propertyAssignment() {
 321         // Capture firstToken.
 322         final long propertyToken = token;
 323         LiteralNode<?> name = null;
 324 
 325         if (type == STRING) {
 326             name = getStringLiteral();
 327         } else if (type == ESCSTRING) {
 328             name = getLiteral();
 329         }
 330 
 331         if (name != null) {
 332             expect(COLON);
 333             final Node value = jsonLiteral();
 334             return new PropertyNode(propertyToken, value.getFinish(), name, value, null, null);
 335         }
 336 
 337         // Raise an error.
 338         throw error(AbstractParser.message("expected", "string", type.getNameOrType()));
 339     }
 340 
 341 }