1 //! Module containing parsers specialized on character streams.
2
3 use crate::{
4 error::ParseError,
5 parser::{
6 combinator::no_partial,
7 repeat::skip_many,
8 token::{satisfy, token, tokens_cmp, Token},
9 },
10 stream::Stream,
11 Parser,
12 };
13
14 /// Parses a character and succeeds if the character is equal to `c`.
15 ///
16 /// ```
17 /// use combine::Parser;
18 /// use combine::parser::char::char;
19 /// assert_eq!(char('!').parse("!"), Ok(('!', "")));
20 /// assert!(char('A').parse("!").is_err());
21 /// ```
char<Input>(c: char) -> Token<Input> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,22 pub fn char<Input>(c: char) -> Token<Input>
23 where
24 Input: Stream<Token = char>,
25 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
26 {
27 token(c)
28 }
29
30 parser! {
31 #[derive(Copy, Clone)]
32 pub struct Digit;
33 /// Parses a base-10 digit.
34 ///
35 /// ```
36 /// use combine::Parser;
37 /// use combine::parser::char::digit;
38 /// assert_eq!(digit().parse("9"), Ok(('9', "")));
39 /// assert!(digit().parse("A").is_err());
40 /// ```
41 pub fn digit[Input]()(Input) -> char
42 where
43 [Input: Stream<Token = char>,]
44 {
45 satisfy(|c: char| c.is_digit(10)).expected("digit")
46 }
47 }
48
49 /// Parse a single whitespace according to [`std::char::is_whitespace`].
50 ///
51 /// This includes space characters, tabs and newlines.
52 ///
53 /// [`std::char::is_whitespace`]: https://doc.rust-lang.org/std/primitive.char.html#method.is_whitespace
54 ///
55 /// ```
56 /// use combine::Parser;
57 /// use combine::parser::char::space;
58 /// assert_eq!(space().parse(" "), Ok((' ', "")));
59 /// assert_eq!(space().parse(" "), Ok((' ', " ")));
60 /// assert!(space().parse("!").is_err());
61 /// assert!(space().parse("").is_err());
62 /// ```
space<Input>() -> impl Parser<Input, Output = char, PartialState = ()> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,63 pub fn space<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
64 where
65 Input: Stream<Token = char>,
66 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
67 {
68 let f: fn(char) -> bool = char::is_whitespace;
69 satisfy(f).expected("whitespace")
70 }
71
72 /// Skips over zero or more spaces according to [`std::char::is_whitespace`].
73 ///
74 /// This includes space characters, tabs and newlines.
75 ///
76 /// [`std::char::is_whitespace`]: https://doc.rust-lang.org/std/primitive.char.html#method.is_whitespace
77 ///
78 /// ```
79 /// use combine::Parser;
80 /// use combine::parser::char::spaces;
81 /// assert_eq!(spaces().parse(""), Ok(((), "")));
82 /// assert_eq!(spaces().parse(" "), Ok(((), "")));
83 /// ```
spaces<Input>() -> impl Parser<Input, Output = ()> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,84 pub fn spaces<Input>() -> impl Parser<Input, Output = ()>
85 where
86 Input: Stream<Token = char>,
87 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
88 {
89 skip_many(space()).expected("whitespaces")
90 }
91
92 /// Parses a newline character (`'\n'`).
93 ///
94 /// ```
95 /// use combine::Parser;
96 /// use combine::parser::char::newline;
97 /// assert_eq!(newline().parse("\n"), Ok(('\n', "")));
98 /// assert!(newline().parse("\r").is_err());
99 /// ```
newline<Input>() -> impl Parser<Input, Output = char, PartialState = ()> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,100 pub fn newline<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
101 where
102 Input: Stream<Token = char>,
103 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
104 {
105 satisfy(|ch: char| ch == '\n').expected("lf newline")
106 }
107
108 /// Parses carriage return and newline (`"\r\n"`), returning the newline character.
109 ///
110 /// ```
111 /// use combine::Parser;
112 /// use combine::parser::char::crlf;
113 /// assert_eq!(crlf().parse("\r\n"), Ok(('\n', "")));
114 /// assert!(crlf().parse("\r").is_err());
115 /// assert!(crlf().parse("\n").is_err());
116 /// ```
crlf<Input>() -> impl Parser<Input, Output = char, PartialState = ()> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,117 pub fn crlf<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
118 where
119 Input: Stream<Token = char>,
120 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
121 {
122 no_partial(satisfy(|ch: char| ch == '\r').with(newline())).expected("crlf newline")
123 }
124
125 /// Parses a tab character (`'\t'`).
126 ///
127 /// ```
128 /// use combine::Parser;
129 /// use combine::parser::char::tab;
130 /// assert_eq!(tab().parse("\t"), Ok(('\t', "")));
131 /// assert!(tab().parse(" ").is_err());
132 /// ```
tab<Input>() -> impl Parser<Input, Output = char, PartialState = ()> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,133 pub fn tab<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
134 where
135 Input: Stream<Token = char>,
136 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
137 {
138 satisfy(|ch: char| ch == '\t').expected("tab")
139 }
140
141 /// Parses an uppercase letter according to [`std::char::is_uppercase`].
142 ///
143 /// [`std::char::is_uppercase`]: https://doc.rust-lang.org/std/primitive.char.html#method.is_uppercase
144 ///
145 /// ```
146 /// use combine::Parser;
147 /// use combine::parser::char::upper;
148 /// assert_eq!(upper().parse("A"), Ok(('A', "")));
149 /// assert!(upper().parse("a").is_err());
150 /// ```
upper<Input>() -> impl Parser<Input, Output = char, PartialState = ()> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,151 pub fn upper<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
152 where
153 Input: Stream<Token = char>,
154 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
155 {
156 satisfy(|ch: char| ch.is_uppercase()).expected("uppercase letter")
157 }
158
159 /// Parses an lowercase letter according to [`std::char::is_lowercase`].
160 ///
161 /// [`std::char::is_lowercase`]: https://doc.rust-lang.org/std/primitive.char.html#method.is_lowercase
162 ///
163 /// ```
164 /// use combine::Parser;
165 /// use combine::parser::char::lower;
166 /// assert_eq!(lower().parse("a"), Ok(('a', "")));
167 /// assert!(lower().parse("A").is_err());
168 /// ```
lower<Input>() -> impl Parser<Input, Output = char, PartialState = ()> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,169 pub fn lower<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
170 where
171 Input: Stream<Token = char>,
172 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
173 {
174 satisfy(|ch: char| ch.is_lowercase()).expected("lowercase letter")
175 }
176
177 /// Parses either an alphabet letter or digit according to [`std::char::is_alphanumeric`].
178 ///
179 /// [`std::char::is_alphanumeric`]: https://doc.rust-lang.org/std/primitive.char.html#method.is_alphanumeric
180 ///
181 /// ```
182 /// use combine::Parser;
183 /// use combine::parser::char::alpha_num;
184 /// assert_eq!(alpha_num().parse("A"), Ok(('A', "")));
185 /// assert_eq!(alpha_num().parse("1"), Ok(('1', "")));
186 /// assert!(alpha_num().parse("!").is_err());
187 /// ```
alpha_num<Input>() -> impl Parser<Input, Output = char, PartialState = ()> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,188 pub fn alpha_num<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
189 where
190 Input: Stream<Token = char>,
191 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
192 {
193 satisfy(|ch: char| ch.is_alphanumeric()).expected("letter or digit")
194 }
195
196 /// Parses an alphabet letter according to [`std::char::is_alphabetic`].
197 ///
198 /// [`std::char::is_alphabetic`]: https://doc.rust-lang.org/std/primitive.char.html#method.is_alphabetic
199 ///
200 /// ```
201 /// use combine::Parser;
202 /// use combine::parser::char::letter;
203 /// assert_eq!(letter().parse("a"), Ok(('a', "")));
204 /// assert_eq!(letter().parse("A"), Ok(('A', "")));
205 /// assert!(letter().parse("9").is_err());
206 /// ```
letter<Input>() -> impl Parser<Input, Output = char, PartialState = ()> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,207 pub fn letter<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
208 where
209 Input: Stream<Token = char>,
210 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
211 {
212 satisfy(|ch: char| ch.is_alphabetic()).expected("letter")
213 }
214
215 /// Parses an octal digit.
216 ///
217 /// ```
218 /// use combine::Parser;
219 /// use combine::parser::char::oct_digit;
220 /// assert_eq!(oct_digit().parse("7"), Ok(('7', "")));
221 /// assert!(oct_digit().parse("8").is_err());
222 /// ```
oct_digit<Input>() -> impl Parser<Input, Output = char, PartialState = ()> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,223 pub fn oct_digit<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
224 where
225 Input: Stream<Token = char>,
226 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
227 {
228 satisfy(|ch: char| ch.is_digit(8)).expected("octal digit")
229 }
230
231 /// Parses a hexdecimal digit with uppercase and lowercase.
232 ///
233 /// ```
234 /// use combine::Parser;
235 /// use combine::parser::char::hex_digit;
236 /// assert_eq!(hex_digit().parse("F"), Ok(('F', "")));
237 /// assert!(hex_digit().parse("H").is_err());
238 /// ```
hex_digit<Input>() -> impl Parser<Input, Output = char, PartialState = ()> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,239 pub fn hex_digit<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
240 where
241 Input: Stream<Token = char>,
242 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
243 {
244 satisfy(|ch: char| ch.is_digit(0x10)).expected("hexadecimal digit")
245 }
246
247 /// Parses the string `s`.
248 ///
249 /// ```
250 /// # extern crate combine;
251 /// # use combine::*;
252 /// # use combine::parser::char::string;
253 /// # fn main() {
254 /// let result = string("rust")
255 /// .parse("rust")
256 /// .map(|x| x.0);
257 /// assert_eq!(result, Ok("rust"));
258 /// # }
259 /// ```
string<'a, Input>(s: &'static str) -> impl Parser<Input, Output = &'a str> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,260 pub fn string<'a, Input>(s: &'static str) -> impl Parser<Input, Output = &'a str>
261 where
262 Input: Stream<Token = char>,
263 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
264 {
265 string_cmp(s, |l, r| l == r)
266 }
267
268 /// Parses the string `s`, using `cmp` to compare each character.
269 ///
270 /// ```
271 /// # extern crate combine;
272 /// # use combine::*;
273 /// # use combine::parser::char::string_cmp;
274 /// # fn main() {
275 /// let result = string_cmp("rust", |l, r| l.eq_ignore_ascii_case(&r))
276 /// .parse("RusT")
277 /// .map(|x| x.0);
278 /// assert_eq!(result, Ok("rust"));
279 /// # }
280 /// ```
string_cmp<'a, C, Input>(s: &'static str, cmp: C) -> impl Parser<Input, Output = &'a str> where C: FnMut(char, char) -> bool, Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,281 pub fn string_cmp<'a, C, Input>(s: &'static str, cmp: C) -> impl Parser<Input, Output = &'a str>
282 where
283 C: FnMut(char, char) -> bool,
284 Input: Stream<Token = char>,
285 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
286 {
287 tokens_cmp(s.chars(), cmp).map(move |_| s).expected(s)
288 }
289
290 #[cfg(all(feature = "std", test))]
291 mod tests {
292
293 use crate::{
294 parser::EasyParser,
295 stream::{
296 easy::{Error, Errors},
297 position::{self, SourcePosition},
298 },
299 };
300
301 use super::*;
302
303 #[test]
space_error()304 fn space_error() {
305 let result = space().easy_parse("");
306 assert!(result.is_err());
307 assert_eq!(
308 result.unwrap_err().errors,
309 vec![Error::end_of_input(), Error::Expected("whitespace".into())]
310 );
311 }
312
313 #[test]
string_committed()314 fn string_committed() {
315 let result = string("a").easy_parse(position::Stream::new("b"));
316 assert!(result.is_err());
317 assert_eq!(
318 result.unwrap_err().position,
319 SourcePosition { line: 1, column: 1 }
320 );
321 }
322
323 #[test]
string_error()324 fn string_error() {
325 let result = string("abc").easy_parse(position::Stream::new("bc"));
326 assert_eq!(
327 result,
328 Err(Errors {
329 position: SourcePosition { line: 1, column: 1 },
330 errors: vec![Error::Unexpected('b'.into()), Error::Expected("abc".into())],
331 })
332 );
333 }
334 }
335