blob: e93b446dd586a51d61f92065a31186468ca88101 [file] [log] [blame]
Ivan Lozano57e660e2021-08-20 09:58:42 -04001//! Module containing parsers specialized on character streams.
2
3use 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/// ```
22pub fn char<Input>(c: char) -> Token<Input>
23where
24 Input: Stream<Token = char>,
25 Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
26{
27 token(c)
28}
29
30parser! {
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/// ```
63pub fn space<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
64where
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/// ```
84pub fn spaces<Input>() -> impl Parser<Input, Output = ()>
85where
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/// ```
100pub fn newline<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
101where
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/// ```
117pub fn crlf<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
118where
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/// ```
133pub fn tab<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
134where
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/// ```
151pub fn upper<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
152where
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/// ```
169pub fn lower<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
170where
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/// ```
188pub fn alpha_num<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
189where
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/// ```
207pub fn letter<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
208where
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/// ```
223pub fn oct_digit<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
224where
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/// ```
239pub fn hex_digit<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
240where
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/// ```
260pub fn string<'a, Input>(s: &'static str) -> impl Parser<Input, Output = &'a str>
261where
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;
Ivan Lozano57e660e2021-08-20 09:58:42 -0400274/// # 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/// ```
281pub fn string_cmp<'a, C, Input>(s: &'static str, cmp: C) -> impl Parser<Input, Output = &'a str>
282where
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))]
291mod 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]
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]
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]
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}