| // Copyright 2024 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef BAZEL_TOOLS_CPP_MODULE_TOOLS_COMMON_HSON_HPP_ |
| #define BAZEL_TOOLS_CPP_MODULE_TOOLS_COMMON_HSON_HPP_ |
| |
| #include <algorithm> |
| #include <any> |
| #include <cmath> |
| #include <iomanip> |
| #include <iostream> |
| #include <map> |
| #include <optional> |
| #include <sstream> |
| #include <stdexcept> |
| #include <string> |
| #include <variant> |
| #include <vector> |
| // forward decl |
| struct JsonValue; |
| inline std::string to_json(const JsonValue &data); |
| |
| // Define a basic struct for JSON values |
| struct JsonValue { |
| using ObjectType = std::map<std::string, JsonValue>; |
| using ArrayType = std::vector<JsonValue>; |
| |
| std::variant<std::string, bool, long, double, ObjectType, ArrayType, |
| std::nullptr_t> |
| value; |
| |
| JsonValue() : value(nullptr) {} |
| JsonValue(const std::string &v) : value(v) {} |
| JsonValue(const char *v) : value(std::string(v)) {} |
| JsonValue(bool v) : value(v) {} |
| JsonValue(long v) : value(v) {} |
| JsonValue(int v) : value((long)v) {} |
| JsonValue(double v) : value(v) {} |
| JsonValue(const ObjectType &v) : value(v) {} |
| JsonValue(const ArrayType &v) : value(v) {} |
| JsonValue(std::nullptr_t) : value(nullptr) {} |
| |
| bool is_null() const { return std::holds_alternative<std::nullptr_t>(value); } |
| bool is_string() const { return std::holds_alternative<std::string>(value); } |
| bool is_object() const { return std::holds_alternative<ObjectType>(value); } |
| bool is_array() const { return std::holds_alternative<ArrayType>(value); } |
| bool is_bool() const { return std::holds_alternative<bool>(value); } |
| bool is_long() const { return std::holds_alternative<long>(value); } |
| bool is_double() const { return std::holds_alternative<double>(value); } |
| |
| const std::string &as_string() const { return std::get<std::string>(value); } |
| const ObjectType &as_object() const { return std::get<ObjectType>(value); } |
| const ArrayType &as_array() const { return std::get<ArrayType>(value); } |
| bool as_bool() const { return std::get<bool>(value); } |
| long as_long() const { return std::get<long>(value); } |
| double as_double() const { return std::get<double>(value); } |
| |
| // Implement equality operator |
| bool operator==(const JsonValue &other) const { |
| if (value.index() != other.value.index()) return false; |
| |
| if (is_null()) return true; |
| if (is_string()) return as_string() == other.as_string(); |
| if (is_bool()) return as_bool() == other.as_bool(); |
| if (is_long()) return as_long() == other.as_long(); |
| if (is_double()) return as_double() == other.as_double(); |
| if (is_object()) return as_object() == other.as_object(); |
| if (is_array()) return as_array() == other.as_array(); |
| |
| return false; |
| } |
| |
| bool operator!=(const JsonValue &other) const { return !(*this == other); } |
| |
| std::string dump() const { return to_json(*this); } |
| }; |
| |
| // Define the JSON parser class |
| class Json { |
| public: |
| // Singleton instance |
| static Json &instance() { |
| static Json INSTANCE; |
| return INSTANCE; |
| } |
| |
| // Function to encode an object to JSON |
| std::string encode(const JsonValue &x) const { |
| Encoder enc; |
| try { |
| enc.encode(x); |
| } catch (const std::overflow_error &e) { |
| throw std::runtime_error("nesting depth limit exceeded"); |
| } |
| return enc.out.str(); |
| } |
| |
| // Function to decode a JSON string to an object |
| JsonValue decode(const std::string &x) const { |
| try { |
| return Decoder(x).decode(); |
| } catch (const std::runtime_error &e) { |
| throw std::runtime_error("Invalid JSON string"); |
| } |
| } |
| |
| private: |
| Json() {} |
| |
| // Encoder class to serialize objects to JSON |
| class Encoder { |
| public: |
| void encode(const JsonValue &x) { |
| if (x.is_null()) { |
| out << "null"; |
| return; |
| } |
| if (x.is_string()) { |
| append_quoted(x.as_string()); |
| return; |
| } |
| if (x.is_bool()) { |
| out << std::boolalpha << x.as_bool(); |
| return; |
| } |
| if (x.is_long()) { |
| out << x.as_long(); |
| return; |
| } |
| if (x.is_double()) { |
| double d = x.as_double(); |
| if (!std::isfinite(d)) { |
| throw std::runtime_error("Cannot encode non-finite float"); |
| } |
| out << std::setprecision(std::numeric_limits<double>::digits10) << d; |
| return; |
| } |
| if (x.is_object()) { |
| const auto &m = x.as_object(); |
| out << '{'; |
| std::string sep = ""; |
| for (const auto &item : m) { |
| out << sep; |
| sep = ","; |
| append_quoted(item.first); |
| out << ':'; |
| encode(item.second); |
| } |
| out << '}'; |
| return; |
| } |
| if (x.is_array()) { |
| const auto &v = x.as_array(); |
| out << '['; |
| std::string sep = ""; |
| for (const auto &value : v) { |
| out << sep; |
| sep = ","; |
| encode(value); |
| } |
| out << ']'; |
| return; |
| } |
| // Add more cases for other types as needed |
| throw std::runtime_error("Cannot encode value as JSON"); |
| } |
| |
| std::ostringstream out; |
| |
| private: |
| void append_quoted(const std::string &s) { |
| out << '"'; |
| for (char c : s) { |
| switch (c) { |
| case '"': |
| out << "\\\""; |
| break; |
| case '\\': |
| out << "\\\\"; |
| break; |
| case '\b': |
| out << "\\b"; |
| break; |
| case '\f': |
| out << "\\f"; |
| break; |
| case '\n': |
| out << "\\n"; |
| break; |
| case '\r': |
| out << "\\r"; |
| break; |
| case '\t': |
| out << "\\t"; |
| break; |
| default: |
| if ('\x00' <= c && c <= '\x1f') { |
| out << "\\u" << std::hex << std::setw(4) << std::setfill('0') |
| << static_cast<long>(c); |
| } else { |
| out << c; |
| } |
| } |
| } |
| out << '"'; |
| } |
| }; |
| |
| // Decoder class to parse JSON strings to objects |
| class Decoder { |
| public: |
| Decoder(const std::string &s) : s(s), i(0) {} |
| |
| JsonValue decode() { |
| auto x = parse(); |
| if (skip_space()) { |
| throw std::runtime_error("Unexpected character after value"); |
| } |
| return x; |
| } |
| |
| private: |
| std::string s; |
| size_t i; |
| |
| JsonValue parse() { |
| char c = next(); |
| switch (c) { |
| case '"': |
| return parse_string(); |
| case 'n': |
| if (s.substr(i, 4) == "null") { |
| i += 4; |
| return nullptr; |
| } |
| break; |
| case 't': |
| if (s.substr(i, 4) == "true") { |
| i += 4; |
| return true; |
| } |
| break; |
| case 'f': |
| if (s.substr(i, 5) == "false") { |
| i += 5; |
| return false; |
| } |
| break; |
| case '[': |
| return parse_array(); |
| case '{': |
| return parse_object(); |
| default: |
| if (isdigit(c) || c == '-') { |
| return parse_number(c); |
| } |
| } |
| throw std::runtime_error("Unexpected character"); |
| } |
| |
| JsonValue parse_string() { |
| i++; // skip " |
| std::ostringstream str; |
| while (i < s.size()) { |
| char c = s[i++]; |
| if (c == '"') return str.str(); |
| if (c == '\\') { |
| c = s[i++]; |
| switch (c) { |
| case 'b': |
| str << '\b'; |
| break; |
| case 'f': |
| str << '\f'; |
| break; |
| case 'n': |
| str << '\n'; |
| break; |
| case 'r': |
| str << '\r'; |
| break; |
| case 't': |
| str << '\t'; |
| break; |
| case 'u': |
| // Handle \uXXXX |
| str << static_cast<char>(std::stoi(s.substr(i, 4), nullptr, 16)); |
| i += 4; |
| break; |
| default: |
| str << c; |
| } |
| } else { |
| str << c; |
| } |
| } |
| throw std::runtime_error("Unclosed string literal"); |
| } |
| |
| JsonValue parse_array() { |
| JsonValue::ArrayType array; |
| i++; // skip [ |
| if (next() != ']') { |
| while (true) { |
| array.push_back(parse()); |
| char c = next(); |
| if (c != ',') { |
| if (c != ']') throw std::runtime_error("Expected ',' or ']'"); |
| break; |
| } |
| i++; // skip , |
| } |
| } |
| i++; // skip ] |
| return array; |
| } |
| |
| JsonValue parse_object() { |
| JsonValue::ObjectType object; |
| i++; // skip { |
| if (next() != '}') { |
| while (true) { |
| std::string key = std::get<std::string>(parse().value); |
| if (next() != ':') throw std::runtime_error("Expected ':'"); |
| i++; // skip : |
| object[key] = parse(); |
| char c = next(); |
| if (c != ',') { |
| if (c != '}') throw std::runtime_error("Expected ',' or '}'"); |
| break; |
| } |
| i++; // skip , |
| } |
| } |
| i++; // skip } |
| return object; |
| } |
| |
| JsonValue parse_number(char c) { |
| size_t j = i + 1; |
| bool isfloat = false; |
| while (j < s.size()) { |
| c = s[j]; |
| if (isdigit(c) || c == '-' || c == '+' || c == '.' || c == 'e' || |
| c == 'E') { |
| if (c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-') |
| isfloat = true; |
| j++; |
| } else { |
| break; |
| } |
| } |
| std::string num = s.substr(i, j - i); |
| i = j; |
| std::istringstream iss(num); |
| if (isfloat) { |
| double d; |
| iss >> d; |
| return d; |
| } else { |
| long n; |
| iss >> n; |
| return n; |
| } |
| } |
| |
| bool skip_space() { |
| while (i < s.size() && isspace(s[i])) { |
| i++; |
| } |
| return i < s.size(); |
| } |
| |
| char next() { |
| if (skip_space()) { |
| return s[i]; |
| } |
| throw std::runtime_error("Unexpected end of input"); |
| } |
| }; |
| }; |
| |
| inline JsonValue parse_json(const std::string &data) { |
| Json &json = Json::instance(); |
| return json.decode(data); |
| } |
| |
| inline std::string to_json(const JsonValue &data) { |
| Json &json = Json::instance(); |
| return json.encode(data); |
| } |
| |
| #endif //BAZEL_TOOLS_CPP_MODULE_TOOLS_COMMON_HSON_HPP_ |