/src/glaze/include/glaze/beve/lazy.hpp
Line | Count | Source |
1 | | // Glaze Library |
2 | | // For the license information refer to glaze.hpp |
3 | | |
4 | | #pragma once |
5 | | |
6 | | #include <string_view> |
7 | | |
8 | | #include "glaze/beve/header.hpp" |
9 | | #include "glaze/beve/read.hpp" |
10 | | #include "glaze/beve/skip.hpp" |
11 | | #include "glaze/beve/write.hpp" |
12 | | #include "glaze/util/expected.hpp" |
13 | | |
14 | | namespace glz |
15 | | { |
16 | | // Forward declarations |
17 | | template <auto Opts> |
18 | | struct lazy_beve_document; |
19 | | template <auto Opts> |
20 | | class lazy_beve_iterator; |
21 | | template <auto Opts> |
22 | | struct indexed_lazy_beve_view; |
23 | | template <auto Opts> |
24 | | class indexed_lazy_beve_iterator; |
25 | | |
26 | | // ============================================================================ |
27 | | // Helper functions for BEVE lazy parsing |
28 | | // ============================================================================ |
29 | | |
30 | | namespace detail |
31 | | { |
32 | | // Skip a BEVE value and return the new position (no context needed for position tracking). |
33 | | // |
34 | | // A failed skip leaves `it` part way through the value, and every caller here treats the |
35 | | // returned pointer as the start of the next one, so handing that position back would resolve |
36 | | // later lookups against the middle of a value. Return `end` instead: the caller's scan stops |
37 | | // and reports nothing found rather than something wrong. Nesting deeper than |
38 | | // max_recursive_depth_limit is the reachable case -- skip_value<BEVE> refuses it, which is what |
39 | | // keeps this from being a stack overflow -- so a lazy view over such a value can no longer |
40 | | // reach the members past it. Distinguishing "too deep" from "absent" needs an error channel |
41 | | // through the lazy API, which these accessors do not have. |
42 | | template <auto Opts> |
43 | | GLZ_ALWAYS_INLINE const char* skip_value_beve_lazy(const char* p, const char* end) noexcept |
44 | | { |
45 | | if (p >= end) return p; |
46 | | |
47 | | context ctx{}; |
48 | | auto it = p; |
49 | | skip_value<BEVE>::op<Opts>(ctx, it, end); |
50 | | if (bool(ctx.error)) [[unlikely]] { |
51 | | return end; |
52 | | } |
53 | | return it; |
54 | | } |
55 | | |
56 | | // Read compressed integer without modifying iterator, returns {value, bytes_consumed} |
57 | | GLZ_ALWAYS_INLINE std::pair<size_t, size_t> peek_compressed_int(const char* p, const char* end) noexcept |
58 | 0 | { |
59 | 0 | if (p >= end) return {0, 0}; |
60 | 0 |
|
61 | 0 | uint8_t header; |
62 | 0 | std::memcpy(&header, p, 1); |
63 | 0 | const uint8_t config = header & 0b000000'11; |
64 | 0 |
|
65 | 0 | switch (config) { |
66 | 0 | case 0: |
67 | 0 | return {header >> 2, 1}; |
68 | 0 | case 1: { |
69 | 0 | if (p + 2 > end) return {0, 0}; |
70 | 0 | uint16_t h; |
71 | 0 | std::memcpy(&h, p, 2); |
72 | 0 | if constexpr (std::endian::native == std::endian::big) { |
73 | 0 | h = std::byteswap(h); |
74 | 0 | } |
75 | 0 | return {h >> 2, 2}; |
76 | 0 | } |
77 | 0 | case 2: { |
78 | 0 | if (p + 4 > end) return {0, 0}; |
79 | 0 | uint32_t h; |
80 | 0 | std::memcpy(&h, p, 4); |
81 | 0 | if constexpr (std::endian::native == std::endian::big) { |
82 | 0 | h = std::byteswap(h); |
83 | 0 | } |
84 | 0 | return {h >> 2, 4}; |
85 | 0 | } |
86 | 0 | case 3: { |
87 | 0 | if constexpr (sizeof(size_t) > sizeof(uint32_t)) { |
88 | 0 | if (p + 8 > end) return {0, 0}; |
89 | 0 | uint64_t h; |
90 | 0 | std::memcpy(&h, p, 8); |
91 | 0 | if constexpr (std::endian::native == std::endian::big) { |
92 | 0 | h = std::byteswap(h); |
93 | 0 | } |
94 | 0 | return {static_cast<size_t>(h >> 2), 8}; |
95 | 0 | } |
96 | 0 | else { |
97 | 0 | return {0, 0}; |
98 | 0 | } |
99 | 0 | } |
100 | 0 | default: |
101 | 0 | return {0, 0}; |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | // Read compressed integer and advance pointer |
106 | | GLZ_ALWAYS_INLINE size_t read_compressed_int(const char*& p, const char* end) noexcept |
107 | 0 | { |
108 | 0 | auto [value, bytes] = peek_compressed_int(p, end); |
109 | 0 | p += bytes; |
110 | 0 | return value; |
111 | 0 | } |
112 | | |
113 | | // Skip a BEVE string (tag already consumed) - returns position after string |
114 | | GLZ_ALWAYS_INLINE const char* skip_string_content(const char* p, const char* end) noexcept |
115 | 0 | { |
116 | 0 | const auto len = read_compressed_int(p, end); |
117 | 0 | if (static_cast<size_t>(end - p) < len) return end; |
118 | 0 | return p + len; |
119 | 0 | } |
120 | | |
121 | | // Get size of a BEVE number based on tag |
122 | 0 | GLZ_ALWAYS_INLINE size_t number_size_from_tag(uint8_t t) noexcept { return byte_count_lookup[t >> 5]; } |
123 | | } // namespace detail |
124 | | |
125 | | // ============================================================================ |
126 | | // lazy_beve_view - Truly lazy view into BEVE data |
127 | | // ============================================================================ |
128 | | |
129 | | /** |
130 | | * @brief A truly lazy view into BEVE data. |
131 | | * |
132 | | * No upfront processing - all navigation uses on-demand scanning. |
133 | | * |
134 | | * For objects, parse_pos_ tracks the current scan position to enable |
135 | | * efficient sequential key access (O(n) total instead of O(n²)). |
136 | | */ |
137 | | template <auto Opts = opts{}> |
138 | | struct lazy_beve_view |
139 | | { |
140 | | private: |
141 | | const lazy_beve_document<Opts>* doc_{}; |
142 | | const char* data_{}; |
143 | | mutable const char* parse_pos_{}; // Current scan position (advances on key access) |
144 | | std::string_view key_{}; |
145 | | error_code error_{error_code::none}; |
146 | | uint8_t synthetic_tag_{}; // Non-zero for typed array elements (tag info without tag byte in data) |
147 | | |
148 | | lazy_beve_view(error_code ec) noexcept : error_(ec) {} |
149 | | |
150 | | public: |
151 | | lazy_beve_view() = default; |
152 | | lazy_beve_view(const lazy_beve_document<Opts>* doc, const char* data) noexcept : doc_(doc), data_(data) {} |
153 | | |
154 | | [[nodiscard]] static lazy_beve_view make_error(error_code ec) noexcept { return lazy_beve_view{ec}; } |
155 | | |
156 | | [[nodiscard]] bool has_error() const noexcept { return error_ != error_code::none; } |
157 | | [[nodiscard]] error_code error() const noexcept { return error_; } |
158 | | |
159 | | // Type checking - direct from BEVE tag byte |
160 | | [[nodiscard]] bool is_null() const noexcept |
161 | | { |
162 | | if (has_error() || !data_) return true; |
163 | | const uint8_t t = uint8_t(*data_); |
164 | | // null tag is 0, but boolean also uses low bits differently |
165 | | // null: tag == 0 |
166 | | return t == tag::null; |
167 | | } |
168 | | |
169 | | [[nodiscard]] bool is_boolean() const noexcept |
170 | | { |
171 | | if (has_error() || !data_) return false; |
172 | | const uint8_t t = uint8_t(*data_); |
173 | | // boolean: (tag & 0b0000'1111) == tag::boolean (which is 0b00001'000) |
174 | | return (t & 0b0000'1111) == tag::boolean; |
175 | | } |
176 | | |
177 | | [[nodiscard]] bool is_number() const noexcept |
178 | | { |
179 | | if (has_error() || !data_) return false; |
180 | | const uint8_t t = uint8_t(*data_) & 0b00000'111; |
181 | | return t == tag::number; |
182 | | } |
183 | | |
184 | | [[nodiscard]] bool is_string() const noexcept |
185 | | { |
186 | | if (has_error() || !data_) return false; |
187 | | const uint8_t t = uint8_t(*data_) & 0b00000'111; |
188 | | return t == tag::string; |
189 | | } |
190 | | |
191 | | [[nodiscard]] bool is_object() const noexcept |
192 | | { |
193 | | if (has_error() || !data_) return false; |
194 | | const uint8_t t = uint8_t(*data_) & 0b00000'111; |
195 | | return t == tag::object; |
196 | | } |
197 | | |
198 | | [[nodiscard]] bool is_array() const noexcept |
199 | | { |
200 | | if (has_error() || !data_) return false; |
201 | | const uint8_t t = uint8_t(*data_) & 0b00000'111; |
202 | | return t == tag::typed_array || t == tag::generic_array; |
203 | | } |
204 | | |
205 | | [[nodiscard]] bool is_typed_array() const noexcept |
206 | | { |
207 | | if (has_error() || !data_) return false; |
208 | | const uint8_t t = uint8_t(*data_) & 0b00000'111; |
209 | | return t == tag::typed_array; |
210 | | } |
211 | | |
212 | | [[nodiscard]] bool is_generic_array() const noexcept |
213 | | { |
214 | | if (has_error() || !data_) return false; |
215 | | const uint8_t t = uint8_t(*data_) & 0b00000'111; |
216 | | return t == tag::generic_array; |
217 | | } |
218 | | |
219 | | explicit operator bool() const noexcept { return !has_error() && data_ && !is_null(); } |
220 | | |
221 | | [[nodiscard]] const char* data() const noexcept { return data_; } |
222 | | [[nodiscard]] const char* beve_end() const noexcept; |
223 | | |
224 | | /// @brief Get the raw BEVE bytes for this value |
225 | | [[nodiscard]] std::string_view raw_beve() const noexcept |
226 | | { |
227 | | if (has_error() || !data_) return {}; |
228 | | const char* end_ptr = detail::skip_value_beve_lazy<Opts>(data_, beve_end()); |
229 | | return {data_, static_cast<size_t>(end_ptr - data_)}; |
230 | | } |
231 | | |
232 | | /// @brief Parse this value directly into a C++ type |
233 | | template <class T> |
234 | | [[nodiscard]] error_ctx read_into(T& value) const |
235 | | { |
236 | | if (has_error()) { |
237 | | return error_ctx{0, error_}; |
238 | | } |
239 | | if (!data_) { |
240 | | return error_ctx{0, error_code::unexpected_end}; |
241 | | } |
242 | | context ctx{}; |
243 | | auto it = data_; |
244 | | auto end = beve_end(); |
245 | | parse<BEVE>::op<Opts>(value, ctx, it, end); |
246 | | finalize_read_context<Opts>(ctx); |
247 | | if (bool(ctx.error)) { |
248 | | return error_ctx{static_cast<size_t>(it - data_), ctx.error}; |
249 | | } |
250 | | return {}; |
251 | | } |
252 | | |
253 | | template <class T> |
254 | | [[nodiscard]] expected<T, error_ctx> get() const; |
255 | | |
256 | | [[nodiscard]] lazy_beve_view operator[](size_t index) const; |
257 | | [[nodiscard]] lazy_beve_view operator[](std::string_view key) const; |
258 | | [[nodiscard]] bool contains(std::string_view key) const; |
259 | | [[nodiscard]] size_t size() const; |
260 | | [[nodiscard]] bool empty() const noexcept; |
261 | | |
262 | | // Key access for object iteration |
263 | | [[nodiscard]] std::string_view key() const noexcept { return key_; } |
264 | | |
265 | | [[nodiscard]] lazy_beve_iterator<Opts> begin() const; |
266 | | [[nodiscard]] lazy_beve_iterator<Opts> end() const; |
267 | | |
268 | | /// @brief Build an index for O(1) iteration and random access |
269 | | [[nodiscard]] indexed_lazy_beve_view<Opts> index() const; |
270 | | |
271 | | private: |
272 | | friend struct lazy_beve_document<Opts>; |
273 | | friend class lazy_beve_iterator<Opts>; |
274 | | friend struct indexed_lazy_beve_view<Opts>; |
275 | | friend class indexed_lazy_beve_iterator<Opts>; |
276 | | |
277 | | // Constructor with key for iteration |
278 | | lazy_beve_view(const lazy_beve_document<Opts>* doc, const char* data, std::string_view key) noexcept |
279 | | : doc_(doc), data_(data), key_(key) |
280 | | {} |
281 | | |
282 | | // Constructor with synthetic tag for typed array elements |
283 | | lazy_beve_view(const lazy_beve_document<Opts>* doc, const char* data, std::string_view key, |
284 | | uint8_t synthetic_tag) noexcept |
285 | | : doc_(doc), data_(data), key_(key), synthetic_tag_(synthetic_tag) |
286 | | {} |
287 | | |
288 | | void set_synthetic_tag(uint8_t tag) noexcept { synthetic_tag_ = tag; } |
289 | | |
290 | | // Helper to read numeric value directly given tag info (for typed array elements) |
291 | | template <class T> |
292 | | [[nodiscard]] expected<T, error_ctx> read_numeric_from_tag(uint8_t tag, const char* value_ptr, |
293 | | const char* end) const; |
294 | | }; |
295 | | |
296 | | // ============================================================================ |
297 | | // lazy_beve_document - Minimal container |
298 | | // ============================================================================ |
299 | | |
300 | | template <auto Opts = opts{}> |
301 | | struct lazy_beve_document |
302 | | { |
303 | | private: |
304 | | const char* beve_{}; |
305 | | size_t len_{}; |
306 | | const char* root_data_{}; |
307 | | mutable lazy_beve_view<Opts> root_view_{}; |
308 | | |
309 | | friend struct lazy_beve_view<Opts>; |
310 | | friend class lazy_beve_iterator<Opts>; |
311 | | |
312 | | template <auto O, class Buffer> |
313 | | friend expected<lazy_beve_document<O>, error_ctx> lazy_beve(Buffer&&); |
314 | | |
315 | | void init_root_view() noexcept { root_view_ = lazy_beve_view<Opts>{this, root_data_}; } |
316 | | |
317 | | public: |
318 | | lazy_beve_document() = default; |
319 | | |
320 | | lazy_beve_document(const lazy_beve_document& other) |
321 | | : beve_(other.beve_), len_(other.len_), root_data_(other.root_data_), size{this} |
322 | | { |
323 | | init_root_view(); |
324 | | root_view_.parse_pos_ = other.root_view_.parse_pos_; |
325 | | } |
326 | | |
327 | | lazy_beve_document& operator=(const lazy_beve_document& other) |
328 | | { |
329 | | if (this != &other) { |
330 | | beve_ = other.beve_; |
331 | | len_ = other.len_; |
332 | | root_data_ = other.root_data_; |
333 | | init_root_view(); |
334 | | root_view_.parse_pos_ = other.root_view_.parse_pos_; |
335 | | size = {this}; |
336 | | } |
337 | | return *this; |
338 | | } |
339 | | |
340 | | lazy_beve_document(lazy_beve_document&& other) noexcept |
341 | | : beve_(other.beve_), len_(other.len_), root_data_(other.root_data_), size{this} |
342 | | { |
343 | | init_root_view(); |
344 | | root_view_.parse_pos_ = other.root_view_.parse_pos_; |
345 | | } |
346 | | |
347 | | lazy_beve_document& operator=(lazy_beve_document&& other) noexcept |
348 | | { |
349 | | if (this != &other) { |
350 | | beve_ = other.beve_; |
351 | | len_ = other.len_; |
352 | | root_data_ = other.root_data_; |
353 | | init_root_view(); |
354 | | root_view_.parse_pos_ = other.root_view_.parse_pos_; |
355 | | size = {this}; |
356 | | } |
357 | | return *this; |
358 | | } |
359 | | |
360 | | [[nodiscard]] lazy_beve_view<Opts>& root() noexcept { return root_view_; } |
361 | | [[nodiscard]] const lazy_beve_view<Opts>& root() const noexcept { return root_view_; } |
362 | | |
363 | | [[nodiscard]] lazy_beve_view<Opts> operator[](std::string_view key) const { return root_view_[key]; } |
364 | | [[nodiscard]] lazy_beve_view<Opts> operator[](size_t index) const { return root_view_[index]; } |
365 | | |
366 | | [[nodiscard]] bool is_null() const noexcept { return !root_data_ || uint8_t(*root_data_) == tag::null; } |
367 | | |
368 | | [[nodiscard]] bool is_array() const noexcept |
369 | | { |
370 | | if (!root_data_) return false; |
371 | | const uint8_t t = uint8_t(*root_data_) & 0b00000'111; |
372 | | return t == tag::typed_array || t == tag::generic_array; |
373 | | } |
374 | | |
375 | | [[nodiscard]] bool is_object() const noexcept |
376 | | { |
377 | | if (!root_data_) return false; |
378 | | const uint8_t t = uint8_t(*root_data_) & 0b00000'111; |
379 | | return t == tag::object; |
380 | | } |
381 | | |
382 | | explicit operator bool() const noexcept { return !is_null(); } |
383 | | |
384 | | [[nodiscard]] const char* beve_data() const noexcept { return beve_; } |
385 | | [[nodiscard]] size_t beve_size() const noexcept { return len_; } |
386 | | |
387 | | void reset_parse_pos() noexcept { root_view_.parse_pos_ = nullptr; } |
388 | | |
389 | | /// @brief Size accessor proxy for getting value sizes without parsing |
390 | | /// Usage: doc.size["key"] returns the string length or element count |
391 | | struct size_accessor |
392 | | { |
393 | | const lazy_beve_document* doc; |
394 | | |
395 | | [[nodiscard]] size_t operator[](std::string_view key) const { return doc->root_view_[key].size(); } |
396 | | |
397 | | [[nodiscard]] size_t operator[](size_t index) const { return doc->root_view_[index].size(); } |
398 | | }; |
399 | | |
400 | | size_accessor size{this}; |
401 | | }; |
402 | | |
403 | | // ============================================================================ |
404 | | // lazy_beve_iterator - Forward iterator with lazy scanning |
405 | | // ============================================================================ |
406 | | |
407 | | template <auto Opts> |
408 | | class lazy_beve_iterator |
409 | | { |
410 | | private: |
411 | | const lazy_beve_document<Opts>* doc_{}; |
412 | | const char* beve_end_{}; |
413 | | const char* current_pos_{}; // Current position in iteration |
414 | | size_t remaining_count_{}; // Elements remaining |
415 | | bool is_object_{}; |
416 | | bool is_typed_array_{}; |
417 | | bool has_string_keys_{true}; // For objects: string keys (default) vs number keys |
418 | | uint8_t key_byte_count_{}; // For number-keyed objects: bytes per key |
419 | | uint8_t element_size_{}; // For typed arrays |
420 | | bool at_end_{true}; |
421 | | lazy_beve_view<Opts> current_view_{}; |
422 | | |
423 | | public: |
424 | | using iterator_category = std::forward_iterator_tag; |
425 | | using value_type = lazy_beve_view<Opts>; |
426 | | using difference_type = std::ptrdiff_t; |
427 | | using pointer = void; |
428 | | using reference = lazy_beve_view<Opts>&; |
429 | | |
430 | | lazy_beve_iterator() = default; |
431 | | |
432 | | lazy_beve_iterator(const lazy_beve_document<Opts>* doc, const char* container_start, const char* end, |
433 | | bool is_object, bool is_typed_array); |
434 | | |
435 | | reference operator*() { return current_view_; } |
436 | | const lazy_beve_view<Opts>& operator*() const { return current_view_; } |
437 | | |
438 | | lazy_beve_iterator& operator++(); |
439 | | |
440 | | lazy_beve_iterator operator++(int) |
441 | | { |
442 | | auto tmp = *this; |
443 | | ++*this; |
444 | | return tmp; |
445 | | } |
446 | | |
447 | | bool operator==(const lazy_beve_iterator& other) const { return at_end_ == other.at_end_; } |
448 | | bool operator!=(const lazy_beve_iterator& other) const { return !(*this == other); } |
449 | | |
450 | | private: |
451 | | void advance_to_current_element(); |
452 | | }; |
453 | | |
454 | | // ============================================================================ |
455 | | // indexed_lazy_beve_view - Pre-built index for O(1) access |
456 | | // ============================================================================ |
457 | | |
458 | | template <auto Opts> |
459 | | struct indexed_lazy_beve_view |
460 | | { |
461 | | private: |
462 | | const lazy_beve_document<Opts>* doc_{}; |
463 | | const char* beve_end_{}; |
464 | | std::vector<const char*> value_starts_; |
465 | | std::vector<std::string_view> keys_; |
466 | | bool is_object_{}; |
467 | | bool is_typed_array_{}; // True if indexing a typed array |
468 | | uint8_t element_tag_{}; // Synthetic element tag for typed arrays |
469 | | |
470 | | friend class indexed_lazy_beve_iterator<Opts>; |
471 | | |
472 | | public: |
473 | | indexed_lazy_beve_view() = default; |
474 | | |
475 | | [[nodiscard]] size_t size() const noexcept { return value_starts_.size(); } |
476 | | [[nodiscard]] bool empty() const noexcept { return value_starts_.empty(); } |
477 | | [[nodiscard]] bool is_object() const noexcept { return is_object_; } |
478 | | [[nodiscard]] bool is_array() const noexcept { return !is_object_; } |
479 | | [[nodiscard]] bool is_typed_array() const noexcept { return is_typed_array_; } |
480 | | |
481 | | [[nodiscard]] lazy_beve_view<Opts> operator[](size_t index) const |
482 | | { |
483 | | if (index >= value_starts_.size()) { |
484 | | return lazy_beve_view<Opts>::make_error(error_code::exceeded_static_array_size); |
485 | | } |
486 | | std::string_view key = is_object_ ? keys_[index] : std::string_view{}; |
487 | | return lazy_beve_view<Opts>{doc_, value_starts_[index], key, element_tag_}; |
488 | | } |
489 | | |
490 | | [[nodiscard]] lazy_beve_view<Opts> operator[](std::string_view key) const |
491 | | { |
492 | | if (!is_object_) { |
493 | | return lazy_beve_view<Opts>::make_error(error_code::get_wrong_type); |
494 | | } |
495 | | for (size_t i = 0; i < keys_.size(); ++i) { |
496 | | if (keys_[i] == key) { |
497 | | return lazy_beve_view<Opts>{doc_, value_starts_[i], keys_[i]}; |
498 | | } |
499 | | } |
500 | | return lazy_beve_view<Opts>::make_error(error_code::key_not_found); |
501 | | } |
502 | | |
503 | | [[nodiscard]] bool contains(std::string_view key) const |
504 | | { |
505 | | if (!is_object_) return false; |
506 | | for (const auto& k : keys_) { |
507 | | if (k == key) return true; |
508 | | } |
509 | | return false; |
510 | | } |
511 | | |
512 | | [[nodiscard]] indexed_lazy_beve_iterator<Opts> begin() const; |
513 | | [[nodiscard]] indexed_lazy_beve_iterator<Opts> end() const; |
514 | | |
515 | | private: |
516 | | friend struct lazy_beve_view<Opts>; |
517 | | |
518 | | indexed_lazy_beve_view(const lazy_beve_document<Opts>* doc, const char* beve_end, bool is_object) |
519 | | : doc_(doc), beve_end_(beve_end), is_object_(is_object) |
520 | | {} |
521 | | |
522 | | void reserve(size_t n) |
523 | | { |
524 | | value_starts_.reserve(n); |
525 | | if (is_object_) { |
526 | | keys_.reserve(n); |
527 | | } |
528 | | } |
529 | | |
530 | | void add_element(const char* value_start, std::string_view key = {}) |
531 | | { |
532 | | value_starts_.push_back(value_start); |
533 | | if (is_object_) { |
534 | | keys_.push_back(key); |
535 | | } |
536 | | } |
537 | | }; |
538 | | |
539 | | // ============================================================================ |
540 | | // indexed_lazy_beve_iterator - O(1) advancement |
541 | | // ============================================================================ |
542 | | |
543 | | template <auto Opts> |
544 | | class indexed_lazy_beve_iterator |
545 | | { |
546 | | private: |
547 | | const indexed_lazy_beve_view<Opts>* parent_{}; |
548 | | size_t index_{}; |
549 | | mutable lazy_beve_view<Opts> current_view_{}; |
550 | | |
551 | | public: |
552 | | using iterator_category = std::random_access_iterator_tag; |
553 | | using value_type = lazy_beve_view<Opts>; |
554 | | using difference_type = std::ptrdiff_t; |
555 | | using pointer = void; |
556 | | using reference = lazy_beve_view<Opts>&; |
557 | | |
558 | | indexed_lazy_beve_iterator() = default; |
559 | | indexed_lazy_beve_iterator(const indexed_lazy_beve_view<Opts>* parent, size_t index) |
560 | | : parent_(parent), index_(index) |
561 | | {} |
562 | | |
563 | | reference operator*() const |
564 | | { |
565 | | std::string_view key = parent_->is_object_ ? parent_->keys_[index_] : std::string_view{}; |
566 | | current_view_ = |
567 | | lazy_beve_view<Opts>{parent_->doc_, parent_->value_starts_[index_], key, parent_->element_tag_}; |
568 | | return current_view_; |
569 | | } |
570 | | |
571 | | lazy_beve_view<Opts>* operator->() const |
572 | | { |
573 | | operator*(); |
574 | | return ¤t_view_; |
575 | | } |
576 | | |
577 | | indexed_lazy_beve_iterator& operator++() |
578 | | { |
579 | | ++index_; |
580 | | return *this; |
581 | | } |
582 | | |
583 | | indexed_lazy_beve_iterator operator++(int) |
584 | | { |
585 | | auto tmp = *this; |
586 | | ++index_; |
587 | | return tmp; |
588 | | } |
589 | | |
590 | | indexed_lazy_beve_iterator& operator--() |
591 | | { |
592 | | --index_; |
593 | | return *this; |
594 | | } |
595 | | |
596 | | indexed_lazy_beve_iterator operator--(int) |
597 | | { |
598 | | auto tmp = *this; |
599 | | --index_; |
600 | | return tmp; |
601 | | } |
602 | | |
603 | | indexed_lazy_beve_iterator& operator+=(difference_type n) |
604 | | { |
605 | | index_ = static_cast<size_t>(static_cast<difference_type>(index_) + n); |
606 | | return *this; |
607 | | } |
608 | | |
609 | | indexed_lazy_beve_iterator& operator-=(difference_type n) |
610 | | { |
611 | | index_ = static_cast<size_t>(static_cast<difference_type>(index_) - n); |
612 | | return *this; |
613 | | } |
614 | | |
615 | | indexed_lazy_beve_iterator operator+(difference_type n) const |
616 | | { |
617 | | return {parent_, static_cast<size_t>(static_cast<difference_type>(index_) + n)}; |
618 | | } |
619 | | |
620 | | indexed_lazy_beve_iterator operator-(difference_type n) const |
621 | | { |
622 | | return {parent_, static_cast<size_t>(static_cast<difference_type>(index_) - n)}; |
623 | | } |
624 | | |
625 | | difference_type operator-(const indexed_lazy_beve_iterator& other) const |
626 | | { |
627 | | return static_cast<difference_type>(index_) - static_cast<difference_type>(other.index_); |
628 | | } |
629 | | |
630 | | reference operator[](difference_type n) const |
631 | | { |
632 | | std::string_view key = |
633 | | parent_->is_object_ ? parent_->keys_[index_ + static_cast<size_t>(n)] : std::string_view{}; |
634 | | current_view_ = |
635 | | lazy_beve_view<Opts>{parent_->doc_, parent_->value_starts_[index_ + static_cast<size_t>(n)], key}; |
636 | | return current_view_; |
637 | | } |
638 | | |
639 | | bool operator==(const indexed_lazy_beve_iterator& other) const { return index_ == other.index_; } |
640 | | bool operator!=(const indexed_lazy_beve_iterator& other) const { return index_ != other.index_; } |
641 | | bool operator<(const indexed_lazy_beve_iterator& other) const { return index_ < other.index_; } |
642 | | bool operator<=(const indexed_lazy_beve_iterator& other) const { return index_ <= other.index_; } |
643 | | bool operator>(const indexed_lazy_beve_iterator& other) const { return index_ > other.index_; } |
644 | | bool operator>=(const indexed_lazy_beve_iterator& other) const { return index_ >= other.index_; } |
645 | | |
646 | | friend indexed_lazy_beve_iterator operator+(difference_type n, const indexed_lazy_beve_iterator& it) |
647 | | { |
648 | | return it + n; |
649 | | } |
650 | | }; |
651 | | |
652 | | // ============================================================================ |
653 | | // Implementation: lazy_beve_view methods |
654 | | // ============================================================================ |
655 | | |
656 | | template <auto Opts> |
657 | | inline const char* lazy_beve_view<Opts>::beve_end() const noexcept |
658 | | { |
659 | | return doc_ ? doc_->beve_ + doc_->len_ : nullptr; |
660 | | } |
661 | | |
662 | | template <auto Opts> |
663 | | inline lazy_beve_view<Opts> lazy_beve_view<Opts>::operator[](size_t index) const |
664 | | { |
665 | | if (has_error()) return *this; |
666 | | if (!is_array()) return make_error(error_code::get_wrong_type); |
667 | | |
668 | | const char* end = beve_end(); |
669 | | const char* p = data_; |
670 | | const uint8_t t = uint8_t(*p); |
671 | | const uint8_t type_bits = t & 0b00000'111; |
672 | | ++p; |
673 | | |
674 | | const auto count = detail::read_compressed_int(p, end); |
675 | | if (index >= count) { |
676 | | return make_error(error_code::exceeded_static_array_size); |
677 | | } |
678 | | |
679 | | if (type_bits == tag::generic_array) { |
680 | | // Generic array - each element has its own tag |
681 | | for (size_t i = 0; i < index; ++i) { |
682 | | p = detail::skip_value_beve_lazy<Opts>(p, end); |
683 | | } |
684 | | return {doc_, p}; |
685 | | } |
686 | | else { |
687 | | // Typed array - homogeneous elements |
688 | | const uint8_t element_type = (t & 0b000'11'000) >> 3; |
689 | | |
690 | | if (element_type == 3) { |
691 | | // Bool or string array |
692 | | const bool is_string = (t & 0b00'1'00'000) >> 5; |
693 | | if (is_string) { |
694 | | // String array - variable length elements |
695 | | for (size_t i = 0; i < index; ++i) { |
696 | | const auto len = detail::read_compressed_int(p, end); |
697 | | p += len; |
698 | | } |
699 | | // Return view with synthetic string tag |
700 | | return {doc_, p, {}, tag::string}; |
701 | | } |
702 | | else { |
703 | | // Boolean array - packed bits, can't easily index |
704 | | // For now, return error for direct indexing of bool arrays |
705 | | return make_error(error_code::get_wrong_type); |
706 | | } |
707 | | } |
708 | | else { |
709 | | // Numeric typed array - fixed size elements |
710 | | const size_t elem_size = byte_count_lookup[t >> 5]; |
711 | | p += index * elem_size; |
712 | | // Return view with synthetic numeric tag |
713 | | const uint8_t synthetic_tag = tag::number | (t & 0b11111000); |
714 | | return {doc_, p, {}, synthetic_tag}; |
715 | | } |
716 | | } |
717 | | } |
718 | | |
719 | | template <auto Opts> |
720 | | inline lazy_beve_view<Opts> lazy_beve_view<Opts>::operator[](std::string_view key) const |
721 | | { |
722 | | if (has_error()) return *this; |
723 | | if (!is_object()) return make_error(error_code::get_wrong_type); |
724 | | |
725 | | const char* end = beve_end(); |
726 | | const char* p = data_; |
727 | | const uint8_t t = uint8_t(*p); |
728 | | ++p; |
729 | | |
730 | | // Check for string-keyed object (key_type bits should be 0 for string keys) |
731 | | const uint8_t key_type = t & 0b000'11'000; |
732 | | if (key_type != 0) { |
733 | | // Number-keyed object - string lookup not supported |
734 | | return make_error(error_code::get_wrong_type); |
735 | | } |
736 | | |
737 | | const auto n_keys = detail::read_compressed_int(p, end); |
738 | | |
739 | | // Determine search start position for progressive scanning |
740 | | const char* search_start = p; |
741 | | size_t start_index = 0; |
742 | | |
743 | | if (parse_pos_ && parse_pos_ > data_) { |
744 | | // Skip past the last found value |
745 | | const char* skip_from = parse_pos_; |
746 | | skip_from = detail::skip_value_beve_lazy<Opts>(skip_from, end); |
747 | | search_start = skip_from; |
748 | | |
749 | | // Count how many keys we've passed |
750 | | const char* counter = p; |
751 | | while (counter < search_start && start_index < n_keys) { |
752 | | const auto key_len = detail::read_compressed_int(counter, end); |
753 | | if (static_cast<size_t>(end - counter) < key_len) [[unlikely]] { |
754 | | break; |
755 | | } |
756 | | counter += key_len; |
757 | | counter = detail::skip_value_beve_lazy<Opts>(counter, end); |
758 | | ++start_index; |
759 | | } |
760 | | } |
761 | | |
762 | | // Forward pass: search from current position |
763 | | const char* iter = search_start; |
764 | | for (size_t i = start_index; i < n_keys; ++i) { |
765 | | const auto key_len = detail::read_compressed_int(iter, end); |
766 | | if (static_cast<size_t>(end - iter) < key_len) [[unlikely]] { |
767 | | return make_error(error_code::unexpected_end); |
768 | | } |
769 | | std::string_view current_key{iter, key_len}; |
770 | | iter += key_len; |
771 | | |
772 | | if (current_key == key) { |
773 | | parse_pos_ = iter; |
774 | | return {doc_, iter}; |
775 | | } |
776 | | |
777 | | iter = detail::skip_value_beve_lazy<Opts>(iter, end); |
778 | | } |
779 | | |
780 | | // Wrap-around pass: search from beginning to where we started |
781 | | if (start_index > 0) { |
782 | | iter = p; |
783 | | for (size_t i = 0; i < start_index; ++i) { |
784 | | const auto key_len = detail::read_compressed_int(iter, end); |
785 | | if (static_cast<size_t>(end - iter) < key_len) [[unlikely]] { |
786 | | return make_error(error_code::unexpected_end); |
787 | | } |
788 | | std::string_view current_key{iter, key_len}; |
789 | | iter += key_len; |
790 | | |
791 | | if (current_key == key) { |
792 | | parse_pos_ = iter; |
793 | | return {doc_, iter}; |
794 | | } |
795 | | |
796 | | iter = detail::skip_value_beve_lazy<Opts>(iter, end); |
797 | | } |
798 | | } |
799 | | |
800 | | return make_error(error_code::key_not_found); |
801 | | } |
802 | | |
803 | | template <auto Opts> |
804 | | inline bool lazy_beve_view<Opts>::contains(std::string_view key) const |
805 | | { |
806 | | auto result = (*this)[key]; |
807 | | return !result.has_error(); |
808 | | } |
809 | | |
810 | | template <auto Opts> |
811 | | inline size_t lazy_beve_view<Opts>::size() const |
812 | | { |
813 | | if (has_error() || !data_) return 0; |
814 | | |
815 | | // Handle synthetic tags (typed array elements) |
816 | | if (synthetic_tag_ != 0) { |
817 | | const uint8_t base_type = synthetic_tag_ & 0b00000'111; |
818 | | if (base_type == tag::string) { |
819 | | // String element in typed string array - length prefixed |
820 | | auto [len, bytes] = detail::peek_compressed_int(data_, beve_end()); |
821 | | return len; |
822 | | } |
823 | | return 0; |
824 | | } |
825 | | |
826 | | const uint8_t t = uint8_t(*data_) & 0b00000'111; |
827 | | |
828 | | if (t == tag::string) { |
829 | | const char* p = data_ + 1; // Skip tag |
830 | | auto [len, bytes] = detail::peek_compressed_int(p, beve_end()); |
831 | | return len; |
832 | | } |
833 | | |
834 | | if (t == tag::object || t == tag::typed_array || t == tag::generic_array) { |
835 | | const char* p = data_ + 1; |
836 | | return detail::read_compressed_int(p, beve_end()); |
837 | | } |
838 | | |
839 | | return 0; |
840 | | } |
841 | | |
842 | | template <auto Opts> |
843 | | inline bool lazy_beve_view<Opts>::empty() const noexcept |
844 | | { |
845 | | if (has_error() || !data_) return true; |
846 | | if (is_null()) return true; |
847 | | if (!is_array() && !is_object()) return false; |
848 | | |
849 | | const char* p = data_ + 1; |
850 | | const auto count = detail::read_compressed_int(p, beve_end()); |
851 | | return count == 0; |
852 | | } |
853 | | |
854 | | // ============================================================================ |
855 | | // lazy_beve_iterator implementation |
856 | | // ============================================================================ |
857 | | |
858 | | template <auto Opts> |
859 | | inline lazy_beve_iterator<Opts>::lazy_beve_iterator(const lazy_beve_document<Opts>* doc, const char* container_start, |
860 | | const char* end, bool is_object, bool is_typed_array) |
861 | | : doc_(doc), beve_end_(end), is_object_(is_object), is_typed_array_(is_typed_array), at_end_(false) |
862 | | { |
863 | | const char* p = container_start; |
864 | | const uint8_t t = uint8_t(*p); |
865 | | ++p; |
866 | | |
867 | | remaining_count_ = detail::read_compressed_int(p, end); |
868 | | |
869 | | if (remaining_count_ == 0) { |
870 | | at_end_ = true; |
871 | | return; |
872 | | } |
873 | | |
874 | | if (is_object_) { |
875 | | // Check key type from bits 3-4 of tag |
876 | | const uint8_t key_type_bits = t & 0b000'11'000; |
877 | | has_string_keys_ = (key_type_bits == 0); |
878 | | if (!has_string_keys_) { |
879 | | key_byte_count_ = static_cast<uint8_t>(byte_count_lookup[t >> 5]); |
880 | | } |
881 | | } |
882 | | |
883 | | if (is_typed_array_) { |
884 | | // For typed arrays, calculate element size |
885 | | const uint8_t element_type = (t & 0b000'11'000) >> 3; |
886 | | if (element_type < 3) { |
887 | | element_size_ = static_cast<uint8_t>(byte_count_lookup[t >> 5]); |
888 | | } |
889 | | else { |
890 | | // Bool or string array - variable handling needed |
891 | | element_size_ = 0; |
892 | | } |
893 | | } |
894 | | |
895 | | current_pos_ = p; |
896 | | advance_to_current_element(); |
897 | | } |
898 | | |
899 | | template <auto Opts> |
900 | | inline void lazy_beve_iterator<Opts>::advance_to_current_element() |
901 | | { |
902 | | std::string_view key{}; |
903 | | |
904 | | if (is_object_) { |
905 | | if (has_string_keys_) { |
906 | | // String key: length prefix + string data |
907 | | const auto key_len = detail::read_compressed_int(current_pos_, beve_end_); |
908 | | if (static_cast<size_t>(beve_end_ - current_pos_) < key_len) [[unlikely]] { |
909 | | // Declared key length runs past the buffer; stop rather than |
910 | | // handing back a view over out-of-bounds memory. |
911 | | at_end_ = true; |
912 | | current_view_ = lazy_beve_view<Opts>::make_error(error_code::unexpected_end); |
913 | | return; |
914 | | } |
915 | | key = std::string_view{current_pos_, key_len}; |
916 | | current_pos_ += key_len; |
917 | | } |
918 | | else { |
919 | | // Number key: just raw bytes (no key view for number keys) |
920 | | current_pos_ += key_byte_count_; |
921 | | } |
922 | | } |
923 | | |
924 | | current_view_ = lazy_beve_view<Opts>{doc_, current_pos_, key}; |
925 | | } |
926 | | |
927 | | template <auto Opts> |
928 | | inline lazy_beve_iterator<Opts>& lazy_beve_iterator<Opts>::operator++() |
929 | | { |
930 | | if (at_end_) return *this; |
931 | | |
932 | | --remaining_count_; |
933 | | if (remaining_count_ == 0) { |
934 | | at_end_ = true; |
935 | | return *this; |
936 | | } |
937 | | |
938 | | // Skip current value |
939 | | if (is_typed_array_ && element_size_ > 0) { |
940 | | current_pos_ += element_size_; |
941 | | } |
942 | | else { |
943 | | current_pos_ = detail::skip_value_beve_lazy<Opts>(current_pos_, beve_end_); |
944 | | } |
945 | | |
946 | | advance_to_current_element(); |
947 | | return *this; |
948 | | } |
949 | | |
950 | | template <auto Opts> |
951 | | inline lazy_beve_iterator<Opts> lazy_beve_view<Opts>::begin() const |
952 | | { |
953 | | if (has_error() || !data_) return end(); |
954 | | if (!is_array() && !is_object()) return end(); |
955 | | |
956 | | const uint8_t t = uint8_t(*data_) & 0b00000'111; |
957 | | const bool is_typed = (t == tag::typed_array); |
958 | | return lazy_beve_iterator<Opts>{doc_, data_, beve_end(), is_object(), is_typed}; |
959 | | } |
960 | | |
961 | | template <auto Opts> |
962 | | inline lazy_beve_iterator<Opts> lazy_beve_view<Opts>::end() const |
963 | | { |
964 | | return lazy_beve_iterator<Opts>{}; |
965 | | } |
966 | | |
967 | | // ============================================================================ |
968 | | // indexed_lazy_beve_view implementation |
969 | | // ============================================================================ |
970 | | |
971 | | template <auto Opts> |
972 | | inline indexed_lazy_beve_iterator<Opts> indexed_lazy_beve_view<Opts>::begin() const |
973 | | { |
974 | | return indexed_lazy_beve_iterator<Opts>{this, 0}; |
975 | | } |
976 | | |
977 | | template <auto Opts> |
978 | | inline indexed_lazy_beve_iterator<Opts> indexed_lazy_beve_view<Opts>::end() const |
979 | | { |
980 | | return indexed_lazy_beve_iterator<Opts>{this, value_starts_.size()}; |
981 | | } |
982 | | |
983 | | template <auto Opts> |
984 | | inline indexed_lazy_beve_view<Opts> lazy_beve_view<Opts>::index() const |
985 | | { |
986 | | if (has_error() || !data_ || (!is_array() && !is_object())) { |
987 | | return indexed_lazy_beve_view<Opts>{}; |
988 | | } |
989 | | |
990 | | const char* end = beve_end(); |
991 | | indexed_lazy_beve_view<Opts> result{doc_, end, is_object()}; |
992 | | |
993 | | const char* p = data_; |
994 | | const uint8_t t = uint8_t(*p); |
995 | | const uint8_t type_bits = t & 0b00000'111; |
996 | | ++p; |
997 | | |
998 | | const auto count = detail::read_compressed_int(p, end); |
999 | | if (count == 0) return result; |
1000 | | |
1001 | | result.reserve(count); |
1002 | | |
1003 | | if (type_bits == tag::object) { |
1004 | | // Check key type from bits 3-4 of tag |
1005 | | const uint8_t key_type_bits = t & 0b000'11'000; |
1006 | | const bool has_string_keys = (key_type_bits == 0); |
1007 | | |
1008 | | if (has_string_keys) { |
1009 | | // Object with string keys |
1010 | | for (size_t i = 0; i < count; ++i) { |
1011 | | const auto key_len = detail::read_compressed_int(p, end); |
1012 | | if (static_cast<size_t>(end - p) < key_len) [[unlikely]] { |
1013 | | // Truncated key; stop indexing instead of storing an |
1014 | | // out-of-bounds key view. |
1015 | | break; |
1016 | | } |
1017 | | std::string_view key{p, key_len}; |
1018 | | p += key_len; |
1019 | | |
1020 | | result.add_element(p, key); |
1021 | | p = detail::skip_value_beve_lazy<Opts>(p, end); |
1022 | | } |
1023 | | } |
1024 | | else { |
1025 | | // Object with number keys |
1026 | | const size_t key_size = byte_count_lookup[t >> 5]; |
1027 | | for (size_t i = 0; i < count; ++i) { |
1028 | | p += key_size; // Skip the number key |
1029 | | result.add_element(p); // No key stored for number keys |
1030 | | p = detail::skip_value_beve_lazy<Opts>(p, end); |
1031 | | } |
1032 | | } |
1033 | | } |
1034 | | else if (type_bits == tag::generic_array) { |
1035 | | // Generic array |
1036 | | for (size_t i = 0; i < count; ++i) { |
1037 | | result.add_element(p); |
1038 | | p = detail::skip_value_beve_lazy<Opts>(p, end); |
1039 | | } |
1040 | | } |
1041 | | else if (type_bits == tag::typed_array) { |
1042 | | // Typed array |
1043 | | const uint8_t element_type = (t & 0b000'11'000) >> 3; |
1044 | | result.is_typed_array_ = true; |
1045 | | |
1046 | | if (element_type < 3) { |
1047 | | // Numeric typed array - fixed size elements |
1048 | | // Synthesize element tag: number base type + same type/width bits from array tag |
1049 | | result.element_tag_ = tag::number | (t & 0b11111000); |
1050 | | const size_t elem_size = byte_count_lookup[t >> 5]; |
1051 | | for (size_t i = 0; i < count; ++i) { |
1052 | | result.add_element(p); |
1053 | | p += elem_size; |
1054 | | } |
1055 | | } |
1056 | | else { |
1057 | | // Bool or string array |
1058 | | const bool is_string_arr = (t & 0b00'1'00'000) >> 5; |
1059 | | if (is_string_arr) { |
1060 | | // String array - elements are length-prefixed strings without tag |
1061 | | result.element_tag_ = tag::string; |
1062 | | for (size_t i = 0; i < count; ++i) { |
1063 | | result.add_element(p); |
1064 | | const auto len = detail::read_compressed_int(p, end); |
1065 | | p += len; |
1066 | | } |
1067 | | } |
1068 | | else { |
1069 | | // Boolean array - packed bits, index each bit position conceptually |
1070 | | // For simplicity, we don't index individual bools |
1071 | | // Users should use size() and iteration instead |
1072 | | } |
1073 | | } |
1074 | | } |
1075 | | |
1076 | | return result; |
1077 | | } |
1078 | | |
1079 | | // ============================================================================ |
1080 | | // lazy_beve_view::get<T>() implementation |
1081 | | // ============================================================================ |
1082 | | |
1083 | | template <auto Opts> |
1084 | | template <class T> |
1085 | | [[nodiscard]] inline expected<T, error_ctx> lazy_beve_view<Opts>::get() const |
1086 | | { |
1087 | | if (has_error()) { |
1088 | | return unexpected(error_ctx{0, error_}); |
1089 | | } |
1090 | | |
1091 | | const char* end = beve_end(); |
1092 | | |
1093 | | // For typed array elements, synthetic_tag_ is non-zero and data_ points directly to value |
1094 | | // For normal values, tag is read from data_ and value starts at data_+1 |
1095 | | const bool has_synthetic_tag = (synthetic_tag_ != 0); |
1096 | | const uint8_t tag = has_synthetic_tag ? synthetic_tag_ : uint8_t(*data_); |
1097 | | const char* value_ptr = has_synthetic_tag ? data_ : data_ + 1; |
1098 | | |
1099 | | if constexpr (std::is_same_v<T, bool>) { |
1100 | | if (has_synthetic_tag) { |
1101 | | return unexpected(error_ctx{0, error_code::get_wrong_type}); // Bool arrays not supported this way |
1102 | | } |
1103 | | if (!is_boolean()) { |
1104 | | return unexpected(error_ctx{0, error_code::get_wrong_type}); |
1105 | | } |
1106 | | // Boolean value is in bit 4 of the tag |
1107 | | return (uint8_t(*data_) >> 4) & 1; |
1108 | | } |
1109 | | else if constexpr (std::is_same_v<T, std::nullptr_t>) { |
1110 | | if (has_synthetic_tag || !is_null()) { |
1111 | | return unexpected(error_ctx{0, error_code::get_wrong_type}); |
1112 | | } |
1113 | | return nullptr; |
1114 | | } |
1115 | | else if constexpr (std::is_same_v<T, std::string>) { |
1116 | | const uint8_t base_type = tag & 0b00000'111; |
1117 | | if (base_type != tag::string) { |
1118 | | return unexpected(error_ctx{0, error_code::get_wrong_type}); |
1119 | | } |
1120 | | // For typed string arrays, value_ptr points to length-prefixed string (no tag) |
1121 | | // For normal strings, we already skipped the tag |
1122 | | const char* p = value_ptr; |
1123 | | const auto len = detail::read_compressed_int(p, end); |
1124 | | if (static_cast<size_t>(end - p) < len) { |
1125 | | return unexpected(error_ctx{0, error_code::unexpected_end}); |
1126 | | } |
1127 | | return std::string{p, len}; |
1128 | | } |
1129 | | else if constexpr (std::is_same_v<T, std::string_view>) { |
1130 | | const uint8_t base_type = tag & 0b00000'111; |
1131 | | if (base_type != tag::string) { |
1132 | | return unexpected(error_ctx{0, error_code::get_wrong_type}); |
1133 | | } |
1134 | | const char* p = value_ptr; |
1135 | | const auto len = detail::read_compressed_int(p, end); |
1136 | | if (static_cast<size_t>(end - p) < len) { |
1137 | | return unexpected(error_ctx{0, error_code::unexpected_end}); |
1138 | | } |
1139 | | return std::string_view{p, len}; |
1140 | | } |
1141 | | else if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, bool>) { |
1142 | | const uint8_t base_type = tag & 0b00000'111; |
1143 | | if (base_type != tag::number) { |
1144 | | return unexpected(error_ctx{0, error_code::get_wrong_type}); |
1145 | | } |
1146 | | |
1147 | | if (has_synthetic_tag) { |
1148 | | // Typed array element - read directly from value_ptr |
1149 | | return read_numeric_from_tag<T>(tag, value_ptr, end); |
1150 | | } |
1151 | | else { |
1152 | | // Normal value - use standard BEVE parsing |
1153 | | T value{}; |
1154 | | auto it = data_; |
1155 | | context ctx{}; |
1156 | | parse<BEVE>::op<Opts>(value, ctx, it, end); |
1157 | | if (bool(ctx.error)) { |
1158 | | return unexpected(error_ctx{0, ctx.error}); |
1159 | | } |
1160 | | return value; |
1161 | | } |
1162 | | } |
1163 | | else { |
1164 | | static_assert(false_v<T>, "Unsupported type for lazy_beve_view::get<T>()"); |
1165 | | } |
1166 | | } |
1167 | | |
1168 | | // Helper to read numeric value directly given tag info (for typed array elements) |
1169 | | template <auto Opts> |
1170 | | template <class T> |
1171 | | [[nodiscard]] inline expected<T, error_ctx> lazy_beve_view<Opts>::read_numeric_from_tag(uint8_t tag, |
1172 | | const char* value_ptr, |
1173 | | const char* end) const |
1174 | | { |
1175 | | const uint8_t type = (tag & 0b000'11'000) >> 3; // 0=float, 1=signed, 2=unsigned |
1176 | | const size_t byte_count = byte_count_lookup[tag >> 5]; |
1177 | | |
1178 | | if (value_ptr + byte_count > end) { |
1179 | | return unexpected(error_ctx{0, error_code::unexpected_end}); |
1180 | | } |
1181 | | |
1182 | | if (type == 0) { |
1183 | | // Floating point |
1184 | | if (byte_count == 4) { |
1185 | | float f; |
1186 | | std::memcpy(&f, value_ptr, 4); |
1187 | | return static_cast<T>(f); |
1188 | | } |
1189 | | else if (byte_count == 8) { |
1190 | | double d; |
1191 | | std::memcpy(&d, value_ptr, 8); |
1192 | | return static_cast<T>(d); |
1193 | | } |
1194 | | } |
1195 | | else if (type == 1) { |
1196 | | // Signed integer |
1197 | | int64_t val = 0; |
1198 | | switch (byte_count) { |
1199 | | case 1: { |
1200 | | int8_t v; |
1201 | | std::memcpy(&v, value_ptr, 1); |
1202 | | val = v; |
1203 | | break; |
1204 | | } |
1205 | | case 2: { |
1206 | | int16_t v; |
1207 | | std::memcpy(&v, value_ptr, 2); |
1208 | | val = v; |
1209 | | break; |
1210 | | } |
1211 | | case 4: { |
1212 | | int32_t v; |
1213 | | std::memcpy(&v, value_ptr, 4); |
1214 | | val = v; |
1215 | | break; |
1216 | | } |
1217 | | case 8: { |
1218 | | std::memcpy(&val, value_ptr, 8); |
1219 | | break; |
1220 | | } |
1221 | | } |
1222 | | return static_cast<T>(val); |
1223 | | } |
1224 | | else if (type == 2) { |
1225 | | // Unsigned integer |
1226 | | uint64_t val = 0; |
1227 | | switch (byte_count) { |
1228 | | case 1: { |
1229 | | uint8_t v; |
1230 | | std::memcpy(&v, value_ptr, 1); |
1231 | | val = v; |
1232 | | break; |
1233 | | } |
1234 | | case 2: { |
1235 | | uint16_t v; |
1236 | | std::memcpy(&v, value_ptr, 2); |
1237 | | val = v; |
1238 | | break; |
1239 | | } |
1240 | | case 4: { |
1241 | | uint32_t v; |
1242 | | std::memcpy(&v, value_ptr, 4); |
1243 | | val = v; |
1244 | | break; |
1245 | | } |
1246 | | case 8: { |
1247 | | std::memcpy(&val, value_ptr, 8); |
1248 | | break; |
1249 | | } |
1250 | | } |
1251 | | return static_cast<T>(val); |
1252 | | } |
1253 | | |
1254 | | return unexpected(error_ctx{0, error_code::get_wrong_type}); |
1255 | | } |
1256 | | |
1257 | | // ============================================================================ |
1258 | | // BEVE writer for lazy_beve_view |
1259 | | // ============================================================================ |
1260 | | |
1261 | | template <auto Opts> |
1262 | | struct to<BEVE, lazy_beve_view<Opts>> |
1263 | | { |
1264 | | template <auto WriteOpts, class B> |
1265 | | GLZ_ALWAYS_INLINE static void op(const lazy_beve_view<Opts>& view, is_context auto&& ctx, B&& b, auto& ix) |
1266 | | { |
1267 | | if (view.has_error()) { |
1268 | | ctx.error = view.error(); |
1269 | | return; |
1270 | | } |
1271 | | if (!view.data()) { |
1272 | | // Write null |
1273 | | dump_type(ctx, uint8_t{tag::null}, b, ix); |
1274 | | return; |
1275 | | } |
1276 | | |
1277 | | auto it = view.data(); |
1278 | | context parse_ctx{}; |
1279 | | skip_value<BEVE>::op<Opts>(parse_ctx, it, view.beve_end()); |
1280 | | if (bool(parse_ctx.error)) [[unlikely]] { |
1281 | | ctx.error = parse_ctx.error; |
1282 | | return; |
1283 | | } |
1284 | | |
1285 | | const size_t n = static_cast<size_t>(it - view.data()); |
1286 | | if constexpr (resizable<B>) { |
1287 | | if (ix + n > b.size()) [[unlikely]] { |
1288 | | grow_buffer(b, ix + n); |
1289 | | } |
1290 | | } |
1291 | | else { |
1292 | | if (ix + n > b.size()) [[unlikely]] { |
1293 | | ctx.error = error_code::buffer_overflow; |
1294 | | return; |
1295 | | } |
1296 | | } |
1297 | | std::memcpy(&b[ix], view.data(), n); |
1298 | | ix += n; |
1299 | | } |
1300 | | }; |
1301 | | |
1302 | | // ============================================================================ |
1303 | | // lazy_beve - Main entry point |
1304 | | // ============================================================================ |
1305 | | |
1306 | | /** |
1307 | | * @brief Create a lazy BEVE document - minimal upfront processing. |
1308 | | * |
1309 | | * @tparam Opts Options for parsing |
1310 | | * @param buffer The BEVE buffer (must remain valid for document lifetime) |
1311 | | * @return lazy_beve_document on success, error_ctx on failure |
1312 | | */ |
1313 | | template <auto Opts = opts{}, class Buffer> |
1314 | | [[nodiscard]] inline expected<lazy_beve_document<Opts>, error_ctx> lazy_beve(Buffer&& buffer) |
1315 | | { |
1316 | | lazy_beve_document<Opts> doc; |
1317 | | doc.beve_ = reinterpret_cast<const char*>(buffer.data()); |
1318 | | doc.len_ = buffer.size(); |
1319 | | |
1320 | | if (buffer.empty()) { |
1321 | | return unexpected(error_ctx{0, error_code::unexpected_end}); |
1322 | | } |
1323 | | |
1324 | | // Validate first byte is a valid BEVE tag |
1325 | | const uint8_t first_byte = static_cast<uint8_t>(buffer[0]); |
1326 | | const uint8_t type_bits = first_byte & 0b00000'111; |
1327 | | |
1328 | | // Valid types: null(0), number(1), string(2), object(3), typed_array(4), generic_array(5), extensions(6) |
1329 | | // Also boolean has tag::boolean pattern |
1330 | | const bool is_boolean = (first_byte & 0b0000'1111) == tag::boolean; |
1331 | | const bool is_valid_type = is_boolean || type_bits <= 6; |
1332 | | |
1333 | | if (!is_valid_type) { |
1334 | | return unexpected(error_ctx{0, error_code::syntax_error}); |
1335 | | } |
1336 | | |
1337 | | doc.root_data_ = doc.beve_; |
1338 | | doc.init_root_view(); |
1339 | | return doc; |
1340 | | } |
1341 | | |
1342 | | // ============================================================================ |
1343 | | // read_beve overload for lazy_beve_view |
1344 | | // ============================================================================ |
1345 | | |
1346 | | template <class T, auto Opts> |
1347 | | [[nodiscard]] inline error_ctx read_beve(T& value, const lazy_beve_view<Opts>& view) |
1348 | | { |
1349 | | return view.template read_into<T>(value); |
1350 | | } |
1351 | | |
1352 | | template <class T, auto Opts> |
1353 | | [[nodiscard]] inline error_ctx read_beve(T& value, lazy_beve_view<Opts>&& view) |
1354 | | { |
1355 | | return view.template read_into<T>(value); |
1356 | | } |
1357 | | |
1358 | | } // namespace glz |