#ifndef HYPEROVERLAY_TEST_JSON_MINI_H
#define HYPEROVERLAY_TEST_JSON_MINI_H
// Minimal JSON reader for the conformance fixtures — only what those files use.
//
// It lives in test/, NOT in include/overlay/: liboverlay is dependency-free so it
// can be lifted out by moving the directory, and a JSON parser is a test
// convenience, not part of the library. Shared by test_messages.cpp (vectors.json)
// and test_protocol.cpp (live-transcripts.json) so the two cannot drift into
// different readings of the same fixtures.
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <map>
#include <sstream>
#include <string>
#include <vector>
struct Json {
enum Type { NUL, BOOL, NUM, STR, ARR, OBJ } type = NUL;
bool b = false;
double num = 0;
std::string str;
std::vector<Json> arr;
std::map<std::string, Json> obj;
const Json *find(const std::string &k) const {
auto it = obj.find(k);
return it == obj.end() ? nullptr : &it->second;
}
};
struct JsonParser {
const char *p;
void ws() { while (*p == ' ' || *p == '\n' || *p == '\t' || *p == '\r') p++; }
Json parse() {
ws();
if (*p == '{') return parse_obj();
if (*p == '[') return parse_arr();
if (*p == '"') { Json j; j.type = Json::STR; j.str = parse_str(); return j; }
if (!strncmp(p, "true", 4)) { p += 4; Json j; j.type = Json::BOOL; j.b = true; return j; }
if (!strncmp(p, "false", 5)) { p += 5; Json j; j.type = Json::BOOL; j.b = false; return j; }
if (!strncmp(p, "null", 4)) { p += 4; return Json(); }
Json j; j.type = Json::NUM; j.num = strtod(p, (char **)&p); return j;
}
std::string parse_str() {
p++; // opening quote
std::string out;
while (*p && *p != '"') {
if (*p == '\\') {
p++;
switch (*p) {
case 'n': out += '\n'; break;
case 't': out += '\t'; break;
case 'r': out += '\r'; break;
case 'u': {
// Fixtures carry UTF-8 paths; node emits them raw, but be safe.
unsigned cp = (unsigned)strtoul(std::string(p + 1, p + 5).c_str(), nullptr, 16);
p += 4;
if (cp < 0x80) out += (char)cp;
else if (cp < 0x800) { out += (char)(0xc0 | (cp >> 6)); out += (char)(0x80 | (cp & 0x3f)); }
else { out += (char)(0xe0 | (cp >> 12)); out += (char)(0x80 | ((cp >> 6) & 0x3f)); out += (char)(0x80 | (cp & 0x3f)); }
break;
}
default: out += *p;
}
p++;
} else out += *p++;
}
p++; // closing quote
return out;
}
Json parse_obj() {
Json j; j.type = Json::OBJ; p++;
ws();
if (*p == '}') { p++; return j; }
while (*p) {
ws();
std::string k = parse_str();
ws(); p++; // ':'
j.obj[k] = parse();
ws();
if (*p == ',') { p++; continue; }
if (*p == '}') { p++; break; }
break;
}
return j;
}
Json parse_arr() {
Json j; j.type = Json::ARR; p++;
ws();
if (*p == ']') { p++; return j; }
while (*p) {
j.arr.push_back(parse());
ws();
if (*p == ',') { p++; continue; }
if (*p == ']') { p++; break; }
break;
}
return j;
}
};
// Whole-file read. Returns false when the fixture is missing, so a caller can say
// so plainly instead of asserting against an empty document and passing.
inline bool read_text_file(const std::string &path, std::string &out) {
std::ifstream f(path);
if (!f) return false;
std::stringstream ss;
ss << f.rdbuf();
out = ss.str();
return true;
}
#endif // HYPEROVERLAY_TEST_JSON_MINI_H