Skip to main content

simdjson_rust/
tape.rs

1//! Tape parser for simdjson DOM tape format.
2//!
3//! The simdjson DOM parser serializes parsed JSON into a "tape" - an array of
4//! 64-bit words. Each word encodes its node type in the top 8 bits and a
5//! payload (e.g., string offset, number of elements, or sibling index) in the
6//! lower 56 bits.
7//!
8//! ## Tape type byte values (top 8 bits of each tape word)
9//!
10//! | Char | Hex  | Meaning                                         |
11//! |------|------|-------------------------------------------------|
12//! | `r`  | 0x72 | Root node (always first and last entries)       |
13//! | `{`  | 0x7B | Start of object (payload = end-of-scope index)  |
14//! | `}`  | 0x7D | End of object (payload = start index)           |
15//! | `[`  | 0x5B | Start of array  (payload = end-of-scope index)  |
16//! | `]`  | 0x5D | End of array    (payload = start index)         |
17//! | `"`  | 0x22 | String (payload = byte offset into string_buf)  |
18//! | `l`  | 0x6C | Signed integer  (next tape word = i64 bits)     |
19//! | `u`  | 0x75 | Unsigned integer(next tape word = u64)          |
20//! | `d`  | 0x64 | Double          (next tape word = f64 bits)     |
21//! | `t`  | 0x74 | Boolean true                                    |
22//! | `f`  | 0x66 | Boolean false                                   |
23//! | `n`  | 0x6E | Null                                            |
24
25use std::str;
26
27use snafu::prelude::*;
28
29/// The error type returned by Tape operations.
30///
31/// Each variant names the concrete failure and carries the positions and
32/// lengths relevant to it, so callers can inspect *what* went wrong and
33/// *where*, rather than parsing a formatted message.
34#[derive(Debug, Snafu)]
35#[snafu(visibility(pub(crate)))]
36pub enum Error {
37    /// The tape cursor advanced past the end of the tape.
38    #[snafu(display("tape position {pos} out of bounds (len={len})"))]
39    OutOfBounds { pos: usize, len: usize },
40
41    /// The tape word's tag byte is not a recognised `TapeType`.
42    #[snafu(display("unknown tape type byte 0x{tag:02X} at position {pos}"))]
43    UnknownTapeType { tag: u8, pos: usize },
44
45    /// The tape word's type is recognised but not valid at this position
46    /// (e.g. a closing `}` where a value is expected, or a non-string node
47    /// where an object key is expected).
48    #[snafu(display("unexpected tape type 0x{found:02X} at position {pos}"))]
49    UnexpectedTapeType { found: u8, pos: usize },
50
51    /// A string node's offset or length runs past the end of `string_buf`.
52    #[snafu(display("string at offset {offset} out of bounds (string_buf len={len})"))]
53    StringOutOfBounds { offset: usize, len: usize },
54
55    /// A number node is missing its second (value) tape word.
56    #[snafu(display("value word missing for tape node at position {pos}"))]
57    MissingValueWord { pos: usize },
58
59    /// The string bytes at the node's offset are not valid UTF-8.
60    #[snafu(display("invalid UTF-8 in string at offset {pos}"))]
61    InvalidUtf8 {
62        source: std::str::Utf8Error,
63        pos: usize,
64    },
65}
66
67pub type Result<T, E = Error> = std::result::Result<T, E>;
68
69/// Mask to extract the lower 56 bits (payload) from a tape word.
70const PAYLOAD_MASK: u64 = 0x00FF_FFFF_FFFF_FFFF;
71
72/// Tape type byte values encoded in the top 8 bits of each tape word.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74#[repr(u8)]
75pub enum TapeType {
76    Root = b'r',
77    StartObject = b'{',
78    EndObject = b'}',
79    StartArray = b'[',
80    EndArray = b']',
81    String = b'"',
82    Int64 = b'l',
83    Uint64 = b'u',
84    Double = b'd',
85    True = b't',
86    False = b'f',
87    Null = b'n',
88}
89
90impl TapeType {
91    /// Decode the tape type from the top byte of a tape word.
92    #[inline]
93    pub fn from_tape_word(word: u64) -> Option<Self> {
94        let tag = (word >> 56) as u8;
95        match tag {
96            b'r' => Some(Self::Root),
97            b'{' => Some(Self::StartObject),
98            b'}' => Some(Self::EndObject),
99            b'[' => Some(Self::StartArray),
100            b']' => Some(Self::EndArray),
101            b'"' => Some(Self::String),
102            b'l' => Some(Self::Int64),
103            b'u' => Some(Self::Uint64),
104            b'd' => Some(Self::Double),
105            b't' => Some(Self::True),
106            b'f' => Some(Self::False),
107            b'n' => Some(Self::Null),
108            _ => None,
109        }
110    }
111}
112/// A zero-copy representation of a JSON value parsed from the simdjson tape.
113#[derive(Debug, Clone, PartialEq)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize))]
115pub enum Value<'a> {
116    Null,
117    Bool(bool),
118    Int64(i64),
119    Uint64(u64),
120    Double(f64),
121    String(&'a str),
122    Array(Vec<Value<'a>>),
123    Object(Vec<(&'a str, Value<'a>)>),
124}
125
126impl<'a> Value<'a> {
127    /// Look up a field in a JSON object by key.
128    pub fn get(&self, key: &str) -> Option<&Value<'a>> {
129        match self {
130            Value::Object(fields) => fields.iter().find(|(k, _)| *k == key).map(|(_, v)| v),
131            _ => None,
132        }
133    }
134
135    /// Look up an element in a JSON array by index.
136    pub fn get_index(&self, index: usize) -> Option<&Value<'a>> {
137        match self {
138            Value::Array(arr) => arr.get(index),
139            _ => None,
140        }
141    }
142
143    /// Returns true if the value is Null.
144    pub fn is_null(&self) -> bool {
145        matches!(self, Value::Null)
146    }
147
148    /// Returns the boolean value if this is a Bool.
149    pub fn as_bool(&self) -> Option<bool> {
150        match self {
151            Value::Bool(b) => Some(*b),
152            _ => None,
153        }
154    }
155
156    /// Returns the i64 value if this is an Int64 or a compatible Uint64.
157    pub fn as_i64(&self) -> Option<i64> {
158        match self {
159            Value::Int64(i) => Some(*i),
160            Value::Uint64(u) => (*u).try_into().ok(),
161            _ => None,
162        }
163    }
164
165    /// Returns the u64 value if this is a Uint64 or a compatible Int64.
166    pub fn as_u64(&self) -> Option<u64> {
167        match self {
168            Value::Uint64(u) => Some(*u),
169            Value::Int64(i) => (*i).try_into().ok(),
170            _ => None,
171        }
172    }
173
174    /// Returns the f64 value if this is a Double.
175    pub fn as_f64(&self) -> Option<f64> {
176        match self {
177            Value::Double(f) => Some(*f),
178            _ => None,
179        }
180    }
181
182    /// Returns the borrowed string slice if this is a String.
183    pub fn as_str(&self) -> Option<&'a str> {
184        match self {
185            Value::String(s) => Some(*s),
186            _ => None,
187        }
188    }
189
190    /// Returns the borrowed array slice if this is an Array.
191    pub fn as_array(&self) -> Option<&[Value<'a>]> {
192        match self {
193            Value::Array(arr) => Some(arr),
194            _ => None,
195        }
196    }
197
198    /// Returns the borrowed object slice if this is an Object.
199    pub fn as_object(&self) -> Option<&[(&'a str, Value<'a>)]> {
200        match self {
201            Value::Object(obj) => Some(obj),
202            _ => None,
203        }
204    }
205}
206
207/// A cursor over the simdjson DOM tape.
208///
209/// `TapeRef` borrows both the tape and the string buffer slices from a
210/// [`simdjson_sys::dom_ffi::TapeView`]. It maintains a `pos` cursor that
211/// advances as values are decoded.
212///
213/// The lifetime `'a` is tied to the [`crate::dom::Parser`] that owns the
214/// underlying memory, ensuring the tape remains valid.
215#[derive(Debug, Clone, Copy)]
216pub struct TapeRef<'a> {
217    pub(crate) tape: &'a [u64],
218    pub(crate) string_buf: &'a [u8],
219    /// Current index into `tape`.
220    pub(crate) pos: usize,
221}
222
223impl<'a> TapeRef<'a> {
224    /// Create a new tape cursor positioned at index 0 (the root `r` node).
225    pub fn new(tape: &'a [u64], string_buf: &'a [u8]) -> Self {
226        Self {
227            tape,
228            string_buf,
229            pos: 0,
230        }
231    }
232
233    /// Returns the current position in the tape.
234    #[inline]
235    pub fn pos(&self) -> usize {
236        self.pos
237    }
238
239    /// Returns the raw tape word at the current position.
240    #[inline]
241    pub(crate) fn current_word(&self) -> u64 {
242        self.tape[self.pos]
243    }
244
245    /// Public accessor for the raw tape word (needed by serde module).
246    #[inline]
247    pub fn current_word_public(&self) -> u64 {
248        self.current_word()
249    }
250
251    /// Returns the type of the current tape node.
252    #[inline]
253    pub fn tape_type(&self) -> Option<TapeType> {
254        if self.pos >= self.tape.len() {
255            return None;
256        }
257        TapeType::from_tape_word(self.current_word())
258    }
259
260    /// Returns the lower-56-bit payload of the current tape word.
261    #[inline]
262    pub(crate) fn payload(&self) -> u64 {
263        self.current_word() & PAYLOAD_MASK
264    }
265
266    /// Public accessor for payload (needed by serde module).
267    #[inline]
268    pub fn payload_public(&self) -> u64 {
269        self.payload()
270    }
271
272    /// Returns the tape index of the closing `}` or `]` for the current
273    /// `StartObject` or `StartArray` node.
274    ///
275    /// In simdjson's tape format, the opening bracket (`{` or `[`) stores a
276    /// packed payload: the lower 32 bits are the **next-sibling index** (the
277    /// tape position of the first element *after* the closing bracket), and
278    /// the upper 24 bits encode the element count. The closing bracket is
279    /// therefore at `next_sibling - 1`.
280    #[inline]
281    pub fn scope_close_idx(&self) -> usize {
282        // Extract lower 32 bits = next sibling index.
283        let next_sibling = (self.payload() & 0x00FF_FFFFFF) as usize;
284        // The closing `}` or `]` is one position before the next sibling.
285        next_sibling.saturating_sub(1)
286    }
287
288    /// Returns the number of direct children in the current `[` / `{` scope.
289    ///
290    /// simdjson packs the element count into bits 32–55 of the opening
291    /// bracket's tape word (mask `0xFFFFFF` after a `>> 32`). This matches
292    /// `simdjson::internal::tape_ref::scope_count`.
293    #[inline]
294    pub fn scope_count(&self) -> usize {
295        ((self.current_word() >> 32) & 0x00FF_FFFF) as usize
296    }
297
298    /// Advance the cursor by `n` tape words.
299    #[inline]
300    pub fn skip(&mut self, n: usize) {
301        self.pos += n;
302    }
303
304    /// Move the cursor to an absolute tape index.
305    #[inline]
306    pub fn seek_public(&mut self, pos: usize) {
307        self.pos = pos;
308    }
309
310    /// Decode the string at the current position.
311    ///
312    /// The string buffer layout is: 4-byte native-endian length prefix followed
313    /// by raw UTF-8 bytes. The tape word payload is a byte offset into
314    /// `string_buf`.
315    ///
316    /// The returned `&str` has lifetime `'a` (tied to the parser), so it can
317    /// be used for zero-copy deserialization.
318    #[inline]
319    pub fn get_string(&self) -> Result<&'a str> {
320        let word = *self.tape.get(self.pos).context(OutOfBoundsSnafu {
321            pos: self.pos,
322            len: self.tape.len(),
323        })?;
324        let tag = (word >> 56) as u8;
325        ensure!(
326            tag == b'"',
327            UnexpectedTapeTypeSnafu {
328                found: tag,
329                pos: self.pos
330            }
331        );
332        let offset = (word & PAYLOAD_MASK) as usize;
333        let bounds = || StringOutOfBoundsSnafu {
334            offset,
335            len: self.string_buf.len(),
336        };
337        let start = offset.checked_add(4).context(bounds())?;
338        let header = self.string_buf.get(offset..start).context(bounds())?;
339        let len = u32::from_ne_bytes(header.try_into().unwrap()) as usize;
340        let end = start.checked_add(len).context(bounds())?;
341        let bytes = self.string_buf.get(start..end).context(bounds())?;
342        str::from_utf8(bytes).context(InvalidUtf8Snafu { pos: offset })
343    }
344
345    /// Decode the `i64` value at the current position.
346    ///
347    /// The actual integer is stored in the *next* tape word (reinterpreted as
348    /// `i64` bits).
349    #[inline]
350    pub fn get_int64(&self) -> Result<i64> {
351        debug_assert_eq!(
352            self.tape_type(),
353            Some(TapeType::Int64),
354            "get_int64 called on non-int64 node"
355        );
356        let next = self.pos + 1;
357        ensure!(
358            next < self.tape.len(),
359            MissingValueWordSnafu { pos: self.pos }
360        );
361        Ok(self.tape[next] as i64)
362    }
363
364    /// Decode the `u64` value at the current position.
365    #[inline]
366    pub fn get_uint64(&self) -> Result<u64> {
367        debug_assert_eq!(
368            self.tape_type(),
369            Some(TapeType::Uint64),
370            "get_uint64 called on non-uint64 node"
371        );
372        let next = self.pos + 1;
373        ensure!(
374            next < self.tape.len(),
375            MissingValueWordSnafu { pos: self.pos }
376        );
377        Ok(self.tape[next])
378    }
379
380    /// Decode the `f64` value at the current position.
381    #[inline]
382    pub fn get_double(&self) -> Result<f64> {
383        debug_assert_eq!(
384            self.tape_type(),
385            Some(TapeType::Double),
386            "get_double called on non-double node"
387        );
388        let next = self.pos + 1;
389        ensure!(
390            next < self.tape.len(),
391            MissingValueWordSnafu { pos: self.pos }
392        );
393        Ok(f64::from_bits(self.tape[next]))
394    }
395
396    /// Parse the value at the current tape position into a zero-copy `Value`.
397    ///
398    /// After a successful call the cursor is advanced past the parsed value,
399    /// ready to decode the next element.
400    pub fn parse_value(&mut self) -> Result<Value<'a>> {
401        ensure!(
402            self.pos < self.tape.len(),
403            OutOfBoundsSnafu {
404                pos: self.pos,
405                len: self.tape.len(),
406            }
407        );
408
409        match self.tape_type() {
410            Some(TapeType::Root) => {
411                // Skip the root node and parse the document value.
412                self.skip(1);
413                self.parse_value()
414            }
415            Some(TapeType::Null) => {
416                self.skip(1);
417                Ok(Value::Null)
418            }
419            Some(TapeType::True) => {
420                self.skip(1);
421                Ok(Value::Bool(true))
422            }
423            Some(TapeType::False) => {
424                self.skip(1);
425                Ok(Value::Bool(false))
426            }
427            Some(TapeType::Int64) => {
428                let v = self.get_int64()?;
429                self.skip(2); // type word + value word
430                Ok(Value::Int64(v))
431            }
432            Some(TapeType::Uint64) => {
433                let v = self.get_uint64()?;
434                self.skip(2);
435                Ok(Value::Uint64(v))
436            }
437            Some(TapeType::Double) => {
438                let v = self.get_double()?;
439                self.skip(2);
440                Ok(Value::Double(v))
441            }
442            Some(TapeType::String) => {
443                let s = self.get_string()?;
444                self.skip(1);
445                Ok(Value::String(s))
446            }
447            Some(TapeType::StartArray) => {
448                // scope_close_idx = position of the matching `]` node
449                let end_idx = self.scope_close_idx();
450                self.skip(1); // move past `[`
451                let mut arr = Vec::new();
452                while self.pos < end_idx {
453                    let v = self.parse_value()?;
454                    arr.push(v);
455                }
456                // Now at the `]` node; skip it.
457                self.skip(1);
458                Ok(Value::Array(arr))
459            }
460            Some(TapeType::StartObject) => {
461                // scope_close_idx = position of the matching `}` node
462                let end_idx = self.scope_close_idx();
463                self.skip(1); // move past `{`
464                let mut obj = Vec::new();
465                while self.pos < end_idx {
466                    // Each field is: key (String node) then value
467                    let key = self.get_string()?;
468                    self.skip(1); // past key string word
469                    let value = self.parse_value()?;
470                    obj.push((key, value));
471                }
472                // Now at the `}` node; skip it.
473                self.skip(1);
474                Ok(Value::Object(obj))
475            }
476            Some(t) => UnexpectedTapeTypeSnafu {
477                found: t as u8,
478                pos: self.pos,
479            }
480            .fail(),
481            None => UnknownTapeTypeSnafu {
482                tag: (self.current_word() >> 56) as u8,
483                pos: self.pos,
484            }
485            .fail(),
486        }
487    }
488
489    /// Visit the tape using a [`TapeVisitor`].
490    ///
491    /// This is the low-level API for custom traversal. The visitor receives
492    /// structured callbacks as the tape is walked.
493    pub fn visit<V: TapeVisitor<'a>>(&mut self, visitor: &mut V) -> Result<V::Output> {
494        ensure!(
495            self.pos < self.tape.len(),
496            OutOfBoundsSnafu {
497                pos: self.pos,
498                len: self.tape.len(),
499            }
500        );
501
502        match self.tape_type() {
503            Some(TapeType::Root) => {
504                self.skip(1);
505                self.visit(visitor)
506            }
507            Some(TapeType::Null) => {
508                self.skip(1);
509                visitor.visit_null()
510            }
511            Some(TapeType::True) => {
512                self.skip(1);
513                visitor.visit_bool(true)
514            }
515            Some(TapeType::False) => {
516                self.skip(1);
517                visitor.visit_bool(false)
518            }
519            Some(TapeType::Int64) => {
520                let v = self.get_int64()?;
521                self.skip(2);
522                visitor.visit_i64(v)
523            }
524            Some(TapeType::Uint64) => {
525                let v = self.get_uint64()?;
526                self.skip(2);
527                visitor.visit_u64(v)
528            }
529            Some(TapeType::Double) => {
530                let v = self.get_double()?;
531                self.skip(2);
532                visitor.visit_f64(v)
533            }
534            Some(TapeType::String) => {
535                let s = self.get_string()?;
536                self.skip(1);
537                visitor.visit_str(s)
538            }
539            Some(TapeType::StartArray) => {
540                let end_idx = self.scope_close_idx();
541                self.skip(1);
542                let result = visitor.visit_array(self, end_idx)?;
543                self.skip(1); // past `]`
544                Ok(result)
545            }
546            Some(TapeType::StartObject) => {
547                let end_idx = self.scope_close_idx();
548                self.skip(1);
549                let result = visitor.visit_object(self, end_idx)?;
550                self.skip(1); // past `}`
551                Ok(result)
552            }
553            Some(t) => UnexpectedTapeTypeSnafu {
554                found: t as u8,
555                pos: self.pos,
556            }
557            .fail(),
558            None => UnknownTapeTypeSnafu {
559                tag: (self.current_word() >> 56) as u8,
560                pos: self.pos,
561            }
562            .fail(),
563        }
564    }
565}
566
567/// Callback-based visitor interface for the simdjson tape.
568///
569/// Implement this trait to build custom data structures from the tape without
570/// allocating intermediate `Value` nodes.
571pub trait TapeVisitor<'a> {
572    /// The value produced after visiting a complete JSON value.
573    type Output;
574
575    fn visit_null(&mut self) -> Result<Self::Output>;
576    fn visit_bool(&mut self, v: bool) -> Result<Self::Output>;
577    fn visit_i64(&mut self, v: i64) -> Result<Self::Output>;
578    fn visit_u64(&mut self, v: u64) -> Result<Self::Output>;
579    fn visit_f64(&mut self, v: f64) -> Result<Self::Output>;
580
581    /// Visit a string value. The `s` borrow is tied to the tape lifetime `'a`.
582    fn visit_str(&mut self, s: &'a str) -> Result<Self::Output>;
583
584    /// Visit an array. The implementor should call `tape.visit(self)` for each
585    /// element while `tape.pos() < end_idx`.
586    fn visit_array(&mut self, tape: &mut TapeRef<'a>, end_idx: usize) -> Result<Self::Output>;
587
588    /// Visit an object. Fields are key–value pairs; the implementor should
589    /// read key strings and call `tape.visit(self)` for values while
590    /// `tape.pos() < end_idx`.
591    fn visit_object(&mut self, tape: &mut TapeRef<'a>, end_idx: usize) -> Result<Self::Output>;
592}
593
594#[cfg(test)]
595mod tests {
596    use super::*;
597
598    #[test]
599    fn tape_type_decoding() {
600        let words: &[u64] = &[
601            (b'r' as u64) << 56 | 3,
602            (b'n' as u64) << 56,
603            (b'r' as u64) << 56,
604        ];
605        let mut r = TapeRef::new(words, &[]);
606        assert_eq!(r.tape_type(), Some(TapeType::Root));
607        r.skip(1);
608        assert_eq!(r.tape_type(), Some(TapeType::Null));
609    }
610
611    #[test]
612    fn tape_bool_types() {
613        let words: &[u64] = &[(b't' as u64) << 56, (b'f' as u64) << 56];
614        let r = TapeRef::new(words, &[]);
615        assert_eq!(r.tape_type(), Some(TapeType::True));
616        let mut r2 = r;
617        r2.skip(1);
618        assert_eq!(r2.tape_type(), Some(TapeType::False));
619    }
620
621    #[test]
622    fn tape_type_from_tape_word() {
623        assert_eq!(
624            TapeType::from_tape_word((b'r' as u64) << 56),
625            Some(TapeType::Root)
626        );
627        assert_eq!(
628            TapeType::from_tape_word((b'{' as u64) << 56),
629            Some(TapeType::StartObject)
630        );
631        assert_eq!(
632            TapeType::from_tape_word((b'}' as u64) << 56),
633            Some(TapeType::EndObject)
634        );
635        assert_eq!(
636            TapeType::from_tape_word((b'[' as u64) << 56),
637            Some(TapeType::StartArray)
638        );
639        assert_eq!(
640            TapeType::from_tape_word((b']' as u64) << 56),
641            Some(TapeType::EndArray)
642        );
643        assert_eq!(
644            TapeType::from_tape_word((b'"' as u64) << 56),
645            Some(TapeType::String)
646        );
647        assert_eq!(
648            TapeType::from_tape_word((b'l' as u64) << 56),
649            Some(TapeType::Int64)
650        );
651        assert_eq!(
652            TapeType::from_tape_word((b'u' as u64) << 56),
653            Some(TapeType::Uint64)
654        );
655        assert_eq!(
656            TapeType::from_tape_word((b'd' as u64) << 56),
657            Some(TapeType::Double)
658        );
659        assert_eq!(
660            TapeType::from_tape_word((b't' as u64) << 56),
661            Some(TapeType::True)
662        );
663        assert_eq!(
664            TapeType::from_tape_word((b'f' as u64) << 56),
665            Some(TapeType::False)
666        );
667        assert_eq!(
668            TapeType::from_tape_word((b'n' as u64) << 56),
669            Some(TapeType::Null)
670        );
671        assert_eq!(TapeType::from_tape_word(0xAA00_0000_0000_0000), None);
672    }
673
674    /// A simple visitor that counts nodes.
675    #[cfg(feature = "serde")]
676    struct NodeCounter {
677        count: usize,
678    }
679
680    #[cfg(feature = "serde")]
681    impl<'a> TapeVisitor<'a> for NodeCounter {
682        type Output = usize;
683
684        fn visit_null(&mut self) -> Result<usize> {
685            self.count += 1;
686            Ok(self.count)
687        }
688
689        fn visit_bool(&mut self, _v: bool) -> Result<usize> {
690            self.count += 1;
691            Ok(self.count)
692        }
693
694        fn visit_i64(&mut self, _v: i64) -> Result<usize> {
695            self.count += 1;
696            Ok(self.count)
697        }
698
699        fn visit_u64(&mut self, _v: u64) -> Result<usize> {
700            self.count += 1;
701            Ok(self.count)
702        }
703
704        fn visit_f64(&mut self, _v: f64) -> Result<usize> {
705            self.count += 1;
706            Ok(self.count)
707        }
708
709        fn visit_str(&mut self, _s: &'a str) -> Result<usize> {
710            self.count += 1;
711            Ok(self.count)
712        }
713
714        fn visit_array(&mut self, tape: &mut TapeRef<'a>, end_idx: usize) -> Result<usize> {
715            while tape.pos() < end_idx {
716                tape.visit(self)?;
717            }
718            Ok(self.count)
719        }
720
721        fn visit_object(&mut self, tape: &mut TapeRef<'a>, end_idx: usize) -> Result<usize> {
722            while tape.pos() < end_idx {
723                // Key
724                tape.visit(self)?;
725                // Value
726                tape.visit(self)?;
727            }
728            Ok(self.count)
729        }
730    }
731
732    #[cfg(feature = "serde")]
733    #[test]
734    fn tape_visitor_counts_nodes() {
735        use crate::dom::Parser;
736        let mut p = Parser::default();
737        // {"a": 1, "b": [true, false]} → nodes: "a", 1, "b", true, false = 5
738        let mut tape = p.parse_str(r#"{"a":1,"b":[true,false]}"#).unwrap();
739        let mut counter = NodeCounter { count: 0 };
740        tape.visit(&mut counter).unwrap();
741        assert_eq!(counter.count, 5);
742    }
743
744    #[test]
745    fn parse_value_empty_array() {
746        use crate::dom::Parser;
747        let mut p = Parser::default();
748        let mut tape = p.parse_str("[]").unwrap();
749        let v = tape.parse_value().unwrap();
750        assert_eq!(v, Value::Array(vec![]));
751    }
752
753    #[test]
754    fn parse_value_empty_object() {
755        use crate::dom::Parser;
756        let mut p = Parser::default();
757        let mut tape = p.parse_str("{}").unwrap();
758        let v = tape.parse_value().unwrap();
759        assert_eq!(v, Value::Object(vec![]));
760    }
761
762    #[test]
763    fn parse_value_uint64() {
764        use crate::dom::Parser;
765        let mut p = Parser::default();
766        // Large positive integer that simdjson stores as u64
767        let mut tape = p.parse_str("18446744073709551615").unwrap(); // u64::MAX
768        let v = tape.parse_value().unwrap();
769        assert_eq!(v.as_u64(), Some(u64::MAX));
770    }
771}