Skip to main content

facet_yaml/
parser.rs

1//! Streaming YAML parser implementing the FormatParser trait.
2//!
3//! This parser uses saphyr-parser's event-based API and translates YAML events
4//! into the `ParseEvent` format expected by `facet-format`'s deserializer.
5
6extern crate alloc;
7
8use alloc::{borrow::Cow, format, vec::Vec};
9
10use facet_format::{
11    ContainerKind, DeserializeErrorKind, FieldKey, FieldLocationHint, FormatParser, ParseError,
12    ParseEvent, ParseEventKind, SavePoint, ScalarValue,
13};
14use facet_reflect::Span;
15use saphyr_parser::{Event, Parser, ScalarStyle, Span as SaphyrSpan, StrInput};
16
17// ============================================================================
18// Parser State
19// ============================================================================
20
21/// Context for tracking where we are in the YAML structure.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23enum ContextState {
24    /// Inside a mapping, expecting a key or end
25    MappingKey,
26    /// Inside a mapping, expecting a value
27    MappingValue,
28    /// Inside a sequence, expecting a value or end
29    SequenceValue,
30}
31
32// ============================================================================
33// YAML Parser
34// ============================================================================
35
36/// Streaming YAML parser backed by `saphyr-parser`.
37///
38/// This parser translates YAML's event stream into the `ParseEvent` format
39/// expected by `facet-format`'s deserializer.
40pub struct YamlParser<'de> {
41    /// Original input string.
42    input: &'de str,
43    /// The underlying saphyr parser.
44    parser: Parser<'de, StrInput<'de>>,
45    /// Stack tracking nested containers.
46    stack: Vec<ContextState>,
47    /// Cached event for peek_event().
48    event_peek: Option<ParseEvent<'de>>,
49    /// Whether we've consumed the stream/document start events.
50    started: bool,
51    /// The span of the most recently consumed event (for error reporting).
52    last_span: Span,
53    /// Counter for save points.
54    save_counter: u64,
55    /// Events recorded since save() was called.
56    recording: Option<Vec<ParseEvent<'de>>>,
57    /// Events to replay before producing new ones.
58    replay_buffer: Vec<ParseEvent<'de>>,
59}
60
61/// Convert a saphyr-parser Span to a facet Span.
62fn span_from_saphyr(span: &SaphyrSpan) -> Span {
63    let start = span.start.index();
64    let end = span.end.index();
65    Span::new(start, end.saturating_sub(start))
66}
67
68impl<'de> YamlParser<'de> {
69    /// Create a new YAML parser from a string slice.
70    pub fn new(input: &'de str) -> Self {
71        Self {
72            input,
73            parser: Parser::new_from_str(input),
74            stack: Vec::new(),
75            event_peek: None,
76            started: false,
77            last_span: Span::new(0, 0),
78            save_counter: 0,
79            recording: None,
80            replay_buffer: Vec::new(),
81        }
82    }
83
84    /// Get the original input string.
85    pub const fn input(&self) -> &'de str {
86        self.input
87    }
88
89    /// Get the next raw event from saphyr, updating span tracking.
90    fn next_raw_event(&mut self) -> Result<Option<(Event<'de>, SaphyrSpan)>, ParseError> {
91        match self.parser.next_event() {
92            Some(Ok((event, span))) => {
93                self.last_span = span_from_saphyr(&span);
94                Ok(Some((event, span)))
95            }
96            Some(Err(e)) => {
97                // saphyr_parser errors have span info via Marker
98                let span = Span::new(e.marker().index(), 1);
99                Err(ParseError::new(
100                    span,
101                    DeserializeErrorKind::InvalidValue {
102                        message: format!("{e}").into(),
103                    },
104                ))
105            }
106            None => Ok(None),
107        }
108    }
109
110    /// Skip stream/document start events.
111    fn skip_preamble(&mut self) -> Result<(), ParseError> {
112        if self.started {
113            return Ok(());
114        }
115        self.started = true;
116
117        // Skip StreamStart
118        if let Some((Event::StreamStart, _)) = self.next_raw_event()? {
119            // Good
120        }
121
122        // Skip DocumentStart if present
123        // We need to peek - but saphyr has peek() too
124        if let Some(Ok((Event::DocumentStart(_), _))) = self.parser.peek() {
125            self.next_raw_event()?;
126        }
127
128        Ok(())
129    }
130
131    /// Produce a ParseEvent from the underlying saphyr parser.
132    fn produce_event(&mut self) -> Result<Option<ParseEvent<'de>>, ParseError> {
133        self.skip_preamble()?;
134
135        let (event, _span) = match self.next_raw_event()? {
136            Some(ev) => ev,
137            None => return Ok(None),
138        };
139
140        match event {
141            Event::StreamStart | Event::DocumentStart(_) => {
142                // Should have been skipped by preamble
143                self.produce_event()
144            }
145            Event::StreamEnd | Event::DocumentEnd => {
146                // End of document - return None
147                Ok(None)
148            }
149            Event::MappingStart(_anchor, _tag) => {
150                self.stack.push(ContextState::MappingKey);
151                Ok(Some(
152                    self.event(ParseEventKind::StructStart(ContainerKind::Object)),
153                ))
154            }
155            Event::MappingEnd => {
156                self.stack.pop();
157                // If the parent was expecting a value, transition back to expecting a key
158                if let Some(ctx @ ContextState::MappingValue) = self.stack.last_mut() {
159                    *ctx = ContextState::MappingKey;
160                }
161                Ok(Some(self.event(ParseEventKind::StructEnd)))
162            }
163            Event::SequenceStart(_anchor, _tag) => {
164                self.stack.push(ContextState::SequenceValue);
165                Ok(Some(self.event(ParseEventKind::SequenceStart(
166                    ContainerKind::Array,
167                ))))
168            }
169            Event::SequenceEnd => {
170                self.stack.pop();
171                // If the parent was expecting a value, transition back to expecting a key
172                if let Some(ctx @ ContextState::MappingValue) = self.stack.last_mut() {
173                    *ctx = ContextState::MappingKey;
174                }
175                Ok(Some(self.event(ParseEventKind::SequenceEnd)))
176            }
177            Event::Scalar(value, style, _anchor, _tag) => {
178                // Check if we're expecting a mapping key
179                if let Some(ctx @ ContextState::MappingKey) = self.stack.last_mut() {
180                    // This scalar is a key
181                    *ctx = ContextState::MappingValue;
182                    Ok(Some(self.event(ParseEventKind::FieldKey(FieldKey::new(
183                        value,
184                        FieldLocationHint::KeyValue,
185                    )))))
186                } else {
187                    // This scalar is a value
188                    if let Some(ctx @ ContextState::MappingValue) = self.stack.last_mut() {
189                        *ctx = ContextState::MappingKey;
190                    }
191                    Ok(Some(self.event(ParseEventKind::Scalar(
192                        self.scalar_to_value(value, style),
193                    ))))
194                }
195            }
196            Event::Alias(_id) => {
197                // For now, treat aliases as null (proper anchor support would be more complex)
198                if let Some(ctx @ ContextState::MappingValue) = self.stack.last_mut() {
199                    *ctx = ContextState::MappingKey;
200                }
201                Ok(Some(self.event(ParseEventKind::Scalar(ScalarValue::Null))))
202            }
203            Event::Nothing => {
204                // Internal event, skip
205                self.produce_event()
206            }
207        }
208    }
209
210    /// Convert a YAML scalar to a ScalarValue.
211    fn scalar_to_value(&self, value: Cow<'de, str>, style: ScalarStyle) -> ScalarValue<'de> {
212        // Quoted strings are always strings
213        if matches!(style, ScalarStyle::SingleQuoted | ScalarStyle::DoubleQuoted) {
214            return ScalarValue::Str(value);
215        }
216
217        // Check for null
218        if is_yaml_null(&value) {
219            return ScalarValue::Null;
220        }
221
222        // Check for boolean
223        if let Some(b) = parse_yaml_bool(&value) {
224            return ScalarValue::Bool(b);
225        }
226
227        // Check for integer
228        if let Ok(n) = value.parse::<i64>() {
229            return ScalarValue::I64(n);
230        }
231        if let Ok(n) = value.parse::<u64>() {
232            return ScalarValue::U64(n);
233        }
234
235        // Check for float
236        if let Ok(f) = value.parse::<f64>() {
237            return ScalarValue::F64(f);
238        }
239
240        // Special float values
241        match value.as_ref() {
242            ".inf" | ".Inf" | ".INF" => return ScalarValue::F64(f64::INFINITY),
243            "-.inf" | "-.Inf" | "-.INF" => return ScalarValue::F64(f64::NEG_INFINITY),
244            ".nan" | ".NaN" | ".NAN" => return ScalarValue::F64(f64::NAN),
245            _ => {}
246        }
247
248        // Default to string
249        ScalarValue::Str(value)
250    }
251
252    /// Skip the current value (handles nested structures).
253    /// This uses next_event_internal to properly handle replay buffers.
254    fn skip_current_value(&mut self) -> Result<(), ParseError> {
255        let mut depth = 0i32;
256
257        loop {
258            let event = self.next_event_internal()?;
259            match event.as_ref().map(|e| &e.kind) {
260                Some(ParseEventKind::StructStart(_) | ParseEventKind::SequenceStart(_)) => {
261                    depth += 1;
262                }
263                Some(ParseEventKind::StructEnd | ParseEventKind::SequenceEnd) => {
264                    depth -= 1;
265                    if depth <= 0 {
266                        return Ok(());
267                    }
268                }
269                Some(ParseEventKind::Scalar(_)) if depth == 0 => {
270                    return Ok(());
271                }
272                Some(_) => {}
273                None => return Ok(()),
274            }
275        }
276    }
277
278    /// Internal next_event that handles replay buffer and recording.
279    fn next_event_internal(&mut self) -> Result<Option<ParseEvent<'de>>, ParseError> {
280        // First check replay buffer
281        if let Some(event) = self.replay_buffer.pop() {
282            return Ok(Some(event));
283        }
284
285        // Then check peeked event
286        if let Some(event) = self.event_peek.take() {
287            // Record if we're in save mode
288            if let Some(ref mut rec) = self.recording {
289                rec.push(event.clone());
290            }
291            return Ok(Some(event));
292        }
293
294        // Produce new event
295        let event = self.produce_event()?;
296        // Record if we're in save mode
297        if let Some(ref mut rec) = self.recording
298            && let Some(ref e) = event
299        {
300            rec.push(e.clone());
301        }
302        Ok(event)
303    }
304}
305
306impl<'de> FormatParser<'de> for YamlParser<'de> {
307    fn next_event(&mut self) -> Result<Option<ParseEvent<'de>>, ParseError> {
308        self.next_event_internal()
309    }
310
311    fn peek_event(&mut self) -> Result<Option<ParseEvent<'de>>, ParseError> {
312        // First check replay buffer (peek at last element without removing)
313        if let Some(event) = self.replay_buffer.last().cloned() {
314            return Ok(Some(event));
315        }
316        // Then check already-peeked event
317        if let Some(event) = self.event_peek.clone() {
318            return Ok(Some(event));
319        }
320        // Finally, produce new event and cache it
321        let event = self.produce_event()?;
322        if let Some(ref e) = event {
323            self.event_peek = Some(e.clone());
324        }
325        Ok(event)
326    }
327
328    fn skip_value(&mut self) -> Result<(), ParseError> {
329        debug_assert!(
330            self.event_peek.is_none(),
331            "skip_value called while an event is buffered"
332        );
333        self.skip_current_value()
334    }
335
336    fn save(&mut self) -> SavePoint {
337        self.save_counter += 1;
338        self.recording = Some(Vec::new());
339        SavePoint(self.save_counter)
340    }
341
342    fn restore(&mut self, _save_point: SavePoint) {
343        if let Some(mut recorded) = self.recording.take() {
344            // Reverse so we can pop from the end
345            recorded.reverse();
346            // Prepend to replay buffer (in case there's already stuff there)
347            recorded.append(&mut self.replay_buffer);
348            self.replay_buffer = recorded;
349        }
350    }
351
352    fn capture_raw(&mut self) -> Result<Option<&'de str>, ParseError> {
353        // YAML doesn't support raw capture (unlike JSON with RawJson)
354        self.skip_value()?;
355        Ok(None)
356    }
357
358    fn current_span(&self) -> Option<Span> {
359        Some(self.last_span)
360    }
361}
362
363impl<'de> YamlParser<'de> {
364    /// Create an event with the current span.
365    #[inline]
366    fn event(&self, kind: ParseEventKind<'de>) -> ParseEvent<'de> {
367        ParseEvent::new(kind, self.last_span)
368    }
369}
370
371// ============================================================================
372// YAML-specific helpers
373// ============================================================================
374
375/// Check if a YAML value represents null.
376fn is_yaml_null(value: &str) -> bool {
377    matches!(
378        value.to_lowercase().as_str(),
379        "null" | "~" | "" | "nil" | "none"
380    )
381}
382
383/// Parse a YAML boolean value.
384fn parse_yaml_bool(value: &str) -> Option<bool> {
385    match value.to_lowercase().as_str() {
386        "true" | "yes" | "on" | "y" => Some(true),
387        "false" | "no" | "off" | "n" => Some(false),
388        _ => None,
389    }
390}