oy/
arg_parse.rs

1//! Implementation details.
2use crate::{ArgParseError, InteractiveError};
3
4/// Parse str -> Self
5pub trait ArgParse: Sized {
6    /// Parse str -> Self
7    fn arg_parse(s: &str) -> Result<Self, ArgParseError<'_>>;
8}
9
10fn parse_arg<'a, T: ArgParse>(
11    method_name: &'a str,
12    haystack: &mut &'a str,
13    expected: usize,
14    found: usize,
15) -> crate::Result<'a, T> {
16    let arg_str = get_next_arg(method_name, haystack, expected, found)?;
17
18    ArgParse::arg_parse(arg_str).map_err(|e| InteractiveError::ArgParseError {
19        method_name,
20        error: e,
21    })
22}
23
24fn get_next_arg<'a>(
25    method_name: &'a str,
26    haystack: &mut &'a str,
27    expected: usize,
28    found: usize,
29) -> crate::Result<'a, &'a str> {
30    match find_next_separator_index(haystack) {
31        Some(arg_end_idx) => {
32            let (arg_str, rest_str) = haystack.split_at(arg_end_idx);
33            let arg_str = arg_str.trim();
34            if arg_str.is_empty() {
35                // no arg before separator
36                return Err(InteractiveError::SyntaxError {});
37            }
38            *haystack = &rest_str[1..]; // skip separator
39            Ok(arg_str)
40        }
41        None => {
42            let arg_str = haystack.trim();
43            if arg_str.is_empty() {
44                // not enough args
45                return Err(InteractiveError::WrongNumberOfArguments {
46                    method_name,
47                    expected,
48                    found,
49                });
50            }
51            *haystack = "";
52            Ok(arg_str)
53        }
54    }
55}
56
57fn clear_args<'a>(
58    method_name: &'a str,
59    haystack: &mut &'a str,
60    expected: usize,
61    mut found: usize,
62) -> crate::Result<'a, ()> {
63    if !haystack.is_empty() {
64        loop {
65            get_next_arg(method_name, haystack, expected, found)?;
66            found += 1;
67        }
68    }
69
70    Ok(())
71}
72
73fn find_next_separator_index(s: &str) -> Option<usize> {
74    let mut chars = s.char_indices();
75    let mut inside_single_quotes = false;
76    let mut inside_double_quotes = false;
77
78    while let Some((idx, c)) = chars.next() {
79        match c {
80            '\\' => {
81                chars.next();
82            }
83            ',' => {
84                if !inside_double_quotes && !inside_single_quotes {
85                    return Some(idx);
86                }
87            }
88            '\'' => {
89                if !inside_double_quotes {
90                    inside_single_quotes = !inside_single_quotes;
91                }
92            }
93            '"' => {
94                if !inside_single_quotes {
95                    inside_double_quotes = !inside_double_quotes;
96                }
97            }
98            _ => {}
99        }
100    }
101    None
102}
103
104#[allow(missing_docs)]
105pub fn parse_0_args<'a>(method_name: &'a str, mut args: &'a str) -> crate::Result<'a, ()> {
106    clear_args(method_name, &mut args, 0, 0)
107}
108
109macro_rules! parse_x_args {
110    ($funcname:ident::<$($TN:ident),*>, ($($i:literal),*), x=$x:literal) => {
111        #[allow(non_snake_case, missing_docs)]
112        pub fn $funcname<'a, $($TN: ArgParse,)*>(
113            method_name: &'a str,
114            mut args: &'a str,
115        ) -> crate::Result<'a, ($($TN,)*)> {
116            $(let $TN  = parse_arg(method_name, &mut args, $x, $i)?;)*
117            clear_args(method_name, &mut args, $x, $x)?;
118            Ok(($($TN,)*))
119        }
120    };
121}
122
123parse_x_args!(parse_1_arg::<T0>, (0), x = 1);
124parse_x_args!(parse_2_args::<T0, T1>, (0, 1), x = 2);
125parse_x_args!(parse_3_args::<T0, T1, T2>, (0, 1, 2), x = 3);
126parse_x_args!(parse_4_args::<T0, T1, T2, T3>, (0, 1, 2, 3), x = 4);
127parse_x_args!(parse_5_args::<T0, T1, T2, T3, T4>, (0, 1, 2, 3, 4), x = 5);
128parse_x_args!(
129    parse_6_args::<T0, T1, T2, T3, T4, T5>,
130    (0, 1, 2, 3, 4, 5),
131    x = 6
132);
133
134macro_rules! parse_int {
135    ($($t:ty),*) => (
136      $(impl ArgParse for $t {
137        fn arg_parse(s: &str) -> Result<Self, ArgParseError<'_>> {
138            s.parse().map_err(ArgParseError::ParseIntError)
139        }
140      })*
141    )
142}
143
144parse_int!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
145
146impl ArgParse for bool {
147    fn arg_parse(s: &str) -> Result<Self, ArgParseError<'_>> {
148        s.parse().map_err(ArgParseError::ParseBoolError)
149    }
150}
151
152impl ArgParse for f32 {
153    fn arg_parse(s: &str) -> Result<Self, ArgParseError<'_>> {
154        s.parse().map_err(ArgParseError::ParseFloatError)
155    }
156}
157
158impl ArgParse for f64 {
159    fn arg_parse(s: &str) -> Result<Self, ArgParseError<'_>> {
160        s.parse().map_err(ArgParseError::ParseFloatError)
161    }
162}
163
164impl ArgParse for char {
165    fn arg_parse(s: &str) -> Result<Self, ArgParseError<'_>> {
166        unescape_char(s)
167    }
168}
169
170#[cfg(feature = "std")]
171impl ArgParse for String {
172    fn arg_parse(s: &str) -> Result<Self, ArgParseError<'_>> {
173        unescape_str(s)
174    }
175}
176
177// "'A'" -> Ok('A')
178fn unescape_char(s: &str) -> Result<char, ArgParseError<'_>> {
179    let mut chars = s.chars();
180    if chars.next() != Some('\'') {
181        return Err(ArgParseError::UnescapeError(s));
182    }
183    if chars.next_back() != Some('\'') {
184        return Err(ArgParseError::UnescapeError(s));
185    }
186    if chars.as_str().starts_with('\\') {
187        chars.next(); // pop '\\'
188        get_escaped_char(&mut chars).ok_or(ArgParseError::UnescapeError(s))
189    } else {
190        chars
191            .as_str()
192            .parse()
193            .map_err(ArgParseError::ParseCharError)
194    }
195}
196
197// "\"asfd\"" -> Ok("asdf")
198#[cfg(feature = "std")]
199fn unescape_str(s: &str) -> Result<String, ArgParseError<'_>> {
200    let mut chars = s.chars();
201    if chars.next() != Some('\"') {
202        return Err(ArgParseError::UnescapeError(s));
203    }
204    if chars.next_back() != Some('\"') {
205        return Err(ArgParseError::UnescapeError(s));
206    }
207
208    let mut res = String::with_capacity(chars.as_str().len());
209
210    while let Some(c) = chars.next() {
211        match c {
212            '\\' => {
213                let c = get_escaped_char(&mut chars).ok_or(ArgParseError::UnescapeError(s))?;
214                res.push(c)
215            }
216            _ => res.push(c),
217        }
218    }
219
220    Ok(res)
221}
222
223// "n" -> Some('\n')
224// https://doc.rust-lang.org/reference/tokens.html
225fn get_escaped_char(after_backslash: &mut core::str::Chars<'_>) -> Option<char> {
226    match after_backslash.next() {
227        None => None,
228        Some(char) => match char {
229            'n' => Some('\n'),
230            'r' => Some('\r'),
231            't' => Some('\t'),
232            '\\' => Some('\\'),
233            '0' => Some('\0'),
234            '\'' => Some('\''),
235            '\"' => Some('\"'),
236            'x' => parse_ascii(after_backslash),
237            'u' => parse_unicode(after_backslash),
238            _ => None,
239        },
240    }
241}
242
243// "41" -> Some('A')
244fn parse_ascii(after_x: &mut core::str::Chars<'_>) -> Option<char> {
245    let hex = after_x.as_str().get(..2)?;
246    let ascii = u32::from_str_radix(hex, 16).ok()?;
247    if ascii > 0x7f {
248        return None;
249    };
250    let res = core::char::from_u32(ascii);
251
252    // pop used chars
253    after_x.nth(1);
254    res
255}
256
257// "{2764}" -> Some('❤')
258fn parse_unicode(after_u: &mut core::str::Chars<'_>) -> Option<char> {
259    if after_u.next() != Some('{') {
260        return None;
261    }
262    if let Some(hex_end) = after_u.as_str().find('}') {
263        let hex = &after_u.as_str()[..hex_end];
264        let res = u32::from_str_radix(hex, 16)
265            .ok()
266            .and_then(core::char::from_u32);
267
268        // pop used chars
269        after_u.nth(hex_end);
270        res
271    } else {
272        None
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    fn test_parse_one_arg<T: ArgParse + PartialEq + core::fmt::Debug>(arg: &str, expected: T) {
281        let result: T = parse_1_arg("", arg).unwrap().0;
282        assert_eq!(result, expected);
283    }
284
285    #[test]
286    fn test_floats() {
287        test_parse_one_arg("1", 1f32);
288        test_parse_one_arg("1", 1f64);
289        test_parse_one_arg("-1", -1f32);
290        test_parse_one_arg("-1.0", -1f32);
291    }
292
293    #[test]
294    fn test_ints() {
295        test_parse_one_arg("1", 1u8);
296        test_parse_one_arg("-1", -1i128);
297    }
298
299    #[test]
300    fn test_bool() {
301        test_parse_one_arg("true", true);
302        test_parse_one_arg("false", false);
303    }
304
305    #[test]
306    fn test_char() {
307        test_parse_one_arg("'t'", 't');
308    }
309
310    #[test]
311    fn test_escape_char() {
312        test_parse_one_arg("'\\n'", '\n');
313    }
314
315    #[test]
316    fn test_heart() {
317        test_parse_one_arg("'\\u{2764}'", '❤');
318    }
319
320    #[test]
321    fn test_hex() {
322        test_parse_one_arg("'\\x41'", 'A');
323    }
324
325    #[test]
326    fn test_easy_string() {
327        test_parse_one_arg("\"test\"", String::from("test"));
328    }
329
330    #[test]
331    fn test_complex_string() {
332        test_parse_one_arg(
333            "\" test \\n '\\u{1f980} is \\u{2764}' \\r \\\"täst\\\" \\x41\"",
334            String::from(" test \n '🦀 is ❤' \r \"täst\" A"),
335        );
336    }
337
338    #[test]
339    fn test_string_with_comma() {
340        test_parse_one_arg("\"1, 2\"", String::from("1, 2"));
341    }
342
343    #[test]
344    fn test_parse_five_args() {
345        let result: (u8, u16, u32, u64, u128) = parse_5_args("", "1, 2, 3, 4, 5").unwrap();
346        assert_eq!(result, (1, 2, 3, 4, 5));
347    }
348
349    #[test]
350    fn test_find_separator() {
351        assert_eq!(find_next_separator_index("\",\", \",\""), Some(3));
352        assert_eq!(find_next_separator_index("',', ','"), Some(3));
353        assert_eq!(find_next_separator_index("4, 5"), Some(1));
354    }
355
356    #[test]
357    fn test_too_many_args() {
358        assert_eq!(
359            parse_2_args::<u32, u32>("test", "1, 2, 3, 4").unwrap_err(),
360            InteractiveError::WrongNumberOfArguments {
361                method_name: "test",
362                expected: 2,
363                found: 4
364            }
365        )
366    }
367
368    #[test]
369    fn test_too_few_args() {
370        assert_eq!(
371            parse_2_args::<u32, u32>("test", "1").unwrap_err(),
372            InteractiveError::WrongNumberOfArguments {
373                method_name: "test",
374                expected: 2,
375                found: 1
376            }
377        )
378    }
379}