Files
reasampler/src/core/json/json.h
T

150 lines
5.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// core/json — the ONE hand-rolled JSON lexical layer (Q-W1; audit T2-02 / §2
// "Parser ×4"). Pure: standard library only — NO REAPER, NO SWELL, NO VST3.
//
// This module owns the lexical half of the house JSON dialect: the escape-aware
// string literal (incl. \uXXXX + surrogate pairs re-encoded as UTF-8), the bare
// scalar tokens, the number parses (strtod/strtoll with full-token + ERANGE
// rejection), key+':' consumption, unknown-value skipping, and the emit side
// (escaping, %.17g / %d / %lld number rendering, the scoped object writer).
// The DOMAIN grammars — which keys exist, what shape each value takes, what is
// rejected at the model boundary — stay in the consumers (bank_model, bank_book,
// view_mode_model, owned_manifest, tail_control). One lexical definition means
// the five decoders can no longer drift on tolerance or escaping.
//
// Byte-compatibility contract (load-bearing): the emit helpers reproduce the
// prior per-module writers EXACTLY — writeEscaped's escape set, %.17g for
// doubles (shortest form that round-trips every IEEE-754 double bit-for-bit),
// plain decimal for ints — so a re-serialized blob is byte-identical to what
// the pre-extraction writers produced. This was a structural dedupe, not a
// format change; persisted .rpp ext-state must not shift by a byte.
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace reasampler::json {
// ---------------------------------------------------------------------------
// emit helpers (writer side)
// ---------------------------------------------------------------------------
// Appends `s` as a quoted JSON string literal: the seven short escapes, \uXXXX
// for remaining control chars, everything else verbatim (UTF-8 passes through).
void writeEscaped(std::string& out, const std::string& s);
// Number rendering. %.17g is the shortest form that round-trips every IEEE-754
// double exactly, so deserialize(serialize(x)) == x holds bit-for-bit.
std::string numToStr(double v);
std::string numToStr(std::int64_t v);
std::string numToStr(int v);
// Flat homogeneous arrays: ["a","b"] / [1,2]. Empty vector -> "[]".
void writeStringArray(std::string& out, const std::vector<std::string>& v);
void writeIntArray(std::string& out, const std::vector<int>& v);
// Scoped object writer: appends '{' on construction and '}' on destruction, with
// comma separation handled internally. Nested values are written by keyBegin()
// followed by the caller emitting the value (e.g. a nested Writer scope or an
// array). NOTE the destructor-close means an enclosing scope must END (brace
// block) before the built string is returned — see the NRVO note in the
// consumers' serialize() implementations.
class Writer {
public:
explicit Writer(std::string& out) : out_(out) { out_ += '{'; }
~Writer() { out_ += '}'; }
Writer(const Writer&) = delete;
Writer& operator=(const Writer&) = delete;
// "key":<rawValue> — rawValue appended verbatim (numbers, bools, null,
// pre-serialized nested blobs).
void keyRaw(const char* key, const std::string& rawValue) {
sep();
writeEscaped(out_, key);
out_ += ':';
out_ += rawValue;
}
// "key":"value" — value escaped.
void keyStr(const char* key, const std::string& value) {
sep();
writeEscaped(out_, key);
out_ += ':';
writeEscaped(out_, value);
}
// "key": — caller writes the value immediately after.
void keyBegin(const char* key) {
sep();
writeEscaped(out_, key);
out_ += ':';
}
private:
void sep() {
if (first_) first_ = false; else out_ += ',';
}
std::string& out_;
bool first_ = true;
};
// ---------------------------------------------------------------------------
// Reader — the lexical cursor (parser side)
// ---------------------------------------------------------------------------
//
// Every method returns false on malformed input and never reads out of bounds.
// Only the subset the house writers emit is supported. The reader borrows the
// input string — it must outlive the Reader.
class Reader {
public:
explicit Reader(const std::string& s) : s_(s) {}
bool eof() const { return pos_ >= s_.size(); }
void skipWs();
// Consumes `c` (after whitespace). False without advancing past `c` if the
// next non-ws char differs.
bool consume(char c);
// JSON string literal (escapes + \uXXXX incl. surrogate pairs -> UTF-8).
bool parseString(std::string& out);
// Bare token (number / true / false / null) up to the next structural char.
bool parseRawScalar(std::string& out);
// Numbers: full-token parse; trailing bytes or ERANGE reject. parseInt
// additionally rejects values outside [INT_MIN, INT_MAX].
bool parseDouble(double& out);
bool parseInt64(std::int64_t& out);
bool parseInt(int& out);
bool parseBool(bool& out);
// Peeks for the `null` token; consumes it if present (wasNull=true),
// otherwise leaves the position untouched (wasNull=false). Returns false
// only on eof.
bool expectNullOr(bool& wasNull);
// An object member key + ':'.
bool parseKey(std::string& key);
// Homogeneous arrays. Appends to `out`; empty array is valid.
bool parseStringArray(std::vector<std::string>& out);
bool parseIntArray(std::vector<int>& out);
// Skips one value of any shape (string / object / array / bare scalar) —
// forward-compat for unknown keys.
bool skipValue();
// Captures the raw source text of one value verbatim (string-aware brace
// matching), so a nested blob can be handed to its own parser — the
// bank_book -> BankModel::deserialize seam.
bool captureValue(std::string& raw);
private:
const std::string& s_;
std::size_t pos_ = 0;
};
} // namespace reasampler::json