diff --git a/src/org/mozilla/javascript/json/JsonParser.java b/src/org/mozilla/javascript/json/JsonParser.java index 5079502..0c06d26 100644 --- a/src/org/mozilla/javascript/json/JsonParser.java +++ b/src/org/mozilla/javascript/json/JsonParser.java @@ -86,23 +86,15 @@ public class JsonParser { } private Object readObject() throws ParseException { - consumeWhitespace(); Scriptable object = cx.newObject(scope); - // handle empty object literal case early - if (pos < length && src.charAt(pos) == '}') { - pos += 1; - return object; - } String id; Object value; boolean needsComma = false; + consumeWhitespace(); while (pos < length) { char c = src.charAt(pos++); switch(c) { case '}': - if (!needsComma) { - throw new ParseException("Unexpected comma in object literal"); - } return object; case ',': if (!needsComma) { @@ -136,21 +128,13 @@ public class JsonParser { } private Object readArray() throws ParseException { - consumeWhitespace(); - // handle empty array literal case early - if (pos < length && src.charAt(pos) == ']') { - pos += 1; - return cx.newArray(scope, 0); - } List list = new ArrayList(); boolean needsComma = false; + consumeWhitespace(); while (pos < length) { char c = src.charAt(pos); switch(c) { case ']': - if (!needsComma) { - throw new ParseException("Unexpected comma in array literal"); - } pos += 1; return cx.newArray(scope, list.toArray()); case ',': @@ -173,166 +157,108 @@ public class JsonParser { } private String readString() throws ParseException { - /* - * Optimization: if the source contains no escaped characters, create the - * string directly from the source text. - */ - int stringStart = pos; + StringBuilder b = new StringBuilder(); while (pos < length) { char c = src.charAt(pos++); if (c <= '\u001F') { throw new ParseException("String contains control character"); - } else if (c == '\\') { - break; - } else if (c == '"') { - return src.substring(stringStart, pos - 1); } - } - - /* - * Slow case: string contains escaped characters. Copy a maximal sequence - * of unescaped characters into a temporary buffer, then an escaped - * character, and repeat until the entire string is consumed. - */ - StringBuilder b = new StringBuilder(); - while (pos < length) { - assert src.charAt(pos - 1) == '\\'; - b.append(src, stringStart, pos - 1); - if (pos >= length) { - throw new ParseException("Unterminated string"); - } - char c = src.charAt(pos++); - switch (c) { - case '"': - b.append('"'); - break; + switch(c) { case '\\': - b.append('\\'); - break; - case '/': - b.append('/'); - break; - case 'b': - b.append('\b'); - break; - case 'f': - b.append('\f'); - break; - case 'n': - b.append('\n'); - break; - case 'r': - b.append('\r'); - break; - case 't': - b.append('\t'); - break; - case 'u': - if (length - pos < 5) { - throw new ParseException("Invalid character code: \\u" + src.substring(pos)); + if (pos >= length) { + throw new ParseException("Unterminated string"); } - int code = fromHex(src.charAt(pos + 0)) << 12 - | fromHex(src.charAt(pos + 1)) << 8 - | fromHex(src.charAt(pos + 2)) << 4 - | fromHex(src.charAt(pos + 3)); - if (code < 0) { - throw new ParseException("Invalid character code: " + src.substring(pos, pos + 4)); + c = src.charAt(pos++); + switch (c) { + case '"': + b.append('"'); + break; + case '\\': + b.append('\\'); + break; + case '/': + b.append('/'); + break; + case 'b': + b.append('\b'); + break; + case 'f': + b.append('\f'); + break; + case 'n': + b.append('\n'); + break; + case 'r': + b.append('\r'); + break; + case 't': + b.append('\t'); + break; + case 'u': + if (length - pos < 5) { + throw new ParseException("Invalid character code: \\u" + src.substring(pos)); + } + try { + b.append((char) Integer.parseInt(src.substring(pos, pos + 4), 16)); + pos += 4; + } catch (NumberFormatException nfx) { + throw new ParseException("Invalid character code: " + src.substring(pos, pos + 4)); + } + break; + default: + throw new ParseException("Unexcpected character in string: '\\" + c + "'"); } - pos += 4; - b.append((char) code); break; + case '"': + return b.toString(); default: - throw new ParseException("Unexpected character in string: '\\" + c + "'"); - } - stringStart = pos; - while (pos < length) { - c = src.charAt(pos++); - if (c <= '\u001F') { - throw new ParseException("String contains control character"); - } else if (c == '\\') { + b.append(c); break; - } else if (c == '"') { - b.append(src, stringStart, pos - 1); - return b.toString(); - } } } throw new ParseException("Unterminated string literal"); } - private int fromHex(char c) { - return c >= '0' && c <= '9' ? c - '0' - : c >= 'A' && c <= 'F' ? c - 'A' + 10 - : c >= 'a' && c <= 'f' ? c - 'a' + 10 - : -1; - } - - private Number readNumber(char c) throws ParseException { - assert c == '-' || (c >= '0' && c <= '9'); - final int numberStart = pos - 1; - if (c == '-') { - c = nextOrNumberError(numberStart); - if (!(c >= '0' && c <= '9')) { - throw numberError(numberStart, pos); - } - } - if (c != '0') { - readDigits(); - } - // read optional fraction part - if (pos < length) { - c = src.charAt(pos); - if (c == '.') { - pos += 1; - c = nextOrNumberError(numberStart); - if (!(c >= '0' && c <= '9')) { - throw numberError(numberStart, pos); - } - readDigits(); + private Number readNumber(char first) throws ParseException { + StringBuilder b = new StringBuilder(); + b.append(first); + while (pos < length) { + char c = src.charAt(pos); + if (!Character.isDigit(c) + && c != '-' + && c != '+' + && c != '.' + && c != 'e' + && c != 'E') { + break; } + pos += 1; + b.append(c); } - // read optional exponent part - if (pos < length) { - c = src.charAt(pos); - if (c == 'e' || c == 'E') { - pos += 1; - c = nextOrNumberError(numberStart); - if (c == '-' || c == '+') { - c = nextOrNumberError(numberStart); - } - if (!(c >= '0' && c <= '9')) { - throw numberError(numberStart, pos); + String num = b.toString(); + int numLength = num.length(); + try { + // check for leading zeroes + for (int i = 0; i < numLength; i++) { + char c = num.charAt(i); + if (Character.isDigit(c)) { + if (c == '0' + && numLength > i + 1 + && Character.isDigit(num.charAt(i + 1))) { + throw new ParseException("Unsupported number format: " + num); + } + break; } - readDigits(); } - } - String num = src.substring(numberStart, pos); - final double dval = Double.parseDouble(num); - final int ival = (int)dval; - if (ival == dval) { - return Integer.valueOf(ival); - } else { - return Double.valueOf(dval); - } - } - - private ParseException numberError(int start, int end) { - return new ParseException("Unsupported number format: " + src.substring(start, end)); - } - - private char nextOrNumberError(int numberStart) throws ParseException { - if (pos >= length) { - throw numberError(numberStart, length); - } - return src.charAt(pos++); - } - - private void readDigits() { - for (; pos < length; ++pos) { - char c = src.charAt(pos); - if (!(c >= '0' && c <= '9')) { - break; + final double dval = Double.parseDouble(num); + final int ival = (int)dval; + if (ival == dval) { + return Integer.valueOf(ival); + } else { + return Double.valueOf(dval); } + } catch (NumberFormatException nfe) { + throw new ParseException("Unsupported number format: " + num); } } diff --git a/testsrc/org/mozilla/javascript/tests/json/JsonParserTest.java b/testsrc/org/mozilla/javascript/tests/json/JsonParserTest.java index ee885ae..2783b50 100644 --- a/testsrc/org/mozilla/javascript/tests/json/JsonParserTest.java +++ b/testsrc/org/mozilla/javascript/tests/json/JsonParserTest.java @@ -196,66 +196,6 @@ public class JsonParserTest { parser.parseValue("[1 "); } - @Test(expected = ParseException.class) - public void shouldFailToParseIllegalUnicodeEscapeSeq() throws Exception { - parser.parseValue("\"\\u-123\""); - } - - @Test(expected = ParseException.class) - public void shouldFailToParseIllegalUnicodeEscapeSeq2() throws Exception { - parser.parseValue("\"\\u006\u0661\""); - } - - @Test(expected = ParseException.class) - public void shouldFailToParseIllegalUnicodeEscapeSeq3() throws Exception { - parser.parseValue("\"\\u006١\""); - } - - @Test(expected = ParseException.class) - public void shouldFailToParseTrailingCommaInObject1() throws Exception { - parser.parseValue("{\"a\": 1,}"); - } - - @Test(expected = ParseException.class) - public void shouldFailToParseTrailingCommaInObject2() throws Exception { - parser.parseValue("{,\"a\": 1}"); - } - - @Test(expected = ParseException.class) - public void shouldFailToParseTrailingCommaInObject3() throws Exception { - parser.parseValue("{,}"); - } - - @Test - public void shouldParseEmptyObject() throws Exception { - parser.parseValue("{}"); - } - - @Test(expected = ParseException.class) - public void shouldFailToParseTrailingCommaInArray1() throws Exception { - parser.parseValue("[1,]"); - } - - @Test(expected = ParseException.class) - public void shouldFailToParseTrailingCommaInArray2() throws Exception { - parser.parseValue("[,1]"); - } - - @Test(expected = ParseException.class) - public void shouldFailToParseTrailingCommaInArray3() throws Exception { - parser.parseValue("[,]"); - } - - @Test - public void shouldParseEmptyArray() throws Exception { - parser.parseValue("[]"); - } - - @Test(expected = ParseException.class) - public void shouldFailToParseIllegalNumber() throws Exception { - parser.parseValue("1."); - } - private String str(char... chars) { return new String(chars); }