// core/json implementation — see json.h. Any behavioral change here changes // every persisted-blob parser that shares this lexical layer at once. #include "core/json/json.h" #include #include #include #include namespace reasampler::json { // -- emit helpers ------------------------------------------------------- void writeEscaped(std::string& out, 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 (static_cast(c) < 0x20) { char buf[8]; std::snprintf(buf, sizeof(buf), "\\u%04x", static_cast(c)); out += buf; } else { out += c; } } } out += '"'; } std::string numToStr(double v) { char buf[32]; std::snprintf(buf, sizeof(buf), "%.17g", v); return buf; } std::string numToStr(std::int64_t v) { char buf[32]; std::snprintf(buf, sizeof(buf), "%lld", static_cast(v)); return buf; } std::string numToStr(int v) { char buf[16]; std::snprintf(buf, sizeof(buf), "%d", v); return buf; } void writeStringArray(std::string& out, const std::vector& v) { out += '['; for (std::size_t i = 0; i < v.size(); ++i) { if (i) out += ','; writeEscaped(out, v[i]); } out += ']'; } void writeIntArray(std::string& out, const std::vector& v) { out += '['; for (std::size_t i = 0; i < v.size(); ++i) { if (i) out += ','; out += numToStr(v[i]); } out += ']'; } // -- Reader --------------------------------------------------------------- void Reader::skipWs() { while (!eof()) { char c = s_[pos_]; if (c == ' ' || c == '\t' || c == '\n' || c == '\r') ++pos_; else break; } } bool Reader::consume(char c) { skipWs(); if (eof() || s_[pos_] != c) return false; ++pos_; return true; } // Parses a JSON string literal (with the escapes our writers emit, plus \uXXXX // for control chars). Positioned before the opening quote (skips leading ws). bool Reader::parseString(std::string& out) { skipWs(); if (eof() || s_[pos_] != '"') return false; ++pos_; out.clear(); while (!eof()) { char c = s_[pos_++]; if (c == '"') return true; if (c == '\\') { if (eof()) return false; char e = s_[pos_++]; switch (e) { case '"': out += '"'; break; 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; case 'u': { auto readHex4 = [&](unsigned int& cp) -> bool { if (pos_ + 4 > s_.size()) return false; cp = 0; for (int i = 0; i < 4; ++i) { char h = s_[pos_++]; cp <<= 4; if (h >= '0' && h <= '9') cp |= static_cast(h - '0'); else if (h >= 'a' && h <= 'f') cp |= static_cast(h - 'a' + 10); else if (h >= 'A' && h <= 'F') cp |= static_cast(h - 'A' + 10); else return false; } return true; }; unsigned int hi = 0; if (!readHex4(hi)) return false; unsigned int codePoint = hi; if (hi >= 0xD800 && hi <= 0xDBFF) { // High surrogate — must be followed by \uDC00–\uDFFF. if (pos_ + 6 > s_.size()) return false; if (s_[pos_] != '\\' || s_[pos_ + 1] != 'u') return false; pos_ += 2; unsigned int lo = 0; if (!readHex4(lo)) return false; if (lo < 0xDC00 || lo > 0xDFFF) return false; // unpaired high surrogate codePoint = 0x10000 + ((hi - 0xD800) << 10) + (lo - 0xDC00); } else if (hi >= 0xDC00 && hi <= 0xDFFF) { return false; // unpaired low surrogate — malformed } if (codePoint <= 0x7F) { out += static_cast(codePoint); } else if (codePoint <= 0x7FF) { out += static_cast(0xC0 | (codePoint >> 6)); out += static_cast(0x80 | (codePoint & 0x3F)); } else if (codePoint <= 0xFFFF) { out += static_cast(0xE0 | (codePoint >> 12)); out += static_cast(0x80 | ((codePoint >> 6) & 0x3F)); out += static_cast(0x80 | (codePoint & 0x3F)); } else { out += static_cast(0xF0 | (codePoint >> 18)); out += static_cast(0x80 | ((codePoint >> 12) & 0x3F)); out += static_cast(0x80 | ((codePoint >> 6) & 0x3F)); out += static_cast(0x80 | (codePoint & 0x3F)); } break; } default: return false; } } else { out += c; } } return false; // unterminated string } bool Reader::parseRawScalar(std::string& out) { skipWs(); std::size_t start = pos_; while (!eof()) { char c = s_[pos_]; if (c == ',' || c == '}' || c == ']' || c == ' ' || c == '\t' || c == '\n' || c == '\r') break; ++pos_; } if (pos_ == start) return false; out.assign(s_, start, pos_ - start); return true; } bool Reader::parseDouble(double& out) { std::string tok; if (!parseRawScalar(tok)) return false; const char* b = tok.c_str(); char* end = nullptr; errno = 0; double v = std::strtod(b, &end); if (end != b + tok.size()) return false; if (errno == ERANGE) return false; // overflow / underflow -> malformed out = v; return true; } bool Reader::parseInt64(std::int64_t& out) { std::string tok; if (!parseRawScalar(tok)) return false; const char* b = tok.c_str(); char* end = nullptr; errno = 0; long long v = std::strtoll(b, &end, 10); if (end != b + tok.size()) return false; if (errno == ERANGE) return false; // overflow -> malformed out = static_cast(v); return true; } bool Reader::parseInt(int& out) { std::int64_t v = 0; if (!parseInt64(v)) return false; if (v < INT_MIN || v > INT_MAX) return false; out = static_cast(v); return true; } bool Reader::parseBool(bool& out) { std::string tok; if (!parseRawScalar(tok)) return false; if (tok == "true") { out = true; return true; } if (tok == "false") { out = false; return true; } return false; } bool Reader::expectNullOr(bool& wasNull) { skipWs(); if (eof()) return false; if (s_.compare(pos_, 4, "null") == 0) { pos_ += 4; wasNull = true; } else { wasNull = false; } return true; } bool Reader::parseKey(std::string& key) { if (!parseString(key)) return false; return consume(':'); } bool Reader::parseStringArray(std::vector& out) { if (!consume('[')) return false; skipWs(); if (consume(']')) return true; // empty array do { std::string s; if (!parseString(s)) return false; out.push_back(std::move(s)); } while (consume(',')); return consume(']'); } bool Reader::parseIntArray(std::vector& out) { if (!consume('[')) return false; skipWs(); if (consume(']')) return true; do { int v = 0; if (!parseInt(v)) return false; out.push_back(v); } while (consume(',')); return consume(']'); } bool Reader::skipValue() { std::string raw; return captureValue(raw); } // Records the raw source span of one JSON value starting at the current position // (after whitespace). Handles nested objects/arrays with string-aware brace // matching (braces inside strings ignored). bool Reader::captureValue(std::string& raw) { skipWs(); if (eof()) return false; std::size_t start = pos_; char c = s_[pos_]; if (c == '"') { std::string tmp; if (!parseString(tmp)) return false; raw.assign(s_, start, pos_ - start); return true; } if (c == '{' || c == '[') { char open = c, close = (c == '{') ? '}' : ']'; ++pos_; int depth = 1; while (!eof() && depth > 0) { char d = s_[pos_]; if (d == '"') { std::string tmp; if (!parseString(tmp)) return false; // advances past the string continue; } if (d == open) ++depth; else if (d == close) --depth; ++pos_; } if (depth != 0) return false; raw.assign(s_, start, pos_ - start); return true; } // bare scalar (number / true / false / null) return parseRawScalar(raw); } } // namespace reasampler::json