#include "cepis/json.hpp" #include #include #include #include #include namespace cepis { namespace { const std::string& empty_string() { static const std::string value; return value; } const Json::Array& empty_array() { static const Json::Array value; return value; } const Json::Object& empty_object() { static const Json::Object value; return value; } void append_utf8(std::string& out, std::uint32_t code_point) { if (code_point <= 0x7F) { out.push_back(static_cast(code_point)); } else if (code_point <= 0x7FF) { out.push_back(static_cast(0xC0 | (code_point >> 6))); out.push_back(static_cast(0x80 | (code_point & 0x3F))); } else if (code_point <= 0xFFFF) { out.push_back(static_cast(0xE0 | (code_point >> 12))); out.push_back(static_cast(0x80 | ((code_point >> 6) & 0x3F))); out.push_back(static_cast(0x80 | (code_point & 0x3F))); } else { out.push_back(static_cast(0xF0 | (code_point >> 18))); out.push_back(static_cast(0x80 | ((code_point >> 12) & 0x3F))); out.push_back(static_cast(0x80 | ((code_point >> 6) & 0x3F))); out.push_back(static_cast(0x80 | (code_point & 0x3F))); } } class Parser { public: explicit Parser(std::string_view text) : text_(text) {} std::optional run() { skip_whitespace(); std::optional value = parse_value(0); if (!value) { return std::nullopt; } skip_whitespace(); if (pos_ != text_.size()) { return std::nullopt; } return value; } private: static constexpr int kMaxDepth = 64; [[nodiscard]] bool at_end() const { return pos_ >= text_.size(); } [[nodiscard]] char peek() const { return text_[pos_]; } void skip_whitespace() { while (!at_end()) { const char c = peek(); if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { ++pos_; } else { break; } } } bool consume(char expected) { if (at_end() || peek() != expected) { return false; } ++pos_; return true; } bool consume(std::string_view literal) { if (text_.compare(pos_, literal.size(), literal) != 0) { return false; } pos_ += literal.size(); return true; } std::optional parse_value(int depth) { if (depth > kMaxDepth || at_end()) { return std::nullopt; } switch (peek()) { case '{': return parse_object(depth); case '[': return parse_array(depth); case '"': { std::optional text = parse_string(); if (!text) { return std::nullopt; } return Json::string(std::move(*text)); } case 't': return consume("true") ? std::optional{Json::boolean(true)} : std::nullopt; case 'f': return consume("false") ? std::optional{Json::boolean(false)} : std::nullopt; case 'n': return consume("null") ? std::optional{Json{}} : std::nullopt; default: return parse_number(); } } std::optional parse_object(int depth) { ++pos_; Json result = Json::object(); skip_whitespace(); if (consume('}')) { return result; } while (true) { skip_whitespace(); if (at_end() || peek() != '"') { return std::nullopt; } std::optional key = parse_string(); if (!key) { return std::nullopt; } skip_whitespace(); if (!consume(':')) { return std::nullopt; } skip_whitespace(); std::optional value = parse_value(depth + 1); if (!value) { return std::nullopt; } result.set(std::move(*key), std::move(*value)); skip_whitespace(); if (consume(',')) { continue; } if (consume('}')) { return result; } return std::nullopt; } } std::optional parse_array(int depth) { ++pos_; Json result = Json::array(); skip_whitespace(); if (consume(']')) { return result; } while (true) { skip_whitespace(); std::optional value = parse_value(depth + 1); if (!value) { return std::nullopt; } result.push_back(std::move(*value)); skip_whitespace(); if (consume(',')) { continue; } if (consume(']')) { return result; } return std::nullopt; } } std::optional parse_string() { ++pos_; std::string out; while (true) { if (at_end()) { return std::nullopt; } const char c = text_[pos_++]; if (c == '"') { return out; } if (static_cast(c) < 0x20) { return std::nullopt; } if (c != '\\') { out.push_back(c); continue; } if (at_end()) { return std::nullopt; } const char escape = text_[pos_++]; switch (escape) { case '"': out.push_back('"'); break; case '\\': out.push_back('\\'); break; case '/': out.push_back('/'); break; case 'b': out.push_back('\b'); break; case 'f': out.push_back('\f'); break; case 'n': out.push_back('\n'); break; case 'r': out.push_back('\r'); break; case 't': out.push_back('\t'); break; case 'u': { std::optional unit = parse_hex4(); if (!unit) { return std::nullopt; } std::uint32_t code_point = *unit; if (code_point >= 0xD800 && code_point <= 0xDBFF) { if (text_.compare(pos_, 2, "\\u") == 0) { const std::size_t saved = pos_; pos_ += 2; std::optional low = parse_hex4(); if (low && *low >= 0xDC00 && *low <= 0xDFFF) { code_point = 0x10000 + ((code_point - 0xD800) << 10) + (*low - 0xDC00); } else { pos_ = saved; code_point = 0xFFFD; } } else { code_point = 0xFFFD; } } else if (code_point >= 0xDC00 && code_point <= 0xDFFF) { code_point = 0xFFFD; } append_utf8(out, code_point); break; } default: return std::nullopt; } } } std::optional parse_hex4() { if (pos_ + 4 > text_.size()) { return std::nullopt; } std::uint32_t value = 0; for (int i = 0; i < 4; ++i) { const char c = text_[pos_++]; value <<= 4; if (c >= '0' && c <= '9') { value |= static_cast(c - '0'); } else if (c >= 'a' && c <= 'f') { value |= static_cast(c - 'a' + 10); } else if (c >= 'A' && c <= 'F') { value |= static_cast(c - 'A' + 10); } else { return std::nullopt; } } return value; } std::optional parse_number() { const std::size_t start = pos_; if (!at_end() && peek() == '-') { ++pos_; } if (at_end() || !is_digit(peek())) { return std::nullopt; } if (peek() == '0') { ++pos_; } else { while (!at_end() && is_digit(peek())) { ++pos_; } } if (!at_end() && peek() == '.') { ++pos_; if (at_end() || !is_digit(peek())) { return std::nullopt; } while (!at_end() && is_digit(peek())) { ++pos_; } } if (!at_end() && (peek() == 'e' || peek() == 'E')) { ++pos_; if (!at_end() && (peek() == '+' || peek() == '-')) { ++pos_; } if (at_end() || !is_digit(peek())) { return std::nullopt; } while (!at_end() && is_digit(peek())) { ++pos_; } } const std::string literal(text_.substr(start, pos_ - start)); return Json::number(std::strtod(literal.c_str(), nullptr)); } static bool is_digit(char c) { return c >= '0' && c <= '9'; } std::string_view text_; std::size_t pos_ = 0; }; void dump_string(const std::string& value, std::string& out) { out.push_back('"'); for (const char c : value) { 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 (static_cast(c) < 0x20) { char buffer[7]; std::snprintf(buffer, sizeof(buffer), "\\u%04x", static_cast(c)); out += buffer; } else { out.push_back(c); } } } out.push_back('"'); } void dump_number(double value, std::string& out) { if (!std::isfinite(value)) { out += "null"; return; } if (value == static_cast(static_cast(value)) && std::fabs(value) < 9.0e15) { out += std::to_string(static_cast(value)); return; } char buffer[40]; for (const int digits : {15, 16, 17}) { std::snprintf(buffer, sizeof(buffer), "%.*g", digits, value); if (std::strtod(buffer, nullptr) == value) { break; } } out += buffer; } void dump_value(const Json& value, std::string& out) { switch (value.kind()) { case Json::Kind::Null: out += "null"; break; case Json::Kind::Bool: out += value.as_bool() ? "true" : "false"; break; case Json::Kind::Number: dump_number(value.as_number(), out); break; case Json::Kind::String: dump_string(value.as_string(), out); break; case Json::Kind::Array: { out.push_back('['); bool first = true; for (const Json& element : value.as_array()) { if (!first) { out.push_back(','); } first = false; dump_value(element, out); } out.push_back(']'); break; } case Json::Kind::Object: { out.push_back('{'); bool first = true; for (const auto& [key, member] : value.as_object()) { if (!first) { out.push_back(','); } first = false; dump_string(key, out); out.push_back(':'); dump_value(member, out); } out.push_back('}'); break; } } } } // namespace Json Json::boolean(bool value) { Json result; result.kind_ = Kind::Bool; result.bool_ = value; return result; } Json Json::number(double value) { Json result; result.kind_ = Kind::Number; result.number_ = value; return result; } Json Json::string(std::string value) { Json result; result.kind_ = Kind::String; result.string_ = std::move(value); return result; } Json Json::array(Array value) { Json result; result.kind_ = Kind::Array; result.array_ = std::move(value); return result; } Json Json::object(Object value) { Json result; result.kind_ = Kind::Object; result.object_ = std::move(value); return result; } std::optional Json::parse(std::string_view text) { return Parser(text).run(); } std::string Json::dump() const { std::string out; dump_value(*this, out); return out; } bool Json::as_bool(bool fallback) const noexcept { return kind_ == Kind::Bool ? bool_ : fallback; } double Json::as_number(double fallback) const noexcept { return kind_ == Kind::Number ? number_ : fallback; } const std::string& Json::as_string() const noexcept { return kind_ == Kind::String ? string_ : empty_string(); } const Json::Array& Json::as_array() const noexcept { return kind_ == Kind::Array ? array_ : empty_array(); } const Json::Object& Json::as_object() const noexcept { return kind_ == Kind::Object ? object_ : empty_object(); } const Json* Json::find(std::string_view key) const noexcept { if (kind_ != Kind::Object) { return nullptr; } for (const auto& member : object_) { if (member.first == key) { return &member.second; } } return nullptr; } void Json::set(std::string key, Json value) { if (kind_ != Kind::Object) { kind_ = Kind::Object; object_.clear(); } for (auto& member : object_) { if (member.first == key) { member.second = std::move(value); return; } } object_.emplace_back(std::move(key), std::move(value)); } void Json::push_back(Json value) { if (kind_ != Kind::Array) { kind_ = Kind::Array; array_.clear(); } array_.push_back(std::move(value)); } } // namespace cepis