Coverage Report

Created: 2026-09-01 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/combine-4.6.8/src/parser/char.rs
Line
Count
Source
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
/// ```
21
0
pub fn char<Input>(c: char) -> Token<Input>
22
0
where
23
0
    Input: Stream<Token = char>,
24
{
25
0
    token(c)
26
0
}
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
0
        satisfy(|c: char| c.is_digit(10)).expected("digit")
Unexecuted instantiation: <combine::parser::char::Digit<_> as combine::parser::Parser<_>>::parse_mode_impl::<_>::{closure#0}
Unexecuted instantiation: <combine::parser::char::Digit<_> as combine::parser::Parser<_>>::add_committed_expected_error::{closure#0}
Unexecuted instantiation: <combine::parser::char::Digit<_> as combine::parser::Parser<_>>::add_error::{closure#0}
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
/// ```
61
0
pub fn space<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
62
0
where
63
0
    Input: Stream<Token = char>,
64
{
65
0
    let f: fn(char) -> bool = char::is_whitespace;
66
0
    satisfy(f).expected("whitespace")
67
0
}
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
/// ```
81
0
pub fn spaces<Input>() -> impl Parser<Input, Output = ()>
82
0
where
83
0
    Input: Stream<Token = char>,
84
{
85
0
    skip_many(space()).expected("whitespaces")
86
0
}
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
/// ```
96
0
pub fn newline<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
97
0
where
98
0
    Input: Stream<Token = char>,
99
{
100
0
    satisfy(|ch: char| ch == '\n').expected("lf newline")
101
0
}
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
/// ```
112
0
pub fn crlf<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
113
0
where
114
0
    Input: Stream<Token = char>,
115
{
116
0
    no_partial(satisfy(|ch: char| ch == '\r').with(newline())).expected("crlf newline")
117
0
}
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
/// ```
127
0
pub fn tab<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
128
0
where
129
0
    Input: Stream<Token = char>,
130
{
131
0
    satisfy(|ch: char| ch == '\t').expected("tab")
132
0
}
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
/// ```
144
0
pub fn upper<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
145
0
where
146
0
    Input: Stream<Token = char>,
147
{
148
0
    satisfy(|ch: char| ch.is_uppercase()).expected("uppercase letter")
149
0
}
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
/// ```
161
0
pub fn lower<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
162
0
where
163
0
    Input: Stream<Token = char>,
164
{
165
0
    satisfy(|ch: char| ch.is_lowercase()).expected("lowercase letter")
166
0
}
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
/// ```
179
0
pub fn alpha_num<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
180
0
where
181
0
    Input: Stream<Token = char>,
182
{
183
0
    satisfy(|ch: char| ch.is_alphanumeric()).expected("letter or digit")
184
0
}
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
/// ```
197
0
pub fn letter<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
198
0
where
199
0
    Input: Stream<Token = char>,
200
{
201
0
    satisfy(|ch: char| ch.is_alphabetic()).expected("letter")
202
0
}
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
/// ```
212
0
pub fn oct_digit<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
213
0
where
214
0
    Input: Stream<Token = char>,
215
{
216
0
    satisfy(|ch: char| ch.is_digit(8)).expected("octal digit")
217
0
}
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
/// ```
227
0
pub fn hex_digit<Input>() -> impl Parser<Input, Output = char, PartialState = ()>
228
0
where
229
0
    Input: Stream<Token = char>,
230
{
231
0
    satisfy(|ch: char| ch.is_digit(0x10)).expected("hexadecimal digit")
232
0
}
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
/// ```
247
0
pub fn string<'a, Input>(s: &'static str) -> impl Parser<Input, Output = &'a str>
248
0
where
249
0
    Input: Stream<Token = char>,
250
{
251
0
    string_cmp(s, |l, r| l == r)
252
0
}
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
/// ```
267
0
pub fn string_cmp<'a, C, Input>(s: &'static str, cmp: C) -> impl Parser<Input, Output = &'a str>
268
0
where
269
0
    C: FnMut(char, char) -> bool,
270
0
    Input: Stream<Token = char>,
271
{
272
0
    tokens_cmp(s.chars(), cmp).map(move |_| s).expected(s)
273
0
}
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]
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
}