Source
stdlib/parsers/json/json.hpp
1
// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).2
// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.3
#pragma once5
// cheatah::parsers::json — a from-scratch JSON parser (pure C++, no deps).6
//7
// This header declares the public API; the implementation lives in json.cpp. The value model is8
// Node (json/node.hpp) — a class wrapping a std::variant; access its alternatives via .variant().9
//10
// Two parse paths trade copying for lifetime (chosen by the Builder, no runtime polymorphism):11
// * Parser::parse (pooled) is ZERO-COPY — an unescaped string is a String<std::string_view> into12
// the SOURCE text, so `text` (and the Parser's pools) must outlive the Document.13
// * the free parse() / Parser::parse_owning produce a SELF-CONTAINED Document — strings are14
// copied into owned String<std::string> — safe to return, cache, or outlive `text`.15
// An escaped string is always decoded into owned storage. No runtime polymorphism anywhere.17
#include <cstddef>18
#include <span>19
#include <string>20
#include <string_view>21
#include <vector>23
#include "cursor.hpp" // Cursor (used by Parser's private parse methods)24
#include "document.hpp" // Document (= Node) + node.hpp25
#include "pool_builder.hpp" // PoolBuilder (the pooled construction policy the Parser owns)27
namespace cheatah::parsers::json {29
/**30
* Read a Node's characters when it is a string (either backing), else an empty view.31
*32
* @complexity O(1)33
* @alloc none34
* @test ParsersJsonDom.ToViewReadsBothBackingsAndRejectsNonStrings35
*/36
[[nodiscard]] std::string_view to_view(const Node& value) noexcept;38
/**39
* Parse `text` into a SELF-CONTAINED Document (owning containers AND owned strings — every string40
* is copied, not a view), so the result is safe to return, cache, or outlive `text`. On success41
* *ok is set true; on malformed input *ok is set false and the result is JSON null. For the42
* zero-copy, source-viewing form (no string copies), reuse a Parser and call Parser::parse.43
*44
* Validate is a COMPILE-TIME switch: the default (true) does full bounds/structure checking and45
* rejects malformed input; parse<false>(...) strips every such check from the binary via46
* `if constexpr` (see the Parser docs) and does not write *ok — for trusted, known-well-formed47
* input only, as feeding it malformed input is undefined behavior.48
*49
* @complexity O(n) in the input length50
* @alloc allocates the owned document tree (arrays, objects, and copied strings)51
* @test ParsersJsonDom.ParsesEveryScalarKind52
*/53
template <bool Validate = true>54
[[nodiscard]] Document parse(std::string_view text, bool* ok = nullptr);56
/**57
* Serialize a Document to compact JSON text (string contents are re-escaped).58
*59
* @complexity O(nodes)60
* @alloc the returned string61
* @test ParsersJsonDom.OwningContainersAndDumpRoundTrip62
*/63
[[nodiscard]] std::string dump(const Document& value);65
/**66
* Serialize a Document by APPENDING to the caller's buffer — stream into a preallocated/reused67
* std::string rather than allocating a fresh one. This is the efficient path (push_back/append68
* into one growing buffer); it deliberately avoids std::stringstream, which adds formatting,69
* locale, and virtual-streambuf overhead per write. Reserve `out` once and reuse it across calls.70
*71
* @complexity O(nodes)72
* @alloc none of its own (grows `out` only if its capacity is exceeded)73
* @test ParsersJsonDom.OwningContainersAndDumpRoundTrip74
*/75
void dump(const Document& value, std::string& out);77
/**78
* A reusable parser that owns reusable node/member POOLS. Parser::parse() builds a Document whose79
* arrays/objects are VIEWS (ArrayView/ObjectView) into these pools — zero per-container heap80
* allocation. Reusing ONE Parser across many parses amortizes the pool allocation/page-faults to81
* ~0 after warm-up (the reusable-parser model), which is the whole point of option B.82
*83
* LIFETIME: the returned Document VIEWS this Parser's pools, so it is valid only until the next84
* parse() on this Parser, and only while the Parser is alive. (For a self-contained, owning85
* Document — e.g. for the cache — use the free parse() above instead.) No runtime polymorphism.86
*87
* VALIDATION: every parse method takes a compile-time `bool Validate` template parameter, defaulted88
* to true. With Validate=true the grammar checks bounds/structure and rejects malformed input89
* (result JSON null, *ok=false). With Validate=false those checks are guarded by `if constexpr` and90
* therefore removed from the binary ENTIRELY — there is no runtime flag and no branch, and *ok is91
* not written at all (an unchecked parse has no validity to report). The unchecked form is for92
* trusted, known-well-formed input (e.g. our own cache); feeding it malformed input is undefined93
* behavior. Call it as p.parse<false>(text) / p.parse_owning<false>(text).94
*95
* @complexity O(n) in the input length96
* @alloc the pools, reused across parses (amortized ~0 after warm-up); owned only for escaped97
* strings98
* @test ParsersJsonDom.PooledParserYieldsViewsIntoSource99
*/100
class Parser {101
public:102
/**103
* Parse @p text into a Document whose arrays/objects are VIEWS (ArrayView/ObjectView) into this104
* Parser's reused pools — zero per-container allocation, amortized to ~0 across parses. The105
* result is valid only until the next parse or dump() on this Parser, and while the Parser lives.106
* @tparam Validate when true (default) reject malformed input; when false all bounds/structure107
* checks are compiled out (trusted, known-well-formed input only — see the class doc).108
* @param text the JSON source to parse.109
* @param ok if non-null, set to true on success and false on a parse error (only written when110
* Validate is true).111
* @return the parsed Document (JSON null on error when validating).112
* @complexity O(|text|)113
* @alloc none after warm-up (reused pools); owned only for escaped strings114
* @test ParsersJsonDom.PooledParserYieldsViewsIntoSource115
*/116
template <bool Validate = true>117
[[nodiscard]] Document parse(std::string_view text, bool* ok = nullptr);119
/**120
* Parse @p text into a self-contained OWNING Document (OwnedArray/OwnedObject AND owned121
* String<std::string> — strings are copied, not views), fully independent of this Parser and of122
* @p text once returned. This is what the free parse() and the cache use.123
* @tparam Validate as for parse().124
* @param text the JSON source to parse.125
* @param ok if non-null, set to true on success and false on a parse error (Validate=true only).126
* @return a self-contained parsed Document (JSON null on error when validating).127
* @complexity O(|text|)128
* @alloc allocates the owned document tree (arrays, objects, and copied strings)129
* @test ParsersJsonDom.OwningParseOutlivesItsSource130
* @crtest ParsersCompileRun.JsonDomParse131
*/132
template <bool Validate = true>133
[[nodiscard]] Document parse_owning(std::string_view text, bool* ok = nullptr);135
/**136
* Serialize @p value into the Parser's own REUSED buffer and return a view of it — no per-call137
* allocation after warm-up. The view is valid until the next dump() on this Parser.138
* @param value the document to serialize.139
* @return a std::string_view of the serialized JSON (valid until the next dump()).140
* @complexity O(output size)141
* @alloc none after warm-up (the buffer is reused)142
* @test ParsersJsonDom.PooledParserYieldsViewsIntoSource143
*/144
[[nodiscard]] std::string_view dump(const Document& value);146
private:147
// ONE ITERATIVE grammar (this IS "all parsing in one class") — the recursion is unrolled into a148
// loop over an explicit frame_stack_, so nesting depth costs heap, not C++ call frames (no stack149
// overflow on adversarially deep input). It is parameterized on (1) a compile-time `bool150
// Validate` that `if constexpr`-gates every bounds/structure check, and (2) a Builder policy151
// (PoolBuilder -> ArrayView/ObjectView, or OwningBuilder -> OwnedArray/OwnedObject) driven as a152
// stack machine (begin/add/finish). Compile-time dispatch; no runtime polymorphism. Defined and153
// instantiated for both validation modes and both builders in json.cpp.154
template <bool Validate, class Builder>155
bool parse_value(Cursor& c, Node& out, Builder& b);157
// One open container on the parse stack. The Builder owns the partial container itself; the158
// grammar only needs to know whether it is an object (so it reads keys) and, for an object, the159
// pending key awaiting its value.160
struct Frame {161
bool is_object;162
Node key; // the in-progress object key (unused for arrays)163
};165
PoolBuilder pool_; // pooled construction policy, reused across parse() (view path)166
std::vector<Frame> frame_stack_; // explicit recursion stack, reused across parses167
std::string dump_buf_; // reused serialization buffer for dump()168
};170
} // namespace cheatah::parsers::json