1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// SPDX-License-Identifier: Apache-2.0
//! A SAX-style JSON push parser.
//!
//! Clean implementation based on handler_design pattern with proper HRTB lifetime management.
use crate::event_processor::{ContentExtractor, EscapeTiming, ParserCore};
use crate::push_content_builder::{PushContentBuilder, PushParserHandler};
use crate::shared::{DataSource, State};
use crate::stream_buffer::StreamBufferError;
use crate::{ujson, BitStackConfig, Event, ParseError};
/// A SAX-style JSON push parser.
///
/// Generic over BitStack storage type for configurable nesting depth. Parsing
/// events are returned to the handler.
///
/// # Generic Parameters
///
/// * `'scratch` - Lifetime for the scratch buffer used for temporary storage
/// * `H` - The event handler type that implements [`PushParserHandler`]
/// * `C` - BitStack configuration type that implements [`BitStackConfig`]
pub struct PushParser<'input, 'scratch, H, C>
where
C: BitStackConfig,
{
/// Content extractor that handles content extraction and event emission
extractor: PushContentBuilder<'input, 'scratch>,
/// The handler that receives events
handler: H,
/// Core parser logic shared with other parsers
core: ParserCore<C::Bucket, C::Counter>,
}
impl<'input, 'scratch, H, C> PushParser<'input, 'scratch, H, C>
where
C: BitStackConfig,
{
/// Creates a new `PushParser`.
pub fn new(handler: H, buffer: &'scratch mut [u8]) -> Self {
Self {
extractor: PushContentBuilder::new(buffer),
handler,
core: ParserCore::new_chunked(),
}
}
/// Processes a chunk of input data.
pub fn write<E>(&mut self, data: &'input [u8]) -> Result<(), PushParseError<E>>
where
H: for<'a, 'b> PushParserHandler<'a, 'b, E>,
E: From<ParseError>,
{
// Apply any queued buffer resets
self.extractor.apply_unescaped_reset_if_queued();
// Set the input slice for the extractor to iterate over
self.extractor.set_chunk(data);
// Use ParserCore to process all bytes in the chunk
loop {
match self.core.next_event_impl_with_flags(
&mut self.extractor,
EscapeTiming::OnEnd, // PushParser uses OnEnd timing like StreamParser
|extractor, byte| {
// Selective accumulation: let PushContentBuilder decide based on its state
// whether this byte should be accumulated or processed directly
extractor.handle_byte_accumulation(byte)
},
true, // always_accumulate_during_escapes: ensure all hex digits reach the accumulator
) {
Ok(Event::EndDocument) => {
// EndDocument during write() means we've consumed all bytes in current chunk
break;
}
Ok(event) => {
// Handle all other events normally
self.handler
.handle_event(event)
.map_err(PushParseError::Handler)?;
// Apply any queued buffer resets after the event has been processed
// This ensures that buffer content from previous tokens doesn't leak into subsequent ones
self.extractor.apply_unescaped_reset_if_queued();
}
Err(ParseError::EndOfData) => {
// No more events available from current chunk
break;
}
Err(e) => {
return Err(PushParseError::Parse(e));
}
}
}
// Check for chunk boundary condition - if still processing a token when chunk ends
let extractor_state = self.extractor.parser_state();
if matches!(
extractor_state,
State::String(_) | State::Key(_) | State::Number(_)
) {
// If we haven't already started using the scratch buffer (e.g., due to escapes)
if !self.extractor.has_unescaped_content() {
// Copy the partial content from this chunk to scratch buffer before it's lost
self.extractor.copy_partial_content_to_scratch()?;
} else {
// Special case: For Numbers, check if the scratch buffer is actually empty
// This handles the byte-by-byte case where the flag is stale from previous Key processing
if matches!(extractor_state, State::Number(_)) {
let buffer_slice = self.extractor.get_unescaped_slice().unwrap_or(&[]);
let buffer_empty = buffer_slice.is_empty();
if buffer_empty {
self.extractor.copy_partial_content_to_scratch()?;
}
}
}
}
// Reset input slice
self.extractor.reset_input();
// Update position offset for next call
self.extractor.add_position_offset(data.len());
Ok(())
}
/// Finishes parsing, flushes any remaining events, and returns the handler.
/// This method consumes the parser.
pub fn finish<E>(mut self) -> Result<H, PushParseError<E>>
where
H: for<'a, 'b> PushParserHandler<'a, 'b, E>,
{
// Check that the JSON document is complete (all containers closed)
// Use a no-op callback since we don't expect any more events
let mut no_op_callback = |_event: ujson::Event, _pos: usize| {};
let _bytes_processed = self.core.tokenizer.finish(&mut no_op_callback)?;
// Handle any remaining content in the buffer
if *self.extractor.parser_state() != State::None {
return Err(crate::push_parser::PushParseError::Parse(
ParseError::EndOfData,
));
}
// Emit EndDocument event
self.handler
.handle_event(Event::EndDocument)
.map_err(PushParseError::Handler)?;
Ok(self.handler)
}
}
/// An error that can occur during push-based parsing.
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PushParseError<E> {
/// An error occurred within the parser itself.
Parse(ParseError),
/// An error was returned by the user's handler.
Handler(E),
}
impl<E> From<ujson::Error> for PushParseError<E> {
fn from(e: ujson::Error) -> Self {
PushParseError::Parse(e.into())
}
}
impl<E> From<ParseError> for PushParseError<E> {
fn from(e: ParseError) -> Self {
PushParseError::Parse(e)
}
}
impl<E> From<StreamBufferError> for PushParseError<E> {
fn from(e: StreamBufferError) -> Self {
PushParseError::Parse(e.into())
}
}
impl<E> From<core::str::Utf8Error> for PushParseError<E> {
fn from(e: core::str::Utf8Error) -> Self {
PushParseError::Parse(ParseError::InvalidUtf8(e))
}
}