combine/parser/char.rs
1//! Module containing parsers specialized on character streams.
2
3use 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/// ```
21pub fn char<Input>(c: char) -> Token<Input>
22where
23 Input: Stream<Token = char>,
24{
25 token(c)
26}
27
28parser! {
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/// ```
61pub fn space<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
62where
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/// ```
81pub fn spaces<Input>() -> impl Parser<Input, Output = ()>
82where
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/// ```
96pub fn newline<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
97where
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/// ```
112pub fn crlf<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
113where
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/// ```
127pub fn tab<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
128where
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/// ```
144pub fn upper<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
145where
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/// ```
161pub fn lower<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
162where
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/// ```
179pub fn alpha_num<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
180where
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/// ```
197pub fn letter<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
198where
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/// ```
212pub fn oct_digit<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
213where
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/// ```
227pub fn hex_digit<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
228where
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/// ```
247pub fn string<'a, Input>(s: &'static str) -> impl Parser<Input, Output = &'a str>
248where
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/// ```
267pub fn string_cmp<'a, C, Input>(s: &'static str, cmp: C) -> impl Parser<Input, Output = &'a str>
268where
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))]
276mod 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]
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]
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]
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}