blob: 994ca9fc966b5b8acb28e15968ab08ca551b3983 [file] [log] [blame]
Manuel Klimekc4850c92011-12-20 09:26:26 +00001//===--- JSONParser.cpp - Simple JSON parser ------------------------------===//
Manuel Klimek76f13012011-12-16 13:09:10 +00002//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// This file implements a JSON parser.
11//
12//===----------------------------------------------------------------------===//
13
14#include "llvm/Support/JSONParser.h"
15
16#include "llvm/ADT/Twine.h"
17#include "llvm/Support/Casting.h"
18
Manuel Klimekc4850c92011-12-20 09:26:26 +000019using namespace llvm;
Manuel Klimek76f13012011-12-16 13:09:10 +000020
21JSONParser::JSONParser(StringRef Input)
22 : Input(Input), Position(Input.begin()) {}
23
24JSONValue *JSONParser::parseRoot() {
25 if (Position != Input.begin())
26 report_fatal_error("Cannot resuse JSONParser.");
27 if (isWhitespace())
28 nextNonWhitespace();
29 if (errorIfAtEndOfFile("'[' or '{' at start of JSON text"))
30 return 0;
31 switch (*Position) {
32 case '[':
33 return new (ValueAllocator.Allocate<JSONArray>(1)) JSONArray(this);
34 case '{':
35 return new (ValueAllocator.Allocate<JSONObject>(1)) JSONObject(this);
36 default:
37 setExpectedError("'[' or '{' at start of JSON text", *Position);
38 return 0;
39 }
40}
41
42bool JSONParser::validate() {
Manuel Klimek9ce69372011-12-20 10:42:52 +000043 return skip(*parseRoot());
44}
45
Manuel Klimek9ce69372011-12-20 10:42:52 +000046bool JSONParser::skip(const JSONAtom &Atom) {
47 switch(Atom.getKind()) {
48 case JSONAtom::JK_Array: return skipContainer(*cast<JSONArray>(&Atom));
49 case JSONAtom::JK_Object: return skipContainer(*cast<JSONObject>(&Atom));
50 case JSONAtom::JK_String: return true;
51 case JSONAtom::JK_KeyValuePair:
52 return skip(*cast<JSONKeyValuePair>(&Atom)->Value);
53 }
54 llvm_unreachable("Impossible enum value.");
Manuel Klimek76f13012011-12-16 13:09:10 +000055}
56
57// Sets the current error to:
58// "Error while parsing JSON: expected <Expected>, but found <Found>".
59void JSONParser::setExpectedError(StringRef Expected, StringRef Found) {
60 ErrorMessage = ("Error while parsing JSON: expected " +
61 Expected + ", but found " + Found + ".").str();
62}
63
64// Sets the current error to:
65// "Error while parsing JSON: expected <Expected>, but found <Found>".
66void JSONParser::setExpectedError(StringRef Expected, char Found) {
67 setExpectedError(Expected, StringRef(&Found, 1));
68}
69
70// If there is no character available, returns true and sets the current error
71// to: "Error while parsing JSON: expected <Expected>, but found EOF.".
72bool JSONParser::errorIfAtEndOfFile(StringRef Expected) {
73 if (Position == Input.end()) {
74 setExpectedError(Expected, "EOF");
75 return true;
76 }
77 return false;
78}
79
80// Sets the current error if the current character is not C to:
81// "Error while parsing JSON: expected 'C', but got <current character>".
82bool JSONParser::errorIfNotAt(char C, StringRef Message) {
83 if (Position == Input.end() || *Position != C) {
84 std::string Expected =
85 ("'" + StringRef(&C, 1) + "' " + Message).str();
86 if (Position == Input.end())
87 setExpectedError(Expected, "EOF");
88 else
89 setExpectedError(Expected, *Position);
90 return true;
91 }
92 return false;
93}
94
95// Forbidding inlining improves performance by roughly 20%.
96// FIXME: Remove once llvm optimizes this to the faster version without hints.
97LLVM_ATTRIBUTE_NOINLINE static bool
98wasEscaped(StringRef::iterator First, StringRef::iterator Position);
99
100// Returns whether a character at 'Position' was escaped with a leading '\'.
101// 'First' specifies the position of the first character in the string.
102static bool wasEscaped(StringRef::iterator First,
103 StringRef::iterator Position) {
104 assert(Position - 1 >= First);
105 StringRef::iterator I = Position - 1;
106 // We calulate the number of consecutive '\'s before the current position
107 // by iterating backwards through our string.
108 while (I >= First && *I == '\\') --I;
109 // (Position - 1 - I) now contains the number of '\'s before the current
110 // position. If it is odd, the character at 'Positon' was escaped.
111 return (Position - 1 - I) % 2 == 1;
112}
113
114// Parses a JSONString, assuming that the current position is on a quote.
115JSONString *JSONParser::parseString() {
116 assert(Position != Input.end());
117 assert(!isWhitespace());
118 if (errorIfNotAt('"', "at start of string"))
119 return 0;
120 StringRef::iterator First = Position + 1;
121
122 // Benchmarking shows that this loop is the hot path of the application with
123 // about 2/3rd of the runtime cycles. Since escaped quotes are not the common
124 // case, and multiple escaped backslashes before escaped quotes are very rare,
125 // we pessimize this case to achieve a smaller inner loop in the common case.
126 // We're doing that by having a quick inner loop that just scans for the next
127 // quote. Once we find the quote we check the last character to see whether
128 // the quote might have been escaped. If the last character is not a '\', we
129 // know the quote was not escaped and have thus found the end of the string.
130 // If the immediately preceding character was a '\', we have to scan backwards
131 // to see whether the previous character was actually an escaped backslash, or
132 // an escape character for the quote. If we find that the current quote was
133 // escaped, we continue parsing for the next quote and repeat.
134 // This optimization brings around 30% performance improvements.
135 do {
136 // Step over the current quote.
137 ++Position;
138 // Find the next quote.
139 while (Position != Input.end() && *Position != '"')
140 ++Position;
141 if (errorIfAtEndOfFile("\" at end of string"))
142 return 0;
143 // Repeat until the previous character was not a '\' or was an escaped
144 // backslash.
145 } while (*(Position - 1) == '\\' && wasEscaped(First, Position));
146
147 return new (ValueAllocator.Allocate<JSONString>())
148 JSONString(StringRef(First, Position - First));
149}
150
151
152// Advances the position to the next non-whitespace position.
153void JSONParser::nextNonWhitespace() {
154 do {
155 ++Position;
156 } while (isWhitespace());
157}
158
159// Checks if there is a whitespace character at the current position.
160bool JSONParser::isWhitespace() {
161 return Position != Input.end() && (*Position == ' ' || *Position == '\t' ||
162 *Position == '\n' || *Position == '\r');
163}
164
165bool JSONParser::failed() const {
166 return !ErrorMessage.empty();
167}
168
169std::string JSONParser::getErrorMessage() const {
170 return ErrorMessage;
171}
172
Manuel Klimek76f13012011-12-16 13:09:10 +0000173// Parses a JSONValue, assuming that the current position is at the first
174// character of the value.
175JSONValue *JSONParser::parseValue() {
176 assert(Position != Input.end());
177 assert(!isWhitespace());
178 switch (*Position) {
179 case '[':
180 return new (ValueAllocator.Allocate<JSONArray>(1)) JSONArray(this);
181 case '{':
182 return new (ValueAllocator.Allocate<JSONObject>(1)) JSONObject(this);
183 case '"':
184 return parseString();
185 default:
186 setExpectedError("'[', '{' or '\"' at start of value", *Position);
187 return 0;
188 }
189}
190
191// Parses a JSONKeyValuePair, assuming that the current position is at the first
192// character of the key, value pair.
193JSONKeyValuePair *JSONParser::parseKeyValuePair() {
194 assert(Position != Input.end());
195 assert(!isWhitespace());
196
197 JSONString *Key = parseString();
198 if (Key == 0)
199 return 0;
200
201 nextNonWhitespace();
202 if (errorIfNotAt(':', "between key and value"))
203 return 0;
204
205 nextNonWhitespace();
206 const JSONValue *Value = parseValue();
207 if (Value == 0)
208 return 0;
209
210 return new (ValueAllocator.Allocate<JSONKeyValuePair>(1))
211 JSONKeyValuePair(Key, Value);
212}
213
214template <> JSONValue *JSONParser::parseElement() {
215 return parseValue();
216}
217
218template <> JSONKeyValuePair *JSONParser::parseElement() {
219 return parseKeyValuePair();
220}