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