kalosm_sample/structured_parser/
literal.rs

1use std::borrow::Cow;
2
3use crate::bail;
4
5use crate::{CreateParserState, ParseStatus, Parser};
6
7/// A parser for a literal.
8#[derive(Debug, PartialEq, Eq, Clone)]
9pub struct LiteralParser {
10    literal: Cow<'static, str>,
11}
12
13impl CreateParserState for LiteralParser {
14    fn create_parser_state(&self) -> <Self as Parser>::PartialState {
15        LiteralParserOffset::default()
16    }
17}
18
19impl<S: Into<Cow<'static, str>>> From<S> for LiteralParser {
20    fn from(literal: S) -> Self {
21        Self {
22            literal: literal.into(),
23        }
24    }
25}
26
27impl LiteralParser {
28    /// Create a new literal parser.
29    pub fn new<S: Into<Cow<'static, str>>>(literal: S) -> Self {
30        Self {
31            literal: literal.into(),
32        }
33    }
34}
35
36/// The state of a literal parser.
37#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
38pub struct LiteralParserOffset {
39    pub(crate) offset: usize,
40}
41
42impl LiteralParserOffset {
43    /// Create a new literal parser state.
44    pub fn new(offset: usize) -> Self {
45        Self { offset }
46    }
47}
48
49/// The error type for a literal parser.
50#[derive(Debug, PartialEq, Eq, Copy, Clone)]
51pub struct LiteralMismatchError;
52
53impl std::fmt::Display for LiteralMismatchError {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        write!(f, "Literal mismatch")
56    }
57}
58
59impl std::error::Error for LiteralMismatchError {}
60
61impl Parser for LiteralParser {
62    type Output = ();
63    type PartialState = LiteralParserOffset;
64
65    fn parse<'a>(
66        &self,
67        state: &LiteralParserOffset,
68        input: &'a [u8],
69    ) -> crate::ParseResult<ParseStatus<'a, Self::PartialState, Self::Output>> {
70        let mut bytes_consumed = 0;
71
72        for (input_byte, literal_byte) in input
73            .iter()
74            .zip(self.literal.as_bytes()[state.offset..].iter())
75        {
76            if input_byte != literal_byte {
77                bail!(LiteralMismatchError);
78            }
79            bytes_consumed += 1;
80        }
81
82        if state.offset + bytes_consumed == self.literal.len() {
83            Ok(ParseStatus::Finished {
84                result: (),
85                remaining: &input[bytes_consumed..],
86            })
87        } else {
88            Ok(ParseStatus::Incomplete {
89                new_state: LiteralParserOffset {
90                    offset: state.offset + bytes_consumed,
91                },
92                required_next: {
93                    match &self.literal {
94                        Cow::Borrowed(cow) => {
95                            Cow::Borrowed(cow.split_at(state.offset + bytes_consumed).1)
96                        }
97                        Cow::Owned(cow) => {
98                            Cow::Owned(cow.split_at(state.offset + bytes_consumed).1.to_string())
99                        }
100                    }
101                },
102            })
103        }
104    }
105}
106
107#[test]
108fn literal_parser() {
109    let parser = LiteralParser::new("Hello, world!");
110    let state = LiteralParserOffset { offset: 0 };
111    assert_eq!(
112        parser.parse(&state, b"Hello, world!").unwrap(),
113        ParseStatus::Finished {
114            result: (),
115            remaining: &[]
116        }
117    );
118    assert_eq!(
119        parser.parse(&state, b"Hello, ").unwrap(),
120        ParseStatus::Incomplete {
121            new_state: LiteralParserOffset { offset: 7 },
122            required_next: "world!".into()
123        }
124    );
125    assert_eq!(
126        parser
127            .parse(
128                &parser
129                    .parse(&state, b"Hello, ")
130                    .unwrap()
131                    .unwrap_incomplete()
132                    .0,
133                b"world!"
134            )
135            .unwrap(),
136        ParseStatus::Finished {
137            result: (),
138            remaining: &[]
139        }
140    );
141    assert!(parser.parse(&state, b"Goodbye, world!").is_err(),);
142}