Coverage Report

Created: 2026-09-14 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 &current_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