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 }