webwire_cli/idl/
fieldset.rs

1use nom::{
2    bytes::complete::tag,
3    character::complete::char,
4    combinator::{cut, map, opt},
5    multi::separated_list0,
6    sequence::{pair, preceded, separated_pair, terminated},
7    IResult,
8};
9
10use crate::common::FilePosition;
11use crate::idl::common::{
12    parse_field_separator, parse_identifier, parse_identifier_with_generics, trailing_comma, ws,
13    ws1, Span,
14};
15use crate::idl::r#type::{parse_type_ref, TypeRef};
16
17#[cfg(test)]
18use crate::idl::common::assert_parse;
19
20#[derive(Clone, Debug, PartialEq)]
21pub struct Field {
22    pub name: String,
23    pub optional: bool,
24}
25
26#[derive(Debug, PartialEq)]
27pub struct Fieldset {
28    pub name: String,
29    pub generics: Vec<String>,
30    pub r#struct: TypeRef,
31    pub fields: Vec<Field>,
32    pub position: FilePosition,
33}
34
35fn parse_field(input: Span) -> IResult<Span, Field> {
36    map(
37        pair(preceded(ws, parse_identifier), preceded(ws, opt(char('?')))),
38        |(name, optional)| Field {
39            name,
40            optional: optional != None,
41        },
42    )(input)
43}
44
45fn parse_fields(input: Span) -> IResult<Span, Vec<Field>> {
46    preceded(
47        preceded(ws, char('{')),
48        cut(terminated(
49            separated_list0(parse_field_separator, parse_field),
50            preceded(trailing_comma, preceded(ws, char('}'))),
51        )),
52    )(input)
53}
54
55pub fn parse_fieldset(input: Span) -> IResult<Span, Fieldset> {
56    map(
57        preceded(
58            terminated(tag("fieldset"), ws1),
59            cut(pair(
60                separated_pair(
61                    preceded(ws, parse_identifier_with_generics),
62                    preceded(ws, tag("for")),
63                    preceded(ws1, parse_type_ref),
64                ),
65                parse_fields,
66            )),
67        ),
68        |(((name, generics), r#struct), fields)| Fieldset {
69            name,
70            generics,
71            r#struct,
72            fields,
73            position: input.into(),
74        },
75    )(input)
76}
77
78#[test]
79fn test_parse_fieldset_0() {
80    let contents = [
81        // minimal whitespace
82        "fieldset PersonName for Person{}",
83        // normal whitespace
84        "fieldset PersonName for Person {}",
85        // whitespace variants
86        "fieldset PersonName for Person { }",
87    ];
88    for content in contents.iter() {
89        assert_parse(
90            parse_fieldset(Span::new(content)),
91            Fieldset {
92                name: "PersonName".to_string(),
93                generics: vec![],
94                position: FilePosition { line: 1, column: 1 },
95                r#struct: TypeRef {
96                    abs: false,
97                    ns: vec![],
98                    name: "Person".to_string(),
99                    generics: vec![],
100                },
101                fields: vec![],
102            },
103        )
104    }
105}
106
107#[test]
108fn test_parse_fieldset_1() {
109    let contents = [
110        // minimal whitespace
111        "fieldset PersonName for Person{name}",
112        // whitespace variants
113        "fieldset PersonName for Person {name}",
114        "fieldset PersonName for Person{ name}",
115        "fieldset PersonName for Person{name }",
116    ];
117    for content in contents.iter() {
118        assert_parse(
119            parse_fieldset(Span::new(content)),
120            Fieldset {
121                name: "PersonName".to_string(),
122                generics: vec![],
123                position: FilePosition { line: 1, column: 1 },
124                r#struct: TypeRef {
125                    abs: false,
126                    ns: vec![],
127                    name: "Person".to_string(),
128                    generics: vec![],
129                },
130                fields: vec![Field {
131                    name: "name".to_string(),
132                    optional: false,
133                }],
134            },
135        )
136    }
137}
138
139#[test]
140fn test_parse_fieldset_2() {
141    let contents = [
142        // minimal whitespace
143        "fieldset PersonName for Person{name,age?}",
144        // normal whitespace
145        "fieldset PersonName for Person { name, age? }",
146        // whitespace variants
147        "fieldset PersonName for Person {name,age?}",
148        "fieldset PersonName for Person{ name,age?}",
149        "fieldset PersonName for Person{name ,age?}",
150        "fieldset PersonName for Person{name, age?}",
151        "fieldset PersonName for Person{name,age ?}",
152        "fieldset PersonName for Person{name,age? }",
153    ];
154    for content in contents.iter() {
155        assert_parse(
156            parse_fieldset(Span::new(content)),
157            Fieldset {
158                name: "PersonName".to_string(),
159                generics: vec![],
160                position: FilePosition { line: 1, column: 1 },
161                r#struct: TypeRef {
162                    abs: false,
163                    ns: vec![],
164                    name: "Person".to_string(),
165                    generics: vec![],
166                },
167                fields: vec![
168                    Field {
169                        name: "name".to_string(),
170                        optional: false,
171                    },
172                    Field {
173                        name: "age".to_string(),
174                        optional: true,
175                    },
176                ],
177            },
178        )
179    }
180}