1use std::collections::HashMap;
10
11use saphyr_parser::{Event, Parser, ScanError, Span as SapSpan, SpannedEventReceiver};
12
13use crate::document::{Document, YamlVersion};
14use crate::node::{CollectionStyle, Entry, Node, NodeData, NodeId, ScalarStyle, Span};
15use crate::tag::{Tag, TagHandle};
16
17#[derive(Debug, thiserror::Error)]
19pub enum ParseError {
20 #[error("YAML parse error: {0}")]
22 Scan(#[from] ScanError),
23 #[error("no YAML document found")]
25 Empty,
26 #[error("malformed YAML event stream: {0}")]
29 Malformed(&'static str),
30}
31
32enum Frame {
34 Sequence { items: Vec<NodeId>, node: NodeId, start: usize },
35 Mapping { entries: Vec<Entry>, pending_key: Option<NodeId>, node: NodeId, start: usize },
36}
37
38#[derive(Default)]
39struct Builder {
40 doc: Document,
41 stack: Vec<Frame>,
42 anchors: HashMap<usize, NodeId>,
44 anchor_order: Vec<usize>,
46 root: Option<NodeId>,
47 done: bool,
48 error: Option<ParseError>,
49}
50
51fn convert_style(style: saphyr_parser::ScalarStyle) -> ScalarStyle {
52 use saphyr_parser::ScalarStyle as S;
53 match style {
54 S::Plain => ScalarStyle::Plain,
55 S::SingleQuoted => ScalarStyle::SingleQuoted,
56 S::DoubleQuoted => ScalarStyle::DoubleQuoted,
57 S::Literal => ScalarStyle::Literal,
58 S::Folded => ScalarStyle::Folded,
59 }
60}
61
62fn convert_span(span: SapSpan) -> Span {
63 Span { start: span.start.index(), end: span.end.index() }
64}
65
66impl Builder {
67 fn place(&mut self, id: NodeId) {
69 match self.stack.last_mut() {
70 None => {
71 if self.root.is_none() {
72 self.root = Some(id);
73 }
74 }
75 Some(Frame::Sequence { items, .. }) => items.push(id),
76 Some(Frame::Mapping { entries, pending_key, .. }) => match pending_key.take() {
77 None => *pending_key = Some(id),
78 Some(key) => entries.push(Entry { key, value: id }),
79 },
80 }
81 }
82
83 fn note_anchor(&mut self, anchor_id: usize, node: NodeId) {
87 if anchor_id == 0 {
88 return;
89 }
90 if self.anchors.insert(anchor_id, node).is_none() {
91 self.anchor_order.push(anchor_id);
92 }
93 let position = self.anchor_order.iter().position(|a| *a == anchor_id).unwrap_or(0);
96 self.doc.node_mut(node).anchor = Some(format!("anc{}", position));
97 }
98
99 fn open(&mut self, frame: Frame) {
100 self.stack.push(frame);
101 }
102
103 fn close(&mut self) -> Result<(), ParseError> {
104 let frame = self
105 .stack
106 .pop()
107 .ok_or(ParseError::Malformed("collection end without a matching start"))?;
108
109 let (node, data, start) = match frame {
110 Frame::Sequence { items, node, start } => {
111 (node, NodeData::Sequence { items, style: CollectionStyle::Auto }, start)
112 }
113 Frame::Mapping { entries, pending_key, node, start } => {
114 if pending_key.is_some() {
115 return Err(ParseError::Malformed("mapping ended with a dangling key"));
116 }
117 (node, NodeData::Mapping { entries, style: CollectionStyle::Auto }, start)
118 }
119 };
120
121 let n = self.doc.node_mut(node);
122 n.data = data;
123 if let Some(span) = n.span.as_mut() {
124 span.start = start;
125 }
126 self.place(node);
127 Ok(())
128 }
129
130 fn handle(&mut self, ev: Event<'_>, span: SapSpan) -> Result<(), ParseError> {
131 if self.done {
134 return Ok(());
135 }
136
137 match ev {
138 Event::StreamStart | Event::StreamEnd | Event::Nothing => {}
139 Event::DocumentStart(_) => {}
140 Event::DocumentEnd => {
141 if self.root.is_some() {
142 self.done = true;
143 }
144 }
145
146 Event::Scalar(value, style, anchor_id, tag) => {
147 let mut node = Node::scalar_styled(value.into_owned(), convert_style(style));
148 node.tag = tag.map(|t| Tag::new(t.handle.clone(), t.suffix.clone()));
149 node.span = Some(convert_span(span));
150 let id = self.doc.add(node);
151 self.note_anchor(anchor_id, id);
152 self.place(id);
153 }
154
155 Event::Alias(anchor_id) => {
156 let target = self.anchors.get(&anchor_id).copied().ok_or(ParseError::Malformed(
157 "alias refers to an anchor that was never defined",
158 ))?;
159 let mut node = Node::new(NodeData::Alias(target));
160 node.span = Some(convert_span(span));
161 let id = self.doc.add(node);
162 self.place(id);
163 }
164
165 Event::SequenceStart(anchor_id, tag) => {
166 let mut node = Node::sequence();
169 node.tag = tag.map(|t| Tag::new(t.handle.clone(), t.suffix.clone()));
170 node.span = Some(convert_span(span));
171 let id = self.doc.add(node);
172 self.note_anchor(anchor_id, id);
173 self.open(Frame::Sequence {
174 items: Vec::new(),
175 node: id,
176 start: span.start.index(),
177 });
178 }
179
180 Event::MappingStart(anchor_id, tag) => {
181 let mut node = Node::mapping();
182 node.tag = tag.map(|t| Tag::new(t.handle.clone(), t.suffix.clone()));
183 node.span = Some(convert_span(span));
184 let id = self.doc.add(node);
185 self.note_anchor(anchor_id, id);
186 self.open(Frame::Mapping {
187 entries: Vec::new(),
188 pending_key: None,
189 node: id,
190 start: span.start.index(),
191 });
192 }
193
194 Event::SequenceEnd | Event::MappingEnd => self.close()?,
195 }
196 Ok(())
197 }
198}
199
200impl<'i> SpannedEventReceiver<'i> for Builder {
201 fn on_event(&mut self, ev: Event<'i>, span: SapSpan) {
202 if self.error.is_some() {
203 return;
204 }
205 if let Err(e) = self.handle(ev, span) {
206 self.error = Some(e);
207 }
208 }
209}
210
211fn scan_directives(input: &str, doc: &mut Document) {
219 for line in input.lines() {
220 let line = line.trim_end();
221 if line.starts_with("---") || line.starts_with("...") {
222 break;
223 }
224 if let Some(rest) = line.strip_prefix("%YAML ") {
225 let mut parts = rest.trim().split('.');
226 if let (Some(major), Some(minor)) = (parts.next(), parts.next())
227 && let (Ok(major), Ok(minor)) = (major.parse(), minor.parse())
228 {
229 doc.version = Some(YamlVersion { major, minor });
230 }
231 } else if let Some(rest) = line.strip_prefix("%TAG ") {
232 let mut parts = rest.split_whitespace();
233 if let (Some(handle), Some(prefix)) = (parts.next(), parts.next()) {
234 doc.tag_handles
235 .push(TagHandle { handle: handle.to_string(), prefix: prefix.to_string() });
236 }
237 }
238 }
239}
240
241pub fn parse_document(input: &str) -> Result<Document, ParseError> {
248 let mut builder = Builder::default();
249 let parse_result = Parser::new_from_str(input).load(&mut builder, true);
250
251 if let Some(err) = builder.error {
252 return Err(err);
253 }
254 parse_result?;
255
256 let root = builder.root.ok_or(ParseError::Empty)?;
257 let mut doc = builder.doc;
258 doc.set_root(root);
259 scan_directives(input, &mut doc);
260 Ok(doc)
261}
262
263#[derive(Clone, Copy, PartialEq, Eq, Debug)]
268pub enum YamlEventKind {
269 StreamStart,
270 StreamEnd,
271 DocumentStart,
272 DocumentEnd,
273 MappingStart,
274 MappingEnd,
275 SequenceStart,
276 SequenceEnd,
277 Scalar,
278 Alias,
279}
280
281#[derive(Clone, PartialEq, Eq, Debug)]
283pub struct YamlEvent {
284 pub kind: YamlEventKind,
285 pub tag: Option<String>,
287 pub value: Option<String>,
289}
290
291#[derive(Default)]
293struct EventCollector {
294 events: Vec<YamlEvent>,
295}
296
297impl EventCollector {
298 fn push(&mut self, kind: YamlEventKind, tag: Option<String>, value: Option<String>) {
299 self.events.push(YamlEvent { kind, tag, value });
300 }
301}
302
303impl<'i> SpannedEventReceiver<'i> for EventCollector {
304 fn on_event(&mut self, ev: Event<'i>, _span: SapSpan) {
305 let tag_of = |tag: Option<alloc::borrow::Cow<'_, saphyr_parser::Tag>>| {
306 tag.map(|t| Tag::new(t.handle.clone(), t.suffix.clone()).full())
307 };
308 match ev {
309 Event::Nothing => {}
310 Event::StreamStart => self.push(YamlEventKind::StreamStart, None, None),
311 Event::StreamEnd => self.push(YamlEventKind::StreamEnd, None, None),
312 Event::DocumentStart(_) => self.push(YamlEventKind::DocumentStart, None, None),
313 Event::DocumentEnd => self.push(YamlEventKind::DocumentEnd, None, None),
314 Event::MappingStart(_, tag) => {
315 self.push(YamlEventKind::MappingStart, tag_of(tag), None);
316 }
317 Event::MappingEnd => self.push(YamlEventKind::MappingEnd, None, None),
318 Event::SequenceStart(_, tag) => {
319 self.push(YamlEventKind::SequenceStart, tag_of(tag), None);
320 }
321 Event::SequenceEnd => self.push(YamlEventKind::SequenceEnd, None, None),
322 Event::Scalar(value, _, _, tag) => {
323 self.push(YamlEventKind::Scalar, tag_of(tag), Some(value.into_owned()));
324 }
325 Event::Alias(_) => self.push(YamlEventKind::Alias, None, None),
326 }
327 }
328}
329
330pub fn scan_events(input: &str) -> Result<Vec<YamlEvent>, ParseError> {
335 let mut collector = EventCollector::default();
336 Parser::new_from_str(input).load(&mut collector, true)?;
337 Ok(collector.events)
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn raw_events_carry_tags_and_scalars() {
346 let events = scan_events(
347 "%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\nk: v\nl: [1]\n",
348 )
349 .unwrap();
350
351 let kinds: Vec<YamlEventKind> = events.iter().map(|e| e.kind).collect();
352 assert_eq!(
353 kinds,
354 vec![
355 YamlEventKind::StreamStart,
356 YamlEventKind::DocumentStart,
357 YamlEventKind::MappingStart,
358 YamlEventKind::Scalar,
359 YamlEventKind::Scalar,
360 YamlEventKind::Scalar,
361 YamlEventKind::SequenceStart,
362 YamlEventKind::Scalar,
363 YamlEventKind::SequenceEnd,
364 YamlEventKind::MappingEnd,
365 YamlEventKind::DocumentEnd,
366 YamlEventKind::StreamEnd,
367 ]
368 );
369
370 assert_eq!(events[2].tag.as_deref(), Some("tag:stsci.edu:asdf/core/asdf-1.1.0"));
372 assert_eq!(events[3].value.as_deref(), Some("k"));
373 assert_eq!(events[4].value.as_deref(), Some("v"));
374 assert!(events[3].tag.is_none());
375 }
376
377 #[test]
378 fn raw_events_report_aliases_without_expanding_them() {
379 let events = scan_events("a: &x 1\nb: *x\n").unwrap();
380 assert_eq!(events.iter().filter(|e| e.kind == YamlEventKind::Alias).count(), 1);
381 }
382
383 use crate::tag::ASDF_STANDARD_TAG_PREFIX;
384
385 #[test]
386 fn parses_a_minimal_asdf_tree() {
387 let doc = parse_document(
388 "%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\nfoo: 42\n...\n",
389 )
390 .unwrap();
391
392 let root = doc.root().unwrap();
393 assert!(doc.node(root).is_mapping());
394
395 let tag = doc.tag_of(root).unwrap();
396 assert_eq!(tag.handle(), ASDF_STANDARD_TAG_PREFIX);
397 assert_eq!(tag.suffix(), "core/asdf-1.1.0");
398
399 let foo = doc.mapping_get(root, "foo").unwrap();
400 assert_eq!(doc.node(foo).as_str(), Some("42"));
401 }
402
403 #[test]
404 fn preserves_scalar_style() {
405 let doc = parse_document("a: yes\nb: \"yes\"\nc: 'yes'\n").unwrap();
406 let root = doc.root().unwrap();
407
408 let a = doc.node(doc.mapping_get(root, "a").unwrap());
409 let b = doc.node(doc.mapping_get(root, "b").unwrap());
410 let c = doc.node(doc.mapping_get(root, "c").unwrap());
411
412 assert_eq!(a.scalar_style(), Some(ScalarStyle::Plain));
413 assert_eq!(b.scalar_style(), Some(ScalarStyle::DoubleQuoted));
414 assert_eq!(c.scalar_style(), Some(ScalarStyle::SingleQuoted));
415 assert_eq!(a.as_str(), Some("yes"));
417 assert_eq!(b.as_str(), Some("yes"));
418 }
419
420 #[test]
421 fn aliases_share_their_target() {
422 let doc = parse_document("shared: &a {x: 1}\nother: *a\n").unwrap();
423 let root = doc.root().unwrap();
424
425 let shared = doc.mapping_get(root, "shared").unwrap();
426 let other = doc.mapping_get(root, "other").unwrap();
427
428 assert!(!doc.node(shared).is_alias());
429 assert!(doc.node(other).is_alias(), "alias must stay distinct from its target");
430 assert_eq!(doc.resolve(other), shared, "alias must resolve to the same node");
431 assert!(doc.node(shared).anchor.is_some());
432
433 let x = doc.mapping_get(other, "x").unwrap();
435 assert_eq!(doc.node(x).as_str(), Some("1"));
436 }
437
438 #[test]
439 fn alias_to_a_sequence_element() {
440 let doc = parse_document("- &v 7\n- *v\n").unwrap();
441 let root = doc.root().unwrap();
442 let items = doc.sequence_items(root).unwrap().to_vec();
443 assert_eq!(items.len(), 2);
444 assert!(doc.node(items[1]).is_alias());
445 assert_eq!(doc.resolved(items[1]).as_str(), Some("7"));
446 }
447
448 #[test]
449 fn nested_tags_are_kept() {
450 let doc = parse_document(
451 "%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
452 data: !core/ndarray-1.1.0\n source: 0\n shape: [8]\n...\n",
453 )
454 .unwrap();
455 let root = doc.root().unwrap();
456 let data = doc.mapping_get(root, "data").unwrap();
457 assert_eq!(doc.tag_of(data).unwrap().full(), "tag:stsci.edu:asdf/core/ndarray-1.1.0");
458 let shape = doc.mapping_get(data, "shape").unwrap();
459 assert_eq!(doc.container_len(shape), Some(1));
460 }
461
462 #[test]
463 fn empty_input_is_an_error() {
464 assert!(matches!(parse_document(""), Err(ParseError::Empty)));
465 }
466
467 #[test]
468 fn malformed_yaml_is_an_error() {
469 assert!(parse_document("a: [1, 2\nb: 3\n").is_err());
470 }
471
472 #[test]
473 fn spans_locate_nodes_in_the_source() {
474 let src = "foo: 42\n";
475 let doc = parse_document(src).unwrap();
476 let root = doc.root().unwrap();
477 let foo = doc.mapping_get(root, "foo").unwrap();
478 let span = doc.node(foo).span.unwrap();
479 assert_eq!(&src[span.start..span.end], "42");
480 }
481}