pahs/combinators/
alternate.rs

1use crate::error_accumulator::ErrorAccumulator;
2use crate::{ParseDriver, Pos, Progress, Recoverable};
3
4/// Try all parsers supplied via [`one`](crate::combinators::Alternate::one) in order
5/// and return the value of the first one that successfully parses.
6///
7/// If none of the parsers were successful, returns the accumulated error.
8#[must_use]
9#[derive(Debug)]
10pub struct Alternate<'pd, P: 'pd, T, E: 'pd, S, A: 'pd = ()> {
11    driver: &'pd mut ParseDriver<S>,
12    current: Option<Progress<P, T, E>>,
13    pos: P,
14    err_accumulator: A,
15}
16
17impl<'pd, P, T, E, S, A> Alternate<'pd, P, T, E, S, A>
18where
19    P: Pos,
20    E: Recoverable,
21    A: ErrorAccumulator<P, E>,
22{
23    fn run_one<F>(&mut self, parser: F)
24    where
25        F: FnOnce(&mut ParseDriver<S>, P) -> Progress<P, T, E>,
26    {
27        self.current = Some(parser(self.driver, self.pos))
28    }
29
30    /// Creates a new `Alternate` with the specified error accumulator.
31    #[inline]
32    pub fn new(driver: &'pd mut ParseDriver<S>, pos: P, err_accumulator: A) -> Self {
33        Self {
34            driver,
35            current: None,
36            pos,
37            err_accumulator,
38        }
39    }
40
41    /// Runs one parser if a previous one didn't parse successfully before.
42    #[inline]
43    pub fn one<F>(mut self, parser: F) -> Self
44    where
45        F: FnOnce(&mut ParseDriver<S>, P) -> Progress<P, T, E>,
46    {
47        match &mut self.current {
48            None => self.run_one(parser),
49            Some(Progress { status: Ok(..), .. }) => {
50                // matched! skip all further parsers
51            }
52            Some(Progress { status: Err(e), .. }) if e.recoverable() => {
53                // just matched on it, unwrap can't fail
54                let current = self.current.take().unwrap();
55
56                // accumulate it
57                let _ = self.err_accumulator.add_progress(current);
58
59                self.run_one(parser)
60            }
61            Some(Progress {
62                status: Err(..), ..
63            }) => {
64                // irrecoverable, skip all further parsers
65            }
66        }
67
68        self
69    }
70
71    /// Completes this `Alternate`, returning the progress of the first successful branch.
72    ///
73    /// If none of parsers were successful, it returns the accumulated errors
74    ///
75    /// Panics if no parser was run via [`one`](Alternate::one).
76    #[inline]
77    pub fn finish(self) -> Progress<P, T, A::Accumulated> {
78        let mut err_accumulator = self.err_accumulator;
79
80        // accumulate the final progress
81        let progress = err_accumulator.add_progress(self.current.unwrap());
82
83        progress.map_err(|_| err_accumulator.finish())
84    }
85}
86
87#[cfg(test)]
88mod test {
89    use crate::error_accumulator::AllErrorsAccumulator;
90    use crate::slice::BytePos;
91    use crate::{ParseDriver, Recoverable};
92
93    #[derive(Debug, PartialEq)]
94    pub struct TestError(bool);
95
96    impl Recoverable for TestError {
97        fn recoverable(&self) -> bool {
98            self.0
99        }
100    }
101
102    #[test]
103    fn it_returns_the_first_successful_branch() {
104        let input = &[0u8, 1, 2, 3, 4];
105        let pos = BytePos::new(input);
106        let pd = &mut ParseDriver::new();
107
108        let (res_pos, val) = pd
109            .alternate(pos)
110            .one(|_, pos| pos.failure(TestError(true)))
111            .one(|_, pos| pos.advance_by(1).success(0u8))
112            .one(|_, pos| pos.advance_by(2).success(1u8))
113            .finish()
114            .unwrap();
115
116        assert_eq!(res_pos.offset, 1usize);
117        assert_eq!(val, 0u8);
118    }
119
120    #[test]
121    fn it_stops_at_irrecoverable_errors() {
122        let input = &[0u8, 1, 2, 3, 4];
123        let pos = BytePos::new(input);
124        let pd = &mut ParseDriver::new();
125
126        let (res_pos, err) = pd
127            .alternate(pos)
128            .one(|_, pos| pos.failure(TestError(true)))
129            .one(|_, pos| pos.failure(TestError(false)))
130            .one(|_, pos| pos.advance_by(1).success(0u8))
131            .finish()
132            .unwrap_err();
133
134        assert_eq!(res_pos.offset, 0usize);
135        assert_eq!(err, TestError(false));
136    }
137
138    #[test]
139    fn it_accumulates_all_errors() {
140        let input = &[0u8, 1, 2, 3, 4];
141        let pos = BytePos::new(input);
142        let pd = &mut ParseDriver::new();
143
144        let (res_pos, err) = pd
145            .alternate_accumulate_errors(pos, AllErrorsAccumulator::new())
146            .one(|_, pos| pos.failure(TestError(true)))
147            .one(|_, pos| pos.failure(TestError(true)))
148            .one(|_, pos| pos.failure::<(), _>(TestError(false)))
149            .one(|_, pos| pos.failure(TestError(true)))
150            .finish()
151            .unwrap_err();
152
153        assert_eq!(res_pos.offset, 0usize);
154        // last branch won't run because the third one was irrecoverable
155        assert_eq!(err, &[TestError(true), TestError(true), TestError(false)]);
156    }
157}