blob: 02187409cbf42f5f21fb9e4315b6e83ba434084e [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
46template <typename ContainerT>
47bool JSONParser::skipContainer(const ContainerT &Container) {
48 for (typename ContainerT::const_iterator I = Container.current(),
49 E = Container.end();
50 I != E; ++I) {
51 assert(*I != 0);
52 if (!skip(**I))
53 return false;
54 }
55 return !failed();
56}
57
58bool JSONParser::skip(const JSONAtom &Atom) {
59 switch(Atom.getKind()) {
60 case JSONAtom::JK_Array: return skipContainer(*cast<JSONArray>(&Atom));
61 case JSONAtom::JK_Object: return skipContainer(*cast<JSONObject>(&Atom));
62 case JSONAtom::JK_String: return true;
63 case JSONAtom::JK_KeyValuePair:
64 return skip(*cast<JSONKeyValuePair>(&Atom)->Value);
65 }
66 llvm_unreachable("Impossible enum value.");
Manuel Klimek76f13012011-12-16 13:09:10 +000067}
68
69// Sets the current error to:
70// "Error while parsing JSON: expected <Expected>, but found <Found>".
71void JSONParser::setExpectedError(StringRef Expected, StringRef Found) {
72 ErrorMessage = ("Error while parsing JSON: expected " +
73 Expected + ", but found " + Found + ".").str();
74}
75
76// Sets the current error to:
77// "Error while parsing JSON: expected <Expected>, but found <Found>".
78void JSONParser::setExpectedError(StringRef Expected, char Found) {
79 setExpectedError(Expected, StringRef(&Found, 1));
80}
81
82// If there is no character available, returns true and sets the current error
83// to: "Error while parsing JSON: expected <Expected>, but found EOF.".
84bool JSONParser::errorIfAtEndOfFile(StringRef Expected) {
85 if (Position == Input.end()) {
86 setExpectedError(Expected, "EOF");
87 return true;
88 }
89 return false;
90}
91
92// Sets the current error if the current character is not C to:
93// "Error while parsing JSON: expected 'C', but got <current character>".
94bool JSONParser::errorIfNotAt(char C, StringRef Message) {
95 if (Position == Input.end() || *Position != C) {
96 std::string Expected =
97 ("'" + StringRef(&C, 1) + "' " + Message).str();
98 if (Position == Input.end())
99 setExpectedError(Expected, "EOF");
100 else
101 setExpectedError(Expected, *Position);
102 return true;
103 }
104 return false;
105}
106
107// Forbidding inlining improves performance by roughly 20%.
108// FIXME: Remove once llvm optimizes this to the faster version without hints.
109LLVM_ATTRIBUTE_NOINLINE static bool
110wasEscaped(StringRef::iterator First, StringRef::iterator Position);
111
112// Returns whether a character at 'Position' was escaped with a leading '\'.
113// 'First' specifies the position of the first character in the string.
114static bool wasEscaped(StringRef::iterator First,
115 StringRef::iterator Position) {
116 assert(Position - 1 >= First);
117 StringRef::iterator I = Position - 1;
118 // We calulate the number of consecutive '\'s before the current position
119 // by iterating backwards through our string.
120 while (I >= First && *I == '\\') --I;
121 // (Position - 1 - I) now contains the number of '\'s before the current
122 // position. If it is odd, the character at 'Positon' was escaped.
123 return (Position - 1 - I) % 2 == 1;
124}
125
126// Parses a JSONString, assuming that the current position is on a quote.
127JSONString *JSONParser::parseString() {
128 assert(Position != Input.end());
129 assert(!isWhitespace());
130 if (errorIfNotAt('"', "at start of string"))
131 return 0;
132 StringRef::iterator First = Position + 1;
133
134 // Benchmarking shows that this loop is the hot path of the application with
135 // about 2/3rd of the runtime cycles. Since escaped quotes are not the common
136 // case, and multiple escaped backslashes before escaped quotes are very rare,
137 // we pessimize this case to achieve a smaller inner loop in the common case.
138 // We're doing that by having a quick inner loop that just scans for the next
139 // quote. Once we find the quote we check the last character to see whether
140 // the quote might have been escaped. If the last character is not a '\', we
141 // know the quote was not escaped and have thus found the end of the string.
142 // If the immediately preceding character was a '\', we have to scan backwards
143 // to see whether the previous character was actually an escaped backslash, or
144 // an escape character for the quote. If we find that the current quote was
145 // escaped, we continue parsing for the next quote and repeat.
146 // This optimization brings around 30% performance improvements.
147 do {
148 // Step over the current quote.
149 ++Position;
150 // Find the next quote.
151 while (Position != Input.end() && *Position != '"')
152 ++Position;
153 if (errorIfAtEndOfFile("\" at end of string"))
154 return 0;
155 // Repeat until the previous character was not a '\' or was an escaped
156 // backslash.
157 } while (*(Position - 1) == '\\' && wasEscaped(First, Position));
158
159 return new (ValueAllocator.Allocate<JSONString>())
160 JSONString(StringRef(First, Position - First));
161}
162
163
164// Advances the position to the next non-whitespace position.
165void JSONParser::nextNonWhitespace() {
166 do {
167 ++Position;
168 } while (isWhitespace());
169}
170
171// Checks if there is a whitespace character at the current position.
172bool JSONParser::isWhitespace() {
173 return Position != Input.end() && (*Position == ' ' || *Position == '\t' ||
174 *Position == '\n' || *Position == '\r');
175}
176
177bool JSONParser::failed() const {
178 return !ErrorMessage.empty();
179}
180
181std::string JSONParser::getErrorMessage() const {
182 return ErrorMessage;
183}
184
Manuel Klimek76f13012011-12-16 13:09:10 +0000185// Parses a JSONValue, assuming that the current position is at the first
186// character of the value.
187JSONValue *JSONParser::parseValue() {
188 assert(Position != Input.end());
189 assert(!isWhitespace());
190 switch (*Position) {
191 case '[':
192 return new (ValueAllocator.Allocate<JSONArray>(1)) JSONArray(this);
193 case '{':
194 return new (ValueAllocator.Allocate<JSONObject>(1)) JSONObject(this);
195 case '"':
196 return parseString();
197 default:
198 setExpectedError("'[', '{' or '\"' at start of value", *Position);
199 return 0;
200 }
201}
202
203// Parses a JSONKeyValuePair, assuming that the current position is at the first
204// character of the key, value pair.
205JSONKeyValuePair *JSONParser::parseKeyValuePair() {
206 assert(Position != Input.end());
207 assert(!isWhitespace());
208
209 JSONString *Key = parseString();
210 if (Key == 0)
211 return 0;
212
213 nextNonWhitespace();
214 if (errorIfNotAt(':', "between key and value"))
215 return 0;
216
217 nextNonWhitespace();
218 const JSONValue *Value = parseValue();
219 if (Value == 0)
220 return 0;
221
222 return new (ValueAllocator.Allocate<JSONKeyValuePair>(1))
223 JSONKeyValuePair(Key, Value);
224}
225
226template <> JSONValue *JSONParser::parseElement() {
227 return parseValue();
228}
229
230template <> JSONKeyValuePair *JSONParser::parseElement() {
231 return parseKeyValuePair();
232}