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}