kalosm_sample/structured_parser/
stop_on.rs

1use crate::{CreateParserState, ParseStatus, Parser};
2
3type CharFilter = fn(char) -> bool;
4
5/// A parser that parses until a literal is found.
6#[derive(Debug, PartialEq, Eq, Copy, Clone)]
7pub struct StopOn<S: AsRef<str> = &'static str, F: Fn(char) -> bool + 'static = CharFilter> {
8    literal: S,
9    character_filter: F,
10}
11
12impl<S: AsRef<str>> CreateParserState for StopOn<S> {
13    fn create_parser_state(&self) -> <Self as Parser>::PartialState {
14        StopOnOffset::default()
15    }
16}
17
18impl<S: AsRef<str>> From<S> for StopOn<S> {
19    fn from(literal: S) -> Self {
20        Self {
21            literal,
22            character_filter: |_| true,
23        }
24    }
25}
26
27impl<S: AsRef<str>> StopOn<S> {
28    /// Create a new literal parser.
29    pub fn new(literal: S) -> Self {
30        Self {
31            literal,
32            character_filter: |_| true,
33        }
34    }
35}
36
37impl<S: AsRef<str>, F: Fn(char) -> bool + 'static> StopOn<S, F> {
38    /// Only allow characters that pass the filter.
39    pub fn filter_characters(self, character_filter: F) -> StopOn<S, F> {
40        StopOn {
41            literal: self.literal,
42            character_filter,
43        }
44    }
45
46    /// Get the literal that this parser stops on.
47    pub fn literal(&self) -> &str {
48        self.literal.as_ref()
49    }
50}
51
52/// An error that can occur while parsing a string literal.
53#[derive(Debug, PartialEq, Eq, Clone)]
54pub struct StopOnParseError;
55
56impl std::fmt::Display for StopOnParseError {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        "StopOnParseError".fmt(f)
59    }
60}
61
62impl std::error::Error for StopOnParseError {}
63
64/// The state of a stop on literal parser.
65#[derive(Default, Debug, PartialEq, Eq, Clone)]
66pub struct StopOnOffset {
67    offset: usize,
68    text: String,
69}
70
71impl StopOnOffset {
72    /// Create a new stop on literal parser state.
73    pub fn new(offset: usize) -> Self {
74        Self {
75            offset,
76            text: String::new(),
77        }
78    }
79}
80
81impl<S: AsRef<str>, F: Fn(char) -> bool + 'static> Parser for StopOn<S, F> {
82    type Output = String;
83    type PartialState = StopOnOffset;
84
85    fn parse<'a>(
86        &self,
87        state: &StopOnOffset,
88        input: &'a [u8],
89    ) -> crate::ParseResult<ParseStatus<'a, Self::PartialState, Self::Output>> {
90        let mut new_offset = state.offset;
91        let mut text = state.text.clone();
92
93        let input_str = std::str::from_utf8(input).unwrap();
94        let literal_length = self.literal.as_ref().len();
95        let mut literal_iter = self.literal.as_ref()[state.offset..].chars();
96
97        for (i, input_char) in input_str.char_indices() {
98            if !(self.character_filter)(input_char) {
99                crate::bail!(StopOnParseError);
100            }
101
102            let literal_char = literal_iter.next();
103
104            if Some(input_char) == literal_char {
105                new_offset += 1;
106
107                if new_offset == literal_length {
108                    text += std::str::from_utf8(&input[..i + 1]).unwrap();
109                    return Ok(ParseStatus::Finished {
110                        result: text,
111                        remaining: &input[i + 1..],
112                    });
113                }
114            } else {
115                literal_iter = self.literal.as_ref()[state.offset..].chars();
116                new_offset = 0;
117            }
118        }
119
120        text.push_str(input_str);
121
122        Ok(ParseStatus::Incomplete {
123            new_state: StopOnOffset {
124                offset: new_offset,
125                text,
126            },
127            required_next: "".into(),
128        })
129    }
130}
131
132#[test]
133fn literal_parser() {
134    let parser = StopOn::new("Hello, world!");
135    let state = StopOnOffset {
136        offset: 0,
137        text: String::new(),
138    };
139    assert_eq!(
140        parser.parse(&state, b"Hello, world!"),
141        Ok(ParseStatus::Finished {
142            result: "Hello, world!".to_string(),
143            remaining: &[]
144        })
145    );
146    assert_eq!(
147        parser.parse(&state, b"Hello, world! This is a test"),
148        Ok(ParseStatus::Finished {
149            result: "Hello, world!".to_string(),
150            remaining: b" This is a test"
151        })
152    );
153    assert_eq!(
154        parser.parse(&state, b"Hello, "),
155        Ok(ParseStatus::Incomplete {
156            new_state: StopOnOffset {
157                offset: 7,
158                text: "Hello, ".into()
159            },
160            required_next: "".into()
161        })
162    );
163    assert_eq!(
164        parser.parse(
165            &parser
166                .parse(&state, b"Hello, ")
167                .unwrap()
168                .unwrap_incomplete()
169                .0,
170            b"world!"
171        ),
172        Ok(ParseStatus::Finished {
173            result: "Hello, world!".to_string(),
174            remaining: &[]
175        })
176    );
177    assert_eq!(
178        parser.parse(&state, b"Goodbye, world!"),
179        Ok(ParseStatus::Incomplete {
180            new_state: StopOnOffset {
181                offset: 0,
182                text: "Goodbye, world!".into()
183            },
184            required_next: "".into()
185        })
186    );
187}