src/jdk/nashorn/internal/parser/JSONParser.java

Fri, 22 Jan 2016 16:18:19 +0100

author
hannesw
date
Fri, 22 Jan 2016 16:18:19 +0100
changeset 1720
c09b105e7be5
parent 1402
523767716eb3
child 1959
61ffdd1b89f2
permissions
-rw-r--r--

8144020: Remove long as an internal numeric type
Reviewed-by: attila, mhaupt

     1 /*
     2  * Copyright (c) 2010, 2015, 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  */
    26 package jdk.nashorn.internal.parser;
    28 import java.util.ArrayList;
    29 import java.util.List;
    30 import jdk.nashorn.internal.codegen.ObjectClassGenerator;
    31 import jdk.nashorn.internal.objects.Global;
    32 import jdk.nashorn.internal.runtime.ECMAErrors;
    33 import jdk.nashorn.internal.runtime.ErrorManager;
    34 import jdk.nashorn.internal.runtime.JSErrorType;
    35 import jdk.nashorn.internal.runtime.JSType;
    36 import jdk.nashorn.internal.runtime.ParserException;
    37 import jdk.nashorn.internal.runtime.Property;
    38 import jdk.nashorn.internal.runtime.PropertyMap;
    39 import jdk.nashorn.internal.runtime.ScriptObject;
    40 import jdk.nashorn.internal.runtime.Source;
    41 import jdk.nashorn.internal.runtime.SpillProperty;
    42 import jdk.nashorn.internal.runtime.arrays.ArrayData;
    43 import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
    44 import jdk.nashorn.internal.scripts.JD;
    45 import jdk.nashorn.internal.scripts.JO;
    47 import static jdk.nashorn.internal.parser.TokenType.STRING;
    49 /**
    50  * Parses JSON text and returns the corresponding IR node. This is derived from
    51  * the objectLiteral production of the main parser.
    52  *
    53  * See: 15.12.1.2 The JSON Syntactic Grammar
    54  */
    55 public class JSONParser {
    57     final private String source;
    58     final private Global global;
    59     final private boolean dualFields;
    60     final int length;
    61     int pos = 0;
    63     private static final int EOF = -1;
    65     private static final String TRUE  = "true";
    66     private static final String FALSE = "false";
    67     private static final String NULL  = "null";
    69     private static final int STATE_EMPTY          = 0;
    70     private static final int STATE_ELEMENT_PARSED = 1;
    71     private static final int STATE_COMMA_PARSED   = 2;
    73     /**
    74      * Constructor.
    75      *
    76      * @param source     the source
    77      * @param global     the global object
    78      * @param dualFields whether the parser should regard dual field representation
    79      */
    80     public JSONParser(final String source, final Global global, final boolean dualFields) {
    81         this.source = source;
    82         this.global = global;
    83         this.length = source.length();
    84         this.dualFields = dualFields;
    85     }
    87     /**
    88      * Implementation of the Quote(value) operation as defined in the ECMAscript
    89      * spec. It wraps a String value in double quotes and escapes characters
    90      * within.
    91      *
    92      * @param value string to quote
    93      *
    94      * @return quoted and escaped string
    95      */
    96     public static String quote(final String value) {
    98         final StringBuilder product = new StringBuilder();
   100         product.append("\"");
   102         for (final char ch : value.toCharArray()) {
   103             // TODO: should use a table?
   104             switch (ch) {
   105             case '\\':
   106                 product.append("\\\\");
   107                 break;
   108             case '"':
   109                 product.append("\\\"");
   110                 break;
   111             case '\b':
   112                 product.append("\\b");
   113                 break;
   114             case '\f':
   115                 product.append("\\f");
   116                 break;
   117             case '\n':
   118                 product.append("\\n");
   119                 break;
   120             case '\r':
   121                 product.append("\\r");
   122                 break;
   123             case '\t':
   124                 product.append("\\t");
   125                 break;
   126             default:
   127                 if (ch < ' ') {
   128                     product.append(Lexer.unicodeEscape(ch));
   129                     break;
   130                 }
   132                 product.append(ch);
   133                 break;
   134             }
   135         }
   137         product.append("\"");
   139         return product.toString();
   140     }
   142     /**
   143      * Public parse method. Parse a string into a JSON object.
   144      *
   145      * @return the parsed JSON Object
   146      */
   147     public Object parse() {
   148         final Object value = parseLiteral();
   149         skipWhiteSpace();
   150         if (pos < length) {
   151             throw expectedError(pos, "eof", toString(peek()));
   152         }
   153         return value;
   154     }
   156     private Object parseLiteral() {
   157         skipWhiteSpace();
   159         final int c = peek();
   160         if (c == EOF) {
   161             throw expectedError(pos, "json literal", "eof");
   162         }
   163         switch (c) {
   164         case '{':
   165             return parseObject();
   166         case '[':
   167             return parseArray();
   168         case '"':
   169             return parseString();
   170         case 'f':
   171             return parseKeyword(FALSE, Boolean.FALSE);
   172         case 't':
   173             return parseKeyword(TRUE, Boolean.TRUE);
   174         case 'n':
   175             return parseKeyword(NULL, null);
   176         default:
   177             if (isDigit(c) || c == '-') {
   178                 return parseNumber();
   179             } else if (c == '.') {
   180                 throw numberError(pos);
   181             } else {
   182                 throw expectedError(pos, "json literal", toString(c));
   183             }
   184         }
   185     }
   187     private Object parseObject() {
   188         PropertyMap propertyMap = dualFields ? JD.getInitialMap() : JO.getInitialMap();
   189         ArrayData arrayData = ArrayData.EMPTY_ARRAY;
   190         final ArrayList<Object> values = new ArrayList<>();
   191         int state = STATE_EMPTY;
   193         assert peek() == '{';
   194         pos++;
   196         while (pos < length) {
   197             skipWhiteSpace();
   198             final int c = peek();
   200             switch (c) {
   201             case '"':
   202                 if (state == STATE_ELEMENT_PARSED) {
   203                     throw expectedError(pos - 1, ", or }", toString(c));
   204                 }
   205                 final String id = parseString();
   206                 expectColon();
   207                 final Object value = parseLiteral();
   208                 final int index = ArrayIndex.getArrayIndex(id);
   209                 if (ArrayIndex.isValidArrayIndex(index)) {
   210                     arrayData = addArrayElement(arrayData, index, value);
   211                 } else {
   212                     propertyMap = addObjectProperty(propertyMap, values, id, value);
   213                 }
   214                 state = STATE_ELEMENT_PARSED;
   215                 break;
   216             case ',':
   217                 if (state != STATE_ELEMENT_PARSED) {
   218                     throw error(AbstractParser.message("trailing.comma.in.json"), pos);
   219                 }
   220                 state = STATE_COMMA_PARSED;
   221                 pos++;
   222                 break;
   223             case '}':
   224                 if (state == STATE_COMMA_PARSED) {
   225                     throw error(AbstractParser.message("trailing.comma.in.json"), pos);
   226                 }
   227                 pos++;
   228                 return createObject(propertyMap, values, arrayData);
   229             default:
   230                 throw expectedError(pos, ", or }", toString(c));
   231             }
   232         }
   233         throw expectedError(pos, ", or }", "eof");
   234     }
   236     private static ArrayData addArrayElement(final ArrayData arrayData, final int index, final Object value) {
   237         final long oldLength = arrayData.length();
   238         final long longIndex = ArrayIndex.toLongIndex(index);
   239         ArrayData newArrayData = arrayData;
   240         if (longIndex >= oldLength) {
   241             newArrayData = newArrayData.ensure(longIndex);
   242             if (longIndex > oldLength) {
   243                 newArrayData = newArrayData.delete(oldLength, longIndex - 1);
   244             }
   245         }
   246         return newArrayData.set(index, value, false);
   247     }
   249     private PropertyMap addObjectProperty(final PropertyMap propertyMap, final List<Object> values,
   250                                                  final String id, final Object value) {
   251         final Property oldProperty = propertyMap.findProperty(id);
   252         final PropertyMap newMap;
   253         final Class<?> type;
   254         final int flags;
   255         if (dualFields) {
   256             type = getType(value);
   257             flags = Property.DUAL_FIELDS;
   258         } else {
   259             type = Object.class;
   260             flags = 0;
   261         }
   263         if (oldProperty != null) {
   264             values.set(oldProperty.getSlot(), value);
   265             newMap = propertyMap.replaceProperty(oldProperty, new SpillProperty(id, flags, oldProperty.getSlot(), type));;
   266         } else {
   267             values.add(value);
   268             newMap = propertyMap.addProperty(new SpillProperty(id, flags, propertyMap.size(), type));
   269         }
   271         return newMap;
   272     }
   274     private Object createObject(final PropertyMap propertyMap, final List<Object> values, final ArrayData arrayData) {
   275         final long[] primitiveSpill = dualFields ? new long[values.size()] : null;
   276         final Object[] objectSpill = new Object[values.size()];
   278         for (final Property property : propertyMap.getProperties()) {
   279             if (!dualFields || property.getType() == Object.class) {
   280                 objectSpill[property.getSlot()] = values.get(property.getSlot());
   281             } else {
   282                 primitiveSpill[property.getSlot()] = ObjectClassGenerator.pack((Number) values.get(property.getSlot()));
   283             }
   284         }
   286         final ScriptObject object = dualFields ?
   287                 new JD(propertyMap, primitiveSpill, objectSpill) : new JO(propertyMap, null, objectSpill);
   288         object.setInitialProto(global.getObjectPrototype());
   289         object.setArray(arrayData);
   290         return object;
   291     }
   293     private static Class<?> getType(final Object value) {
   294         if (value instanceof Integer) {
   295             return int.class;
   296         } else if (value instanceof Double) {
   297             return double.class;
   298         } else {
   299             return Object.class;
   300         }
   301     }
   303     private void expectColon() {
   304         skipWhiteSpace();
   305         final int n = next();
   306         if (n != ':') {
   307             throw expectedError(pos - 1, ":", toString(n));
   308         }
   309     }
   311     private Object parseArray() {
   312         ArrayData arrayData = ArrayData.EMPTY_ARRAY;
   313         int state = STATE_EMPTY;
   315         assert peek() == '[';
   316         pos++;
   318         while (pos < length) {
   319             skipWhiteSpace();
   320             final int c = peek();
   322             switch (c) {
   323             case ',':
   324                 if (state != STATE_ELEMENT_PARSED) {
   325                     throw error(AbstractParser.message("trailing.comma.in.json"), pos);
   326                 }
   327                 state = STATE_COMMA_PARSED;
   328                 pos++;
   329                 break;
   330             case ']':
   331                 if (state == STATE_COMMA_PARSED) {
   332                     throw error(AbstractParser.message("trailing.comma.in.json"), pos);
   333                 }
   334                 pos++;
   335                 return global.wrapAsObject(arrayData);
   336             default:
   337                 if (state == STATE_ELEMENT_PARSED) {
   338                     throw expectedError(pos, ", or ]", toString(c));
   339                 }
   340                 final long index = arrayData.length();
   341                 arrayData = arrayData.ensure(index).set((int) index, parseLiteral(), true);
   342                 state = STATE_ELEMENT_PARSED;
   343                 break;
   344             }
   345         }
   347         throw expectedError(pos, ", or ]", "eof");
   348     }
   350     private String parseString() {
   351         // String buffer is only instantiated if string contains escape sequences.
   352         int start = ++pos;
   353         StringBuilder sb = null;
   355         while (pos < length) {
   356             final int c = next();
   357             if (c <= 0x1f) {
   358                 // Characters < 0x1f are not allowed in JSON strings.
   359                 throw syntaxError(pos, "String contains control character");
   361             } else if (c == '\\') {
   362                 if (sb == null) {
   363                     sb = new StringBuilder(pos - start + 16);
   364                 }
   365                 sb.append(source, start, pos - 1);
   366                 sb.append(parseEscapeSequence());
   367                 start = pos;
   369             } else if (c == '"') {
   370                 if (sb != null) {
   371                     sb.append(source, start, pos - 1);
   372                     return sb.toString();
   373                 }
   374                 return source.substring(start, pos - 1);
   375             }
   376         }
   378         throw error(Lexer.message("missing.close.quote"), pos, length);
   379     }
   381     private char parseEscapeSequence() {
   382         final int c = next();
   383         switch (c) {
   384         case '"':
   385             return '"';
   386         case '\\':
   387             return '\\';
   388         case '/':
   389             return '/';
   390         case 'b':
   391             return '\b';
   392         case 'f':
   393             return '\f';
   394         case 'n':
   395             return '\n';
   396         case 'r':
   397             return '\r';
   398         case 't':
   399             return '\t';
   400         case 'u':
   401             return parseUnicodeEscape();
   402         default:
   403             throw error(Lexer.message("invalid.escape.char"), pos - 1, length);
   404         }
   405     }
   407     private char parseUnicodeEscape() {
   408         return (char) (parseHexDigit() << 12 | parseHexDigit() << 8 | parseHexDigit() << 4 | parseHexDigit());
   409     }
   411     private int parseHexDigit() {
   412         final int c = next();
   413         if (c >= '0' && c <= '9') {
   414             return c - '0';
   415         } else if (c >= 'A' && c <= 'F') {
   416             return c + 10 - 'A';
   417         } else if (c >= 'a' && c <= 'f') {
   418             return c + 10 - 'a';
   419         }
   420         throw error(Lexer.message("invalid.hex"), pos - 1, length);
   421     }
   423     private boolean isDigit(final int c) {
   424         return c >= '0' && c <= '9';
   425     }
   427     private void skipDigits() {
   428         while (pos < length) {
   429             final int c = peek();
   430             if (!isDigit(c)) {
   431                 break;
   432             }
   433             pos++;
   434         }
   435     }
   437     private Number parseNumber() {
   438         final int start = pos;
   439         int c = next();
   441         if (c == '-') {
   442             c = next();
   443         }
   444         if (!isDigit(c)) {
   445             throw numberError(start);
   446         }
   447         // no more digits allowed after 0
   448         if (c != '0') {
   449             skipDigits();
   450         }
   452         // fraction
   453         if (peek() == '.') {
   454             pos++;
   455             if (!isDigit(next())) {
   456                 throw numberError(pos - 1);
   457             }
   458             skipDigits();
   459         }
   461         // exponent
   462         c = peek();
   463         if (c == 'e' || c == 'E') {
   464             pos++;
   465             c = next();
   466             if (c == '-' || c == '+') {
   467                 c = next();
   468             }
   469             if (!isDigit(c)) {
   470                 throw numberError(pos - 1);
   471             }
   472             skipDigits();
   473         }
   475         final double d = Double.parseDouble(source.substring(start, pos));
   476         if (JSType.isRepresentableAsInt(d)) {
   477             return (int) d;
   478         }
   479         return d;
   480     }
   482     private Object parseKeyword(final String keyword, final Object value) {
   483         if (!source.regionMatches(pos, keyword, 0, keyword.length())) {
   484             throw expectedError(pos, "json literal", "ident");
   485         }
   486         pos += keyword.length();
   487         return value;
   488     }
   490     private int peek() {
   491         if (pos >= length) {
   492             return -1;
   493         }
   494         return source.charAt(pos);
   495     }
   497     private int next() {
   498         final int next = peek();
   499         pos++;
   500         return next;
   501     }
   503     private void skipWhiteSpace() {
   504         while (pos < length) {
   505             switch (peek()) {
   506             case '\t':
   507             case '\r':
   508             case '\n':
   509             case ' ':
   510                 pos++;
   511                 break;
   512             default:
   513                 return;
   514             }
   515         }
   516     }
   518     private static String toString(final int c) {
   519         return c == EOF ? "eof" : String.valueOf((char) c);
   520     }
   522     ParserException error(final String message, final int start, final int length) throws ParserException {
   523         final long token     = Token.toDesc(STRING, start, length);
   524         final int  pos       = Token.descPosition(token);
   525         final Source src     = Source.sourceFor("<json>", source);
   526         final int  lineNum   = src.getLine(pos);
   527         final int  columnNum = src.getColumn(pos);
   528         final String formatted = ErrorManager.format(message, src, lineNum, columnNum, token);
   529         return new ParserException(JSErrorType.SYNTAX_ERROR, formatted, src, lineNum, columnNum, token);
   530     }
   532     private ParserException error(final String message, final int start) {
   533         return error(message, start, length);
   534     }
   536     private ParserException numberError(final int start) {
   537         return error(Lexer.message("json.invalid.number"), start);
   538     }
   540     private ParserException expectedError(final int start, final String expected, final String found) {
   541         return error(AbstractParser.message("expected", expected, found), start);
   542     }
   544     private ParserException syntaxError(final int start, final String reason) {
   545         final String message = ECMAErrors.getMessage("syntax.error.invalid.json", reason);
   546         return error(message, start);
   547     }
   548 }

mercurial