mproto_codegen/
parse.rs

1use nom::{
2    branch::alt,
3    bytes::complete::{tag, take_while1},
4    character::complete::{char, multispace0},
5    combinator::{cut, map, opt},
6    error::{context, ParseError},
7    multi::separated_list0,
8    sequence::{preceded, separated_pair, terminated},
9    IResult,
10};
11
12use crate::ast::{
13    Enum, EnumVariant, NamedField, PrimitiveType, QualifiedIdentifier, Struct, Type, TypeBody,
14    TypeDef,
15};
16
17fn identifier(i: &str) -> IResult<&str, &str> {
18    take_while1(|c: char| c.is_alphanumeric() || c == '_')(i)
19}
20
21fn qualified_identifier(i: &str) -> IResult<&str, QualifiedIdentifier> {
22    let (i, maybe_module): (_, Option<&str>) = opt(|i| {
23        let (i, module) = identifier(i)?;
24        let (i, _) = char('.')(i)?;
25        Ok((i, module))
26    })(i)?;
27
28    let (i, name) = identifier(i)?;
29
30    Ok((
31        i,
32        QualifiedIdentifier {
33            name: name.to_string(),
34            module: maybe_module.map(|x| x.to_string()),
35        },
36    ))
37}
38
39fn opt_trailing_comma<I, O, E: ParseError<I>>(
40    f: impl FnMut(I) -> IResult<I, O, E>,
41) -> impl FnMut(I) -> IResult<I, O, E>
42where
43    I: nom::Slice<std::ops::RangeFrom<usize>> + nom::InputIter + Clone,
44    <I as nom::InputIter>::Item: nom::AsChar,
45{
46    terminated(f, opt(char(',')))
47}
48
49fn box_ty(i: &str) -> IResult<&str, PrimitiveType> {
50    let (i, _) = tag("box")(i)?;
51    let (i, _) = multispace0(i)?;
52    let (i, _) = tag("<")(i)?;
53    let (i, _) = multispace0(i)?;
54    let (i, inner_ty) = ty(i)?;
55    let (i, _) = multispace0(i)?;
56    let (i, _) = tag(">")(i)?;
57
58    Ok((i, PrimitiveType::Box(inner_ty.into())))
59}
60
61fn list_ty(i: &str) -> IResult<&str, PrimitiveType> {
62    let (i, _) = char('[')(i)?;
63    let (i, _) = multispace0(i)?;
64    let (i, ty) = ty(i)?;
65    let (i, _) = multispace0(i)?;
66    let (i, _) = char(']')(i)?;
67
68    Ok((i, PrimitiveType::List(ty.into())))
69}
70
71fn option_ty(i: &str) -> IResult<&str, PrimitiveType> {
72    let (i, _) = tag("option")(i)?;
73    let (i, _) = multispace0(i)?;
74    let (i, _) = tag("<")(i)?;
75    let (i, _) = multispace0(i)?;
76    let (i, ty) = ty(i)?;
77    let (i, _) = multispace0(i)?;
78    let (i, _) = tag(">")(i)?;
79
80    Ok((i, PrimitiveType::Option(ty.into())))
81}
82
83fn result_ty(i: &str) -> IResult<&str, PrimitiveType> {
84    let (i, _) = tag("result")(i)?;
85    let (i, _) = multispace0(i)?;
86    let (i, _) = tag("<")(i)?;
87    let (i, ok_ty) = ty(i)?;
88    let (i, _) = multispace0(i)?;
89    let (i, _) = char(',')(i)?;
90    let (i, _) = multispace0(i)?;
91    let (i, err_ty) = ty(i)?;
92    let (i, _) = multispace0(i)?;
93    let (i, _) = opt(char(','))(i)?;
94    let (i, _) = multispace0(i)?;
95    let (i, _) = tag(">")(i)?;
96
97    Ok((i, PrimitiveType::Result(ok_ty.into(), err_ty.into())))
98}
99
100fn builtin_ty(i: &str) -> IResult<&str, PrimitiveType> {
101    alt((
102        map(tag("void"), |_| PrimitiveType::Void),
103        map(tag("u8"), |_| PrimitiveType::U8),
104        map(tag("u16"), |_| PrimitiveType::U16),
105        map(tag("u32"), |_| PrimitiveType::U32),
106        map(tag("u64"), |_| PrimitiveType::U64),
107        map(tag("u128"), |_| PrimitiveType::U128),
108        map(tag("i8"), |_| PrimitiveType::I8),
109        map(tag("i16"), |_| PrimitiveType::I16),
110        map(tag("i32"), |_| PrimitiveType::I32),
111        map(tag("i64"), |_| PrimitiveType::I64),
112        map(tag("i128"), |_| PrimitiveType::I128),
113        map(tag("f32"), |_| PrimitiveType::F32),
114        map(tag("bool"), |_| PrimitiveType::Bool),
115        map(tag("string"), |_| PrimitiveType::String),
116        box_ty,
117        list_ty,
118        option_ty,
119        result_ty,
120    ))(i)
121}
122
123pub fn type_params_list(i: &str) -> IResult<&str, Vec<String>> {
124    let (i, _) = tag("<")(i)?;
125    let (i, params) = separated_list0(
126        preceded(multispace0, char(',')),
127        preceded(multispace0, identifier),
128    )(i)?;
129    let (i, _) = multispace0(i)?;
130    let (i, _) = opt(char(','))(i)?;
131    let (i, _) = multispace0(i)?;
132    let (i, _) = tag(">")(i)?;
133
134    let params = params.into_iter().map(|p| p.into()).collect();
135
136    Ok((i, params))
137}
138
139pub fn type_args_list(i: &str) -> IResult<&str, Vec<Type>> {
140    let (i, _) = tag("<")(i)?;
141    let (i, args) =
142        separated_list0(preceded(multispace0, char(',')), preceded(multispace0, ty))(i)?;
143    let (i, _) = multispace0(i)?;
144    let (i, _) = opt(char(','))(i)?;
145    let (i, _) = multispace0(i)?;
146    let (i, _) = tag(">")(i)?;
147
148    let args = args.into_iter().map(|p| p.into()).collect();
149
150    Ok((i, args))
151}
152
153pub fn struct_def(i: &str) -> IResult<&str, TypeDef> {
154    let (i, _) = tag("struct")(i)?;
155    let (i, _) = multispace0(i)?;
156    let (i, name) = identifier(i)?;
157    let (i, _) = multispace0(i)?;
158    let (i, maybe_params) = opt(type_params_list)(i)?;
159    let (i, _) = multispace0(i)?;
160    let (i, fields) = named_fields(i)?;
161
162    let params = maybe_params.unwrap_or(Vec::new());
163
164    let type_def = TypeDef {
165        name: name.into(),
166        params,
167        body: TypeBody::Struct(Struct { fields }),
168    };
169
170    Ok((i, type_def))
171}
172
173fn enum_def<'a>(i: &'a str) -> IResult<&'a str, TypeDef> {
174    let (i, _) = tag("enum")(i)?;
175    let (i, _) = multispace0(i)?;
176    let (i, name) = identifier(i)?;
177    let (i, _) = multispace0(i)?;
178    let (i, maybe_params) = opt(type_params_list)(i)?;
179    let (i, _) = multispace0(i)?;
180    let (i, variants) = enum_variants(i)?;
181
182    let params = maybe_params.unwrap_or(Vec::new());
183
184    let type_def = TypeDef {
185        name: name.into(),
186        params,
187        body: TypeBody::Enum(Enum { variants }),
188    };
189
190    Ok((i, type_def))
191}
192
193fn enum_variants<'a>(i: &'a str) -> IResult<&'a str, Vec<(String, EnumVariant)>> {
194    context(
195        "map",
196        preceded(
197            char('{'),
198            cut(terminated(
199                opt_trailing_comma(map(
200                    separated_list0(
201                        preceded(multispace0, char(',')),
202                        preceded(multispace0, enum_variant),
203                    ),
204                    |tuple_vec| {
205                        tuple_vec
206                            .into_iter()
207                            .map(|(name, variant)| (name.into(), variant))
208                            .collect()
209                    },
210                )),
211                preceded(multispace0, char('}')),
212            )),
213        ),
214    )(i)
215}
216
217fn enum_variant<'a>(i: &'a str) -> IResult<&'a str, (&'a str, EnumVariant)> {
218    alt((
219        separated_pair(
220            identifier,
221            multispace0,
222            map(named_fields, |fields| EnumVariant::NamedFields { fields }),
223        ),
224        map(identifier, |x| (x, EnumVariant::Empty)),
225    ))(i)
226}
227
228pub fn defined_ty<'a>(i: &'a str) -> IResult<&'a str, Type> {
229    let (i, ident) = qualified_identifier(i)?;
230    let (i, _) = multispace0(i)?;
231    let (i, maybe_args) = opt(type_args_list)(i)?;
232
233    let args = maybe_args.unwrap_or(Vec::new());
234    let defined_type = Type::Defined { ident, args };
235
236    Ok((i, defined_type))
237}
238
239pub fn ty<'a>(i: &'a str) -> IResult<&'a str, Type> {
240    alt((map(builtin_ty, |x| Type::Primitive(x)), defined_ty))(i)
241}
242
243pub fn type_def<'a>(i: &'a str) -> IResult<&'a str, TypeDef> {
244    alt((struct_def, enum_def))(i)
245}
246
247fn named_field<'a>(i: &'a str) -> IResult<&'a str, (&'a str, Type)> {
248    separated_pair(
249        identifier,
250        cut(preceded(multispace0, char(':'))),
251        preceded(multispace0, ty),
252    )(i)
253}
254
255fn named_fields<'a>(i: &'a str) -> IResult<&'a str, Vec<NamedField>> {
256    context(
257        "map",
258        preceded(
259            char('{'),
260            cut(terminated(
261                opt_trailing_comma(map(
262                    separated_list0(
263                        preceded(multispace0, char(',')),
264                        preceded(multispace0, named_field),
265                    ),
266                    |tuple_vec| {
267                        tuple_vec
268                            .into_iter()
269                            .map(|(k, v)| NamedField {
270                                name: k.to_owned(),
271                                ty: v,
272                            })
273                            .collect()
274                    },
275                )),
276                preceded(multispace0, char('}')),
277            )),
278        ),
279    )(i)
280}
281
282pub fn root<'a>(i: &'a str) -> IResult<&'a str, Vec<TypeDef>> {
283    separated_list0(multispace0, type_def)(i)
284}
285
286pub fn parse_file(path: impl AsRef<std::path::Path>) -> Result<Vec<TypeDef>, String> {
287    use std::io::Read;
288
289    // Open input file
290    let Ok(mut file) = std::fs::File::open(path.as_ref()) else {
291        return Err(format!("Failed to open file '{}'", path.as_ref().display()));
292    };
293
294    // Load file to string
295    let mut file_str = String::new();
296    if let Err(e) = file.read_to_string(&mut file_str) {
297        return Err(format!(
298            "Failed to read file '{}': {}",
299            path.as_ref().display(),
300            e
301        ));
302    }
303
304    let (_, type_defs) = root(&file_str).map_err(|e| format!("mproto schema parse error: {e}"))?;
305
306    Ok(type_defs)
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    #[test]
314    fn test_builtin_u8() {
315        let data = "u8";
316        let (_, parsed) = builtin_ty(data).unwrap();
317
318        assert_eq!(parsed, PrimitiveType::U8);
319    }
320
321    #[test]
322    fn test_list_u8() {
323        let data = "[u8]";
324        let (_, parsed) = list_ty(data).unwrap();
325
326        assert_eq!(
327            parsed,
328            PrimitiveType::List(Box::new(Type::Primitive(PrimitiveType::U8))),
329        );
330    }
331
332    #[test]
333    fn test_box_u8() {
334        let data = "box<u8>";
335        let (_, parsed) = box_ty(data).unwrap();
336
337        assert_eq!(
338            parsed,
339            PrimitiveType::Box(Box::new(Type::Primitive(PrimitiveType::U8))),
340        );
341    }
342
343    #[test]
344    fn test_option_u8() {
345        let data = "option<u8>";
346        let (_, parsed) = option_ty(data).unwrap();
347
348        assert_eq!(
349            parsed,
350            PrimitiveType::Option(Box::new(Type::Primitive(PrimitiveType::U8))),
351        );
352    }
353
354    #[test]
355    fn test_result() {
356        let data = "result<void, string>";
357        let (_, parsed) = result_ty(data).unwrap();
358
359        assert_eq!(
360            parsed,
361            PrimitiveType::Result(
362                Box::new(Type::Primitive(PrimitiveType::Void)),
363                Box::new(Type::Primitive(PrimitiveType::String)),
364            ),
365        );
366    }
367
368    #[test]
369    fn test_struct_named_fields() {
370        use PrimitiveType::*;
371
372        let data = "struct Foo { bar : u32, baz : i8 }";
373        let (_, parsed) = struct_def(data).unwrap();
374
375        assert_eq!(
376            parsed,
377            TypeDef {
378                name: "Foo".into(),
379                params: vec![],
380                body: TypeBody::Struct(Struct {
381                    fields: vec![
382                        NamedField {
383                            name: "bar".into(),
384                            ty: Type::Primitive(U32)
385                        },
386                        NamedField {
387                            name: "baz".into(),
388                            ty: Type::Primitive(I8)
389                        },
390                    ]
391                }),
392            }
393        );
394    }
395
396    #[test]
397    fn test_struct_type_param() {
398        use PrimitiveType::*;
399
400        let data = "struct Foo <T, F>  { bar : u32, baz : i8 }";
401        let (_, parsed) = struct_def(data).unwrap();
402
403        assert_eq!(
404            parsed,
405            TypeDef {
406                name: "Foo".into(),
407                params: vec!["T".into(), "F".into()],
408                body: TypeBody::Struct(Struct {
409                    fields: vec![
410                        NamedField {
411                            name: "bar".into(),
412                            ty: Type::Primitive(U32)
413                        },
414                        NamedField {
415                            name: "baz".into(),
416                            ty: Type::Primitive(I8)
417                        },
418                    ]
419                }),
420            }
421        );
422    }
423
424    #[test]
425    fn test_defined_type() {
426        use PrimitiveType::*;
427
428        let data = "struct Foo { bar : bar_proto.Bar, baz : i8 }";
429        let (_, parsed) = struct_def(data).unwrap();
430
431        assert_eq!(
432            parsed,
433            TypeDef {
434                name: "Foo".into(),
435                params: vec![],
436                body: TypeBody::Struct(Struct {
437                    fields: vec![
438                        NamedField {
439                            name: "bar".into(),
440                            ty: Type::Defined {
441                                ident: QualifiedIdentifier {
442                                    module: Some("bar_proto".into()),
443                                    name: "Bar".into(),
444                                },
445                                args: vec![],
446                            },
447                        },
448                        NamedField {
449                            name: "baz".into(),
450                            ty: Type::Primitive(I8)
451                        },
452                    ]
453                }),
454            }
455        );
456    }
457
458    #[test]
459    fn test_enum_named_fields() {
460        use PrimitiveType::*;
461
462        let data = "enum Foo { Bar { x: u32, y: u8 }, Baz { bip: i8 } }";
463        let (_, parsed) = enum_def(data).unwrap();
464
465        assert_eq!(
466            parsed,
467            TypeDef {
468                name: "Foo".into(),
469                params: vec![],
470                body: TypeBody::Enum(Enum {
471                    variants: vec![
472                        (
473                            "Bar".into(),
474                            EnumVariant::NamedFields {
475                                fields: vec![
476                                    NamedField {
477                                        name: "x".into(),
478                                        ty: Type::Primitive(U32)
479                                    },
480                                    NamedField {
481                                        name: "y".into(),
482                                        ty: Type::Primitive(U8)
483                                    },
484                                ],
485                            }
486                        ),
487                        (
488                            "Baz".into(),
489                            EnumVariant::NamedFields {
490                                fields: vec![NamedField {
491                                    name: "bip".into(),
492                                    ty: Type::Primitive(I8)
493                                },],
494                            }
495                        ),
496                    ]
497                }),
498            }
499        );
500    }
501}