cheatah
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 once
5// 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 is
8// 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> into
12// 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 are
14// 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.hpp
25#include "pool_builder.hpp" // PoolBuilder (the pooled construction policy the Parser owns)
27namespace 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 none
34 * @test ParsersJsonDom.ToViewReadsBothBackingsAndRejectsNonStrings
35 */
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 string
40 * is copied, not a view), so the result is safe to return, cache, or outlive `text`. On success
41 * *ok is set true; on malformed input *ok is set false and the result is JSON null. For the
42 * 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 and
45 * rejects malformed input; parse<false>(...) strips every such check from the binary via
46 * `if constexpr` (see the Parser docs) and does not write *ok — for trusted, known-well-formed
47 * input only, as feeding it malformed input is undefined behavior.
48 *
49 * @complexity O(n) in the input length
50 * @alloc allocates the owned document tree (arrays, objects, and copied strings)
51 * @test ParsersJsonDom.ParsesEveryScalarKind
52 */
53template <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 string
61 * @test ParsersJsonDom.OwningContainersAndDumpRoundTrip
62 */
63[[nodiscard]] std::string dump(const Document& value);
65/**
66 * Serialize a Document by APPENDING to the caller's buffer — stream into a preallocated/reused
67 * std::string rather than allocating a fresh one. This is the efficient path (push_back/append
68 * 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.OwningContainersAndDumpRoundTrip
74 */
75void dump(const Document& value, std::string& out);
77/**
78 * A reusable parser that owns reusable node/member POOLS. Parser::parse() builds a Document whose
79 * arrays/objects are VIEWS (ArrayView/ObjectView) into these pools — zero per-container heap
80 * allocation. Reusing ONE Parser across many parses amortizes the pool allocation/page-faults to
81 * ~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 next
84 * parse() on this Parser, and only while the Parser is alive. (For a self-contained, owning
85 * 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, defaulted
88 * to true. With Validate=true the grammar checks bounds/structure and rejects malformed input
89 * (result JSON null, *ok=false). With Validate=false those checks are guarded by `if constexpr` and
90 * therefore removed from the binary ENTIRELY — there is no runtime flag and no branch, and *ok is
91 * not written at all (an unchecked parse has no validity to report). The unchecked form is for
92 * trusted, known-well-formed input (e.g. our own cache); feeding it malformed input is undefined
93 * behavior. Call it as p.parse<false>(text) / p.parse_owning<false>(text).
94 *
95 * @complexity O(n) in the input length
96 * @alloc the pools, reused across parses (amortized ~0 after warm-up); owned only for escaped
97 * strings
98 * @test ParsersJsonDom.PooledParserYieldsViewsIntoSource
99 */
100class Parser {
101public:
102 /**
103 * Parse @p text into a Document whose arrays/objects are VIEWS (ArrayView/ObjectView) into this
104 * Parser's reused pools — zero per-container allocation, amortized to ~0 across parses. The
105 * 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/structure
107 * 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 when
110 * 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 strings
114 * @test ParsersJsonDom.PooledParserYieldsViewsIntoSource
115 */
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 owned
121 * String<std::string> — strings are copied, not views), fully independent of this Parser and of
122 * @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.OwningParseOutlivesItsSource
130 * @crtest ParsersCompileRun.JsonDomParse
131 */
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-call
137 * 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.PooledParserYieldsViewsIntoSource
143 */
144 [[nodiscard]] std::string_view dump(const Document& value);
146private:
147 // ONE ITERATIVE grammar (this IS "all parsing in one class") — the recursion is unrolled into a
148 // loop over an explicit frame_stack_, so nesting depth costs heap, not C++ call frames (no stack
149 // overflow on adversarially deep input). It is parameterized on (1) a compile-time `bool
150 // Validate` that `if constexpr`-gates every bounds/structure check, and (2) a Builder policy
151 // (PoolBuilder -> ArrayView/ObjectView, or OwningBuilder -> OwnedArray/OwnedObject) driven as a
152 // stack machine (begin/add/finish). Compile-time dispatch; no runtime polymorphism. Defined and
153 // 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; the
158 // grammar only needs to know whether it is an object (so it reads keys) and, for an object, the
159 // 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 parses
167 std::string dump_buf_; // reused serialization buffer for dump()
168};
170} // namespace cheatah::parsers::json