1use crate::{
2 parsers::{BasicParseIter, MapParser},
3 ParseContext, ParseIter, Parser, Reported, Result,
4};
5
6#[derive(Clone, Copy)]
7pub struct CharParser {
8 noun: &'static str,
9 predicate: fn(char) -> bool,
10}
11
12pub struct CharParseIter {
13 c: char,
14 end: usize,
15}
16
17#[derive(Clone, Copy)]
19pub struct CharOfParser {
20 options: &'static str,
21}
22
23impl Parser for CharParser {
24 type Output = char;
25 type RawOutput = (char,);
26 type Iter<'parse> = CharParseIter;
27
28 fn parse_iter<'parse>(
29 &'parse self,
30 context: &mut ParseContext<'parse>,
31 start: usize,
32 ) -> Result<Self::Iter<'parse>, Reported> {
33 match context.source()[start..].chars().next() {
34 Some(c) if (self.predicate)(c) => Ok(CharParseIter {
35 c,
36 end: start + c.len_utf8(),
37 }),
38 _ => Err(context.error_expected(start, self.noun)),
39 }
40 }
41}
42
43impl<'parse> ParseIter<'parse> for CharParseIter {
44 type RawOutput = (char,);
45 fn match_end(&self) -> usize {
46 self.end
47 }
48 fn backtrack(&mut self, _context: &mut ParseContext<'parse>) -> Result<(), Reported> {
49 Err(Reported)
50 }
51 fn convert(&self) -> (char,) {
52 (self.c,)
53 }
54}
55
56impl Parser for CharOfParser {
57 type Output = usize;
58 type RawOutput = (usize,);
59 type Iter<'parse> = BasicParseIter<usize>;
60 fn parse_iter<'parse>(
61 &'parse self,
62 context: &mut ParseContext<'parse>,
63 start: usize,
64 ) -> Result<Self::Iter<'parse>, Reported> {
65 if let Some(c) = context.source()[start..].chars().next() {
66 for (i, x) in self.options.chars().enumerate() {
69 if c == x {
70 return Ok(BasicParseIter {
71 value: i,
72 end: start + c.len_utf8(),
73 });
74 }
75 }
76 }
77 Err(context.error_expected(start, &format!("one of {:?}", self.options)))
78 }
79}
80
81#[allow(non_upper_case_globals)]
83pub const alpha: CharParser = CharParser {
84 noun: "letter",
85 predicate: char::is_alphabetic,
86};
87
88#[allow(non_upper_case_globals)]
91pub const alnum: CharParser = CharParser {
92 noun: "letter or digit",
93 predicate: char::is_alphanumeric,
94};
95
96#[allow(non_upper_case_globals)]
98pub const upper: CharParser = CharParser {
99 noun: "uppercase letter",
100 predicate: char::is_uppercase,
101};
102
103#[allow(non_upper_case_globals)]
105pub const lower: CharParser = CharParser {
106 noun: "lowercase letter",
107 predicate: char::is_lowercase,
108};
109
110#[allow(non_upper_case_globals)]
112pub const any_char: CharParser = CharParser {
113 noun: "any character",
114 predicate: |_| true,
115};
116
117#[allow(non_upper_case_globals)]
120pub const digit: MapParser<CharParser, fn(char) -> usize> = MapParser {
121 inner: CharParser {
122 noun: "decimal digit",
123 predicate: |c| c.is_ascii_digit(),
124 },
125 mapper: |c| c.to_digit(10).unwrap() as usize,
126};
127
128#[allow(non_upper_case_globals)]
131pub const digit_bin: MapParser<CharParser, fn(char) -> usize> = MapParser {
132 inner: CharParser {
133 noun: "hexadecimal digit",
134 predicate: |c| c.is_digit(2),
135 },
136 mapper: |c| c.to_digit(2).unwrap() as usize,
137};
138
139#[allow(non_upper_case_globals, clippy::is_digit_ascii_radix)]
142pub const digit_hex: MapParser<CharParser, fn(char) -> usize> = MapParser {
143 inner: CharParser {
144 noun: "hexadecimal digit",
145 predicate: |c| c.is_digit(16),
146 },
147 mapper: |c| c.to_digit(16).unwrap() as usize,
148};
149
150pub fn char_of(options: &'static str) -> CharOfParser {
154 CharOfParser { options }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use crate::testing::*;
161
162 #[test]
163 fn test_char_of() {
164 assert_parse_eq(char_of("<=>"), "<", 0);
165 assert_parse_eq(char_of("<=>"), ">", 2);
166 assert_parse_error(char_of("<=>"), "", "expected one of \"<=>\"");
167 assert_parse_error(
168 char_of("<=>"),
169 " <",
170 "expected one of \"<=>\" at line 1 column 1",
171 );
172 assert_parse_eq(char_of("DCBA"), "D", 0);
173 assert_parse_eq(char_of("DCBA"), "C", 1);
174 assert_parse_error(char_of("DCBA"), "DC", "at line 1 column 2");
175
176 assert_parse_error(char_of(""), "", "expected one of \"\"");
179
180 assert_parse_eq(char_of("😂😃🌍"), "🌍", 2);
184
185 assert_parse_error(char_of("😂😃🌍"), "L", "expected one of \"😂😃🌍\"");
186 }
187}