webwire_cli/idl/
common.rs

1use nom::{
2    bytes::complete::{take_while, take_while1},
3    character::complete::char,
4    combinator::{cut, map, opt},
5    multi::separated_list0,
6    sequence::{pair, preceded, terminated},
7    IResult,
8};
9use nom_locate::LocatedSpan;
10
11pub type Span<'a> = LocatedSpan<&'a str>;
12
13const WHITSPACE: &str = " \t\r\n";
14const ALPHA_EXTRA: &str = "_";
15
16pub fn ws(input: Span) -> IResult<Span, Span> {
17    take_while(move |c| WHITSPACE.contains(c))(input)
18}
19
20pub fn ws1(input: Span) -> IResult<Span, Span> {
21    take_while1(move |c| WHITSPACE.contains(c))(input)
22}
23
24pub fn trailing_comma(input: Span) -> IResult<Span, Option<char>> {
25    opt(preceded(ws, char(',')))(input)
26}
27
28pub fn parse_identifier(input: Span) -> IResult<Span, String> {
29    map(
30        pair(
31            take_while1(move |c: char| c.is_ascii_alphabetic()),
32            take_while(move |c: char| c.is_ascii_alphanumeric() || ALPHA_EXTRA.contains(c)),
33        ),
34        |t| format!("{}{}", t.0, t.1),
35    )(input)
36}
37
38fn parse_generics(input: Span) -> IResult<Span, Vec<String>> {
39    map(
40        opt(preceded(
41            preceded(ws, char('<')),
42            cut(terminated(
43                separated_list0(parse_field_separator, preceded(ws, parse_identifier)),
44                preceded(trailing_comma, preceded(ws, char('>'))),
45            )),
46        )),
47        |v| match v {
48            Some(v) => v,
49            None => Vec::with_capacity(0),
50        },
51    )(input)
52}
53
54pub fn parse_identifier_with_generics(input: Span) -> IResult<Span, (String, Vec<String>)> {
55    pair(parse_identifier, parse_generics)(input)
56}
57
58#[cfg(test)]
59pub(crate) fn assert_parse<'a, T: std::fmt::Debug + PartialEq>(
60    output: IResult<LocatedSpan<&'a str>, T>,
61    expected_value: T,
62) {
63    assert!(output.is_ok(), "{:?}", output);
64    let output = output.unwrap();
65    assert_eq!(output.0.fragment(), &"");
66    assert_eq!(output.1, expected_value);
67}
68
69#[test]
70fn test_parse_identifier() {
71    assert_parse(parse_identifier(Span::new("test")), "test".to_string());
72    assert_parse(
73        parse_identifier(Span::new("test123")),
74        "test123".to_string(),
75    );
76}
77
78#[test]
79fn test_parse_identifier_invalid() {
80    use nom::error::ErrorKind;
81    assert_eq!(
82        parse_identifier(Span::new("123test")),
83        Err(nom::Err::Error(nom::error::Error {
84            input: Span::new("123test"),
85            code: ErrorKind::TakeWhile1
86        }))
87    );
88    assert_eq!(
89        parse_identifier(Span::new("_test")),
90        Err(nom::Err::Error(nom::error::Error {
91            input: Span::new("_test"),
92            code: ErrorKind::TakeWhile1
93        }))
94    );
95}
96
97pub fn parse_field_separator(input: Span) -> IResult<Span, char> {
98    preceded(ws, char(','))(input)
99}