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}