Index: include/llvm/Support/JSON.h =================================================================== --- /dev/null +++ include/llvm/Support/JSON.h @@ -0,0 +1,287 @@ +//===---------------------JSON.h --------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_JSON_H +#define LLVM_SUPPORT_JSON_H + +#include +#include +#include +#include +#include +#include + +#include "llvm/Support/Casting.h" +#include "llvm/Support/raw_ostream.h" + +namespace llvm { + +class JSONValue { +public: + typedef std::shared_ptr SP; + enum class Kind { String, Number, True, False, Null, Object, Array }; + + JSONValue(Kind K) : TheKind(K) {} + virtual ~JSONValue() = default; + + virtual void write(raw_ostream &S) const = 0; + + Kind getKind() const { return TheKind; } + +private: + const Kind TheKind; +}; + +class JSONString : public JSONValue { +public: + typedef std::shared_ptr SP; + + JSONString(); + JSONString(const char *S); + JSONString(const std::string &S); + + JSONString(const JSONString &S) = delete; + JSONString &operator=(const JSONString &S) = delete; + + ~JSONString() override = default; + + void write(raw_ostream &S) const override; + + std::string getData() const { return Data; } + + static bool classof(const JSONValue *V) { + return V->getKind() == JSONValue::Kind::String; + } + +private: + static std::string jsonStringQuoteMetachars(const std::string &); + + std::string Data; +}; + +class JSONNumber : public JSONValue { +public: + typedef std::shared_ptr SP; + + // We cretae a constructor for all integer and floating point type with using + // templates and + // SFINAE to avoid having ambiguous overloads because of the implicit type + // promotion. If we + // would have constructors only with int64_t, uint64_t and double types then + // constructing a + // JSONNumber from an int32_t (or any other similar type) would fail to + // compile. + + template ::value && + std::is_unsigned::value>::type * = nullptr> + explicit JSONNumber(T U) + : JSONValue(JSONValue::Kind::Number), TheDataType(DataType::Unsigned) { + Data.Unsigned = U; + } + + template ::value && + std::is_signed::value>::type * = nullptr> + explicit JSONNumber(T S) + : JSONValue(JSONValue::Kind::Number), TheDataType(DataType::Signed) { + Data.Signed = S; + } + + template ::value>::type * = nullptr> + explicit JSONNumber(T D) + : JSONValue(JSONValue::Kind::Number), TheDataType(DataType::Double) { + Data.Double = D; + } + + ~JSONNumber() override = default; + + JSONNumber(const JSONNumber &S) = delete; + JSONNumber &operator=(const JSONNumber &S) = delete; + + void write(raw_ostream &S) const override; + + uint64_t getAsUnsigned() const; + + int64_t getAsSigned() const; + + double getAsDouble() const; + + static bool classof(const JSONValue *V) { + return V->getKind() == JSONValue::Kind::Number; + } + +private: + enum class DataType : uint8_t { Unsigned, Signed, Double } TheDataType; + + union { + uint64_t Unsigned; + int64_t Signed; + double Double; + } Data; +}; + +class JSONTrue : public JSONValue { +public: + JSONTrue(); + + JSONTrue(const JSONTrue &S) = delete; + JSONTrue &operator=(const JSONTrue &S) = delete; + + void write(raw_ostream &S) const override; + + typedef std::shared_ptr SP; + + static bool classof(const JSONValue *V) { + return V->getKind() == JSONValue::Kind::True; + } + + ~JSONTrue() override = default; +}; + +class JSONFalse : public JSONValue { +public: + JSONFalse(); + + JSONFalse(const JSONFalse &S) = delete; + JSONFalse &operator=(const JSONFalse &S) = delete; + + void write(raw_ostream &S) const override; + + typedef std::shared_ptr SP; + + static bool classof(const JSONValue *V) { + return V->getKind() == JSONValue::Kind::False; + } + + ~JSONFalse() override = default; +}; + +class JSONNull : public JSONValue { +public: + JSONNull(); + + JSONNull(const JSONNull &S) = delete; + JSONNull &operator=(const JSONNull &S) = delete; + + void write(raw_ostream &S) const override; + + typedef std::shared_ptr SP; + + static bool classof(const JSONValue *V) { + return V->getKind() == JSONValue::Kind::Null; + } + + ~JSONNull() override = default; +}; + +class JSONObject : public JSONValue { +public: + JSONObject(); + + JSONObject(const JSONObject &S) = delete; + JSONObject &operator=(const JSONObject &S) = delete; + + void write(raw_ostream &S) const override; + + typedef std::shared_ptr SP; + + static bool classof(const JSONValue *V) { + return V->getKind() == JSONValue::Kind::Object; + } + + bool setObject(const std::string &Key, JSONValue::SP Value); + + JSONValue::SP getObject(const std::string &Key); + + ~JSONObject() override = default; + +private: + typedef std::map Map; + typedef Map::iterator Iterator; + Map Elements; +}; + +class JSONArray : public JSONValue { +public: + JSONArray(); + + JSONArray(const JSONArray &S) = delete; + JSONArray &operator=(const JSONArray &S) = delete; + + void write(raw_ostream &S) const override; + + typedef std::shared_ptr SP; + + static bool classof(const JSONValue *V) { + return V->getKind() == JSONValue::Kind::Array; + } + +private: + typedef std::vector Vector; + typedef Vector::iterator Iterator; + typedef Vector::size_type Index; + typedef Vector::size_type Size; + +public: + bool setObject(Index I, JSONValue::SP Value); + + bool appendObject(JSONValue::SP Value); + + JSONValue::SP getObject(Index I); + + Size getNumElements(); + + ~JSONArray() override = default; + + Vector Elements; +}; + +class JSONParser { +public: + enum Token { + Invalid, + Error, + ObjectStart, + ObjectEnd, + ArrayStart, + ArrayEnd, + Comma, + Colon, + String, + Integer, + Float, + True, + False, + Null, + EndOfFile + }; + + JSONParser(const char *Cstr); + JSONValue::SP parseJSONValue(); + +protected: + int getEscapedChar(bool &WasEscaped); + Token getToken(std::string &Value); + JSONValue::SP parseJSONObject(); + JSONValue::SP parseJSONArray(); + + void skipSpaces(); + uint8_t decodeHexU8(); + char getChar(); + char peekChar() const; + size_t getBytesLeft() const { return Buffer.size() - Index; } + + std::string Buffer; + size_t Index; +}; +} // namespace llvm + +#endif // LLVM_SUPPORT_JSON_H Index: lib/Support/CMakeLists.txt =================================================================== --- lib/Support/CMakeLists.txt +++ lib/Support/CMakeLists.txt @@ -61,6 +61,7 @@ IntervalMap.cpp IntrusiveRefCntPtr.cpp JamCRC.cpp + JSON.cpp LEB128.cpp LineIterator.cpp Locale.cpp Index: lib/Support/JSON.cpp =================================================================== --- /dev/null +++ lib/Support/JSON.cpp @@ -0,0 +1,595 @@ +//===--------------------- JSON.cpp -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/JSON.h" + +#include +#include + +#include "llvm/Support/ErrorHandling.h" + +using namespace llvm; + +std::string JSONString::jsonStringQuoteMetachars(const std::string &S) { + if (S.find('"') == std::string::npos) + return S; + + std::string Output; + const size_t Size = S.size(); + const char *Chars = S.c_str(); + for (size_t I = 0; I < Size; I++) { + unsigned char Ch = *(Chars + I); + if (Ch == '"') { + Output.push_back('\\'); + } + Output.push_back(Ch); + } + return Output; +} + +JSONString::JSONString() : JSONValue(JSONValue::Kind::String), Data() {} + +JSONString::JSONString(const char *S) + : JSONValue(JSONValue::Kind::String), Data(S ? S : "") {} + +JSONString::JSONString(const std::string &S) + : JSONValue(JSONValue::Kind::String), Data(S) {} + +void JSONString::write(raw_ostream &OS) const { + OS << "\"" << jsonStringQuoteMetachars(Data) << "\""; +} + +uint64_t JSONNumber::getAsUnsigned() const { + switch (TheDataType) { + case DataType::Unsigned: + return Data.Unsigned; + case DataType::Signed: + return (uint64_t)Data.Signed; + case DataType::Double: + return (uint64_t)Data.Double; + } + llvm_unreachable("Unhandled data type"); +} + +int64_t JSONNumber::getAsSigned() const { + switch (TheDataType) { + case DataType::Unsigned: + return (int64_t)Data.Unsigned; + case DataType::Signed: + return Data.Signed; + case DataType::Double: + return (int64_t)Data.Double; + } + llvm_unreachable("Unhandled data type"); +} + +double JSONNumber::getAsDouble() const { + switch (TheDataType) { + case DataType::Unsigned: + return (double)Data.Unsigned; + case DataType::Signed: + return (double)Data.Signed; + case DataType::Double: + return Data.Double; + } + llvm_unreachable("Unhandled data type"); +} + +void JSONNumber::write(raw_ostream &OS) const { + switch (TheDataType) { + case DataType::Unsigned: + OS << Data.Unsigned; + break; + case DataType::Signed: + OS << Data.Signed; + break; + case DataType::Double: + OS << Data.Double; + break; + } +} + +JSONTrue::JSONTrue() : JSONValue(JSONValue::Kind::True) {} + +void JSONTrue::write(raw_ostream &OS) const { OS << "true"; } + +JSONFalse::JSONFalse() : JSONValue(JSONValue::Kind::False) {} + +void JSONFalse::write(raw_ostream &OS) const { OS << "false"; } + +JSONNull::JSONNull() : JSONValue(JSONValue::Kind::Null) {} + +void JSONNull::write(raw_ostream &OS) const { OS << "null"; } + +JSONObject::JSONObject() : JSONValue(JSONValue::Kind::Object) {} + +void JSONObject::write(raw_ostream &OS) const { + bool First = true; + OS << '{'; + auto Iter = Elements.begin(), End = Elements.end(); + for (; Iter != End; Iter++) { + if (First) + First = false; + else + OS << ','; + JSONString Key(Iter->first); + JSONValue::SP Value(Iter->second); + Key.write(OS); + OS << ':'; + Value->write(OS); + } + OS << '}'; +} + +bool JSONObject::setObject(const std::string &Key, JSONValue::SP Value) { + if (Key.empty() || nullptr == Value.get()) + return false; + Elements[Key] = Value; + return true; +} + +JSONValue::SP JSONObject::getObject(const std::string &Key) { + auto Iter = Elements.find(Key), End = Elements.end(); + if (Iter == End) + return JSONValue::SP(); + return Iter->second; +} + +JSONArray::JSONArray() : JSONValue(JSONValue::Kind::Array) {} + +void JSONArray::write(raw_ostream &OS) const { + bool First = true; + OS << '['; + auto Iter = Elements.begin(), End = Elements.end(); + for (; Iter != End; Iter++) { + if (First) + First = false; + else + OS << ','; + (*Iter)->write(OS); + } + OS << ']'; +} + +bool JSONArray::setObject(Index I, JSONValue::SP Value) { + if (Value.get() == nullptr) + return false; + if (I < Elements.size()) { + Elements[I] = Value; + return true; + } + if (I == Elements.size()) { + Elements.push_back(Value); + return true; + } + return false; +} + +bool JSONArray::appendObject(JSONValue::SP Value) { + if (Value.get() == nullptr) + return false; + Elements.push_back(Value); + return true; +} + +JSONValue::SP JSONArray::getObject(Index I) { + if (I < Elements.size()) + return Elements[I]; + return JSONValue::SP(); +} + +JSONArray::Size JSONArray::getNumElements() { return Elements.size(); } + +JSONParser::JSONParser(const char *CStr) : Index(0) { + if (CStr) + Buffer.assign(CStr); +} + +JSONParser::Token JSONParser::getToken(std::string &Value) { + std::stringstream Error; + + Value.clear(); + skipSpaces(); + const uint64_t StartIndex = Index; + const char Ch = getChar(); + switch (Ch) { + case '{': + return Token::ObjectStart; + case '}': + return Token::ObjectEnd; + case '[': + return Token::ArrayStart; + case ']': + return Token::ArrayEnd; + case ',': + return Token::Comma; + case ':': + return Token::Colon; + case '\0': + return Token::EndOfFile; + case 't': + if (getChar() == 'r') + if (getChar() == 'u') + if (getChar() == 'e') + return Token::True; + break; + + case 'f': + if (getChar() == 'a') + if (getChar() == 'l') + if (getChar() == 's') + if (getChar() == 'e') + return Token::False; + break; + + case 'n': + if (getChar() == 'u') + if (getChar() == 'l') + if (getChar() == 'l') + return Token::Null; + break; + + case '"': { + while (1) { + bool WasEscaped = false; + int EscapedCh = getEscapedChar(WasEscaped); + if (EscapedCh == -1) { + Error << "error: an error occurred getting a character from offset " + << StartIndex; + Value = Error.str(); + return Token::Error; + + } else { + const bool IsEndQuote = EscapedCh == '"'; + const bool IsNull = EscapedCh == 0; + if (WasEscaped || (!IsEndQuote && !IsNull)) { + if (CHAR_MIN <= EscapedCh && EscapedCh <= CHAR_MAX) { + Value.append(1, (char)EscapedCh); + } else { + Error << "error: wide character support is needed for unicode " + "character 0x" + << std::hex << EscapedCh << std::dec << " at offset " + << StartIndex; + Value = Error.str(); + return Token::Error; + } + } else if (IsEndQuote) { + return Token::String; + } else if (IsNull) { + Value = "error: missing end quote for string"; + return Token::Error; + } + } + } + } break; + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + uint64_t ExpIndex = 0; + bool Done = false; + bool GotDecimalPoint = false; + bool GotIntDigits = (Ch >= '0') && (Ch <= '9'); + bool GotFracDigits = false; + bool GotExpDigits = false; + while (!Done) { + const char NextCh = peekChar(); + switch (NextCh) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (ExpIndex != 0) { + GotExpDigits = true; + } else if (GotDecimalPoint) { + GotFracDigits = true; + } else { + GotIntDigits = true; + } + ++Index; // Skip this character + break; + + case '.': + if (GotDecimalPoint) { + Error << "error: extra decimal point found at offset " << StartIndex; + Value = Error.str(); + return Token::Error; + } else { + GotDecimalPoint = true; + ++Index; // Skip this character + } + break; + + case 'e': + case 'E': + if (ExpIndex != 0) { + Error << "error: extra exponent character found at offset " + << StartIndex; + Value = Error.str(); + return Token::Error; + } else { + ExpIndex = Index; + ++Index; // Skip this character + } + break; + + case '+': + case '-': + // The '+' and '-' can only come after an exponent character... + if (ExpIndex == Index - 1) { + ++Index; // Skip the exponent sign character + } else { + Error << "error: unexpected " << NextCh << " character at offset " + << StartIndex; + Value = Error.str(); + return Token::Error; + } + break; + + default: + Done = true; + break; + } + } + + if (Index > StartIndex) { + Value = Buffer.substr(StartIndex, Index - StartIndex); + if (GotDecimalPoint) { + if (ExpIndex != 0) { + // We have an exponent, make sure we got exponent digits + if (GotExpDigits) { + return Token::Float; + } else { + Error << "error: got exponent character but no exponent digits at " + "offset in float value \"" + << Value << "\""; + Value = Error.str(); + return Token::Error; + } + } else { + // No exponent, but we need at least one decimal after the decimal + // point + if (GotFracDigits) { + return Token::Float; + } else { + Error << "error: no digits after decimal point \"" << Value << "\""; + Value = Error.str(); + return Token::Error; + } + } + } else { + // No decimal point + if (GotIntDigits) { + // We need at least some integer digits to make an integer + return Token::Integer; + } else { + Error << "error: no digits negate sign \"" << Value << "%s\""; + Value = Error.str(); + return Token::Error; + } + } + } else { + Error << "error: invalid number found at offset " << StartIndex; + Value = Error.str(); + return Token::Error; + } + } break; + default: + break; + } + Error << "error: failed to parse token at offset " << StartIndex + << " (around character '" << Ch << "')"; + Value = Error.str(); + return Token::Error; +} + +int JSONParser::getEscapedChar(bool &WasEscaped) { + WasEscaped = false; + const char Ch = getChar(); + if (Ch == '\\') { + WasEscaped = true; + const char Ch2 = getChar(); + switch (Ch2) { + case '"': + case '\\': + case '/': + default: + break; + + case 'b': + return '\b'; + case 'f': + return '\f'; + case 'n': + return '\n'; + case 'r': + return '\r'; + case 't': + return '\t'; + case 'u': { + const int HiByte = decodeHexU8(); + const int LoByte = decodeHexU8(); + if (HiByte >= 0 && LoByte >= 0) + return HiByte << 8 | LoByte; + return -1; + } break; + } + return Ch2; + } + return Ch; +} + +JSONValue::SP JSONParser::parseJSONObject() { + // The "JSONParser::Token::ObjectStart" token should have already been + // consumed + // by the time this function is called + std::unique_ptr DictUp(new JSONObject()); + + std::string Value; + std::string Key; + while (1) { + JSONParser::Token Token = getToken(Value); + + if (Token == JSONParser::Token::String) { + Key.swap(Value); + Token = getToken(Value); + if (Token == JSONParser::Token::Colon) { + JSONValue::SP ValueSP = parseJSONValue(); + if (ValueSP) + DictUp->setObject(Key, ValueSP); + else + break; + } + } else if (Token == JSONParser::Token::ObjectEnd) { + return JSONValue::SP(DictUp.release()); + } else if (Token == JSONParser::Token::Comma) { + continue; + } else { + break; + } + } + return JSONValue::SP(); +} + +JSONValue::SP JSONParser::parseJSONArray() { + // The "JSONParser::Token::ObjectStart" token should have already been + // consumed + // by the time this function is called + std::unique_ptr ArrayUp(new JSONArray()); + + std::string Value; + while (1) { + skipSpaces(); + + char Peek = peekChar(); + if (Peek == ']' || Peek == ',') { + JSONParser::Token Token = getToken(Value); + if (Token == JSONParser::Token::Comma) { + continue; + } else if (Token == JSONParser::Token::ArrayEnd) { + return JSONValue::SP(ArrayUp.release()); + } + } + + JSONValue::SP ValueSP = parseJSONValue(); + if (ValueSP) + ArrayUp->appendObject(ValueSP); + else + break; + } + return JSONValue::SP(); +} + +JSONValue::SP JSONParser::parseJSONValue() { + std::string Value; + const JSONParser::Token Token = getToken(Value); + switch (Token) { + case JSONParser::Token::ObjectStart: + return parseJSONObject(); + + case JSONParser::Token::ArrayStart: + return parseJSONArray(); + + case JSONParser::Token::Integer: { + char *End = nullptr; + if (Value.front() == '-') { + int64_t SVal = ::strtoll(Value.c_str(), &End, 10); + bool Success = *End == '\0'; // all characters were used. + if (Success) + return JSONValue::SP(new JSONNumber(SVal)); + } else { + uint64_t UVal = ::strtoul(Value.c_str(), &End, 10); + bool Success = *End == '\0'; // all characters were used. + if (Success) + return JSONValue::SP(new JSONNumber(UVal)); + } + } break; + + case JSONParser::Token::Float: { + char *End = nullptr; + double Val = ::strtod(Value.c_str(), &End); + bool Success = *End == '\0'; // all characters were used. + if (Success) + return JSONValue::SP(new JSONNumber(Val)); + } break; + + case JSONParser::Token::String: + return JSONValue::SP(new JSONString(Value)); + + case JSONParser::Token::True: + return JSONValue::SP(new JSONTrue()); + + case JSONParser::Token::False: + return JSONValue::SP(new JSONFalse()); + + case JSONParser::Token::Null: + return JSONValue::SP(new JSONNull()); + + default: + break; + } + return JSONValue::SP(); +} + +void JSONParser::skipSpaces() { + const size_t S = Buffer.size(); + while (Index < S && isspace(Buffer[Index])) + ++Index; +} + +char JSONParser::getChar() { + if (getBytesLeft() < 1) + return 0; + return Buffer[Index++]; +} + +char JSONParser::peekChar() const { + if (getBytesLeft() == 0) + return 0; + + return Buffer[Index]; +} + +static inline int hexDigitToSInt(char Ch) { + if (Ch >= 'a' && Ch <= 'f') + return 10 + Ch - 'a'; + if (Ch >= 'A' && Ch <= 'F') + return 10 + Ch - 'A'; + if (Ch >= '0' && Ch <= '9') + return Ch - '0'; + return -1; +} + +uint8_t JSONParser::decodeHexU8() { + skipSpaces(); + if (getBytesLeft() < 2) { + return -1; + } + const int HiNibble = hexDigitToSInt(Buffer[Index]); + const int LoNibble = hexDigitToSInt(Buffer[Index + 1]); + if (HiNibble == -1 || LoNibble == -1) { + return -1; + } + Index += 2; + return (uint8_t)((HiNibble << 4) + LoNibble); +} Index: unittests/Support/CMakeLists.txt =================================================================== --- unittests/Support/CMakeLists.txt +++ unittests/Support/CMakeLists.txt @@ -19,6 +19,7 @@ ErrorTest.cpp ErrorOrTest.cpp FileOutputBufferTest.cpp + JSONTest.cpp LEB128Test.cpp LineIteratorTest.cpp LockFileManagerTest.cpp Index: unittests/Support/JSONTest.cpp =================================================================== --- /dev/null +++ unittests/Support/JSONTest.cpp @@ -0,0 +1,46 @@ +//===- llvm/unittest/Support/JSONTest.cpp - JSON.cpp tests ---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "gtest/gtest.h" + +#include "llvm/Support/JSON.h" + +using namespace llvm; + +static ::testing::AssertionResult MatchRoundtrip(std::string Text) { + JSONParser Parser(Text.c_str()); + auto Obj = Parser.parseJSONValue(); + + if (!Obj) { + return Text == "null" + ? ::testing::AssertionSuccess() + : ::testing::AssertionFailure() << "can't parse input: " << Text; + } + + std::string S; + raw_string_ostream Out(S); + Obj->write(Out); + + std::string Actual = Out.str(); + if (Actual != Text) { + return ::testing::AssertionFailure() << "expected: " << Text + << " actual: " << Actual; + } + return ::testing::AssertionSuccess(); +} + +TEST(JSON, Roundtrip) { + EXPECT_TRUE(MatchRoundtrip("0")); + EXPECT_TRUE(MatchRoundtrip("3.145150e+00")); + EXPECT_TRUE(MatchRoundtrip("{}")); + EXPECT_TRUE(MatchRoundtrip("{\"a\":1,\"b\":2}")); + EXPECT_TRUE(MatchRoundtrip("[]")); + EXPECT_TRUE(MatchRoundtrip("[0]")); + EXPECT_TRUE(MatchRoundtrip("[1,\"two\",3]")); +}