Q-W1 pt1: extract core/json (json::Reader/Writer), collapse wire Cursor family into core/wire, shared readFileBytes — five JSON decoders and three cursor copies deleted, byte-identical formats, 59/59 green

This commit is contained in:
2026-07-28 19:59:02 -04:00
parent 88e7765ee5
commit 67a41728f3
25 changed files with 1695 additions and 1696 deletions
+319
View File
@@ -0,0 +1,319 @@
// core/json implementation — see json.h. The bodies are the (previously
// quintuplicated) bank_model / view_mode_model lexical layer, verbatim; any
// behavioral change here changes five persisted-blob parsers at once.
#include "core/json/json.h"
#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstdlib>
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<unsigned char>(c) < 0x20) {
char buf[8];
std::snprintf(buf, sizeof(buf), "\\u%04x", static_cast<unsigned char>(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<long long>(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<std::string>& 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<int>& 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': {
// Decode a \uXXXX escape to its code point.
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<unsigned>(h - '0');
else if (h >= 'a' && h <= 'f') cp |= static_cast<unsigned>(h - 'a' + 10);
else if (h >= 'A' && h <= 'F') cp |= static_cast<unsigned>(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
}
// Encode codePoint as UTF-8.
if (codePoint <= 0x7F) {
out += static_cast<char>(codePoint);
} else if (codePoint <= 0x7FF) {
out += static_cast<char>(0xC0 | (codePoint >> 6));
out += static_cast<char>(0x80 | (codePoint & 0x3F));
} else if (codePoint <= 0xFFFF) {
out += static_cast<char>(0xE0 | (codePoint >> 12));
out += static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F));
out += static_cast<char>(0x80 | (codePoint & 0x3F));
} else {
out += static_cast<char>(0xF0 | (codePoint >> 18));
out += static_cast<char>(0x80 | ((codePoint >> 12) & 0x3F));
out += static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F));
out += static_cast<char>(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<std::int64_t>(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<int>(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<std::string>& 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<int>& 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