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