| //===--------------------- JSON.cpp -----------------------------*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "JSON.h" | |
| // C includes | |
| #include <cassert> | |
| #include <climits> | |
| // C++ includes | |
| #include "StringConvert.h" | |
| #include <iomanip> | |
| #include <sstream> | |
| std::string JSONString::json_string_quote_metachars(const std::string &s) { | |
| if (s.find('"') == std::string::npos) | |
| return s; | |
| std::string output; | |
| const size_t s_size = s.size(); | |
| const char *s_chars = s.c_str(); | |
| for (size_t i = 0; i < s_size; i++) { | |
| unsigned char ch = *(s_chars + i); | |
| if (ch == '"') { | |
| output.push_back('\\'); | |
| } | |
| output.push_back(ch); | |
| } | |
| return output; | |
| } | |
| JSONString::JSONString() : JSONValue(JSONValue::Kind::String), m_data() {} | |
| JSONString::JSONString(const char *s) | |
| : JSONValue(JSONValue::Kind::String), m_data(s ? s : "") {} | |
| JSONString::JSONString(const std::string &s) | |
| : JSONValue(JSONValue::Kind::String), m_data(s) {} | |
| void JSONString::Write(std::ostream &s) { | |
| s << "\"" << json_string_quote_metachars(m_data) << "\""; | |
| } | |
| uint64_t JSONNumber::GetAsUnsigned() const { | |
| switch (m_data_type) { | |
| case DataType::Unsigned: | |
| return m_data.m_unsigned; | |
| case DataType::Signed: | |
| return (uint64_t)m_data.m_signed; | |
| case DataType::Double: | |
| return (uint64_t)m_data.m_double; | |
| } | |
| } | |
| int64_t JSONNumber::GetAsSigned() const { | |
| switch (m_data_type) { | |
| case DataType::Unsigned: | |
| return (int64_t)m_data.m_unsigned; | |
| case DataType::Signed: | |
| return m_data.m_signed; | |
| case DataType::Double: | |
| return (int64_t)m_data.m_double; | |
| } | |
| } | |
| double JSONNumber::GetAsDouble() const { | |
| switch (m_data_type) { | |
| case DataType::Unsigned: | |
| return (double)m_data.m_unsigned; | |
| case DataType::Signed: | |
| return (double)m_data.m_signed; | |
| case DataType::Double: | |
| return m_data.m_double; | |
| } | |
| } | |
| void JSONNumber::Write(std::ostream &s) { | |
| switch (m_data_type) { | |
| case DataType::Unsigned: | |
| s << m_data.m_unsigned; | |
| break; | |
| case DataType::Signed: | |
| s << m_data.m_signed; | |
| break; | |
| case DataType::Double: | |
| // Set max precision to emulate %g. | |
| s << std::setprecision(std::numeric_limits<double>::digits10 + 1); | |
| s << m_data.m_double; | |
| break; | |
| } | |
| } | |
| JSONTrue::JSONTrue() : JSONValue(JSONValue::Kind::True) {} | |
| void JSONTrue::Write(std::ostream &s) { s << "true"; } | |
| JSONFalse::JSONFalse() : JSONValue(JSONValue::Kind::False) {} | |
| void JSONFalse::Write(std::ostream &s) { s << "false"; } | |
| JSONNull::JSONNull() : JSONValue(JSONValue::Kind::Null) {} | |
| void JSONNull::Write(std::ostream &s) { s << "null"; } | |
| JSONObject::JSONObject() : JSONValue(JSONValue::Kind::Object) {} | |
| void JSONObject::Write(std::ostream &s) { | |
| bool first = true; | |
| s << '{'; | |
| auto iter = m_elements.begin(), end = m_elements.end(); | |
| for (; iter != end; iter++) { | |
| if (first) | |
| first = false; | |
| else | |
| s << ','; | |
| JSONString key(iter->first); | |
| JSONValue::SP value(iter->second); | |
| key.Write(s); | |
| s << ':'; | |
| value->Write(s); | |
| } | |
| s << '}'; | |
| } | |
| bool JSONObject::SetObject(const std::string &key, JSONValue::SP value) { | |
| if (key.empty() || nullptr == value.get()) | |
| return false; | |
| m_elements[key] = value; | |
| return true; | |
| } | |
| JSONValue::SP JSONObject::GetObject(const std::string &key) const { | |
| auto iter = m_elements.find(key), end = m_elements.end(); | |
| if (iter == end) | |
| return JSONValue::SP(); | |
| return iter->second; | |
| } | |
| bool JSONObject::GetObjectAsBool(const std::string &key, bool &value) const { | |
| auto value_sp = GetObject(key); | |
| if (!value_sp) { | |
| // The given key doesn't exist, so we have no value. | |
| return false; | |
| } | |
| if (JSONTrue::classof(value_sp.get())) { | |
| // We have the value, and it is true. | |
| value = true; | |
| return true; | |
| } else if (JSONFalse::classof(value_sp.get())) { | |
| // We have the value, and it is false. | |
| value = false; | |
| return true; | |
| } else { | |
| // We don't have a valid bool value for the given key. | |
| return false; | |
| } | |
| } | |
| bool JSONObject::GetObjectAsString(const std::string &key, | |
| std::string &value) const { | |
| auto value_sp = GetObject(key); | |
| if (!value_sp) { | |
| // The given key doesn't exist, so we have no value. | |
| return false; | |
| } | |
| if (!JSONString::classof(value_sp.get())) | |
| return false; | |
| value = static_cast<JSONString *>(value_sp.get())->GetData(); | |
| return true; | |
| } | |
| JSONArray::JSONArray() : JSONValue(JSONValue::Kind::Array) {} | |
| void JSONArray::Write(std::ostream &s) { | |
| bool first = true; | |
| s << '['; | |
| auto iter = m_elements.begin(), end = m_elements.end(); | |
| for (; iter != end; iter++) { | |
| if (first) | |
| first = false; | |
| else | |
| s << ','; | |
| (*iter)->Write(s); | |
| } | |
| s << ']'; | |
| } | |
| bool JSONArray::SetObject(Index i, JSONValue::SP value) { | |
| if (value.get() == nullptr) | |
| return false; | |
| if (i < m_elements.size()) { | |
| m_elements[i] = value; | |
| return true; | |
| } | |
| if (i == m_elements.size()) { | |
| m_elements.push_back(value); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool JSONArray::AppendObject(JSONValue::SP value) { | |
| if (value.get() == nullptr) | |
| return false; | |
| m_elements.push_back(value); | |
| return true; | |
| } | |
| JSONValue::SP JSONArray::GetObject(Index i) { | |
| if (i < m_elements.size()) | |
| return m_elements[i]; | |
| return JSONValue::SP(); | |
| } | |
| JSONArray::Size JSONArray::GetNumElements() { return m_elements.size(); } | |
| JSONParser::JSONParser(const char *cstr) : StdStringExtractor(cstr) {} | |
| JSONParser::Token JSONParser::GetToken(std::string &value) { | |
| std::ostringstream error; | |
| value.clear(); | |
| SkipSpaces(); | |
| const uint64_t start_index = m_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 (true) { | |
| bool was_escaped = false; | |
| int escaped_ch = GetEscapedChar(was_escaped); | |
| if (escaped_ch == -1) { | |
| error << "error: an error occurred getting a character from offset " | |
| << start_index; | |
| value = error.str(); | |
| return Token::Status; | |
| } else { | |
| const bool is_end_quote = escaped_ch == '"'; | |
| const bool is_null = escaped_ch == 0; | |
| if (was_escaped || (!is_end_quote && !is_null)) { | |
| if (CHAR_MIN <= escaped_ch && escaped_ch <= CHAR_MAX) { | |
| value.append(1, (char)escaped_ch); | |
| } else { | |
| error << "error: wide character support is needed for unicode " | |
| "character 0x" | |
| << std::setprecision(4) << std::hex << escaped_ch; | |
| error << " at offset " << start_index; | |
| value = error.str(); | |
| return Token::Status; | |
| } | |
| } else if (is_end_quote) { | |
| return Token::String; | |
| } else if (is_null) { | |
| value = "error: missing end quote for string"; | |
| return Token::Status; | |
| } | |
| } | |
| } | |
| } break; | |
| case '-': | |
| case '0': | |
| case '1': | |
| case '2': | |
| case '3': | |
| case '4': | |
| case '5': | |
| case '6': | |
| case '7': | |
| case '8': | |
| case '9': { | |
| bool done = false; | |
| bool got_decimal_point = false; | |
| uint64_t exp_index = 0; | |
| bool got_int_digits = (ch >= '0') && (ch <= '9'); | |
| bool got_frac_digits = false; | |
| bool got_exp_digits = false; | |
| while (!done) { | |
| const char next_ch = PeekChar(); | |
| switch (next_ch) { | |
| case '0': | |
| case '1': | |
| case '2': | |
| case '3': | |
| case '4': | |
| case '5': | |
| case '6': | |
| case '7': | |
| case '8': | |
| case '9': | |
| if (exp_index != 0) { | |
| got_exp_digits = true; | |
| } else if (got_decimal_point) { | |
| got_frac_digits = true; | |
| } else { | |
| got_int_digits = true; | |
| } | |
| ++m_index; // Skip this character | |
| break; | |
| case '.': | |
| if (got_decimal_point) { | |
| error << "error: extra decimal point found at offset " << start_index; | |
| value = error.str(); | |
| return Token::Status; | |
| } else { | |
| got_decimal_point = true; | |
| ++m_index; // Skip this character | |
| } | |
| break; | |
| case 'e': | |
| case 'E': | |
| if (exp_index != 0) { | |
| error << "error: extra exponent character found at offset " | |
| << start_index; | |
| value = error.str(); | |
| return Token::Status; | |
| } else { | |
| exp_index = m_index; | |
| ++m_index; // Skip this character | |
| } | |
| break; | |
| case '+': | |
| case '-': | |
| // The '+' and '-' can only come after an exponent character... | |
| if (exp_index == m_index - 1) { | |
| ++m_index; // Skip the exponent sign character | |
| } else { | |
| error << "error: unexpected " << next_ch << " character at offset " | |
| << start_index; | |
| value = error.str(); | |
| return Token::Status; | |
| } | |
| break; | |
| default: | |
| done = true; | |
| break; | |
| } | |
| } | |
| if (m_index > start_index) { | |
| value = m_packet.substr(start_index, m_index - start_index); | |
| if (got_decimal_point) { | |
| if (exp_index != 0) { | |
| // We have an exponent, make sure we got exponent digits | |
| if (got_exp_digits) { | |
| return Token::Float; | |
| } else { | |
| error << "error: got exponent character but no exponent digits at " | |
| "offset in float value \"" | |
| << value << "\""; | |
| value = error.str(); | |
| return Token::Status; | |
| } | |
| } else { | |
| // No exponent, but we need at least one decimal after the decimal | |
| // point | |
| if (got_frac_digits) { | |
| return Token::Float; | |
| } else { | |
| error << "error: no digits after decimal point \"" << value << "\""; | |
| value = error.str(); | |
| return Token::Status; | |
| } | |
| } | |
| } else { | |
| // No decimal point | |
| if (got_int_digits) { | |
| // We need at least some integer digits to make an integer | |
| return Token::Integer; | |
| } else { | |
| error << "error: no digits negate sign \"" << value << "\""; | |
| value = error.str(); | |
| return Token::Status; | |
| } | |
| } | |
| } else { | |
| error << "error: invalid number found at offset " << start_index; | |
| value = error.str(); | |
| return Token::Status; | |
| } | |
| } break; | |
| default: | |
| break; | |
| } | |
| error << "error: failed to parse token at offset " << start_index | |
| << " (around character '" << ch << "')"; | |
| value = error.str(); | |
| return Token::Status; | |
| } | |
| int JSONParser::GetEscapedChar(bool &was_escaped) { | |
| was_escaped = false; | |
| const char ch = GetChar(); | |
| if (ch == '\\') { | |
| was_escaped = 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 hi_byte = DecodeHexU8(); | |
| const int lo_byte = DecodeHexU8(); | |
| if (hi_byte >= 0 && lo_byte >= 0) | |
| return hi_byte << 8 | lo_byte; | |
| 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<JSONObject> dict_up(new JSONObject()); | |
| std::string value; | |
| std::string key; | |
| while (true) { | |
| JSONParser::Token token = GetToken(value); | |
| if (token == JSONParser::Token::String) { | |
| key.swap(value); | |
| token = GetToken(value); | |
| if (token == JSONParser::Token::Colon) { | |
| JSONValue::SP value_sp = ParseJSONValue(); | |
| if (value_sp) | |
| dict_up->SetObject(key, value_sp); | |
| else | |
| break; | |
| } | |
| } else if (token == JSONParser::Token::ObjectEnd) { | |
| return JSONValue::SP(dict_up.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<JSONArray> array_up(new JSONArray()); | |
| std::string value; | |
| std::string key; | |
| while (true) { | |
| JSONParser::Token token = GetToken(value); | |
| if (token == JSONParser::Token::ArrayEnd) | |
| return JSONValue::SP(array_up.release()); | |
| JSONValue::SP value_sp = ParseJSONValue(value, token); | |
| if (value_sp) | |
| array_up->AppendObject(value_sp); | |
| else | |
| break; | |
| token = GetToken(value); | |
| if (token == JSONParser::Token::Comma) { | |
| continue; | |
| } else if (token == JSONParser::Token::ArrayEnd) { | |
| return JSONValue::SP(array_up.release()); | |
| } else { | |
| break; | |
| } | |
| } | |
| return JSONValue::SP(); | |
| } | |
| JSONValue::SP JSONParser::ParseJSONValue() { | |
| std::string value; | |
| const JSONParser::Token token = GetToken(value); | |
| return ParseJSONValue(value, token); | |
| } | |
| JSONValue::SP JSONParser::ParseJSONValue(const std::string &value, | |
| const Token &token) { | |
| switch (token) { | |
| case JSONParser::Token::ObjectStart: | |
| return ParseJSONObject(); | |
| case JSONParser::Token::ArrayStart: | |
| return ParseJSONArray(); | |
| case JSONParser::Token::Integer: { | |
| if (value.front() == '-') { | |
| bool success = false; | |
| int64_t sval = StringConvert::ToSInt64(value.c_str(), 0, 0, &success); | |
| if (success) | |
| return JSONValue::SP(new JSONNumber(sval)); | |
| } else { | |
| bool success = false; | |
| uint64_t uval = StringConvert::ToUInt64(value.c_str(), 0, 0, &success); | |
| if (success) | |
| return JSONValue::SP(new JSONNumber(uval)); | |
| } | |
| } break; | |
| case JSONParser::Token::Float: { | |
| bool success = false; | |
| double val = StringConvert::ToDouble(value.c_str(), 0.0, &success); | |
| 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(); | |
| } |