kalosm_sample/structured_parser/
stop_on.rs1use crate::{CreateParserState, ParseStatus, Parser};
2
3type CharFilter = fn(char) -> bool;
4
5#[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 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 pub fn filter_characters(self, character_filter: F) -> StopOn<S, F> {
40 StopOn {
41 literal: self.literal,
42 character_filter,
43 }
44 }
45
46 pub fn literal(&self) -> &str {
48 self.literal.as_ref()
49 }
50}
51
52#[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#[derive(Default, Debug, PartialEq, Eq, Clone)]
66pub struct StopOnOffset {
67 offset: usize,
68 text: String,
69}
70
71impl StopOnOffset {
72 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}