webwire_cli/idl/
enum.rs

1use nom::{
2    bytes::complete::tag,
3    character::complete::char,
4    combinator::{cut, map, opt},
5    error::context,
6    multi::separated_list0,
7    sequence::{pair, preceded, terminated, tuple},
8    IResult,
9};
10
11use crate::common::FilePosition;
12use crate::idl::common::{
13    parse_field_separator, parse_identifier, parse_identifier_with_generics, trailing_comma, ws,
14    ws1, Span,
15};
16use crate::idl::r#type::{parse_type, Type};
17
18#[cfg(test)]
19use crate::idl::common::assert_parse;
20
21use super::{r#type::parse_type_ref, TypeRef};
22
23#[derive(Debug, PartialEq)]
24pub struct Enum {
25    pub name: String,
26    pub generics: Vec<String>,
27    pub extends: Option<TypeRef>,
28    pub variants: Vec<EnumVariant>,
29    pub position: FilePosition,
30}
31
32#[derive(Debug, PartialEq)]
33pub struct EnumVariant {
34    pub name: String,
35    pub value_type: Option<Type>,
36}
37
38pub fn parse_enum(input: Span) -> IResult<Span, Enum> {
39    map(
40        tuple((
41            preceded(terminated(tag("enum"), ws1), parse_identifier_with_generics),
42            parse_enum_extends,
43            parse_enum_variants,
44        )),
45        |((name, generics), extends, variants)| Enum {
46            name,
47            generics,
48            extends,
49            variants,
50            position: input.into(),
51        },
52    )(input)
53}
54
55fn parse_enum_extends(input: Span) -> IResult<Span, Option<TypeRef>> {
56    context(
57        "enum_extends",
58        opt(preceded(
59            terminated(preceded(ws1, tag("extends")), ws1),
60            parse_type_ref,
61        )),
62    )(input)
63}
64
65fn parse_enum_variants(input: Span) -> IResult<Span, Vec<EnumVariant>> {
66    context(
67        "enum_variants",
68        preceded(
69            preceded(ws, char('{')),
70            cut(terminated(
71                separated_list0(parse_field_separator, preceded(ws, parse_enum_variant)),
72                preceded(trailing_comma, preceded(ws, char('}'))),
73            )),
74        ),
75    )(input)
76}
77
78fn parse_enum_variant(input: Span) -> IResult<Span, EnumVariant> {
79    context(
80        "enum_variant",
81        map(
82            pair(
83                parse_identifier,
84                opt(preceded(
85                    preceded(ws, char('(')),
86                    cut(terminated(
87                        parse_type,
88                        preceded(trailing_comma, preceded(ws, char(')'))),
89                    )),
90                )),
91            ),
92            |(name, value_type)| EnumVariant { name, value_type },
93        ),
94    )(input)
95}
96
97#[test]
98fn test_parse_enum_0() {
99    let contents = [
100        // minimal whitespace
101        "enum Nothing{}",
102        // normal whitespace
103        "enum Nothing {}",
104        // whitespace variants
105        "enum Nothing { }",
106    ];
107    for content in contents.iter() {
108        assert_parse(
109            parse_enum(Span::new(content)),
110            Enum {
111                name: "Nothing".to_string(),
112                generics: vec![],
113                position: FilePosition { line: 1, column: 1 },
114                extends: None,
115                variants: vec![],
116            },
117        )
118    }
119}
120
121#[test]
122fn test_parse_enum_1() {
123    let contents = [
124        // minimal whitespace
125        "enum OneThing{Thing}",
126        // whitespace variants
127        "enum OneThing {Thing}",
128        "enum OneThing{ Thing}",
129        "enum OneThing{Thing }",
130        "enum OneThing { Thing }",
131    ];
132    for content in contents.iter() {
133        assert_parse(
134            parse_enum(Span::new(content)),
135            Enum {
136                name: "OneThing".to_string(),
137                generics: vec![],
138                position: FilePosition { line: 1, column: 1 },
139                extends: None,
140                variants: vec![EnumVariant {
141                    name: "Thing".to_string(),
142                    value_type: None,
143                }],
144            },
145        )
146    }
147}
148
149#[test]
150fn test_parse_enum_2() {
151    let contents = [
152        // minimal whitespace
153        "enum Direction{Left,Right}",
154        // normal whitespace
155        "enum Direction { Left, Right }",
156        // whitespace variants
157        "enum Direction {Left,Right}",
158        "enum Direction{ Left,Right}",
159        "enum Direction{Left ,Right}",
160        "enum Direction{Left, Right}",
161        "enum Direction{Left,Right }",
162    ];
163    for content in contents.iter() {
164        assert_parse(
165            parse_enum(Span::new(content)),
166            Enum {
167                name: "Direction".to_string(),
168                generics: vec![],
169                position: FilePosition { line: 1, column: 1 },
170                extends: None,
171                variants: vec![
172                    EnumVariant {
173                        name: "Left".to_string(),
174                        value_type: None,
175                    },
176                    EnumVariant {
177                        name: "Right".to_string(),
178                        value_type: None,
179                    },
180                ],
181            },
182        )
183    }
184}
185
186#[test]
187fn test_parse_enum_with_value() {
188    use crate::idl::r#type::TypeRef;
189    let contents = [
190        // minimal whitespace
191        "enum Value{S(String),I(Integer)}",
192        // normal whitespace
193        "enum Value { S(String), I(Integer) }",
194        // whitespace variants
195        "enum Value {S(String),I(Integer)}",
196        "enum Value{ S(String),I(Integer)}",
197        "enum Value{S (String),I(Integer)}",
198        "enum Value{S( String),I(Integer)}",
199        "enum Value{S(String ),I(Integer)}",
200        "enum Value{S(String) ,I(Integer)}",
201        "enum Value{S(String), I(Integer)}",
202        "enum Value{S(String),I (Integer)}",
203        "enum Value{S(String),I( Integer)}",
204        "enum Value{S(String),I(Integer )}",
205        "enum Value{S(String),I(Integer) }",
206    ];
207    for content in contents.iter() {
208        assert_parse(
209            parse_enum(Span::new(content)),
210            Enum {
211                name: "Value".to_string(),
212                generics: vec![],
213                position: FilePosition { line: 1, column: 1 },
214                extends: None,
215                variants: vec![
216                    EnumVariant {
217                        name: "S".to_string(),
218                        value_type: Some(Type::Ref(TypeRef {
219                            abs: false,
220                            ns: vec![],
221                            name: "String".to_string(),
222                            generics: vec![],
223                        })),
224                    },
225                    EnumVariant {
226                        name: "I".to_string(),
227                        value_type: Some(Type::Ref(TypeRef {
228                            abs: false,
229                            ns: vec![],
230                            name: "Integer".to_string(),
231                            generics: vec![],
232                        })),
233                    },
234                ],
235            },
236        )
237    }
238}
239
240#[test]
241fn test_parse_enum_extends() {
242    use crate::idl::r#type::TypeRef;
243    let contents = [
244        // minimal whitespace
245        "enum GetError extends GenericError{}",
246        // normal whitespace
247        "enum GetError extends GenericError {}",
248        // whitespace variants
249        "enum GetError extends GenericError{ }",
250    ];
251    for content in contents.iter() {
252        assert_parse(
253            parse_enum(Span::new(content)),
254            Enum {
255                name: "GetError".to_string(),
256                generics: vec![],
257                position: FilePosition { line: 1, column: 1 },
258                extends: Some(TypeRef {
259                    abs: false,
260                    ns: vec![],
261                    name: "GenericError".to_string(),
262                    generics: vec![],
263                }),
264                variants: vec![],
265            },
266        )
267    }
268}