idm/de/parser.rs
1//! Main parsing complexity lives in this module.
2
3use std::{borrow::Cow, fmt, str::FromStr};
4
5use crate::{
6 de::{
7 fragment::{Fragment, Item, Outline},
8 parse,
9 },
10 err, Error, Result,
11};
12
13#[derive(Debug, Clone)]
14pub struct Parser<'a> {
15 stack: Vec<State<'a>>,
16 /// Flag for the weird special stuff part.
17 ///
18 /// When entering a two-element sequence, no immediate stack ops are done,
19 /// instead, at_pair_start is set to true. If the very next element is a
20 /// one-element sequence (singleton tuple), this marks the start of the
21 /// special mode and different stack operations will be performed. Any
22 /// other element will cause normal sequence processing to commence.
23 at_pair_start: bool,
24}
25
26impl<'a> Parser<'a> {
27 pub fn from_str(input: &'a str) -> Result<Self> {
28 Ok(Parser {
29 stack: vec![State::Document(Fragment::from_str(input)?)],
30 at_pair_start: false,
31 })
32 }
33
34 pub fn read<T: FromStr>(&mut self) -> Result<T> {
35 let s = self.read_str()?;
36
37 // Do an extra trim here, values might have NBSP padding that passes
38 // through main IDM parsing but does not apply to primitives.
39 T::from_str(s.trim()).map_err(|_| Error::new("Failed to parse value"))
40 // TODO Attach line number to error
41 }
42
43 /// Return the next concrete string token from current sequence or `None`
44 /// if at the end of sequence.
45 ///
46 /// If called immediately after `enter_special`, will parse the next input
47 /// line in raw mode.
48 pub fn read_str(&mut self) -> Result<Cow<str>> {
49 self.normal_mode()?;
50
51 let top = self.stack.len() - 1;
52 self.stack[top].next()
53 }
54
55 /// Enter a sequence of values.
56 ///
57 /// A line will be parsed as a horizontal sequence of words and a block
58 /// will be parsed as a vertical sequence of block items.
59 ///
60 /// Will fail if already parsing a horizontal sequence.
61 pub fn enter_seq(&mut self) -> Result<()> {
62 self.normal_mode()?;
63
64 let top = self.stack.len() - 1;
65 let new_top = self.stack[top].enter_seq(SeqConfig::Seq)?;
66 self.stack.push(new_top);
67 Ok(())
68 }
69
70 /// Enter a tuple with a known length.
71 ///
72 /// Tuples can have special parsing for first and last items and can parse
73 /// a section-shaped value.
74 pub fn enter_tuple(&mut self, n: usize) -> Result<()> {
75 self.normal_mode()?;
76
77 if n == 2 {
78 // Fake pair logic for inline struct traversal.
79 let top = self.stack.len() - 1;
80 if let State::InlineStruct {
81 ref mut fake_seq, ..
82 } = self.stack[top]
83 {
84 if !*fake_seq {
85 *fake_seq = true;
86 return Ok(());
87 } else {
88 return err!("Invalid nesting");
89 }
90 }
91
92 // Pairs can switch into special mode. When a pair tuple is
93 // entered, instead of doing anything immediately, just toggle the
94 // at_pair_start flag.
95 self.at_pair_start = true;
96 Ok(())
97 } else {
98 self.really_enter_tuple(n)
99 }
100 }
101
102 /// Enter a sequence if in pair-start state. Otherwise do nothing.
103 pub fn normal_mode(&mut self) -> Result<()> {
104 if self.at_pair_start {
105 self.at_pair_start = false;
106 self.really_enter_tuple(2)
107 } else {
108 Ok(())
109 }
110 }
111
112 fn really_enter_tuple(&mut self, n: usize) -> Result<()> {
113 let top = self.stack.len() - 1;
114 let new_top = self.stack[top].enter_seq(SeqConfig::Tuple(n))?;
115 self.stack.push(new_top);
116 Ok(())
117 }
118
119 /// Enter a map of key-value data.
120 ///
121 /// Maps can only have a vertical structure. After entering a map, `next`
122 /// will interleave keys and values for as long as the map has content.
123 ///
124 /// Will fail if already parsing a horizontal sequence.
125 pub fn enter_map(&mut self) -> Result<()> {
126 self.normal_mode()?;
127
128 let top = self.stack.len() - 1;
129 let new_top = self.stack[top].enter_seq(Map)?;
130 self.stack.push(new_top);
131 Ok(())
132 }
133
134 /// Enter a struct.
135 ///
136 /// Structs are treated very similarly to maps. Unlike maps, structs have
137 /// a horizontal form where the field names are not written out. A
138 /// horizontal struct will have field names fed into `next` from the field
139 /// name list provided as an argument.
140 ///
141 /// If the struct is entered immediately after `enter_special`, structs
142 /// are treated entirely like maps and horizontal structs are not
143 /// recognized.
144 pub fn enter_struct(
145 &mut self,
146 fields: &'static [&'static str],
147 ) -> Result<()> {
148 self.normal_mode()?;
149
150 let top = self.stack.len() - 1;
151 let new_top = self.stack[top].enter_seq(Struct(fields))?;
152 self.stack.push(new_top);
153 Ok(())
154 }
155
156 /// Enter the special state.
157 ///
158 /// This is a special method that enables irregular features in IDM. It
159 /// must be followed by either `next`, `enter_map` or `enter_struct` to
160 /// read the first pair element.
161 ///
162 /// Calling `next` will read a line in raw input mode (treating comments
163 /// and blank lines as input). The second element will be the indented
164 /// section body under the line. If the line has no section body, the next
165 /// element will be an empty block.
166 ///
167 /// Entering a map or struct after `enter_special` will start parsing a
168 /// block and will treat the first block item as the map or struct and the
169 /// rest of the block as the second element of the pair.
170 ///
171 /// If `enter_map` or `enter_struct` was called for the first pair
172 /// element, `enter_map` or `enter_struct` must not be called for the
173 /// second element since the fused block syntax would make the separation
174 /// of the second map from the first undetectable.
175 pub fn enter_special(&mut self) -> Result<()> {
176 if self.at_pair_start {
177 self.at_pair_start = false;
178 } else {
179 return err!("Special mode marker not at start of pair");
180 }
181
182 let top = self.stack.len() - 1;
183 let new_top = self.stack[top].enter_special()?;
184 self.stack.push(new_top);
185 Ok(())
186 }
187
188 /// Exit the current entered scope.
189 ///
190 /// The scope must not have any unparsed input (that would be returned by
191 /// `next`, if comments are being skipped, remaining comments do not
192 /// count) remamining. If there is, an error will be raised.
193 ///
194 /// Calling exit without a preceding enter call will panic.
195 pub fn exit(&mut self) -> Result<()> {
196 self.normal_mode()?;
197
198 let top = self.stack.len() - 1;
199
200 // Fake pair logic for inline struct traversal.
201 if let State::InlineStruct {
202 ref mut fake_seq, ..
203 } = self.stack[top]
204 {
205 if *fake_seq {
206 *fake_seq = false;
207 return Ok(());
208 }
209 }
210
211 if !self.stack[top].is_empty() {
212 return err!("Unparsed input remains");
213 }
214 self.stack.pop();
215 Ok(())
216 }
217
218 /// Return true if the current scope has no more items.
219 pub fn is_empty(&self) -> bool {
220 // Can't tell what the state is before taking another deser step if
221 // `at_pair_start` is set.
222 !self.at_pair_start && self.stack[self.stack.len() - 1].is_empty()
223 }
224
225 /// Return true if the current scope has no more items, not even commented
226 /// out items.
227 pub fn is_really_empty(&self) -> bool {
228 // Can't tell what the state is before taking another deser step if
229 // `at_pair_start` is set.
230 !self.at_pair_start
231 && self.stack[self.stack.len() - 1].is_really_empty()
232 }
233
234 /// Return if the current state is exactly one word
235 pub fn is_word(&self) -> bool {
236 // XXX: Calling next on the top item, whatever it is, can be very
237 // expensive. There is probably a smarter way to do this.
238 let mut probe = self.clone();
239 let text = probe.read_str().unwrap_or_else(|_| Cow::from(""));
240 let text = text.trim();
241 !text.is_empty() && !text.chars().any(|c| c.is_whitespace())
242 }
243}
244
245#[derive(Copy, Clone, Debug)]
246enum SeqConfig {
247 /// Variable-length Vec or similar.
248 Seq,
249 /// Fixed-length tuple
250 Tuple(usize),
251 /// Homogeneous map.
252 Map,
253 /// Struct with a fixed set of fields.
254 Struct(&'static [&'static str]),
255}
256
257use SeqConfig::*;
258
259impl SeqConfig {
260 fn allows_inline(&self) -> bool {
261 !matches!(self, Map)
262 }
263
264 /// Turn an outline that's all one colon-indented block into a regular
265 /// block if there's no syntactic ambiguity for parsing. Used to maintain
266 /// the pretense that colons are map field syntax instead of an
267 /// indentation mechanism.
268 fn try_unfold(&self, outline: &mut Outline) {
269 if !matches!(self, Seq) {
270 outline.try_unfold_only_child_outline();
271 }
272 }
273
274 fn inline_state<'a>(&self, item: Item<'a>) -> Option<State<'a>> {
275 match self {
276 Seq if item.is_line() => Some(State::SectionSeq {
277 i: 0,
278 n: None,
279 item,
280 }),
281 Struct(fields) if item.is_line() => {
282 let Ok(vals) = parse::n_elements(item.head, fields.len())
283 else {
284 return None;
285 };
286 let mut vals = vals.0;
287 vals.reverse();
288 let mut fields = fields.to_vec();
289 fields.reverse();
290 Some(State::InlineStruct {
291 vals,
292 fields,
293 fake_seq: false,
294 })
295 }
296 // Only tuples can be built from section-shaped (non-line) items.
297 Tuple(n) => Some(State::SectionSeq {
298 i: 0,
299 n: Some(*n),
300 item,
301 }),
302 _ => None,
303 }
304 }
305
306 fn is_pair(&self) -> bool {
307 matches!(self, Tuple(2))
308 }
309}
310
311/// Parsing states that carry context fragments and are stacked in the parser
312/// stack.
313///
314/// The implementation can be thought of as a two-dimensional table. The rows
315/// are the transition methods in `State` like `enter_seq` and `enter_map`.
316/// The columns are the behaviors of each method given the current value of
317/// `State` for the given transition method.
318#[derive(Clone, Debug)]
319enum State<'a> {
320 /// Complete document
321 Document(Fragment<'a>),
322
323 /// Vertical list of items.
324 VerticalSeq(Outline<'a>),
325
326 /// A line being split into individual words.
327 SectionSeq {
328 /// Current element index.
329 i: usize,
330 /// Total element count (not available for inline vecs).
331 n: Option<usize>,
332 /// Remaining item.
333 item: Item<'a>,
334 },
335
336 /// Value fields of an inline struct.
337 InlineStruct {
338 vals: Vec<&'a str>,
339 fields: Vec<&'static str>,
340 // Set to true to spoof entering and false to spoof exiting a pair
341 // when reading an inline struct.
342 fake_seq: bool,
343 },
344
345 /// First element of the special form.
346 ///
347 /// Waiting for next operation to decide how to proceed.
348 SpecialFirst(Fragment<'a>),
349
350 /// Second element of the special form.
351 SpecialSecond(Outline<'a>),
352}
353
354impl Default for State<'_> {
355 fn default() -> Self {
356 // This is the standard representation for an empty fragment.
357 State::Document(Default::default())
358 }
359}
360
361impl<'a> State<'a> {
362 fn next(&mut self) -> Result<Cow<str>> {
363 let mut ret = err!("Out of input");
364
365 take_mut::take(self, |s| match s {
366 State::Document(f) => {
367 if !f.is_empty() {
368 ret = Ok(Cow::from(f.to_string()))
369 }
370 Default::default()
371 }
372 State::VerticalSeq(mut o) => {
373 if let Some(next) = o.pop_nonblank() {
374 ret = Ok(Cow::from(next.to_string()));
375 }
376 State::VerticalSeq(o)
377 }
378 State::SectionSeq { i, n, mut item } => {
379 // Special logic for tuples.
380 if let Some(len) = n {
381 // Known length
382 if i == 0 && len == 2 && item.is_section() {
383 // Grab whole headline from section as first item of a
384 // pair when the item is section-shaped.
385 ret = Ok(Cow::from(item.head));
386 // Blank out the headline, we're left with the body
387 // for the second (and last) part.
388 item.head = "";
389 return State::SectionSeq { i: i + 1, n, item };
390 } else if i == len - 1 {
391 // Last item, apply tail-lining.
392 if item.is_line() {
393 // Tail-line the last part of a line item.
394 if !item.is_blank() {
395 ret = Ok(Cow::from(item.head));
396 }
397 return Default::default();
398 } else {
399 // Section-shaped item.
400 if !item.has_blank_line() {
401 // Mixed headline and body content in last
402 // item, must be one or the other.
403 ret =
404 err!("Unparsed input in section headline");
405 } else {
406 // Last value is entire section body.
407 ret = Ok(Cow::from(item.body.to_string()));
408 }
409 return Default::default();
410 }
411 }
412 }
413
414 if let Some(word) = item.pop_word() {
415 ret = Ok(Cow::from(word));
416 }
417 State::SectionSeq { i: i + 1, n, item }
418 }
419 State::InlineStruct {
420 mut vals,
421 mut fields,
422 fake_seq,
423 } => {
424 // Alternate between struct field names (acquired from serde,
425 // not present in input) and field values.
426 if vals.is_empty() {
427 return State::Document(Default::default());
428 } else if fields.len() == vals.len() {
429 // When vecs are balanced, hand out a field name first.
430 ret = Ok(Cow::from(fields.pop().unwrap()));
431 } else {
432 // If out of balance, assume a field name was handed out
433 // the last time, give the corresponding value.
434 debug_assert!(fields.len() + 1 == vals.len());
435 ret = Ok(Cow::from(vals.pop().unwrap()));
436 }
437 State::InlineStruct {
438 vals,
439 fields,
440 fake_seq,
441 }
442 }
443 State::SpecialFirst(Fragment::Item(i)) => {
444 ret = Ok(Cow::from(i.head));
445 State::SpecialSecond(i.body)
446 }
447 State::SpecialFirst(Fragment::Outline(mut o)) => {
448 match o.pop() {
449 Some(i) => {
450 ret = Ok(Cow::from(i.to_string()));
451 // The remaining content.
452 State::SpecialSecond(o)
453 }
454 None => {
455 // XXX: Is it okay to do a blank here, or should we error
456 // out if the content is actually being directly read
457 // instead of gone into a seq/map that will then
458 // terminate with no items?
459 ret = Ok(Cow::from(""));
460 State::SpecialSecond(o)
461 }
462 }
463 }
464 State::SpecialSecond(o) => {
465 ret = Ok(Cow::from(o.to_string()));
466 Default::default()
467 }
468 });
469
470 ret
471 }
472
473 fn enter_seq(&mut self, config: SeqConfig) -> Result<State<'a>> {
474 let mut ret = err!("Out of input");
475
476 take_mut::take(self, |s| match s {
477 State::Document(f) if f.is_empty() => {
478 ret = Ok(State::VerticalSeq(Default::default()));
479 Default::default()
480 }
481 State::Document(Fragment::Item(item)) => {
482 if let Some(next) = config.inline_state(item) {
483 ret = Ok(next);
484 } else {
485 ret = err!("Invalid sequence shape");
486 }
487 Default::default()
488 }
489 // Parsing a pair and there's a single item in the sequence, so no
490 // chance of parsing a vertical pair, see if it works as a
491 // section.
492 State::Document(Fragment::Outline(mut o))
493 if matches!(config, Tuple(2)) && o.len() == 1 =>
494 {
495 let item = o.pop().unwrap();
496 if let Some(next) = config.inline_state(item) {
497 ret = Ok(next);
498 } else {
499 ret = err!("Invalid sequence shape");
500 }
501 Default::default()
502 }
503 State::Document(Fragment::Outline(mut o)) => {
504 config.try_unfold(&mut o);
505 ret = Ok(State::VerticalSeq(o));
506 Default::default()
507 }
508 State::VerticalSeq(mut o) => {
509 let prev_o = o.clone();
510 match o.pop_nonblank() {
511 Some(Fragment::Outline(mut a)) => {
512 // Nested block.
513 config.try_unfold(&mut a);
514 ret = Ok(State::VerticalSeq(a));
515 }
516 None | Some(Fragment::Item(_))
517 if !config.allows_inline() =>
518 {
519 ret = Ok(State::VerticalSeq(Default::default()));
520 return State::VerticalSeq(prev_o);
521 }
522 Some(Fragment::Item(item)) => {
523 if let Some(next) = config.inline_state(item) {
524 ret = Ok(next);
525 }
526 }
527 _ => {
528 ret = err!("Invalid sequence shape");
529 }
530 }
531 State::VerticalSeq(o)
532 }
533 State::SectionSeq { i, n, mut item } => {
534 if let Some(len) = n {
535 if i == 0
536 && len == 2
537 && item.is_section()
538 && config.allows_inline()
539 {
540 // Whole headline at start of section-pair.
541 ret = config
542 .inline_state(item.detach_head())
543 .ok_or_else(|| Error::new("Can't inline"));
544 item.head = "";
545 return State::SectionSeq { i: i + 1, n, item };
546 } else if i == len - 1
547 && item.is_line()
548 && config.allows_inline()
549 {
550 // Trailing tail at the end of line-tuple.
551 ret = config
552 .inline_state(item)
553 .ok_or_else(|| Error::new("Can't inline"));
554 return Default::default();
555 } else if i == len - 1 && item.is_block() {
556 let mut body = item.body;
557 config.try_unfold(&mut body);
558 if config.is_pair()
559 && body.0.len() == 1
560 && body.0[0].is_section()
561 {
562 // Recurse into another section.
563 ret = Ok(State::SectionSeq {
564 i: 0,
565 n: Some(2),
566 item: body.0.pop().unwrap(),
567 });
568 } else {
569 // Entire body at end of section-tuple.
570 ret = Ok(State::VerticalSeq(body));
571 }
572 return Default::default();
573 }
574 } else {
575 ret = err!("Invalid inline nesting");
576 }
577
578 // If no special case applies, deny further nesting.
579 Default::default()
580 }
581
582 State::InlineStruct { vals, fields, .. } => {
583 if fields.is_empty()
584 && vals.len() == 1
585 && config.allows_inline()
586 {
587 // We're recursing into a tail-lined sequence value at the
588 // end of an inline struct.
589 ret = config
590 .inline_state(Item::new_head(vals[0]))
591 .ok_or_else(|| Error::new("Can't inline"));
592 // Make sure our inner sequence state stays wrapped in a
593 // fake_seq flag carrying InlineStruct state.
594 return State::InlineStruct {
595 vals: Default::default(),
596 fields: Default::default(),
597 fake_seq: true,
598 };
599 } else {
600 ret = err!("Invalid inline nesting");
601 }
602 Default::default()
603 }
604
605 State::SpecialFirst(Fragment::Item(i)) => {
606 // Synthesize an outline.
607 let outline = Outline(vec![i]);
608 if let Some(Fragment::Outline(mut a)) =
609 outline.clone().pop_nonblank()
610 {
611 // Single item looks like a vertical seq.
612 config.try_unfold(&mut a);
613 ret = Ok(State::VerticalSeq(a));
614 State::SpecialSecond(Default::default())
615 } else {
616 // Nothing that looks like a vertical seq, pass an empty
617 // container and read the contents as the tail of the
618 // special form.
619 ret = Ok(State::VerticalSeq(Default::default()));
620 State::SpecialSecond(outline)
621 }
622 }
623
624 State::SpecialFirst(Fragment::Outline(mut o)) => {
625 let prev_o = o.clone();
626
627 match o.pop_nonblank() {
628 Some(Fragment::Outline(a)) => {
629 // Nested block, that's a map.
630 ret = Ok(State::VerticalSeq(a));
631 State::SpecialSecond(o)
632 }
633 Some(Fragment::Item(_)) => {
634 // Inline item, inject an empty map, don't consume the
635 // item.
636 ret = Ok(State::VerticalSeq(Default::default()));
637 State::SpecialSecond(prev_o)
638 }
639 None => {
640 // Nothing here, empty map.
641 ret = Ok(State::VerticalSeq(Default::default()));
642 State::SpecialSecond(prev_o)
643 }
644 }
645 }
646 State::SpecialSecond(mut o) => {
647 config.try_unfold(&mut o);
648 ret = Ok(State::VerticalSeq(o));
649 Default::default()
650 }
651 });
652
653 ret
654 }
655
656 fn enter_special(&mut self) -> Result<State<'a>> {
657 let mut ret = err!("Invalid pair");
658 take_mut::take(self, |s| match s {
659 State::Document(Fragment::Item(i)) => {
660 ret = Ok(State::SpecialFirst(i.into()));
661 State::Document(Default::default())
662 }
663 State::Document(Fragment::Outline(mut o)) => {
664 let content = if o.len() == 1 {
665 Fragment::Item(o.pop().unwrap())
666 } else {
667 Fragment::Outline(o)
668 };
669 ret = Ok(State::SpecialFirst(content));
670 State::Document(Default::default())
671 }
672 State::VerticalSeq(mut o) => {
673 // Sequence -> pair, prepare for raw mode, no blank filtering.
674 if let Some(i) = o.pop() {
675 ret = Ok(State::SpecialFirst(i.into()));
676 }
677 State::VerticalSeq(o)
678 }
679 State::SectionSeq { i, item, n } if item.is_block() => {
680 // Map values etc.
681 ret = Ok(State::SpecialFirst(item.body.into()));
682 State::SectionSeq {
683 i: i + 1,
684 n,
685 item: Default::default(),
686 }
687 }
688 State::SpecialSecond(o) => {
689 ret = Ok(State::SpecialFirst(o.into()));
690 State::Document(Default::default())
691 }
692 // Others are no-go.
693 _ => s,
694 });
695
696 ret
697 }
698
699 fn is_empty(&self) -> bool {
700 match self {
701 State::Document(f) => f.is_empty(),
702 State::VerticalSeq(o) => o.is_empty_or_blank(),
703 State::SectionSeq { item, .. } => item.is_blank(),
704 State::InlineStruct { vals, .. } => vals.is_empty(),
705 // Special halves are considered nonempty if pair was entered
706 // succesfully.
707 State::SpecialFirst(_) => false,
708 State::SpecialSecond(_) => false,
709 }
710 }
711
712 fn is_really_empty(&self) -> bool {
713 match self {
714 State::Document(f) => f.is_empty(),
715 State::VerticalSeq(o) => o.is_empty(),
716 State::SectionSeq { item, .. } => item.is_blank(),
717 State::InlineStruct { vals, .. } => vals.is_empty(),
718 // Special halves are considered nonempty if pair was entered
719 // succesfully.
720 State::SpecialFirst(_) => false,
721 State::SpecialSecond(_) => false,
722 }
723 }
724}
725
726impl fmt::Display for State<'_> {
727 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
728 match self {
729 State::Document(Fragment::Outline(o)) => {
730 write!(f, "Document(\n{o})")
731 }
732 State::Document(Fragment::Item(i)) => write!(f, "Document({i})"),
733 State::VerticalSeq(o) => write!(f, "VerticalSeq(\n{o})"),
734 State::SectionSeq { item, .. } => {
735 write!(f, "SectionSeq({item})")
736 }
737 State::InlineStruct { vals, fields, .. } => {
738 writeln!(f, "InlineStruct(")?;
739 write!(f, " ")?;
740 for a in fields.iter().rev() {
741 write!(f, " {a}")?;
742 }
743 writeln!(f)?;
744 for w in vals.iter().rev() {
745 write!(f, " {w}")?;
746 }
747 write!(f, ")")
748 }
749 State::SpecialFirst(Fragment::Outline(o)) => {
750 write!(f, "SpecialFirst(\n{o})")
751 }
752 State::SpecialFirst(Fragment::Item(i)) => {
753 write!(f, "SpecialFirst({i})")
754 }
755 State::SpecialSecond(o) => write!(f, "SpecialSecond(\n{o})"),
756 }
757 }
758}
759
760#[cfg(test)]
761mod tests {
762 use super::*;
763
764 // Helper trait for automating enter/exit calls with scopes.
765 // Unit tests become very noisy without this.
766 trait ParserExt {
767 fn seq(&mut self, p: impl FnOnce(&mut Self));
768 fn pair(&mut self, p: impl FnOnce(&mut Self));
769 fn map(&mut self, p: impl FnOnce(&mut Self));
770
771 // Syntax sugar for reading next value.
772 fn n(&mut self) -> Cow<str>;
773 }
774
775 impl ParserExt for Parser<'_> {
776 fn seq(&mut self, p: impl FnOnce(&mut Self)) {
777 self.enter_seq().unwrap();
778 p(self);
779 self.exit().unwrap();
780 }
781
782 fn pair(&mut self, p: impl FnOnce(&mut Self)) {
783 self.enter_tuple(2).unwrap();
784 self.enter_special().unwrap();
785 p(self);
786 self.exit().unwrap();
787 }
788
789 fn map(&mut self, p: impl FnOnce(&mut Self)) {
790 self.enter_map().unwrap();
791 p(self);
792 self.exit().unwrap();
793 }
794
795 fn n(&mut self) -> Cow<str> {
796 self.read_str().unwrap()
797 }
798 }
799
800 #[test]
801 fn construction() {
802 assert!(Parser::from_str("").is_ok());
803 assert!(Parser::from_str("a").is_ok());
804 assert!(Parser::from_str(
805 "\
806a"
807 )
808 .is_ok());
809 assert!(Parser::from_str(
810 "\
811a
812 b
813 c"
814 )
815 .is_ok());
816
817 // Mixed indentation, not ok.
818 assert!(Parser::from_str(
819 "\
820a
821 b
822\tc"
823 )
824 .is_err());
825
826 // Bad dedentation, not ok.
827 assert!(Parser::from_str(
828 "\
829a
830 b
831 c"
832 )
833 .is_err());
834 }
835
836 #[test]
837 fn seq_1() {
838 Parser::from_str(
839 "\
840A
841B
842C",
843 )
844 .unwrap()
845 .seq(|p| {
846 assert_eq!(p.n(), "A");
847 assert_eq!(p.n(), "B");
848 assert_eq!(p.n(), "C");
849 });
850 }
851
852 #[test]
853 fn fragment_seq() {
854 // Note lack of newline in input.
855 Parser::from_str("A B C").unwrap().seq(|p| {
856 assert_eq!(p.n(), "A");
857 assert_eq!(p.n(), "B");
858 assert_eq!(p.n(), "C");
859 });
860 }
861
862 #[test]
863 fn seq_2() {
864 Parser::from_str(
865 "\
866A B
867C D",
868 )
869 .unwrap()
870 .seq(|p| {
871 p.seq(|p| {
872 assert_eq!(p.n(), "A");
873 assert_eq!(p.n(), "B");
874 });
875 p.seq(|p| {
876 assert_eq!(p.n(), "C");
877 assert_eq!(p.n(), "D");
878 });
879 });
880 }
881
882 #[test]
883 fn outline() {
884 // Simulate outline traversal, alternating sequences and raw element
885 // pairs.
886 Parser::from_str(
887 "\
888A
889 B
890 -- C
891D",
892 )
893 .unwrap()
894 .seq(|p| {
895 p.pair(|p| {
896 assert_eq!(p.n(), "A");
897 p.seq(|p| {
898 p.pair(|p| {
899 assert_eq!(p.n(), "B");
900 p.seq(|_| {});
901 });
902 // Check that comment-looking things get read in, pair
903 // heads have raw mode.
904 p.pair(|p| {
905 assert_eq!(p.n(), "-- C");
906 p.seq(|_| {});
907 });
908 });
909 });
910 p.pair(|p| {
911 assert_eq!(p.n(), "D");
912 p.seq(|_| {});
913 });
914 });
915 }
916
917 #[test]
918 fn outline_attributes() {
919 Parser::from_str(
920 "\
921Title
922 :a 1
923 :b 2
924 Content
925Title 2
926 Stuff",
927 )
928 .unwrap()
929 .seq(|p| {
930 p.pair(|p| {
931 assert_eq!(p.n(), "Title");
932 p.pair(|p| {
933 p.map(|p| {
934 p.seq(|p| {
935 assert_eq!(p.n(), "a");
936 assert_eq!(p.n(), "1");
937 });
938 p.seq(|p| {
939 assert_eq!(p.n(), "b");
940 assert_eq!(p.n(), "2");
941 });
942 });
943 p.seq(|p| {
944 assert_eq!(p.n(), "Content");
945 });
946 });
947 });
948 p.pair(|p| {
949 assert_eq!(p.n(), "Title 2");
950 p.pair(|p| {
951 p.map(|_| {
952 // Allow an empty map, don't consume content.
953 });
954 p.seq(|p| {
955 assert_eq!(p.n(), "Stuff");
956 });
957 });
958 });
959 });
960 }
961}