Skip to main content

modrpc_codegen/
parse.rs

1use nom::{
2  branch::alt,
3  bytes::complete::{tag, take_while1},
4  character::complete::{char, multispace0},
5  combinator::{map, opt, cut},
6  error::ParseError,
7  multi::separated_list0,
8  sequence::{preceded, terminated},
9  IResult,
10};
11
12use mproto_codegen;
13use mproto_codegen::ast::TypeDef;
14
15use crate::ast::{
16    Import,
17    Interface,
18    InterfaceEvent,
19    InterfaceEventsList,
20    InterfaceObject,
21    InterfaceConfigItem,
22    InterfaceConfigList,
23    InterfaceRequiredImpl,
24    InterfaceRequiredImplsList,
25    InterfaceState,
26    ObjectMethodsList,
27    ObjectMethod,
28    QualifiedIdentifier,
29    Schema,
30};
31
32pub enum SchemaItem {
33    Import(Import),
34    Interface(Interface),
35    TypeDef(TypeDef),
36}
37
38enum InterfaceItem {
39    Events(InterfaceEventsList),
40    State(Vec<InterfaceState>),
41    Objects(Vec<InterfaceObject>),
42    ConfigList(InterfaceConfigList),
43    RequiredImpls(InterfaceRequiredImplsList),
44    ObjectMethods(ObjectMethodsList),
45}
46
47fn import(i: &str) -> IResult<&str, Import> {
48    let (i, _) = tag("import")(i)?;
49    let (i, _) = multispace0(i)?;
50    let (i, name) = identifier(i)?;
51    let (i, _) = multispace0(i)?;
52    let (i, _) = char('"')(i)?;
53    let (i, path) = take_while1(|x: char| x != '"')(i)?;
54    let (i, _) = char('"')(i)?;
55
56    Ok((i, Import {
57        name: name.to_string(),
58        path: path.to_string(),
59    }))
60}
61
62fn interface_def(i: &str) -> IResult<&str, Interface> {
63    let (i, _) = tag("interface")(i)?;
64    let (i, _) = multispace0(i)?;
65    let (i, name) = identifier(i)?;
66    let (i, _) = multispace0(i)?;
67    let (i, type_params) = opt(mproto_codegen::parse::type_params_list)(i)?;
68    let (i, _) = multispace0(i)?;
69    let (i, roles) = roles_list(i)?;
70    let (i, _) = multispace0(i)?;
71    let (i, _) = char('{')(i)?;
72    let (i, _) = multispace0(i)?;
73
74    let (i, items) = separated_list0(multispace0, interface_item)(i)?;
75
76    let (i, _) = multispace0(i)?;
77    let (i, _) = char('}')(i)?;
78
79    let mut events = vec![];
80    let mut state = vec![];
81    let mut objects = vec![];
82    let mut config = vec![];
83    let mut required_impls = vec![];
84    let mut methods = vec![];
85    for item in items {
86        match item {
87            InterfaceItem::Events(item_events) => {
88                events.push(item_events);
89            }
90            InterfaceItem::State(item_state_fields) => {
91                state.extend(item_state_fields);
92            }
93            InterfaceItem::Objects(item_objects) => {
94                objects.extend(item_objects);
95            }
96            InterfaceItem::ConfigList(item_config_list) => {
97                config.push(item_config_list);
98            }
99            InterfaceItem::RequiredImpls(item_required_impls) => {
100                required_impls.push(item_required_impls);
101            }
102            InterfaceItem::ObjectMethods(item_object_methods) => {
103                methods.push(item_object_methods);
104            }
105        }
106    }
107
108    let interface = Interface {
109        name: name.into(),
110        type_params: type_params.unwrap_or_else(|| Vec::new()),
111        roles,
112        events,
113        objects,
114        config,
115        required_impls,
116        methods,
117        state,
118    };
119
120    Ok((i, interface))
121}
122
123fn interface_item<'a>(i: &'a str) -> IResult<&'a str, InterfaceItem> {
124    alt((
125        map(interface_events_list, |events| InterfaceItem::Events(events)),
126        map(interface_state_list, |state_fields| InterfaceItem::State(state_fields)),
127        map(interface_objects, |objects| InterfaceItem::Objects(objects)),
128        map(interface_config_list, |config_list| InterfaceItem::ConfigList(config_list)),
129        map(interface_required_impls_list, |required_impls| InterfaceItem::RequiredImpls(required_impls)),
130        map(object_methods_list, |object_methods| InterfaceItem::ObjectMethods(object_methods)),
131    ))(i)
132}
133
134fn opt_trailing_comma<I, O, E: ParseError<I>>(
135    f: impl FnMut(I) -> IResult<I, O, E>,
136) -> impl FnMut(I) -> IResult<I, O, E>
137    where
138        I: nom::Slice<std::ops::RangeFrom<usize>> + nom::InputIter + Clone,
139        <I as nom::InputIter>::Item: nom::AsChar,
140{
141    terminated(f, opt(char(',')))
142}
143
144fn interface_events_list<'a>(i: &'a str) -> IResult<&'a str, InterfaceEventsList> {
145    let (i, _) = tag("events")(i)?;
146    let (i, _) = multispace0(i)?;
147    let (i, from_roles) = roles_list(i)?;
148    let (i, _) = multispace0(i)?;
149    let (i, _) = tag("->")(i)?;
150    let (i, _) = multispace0(i)?;
151    let (i, to_roles) = roles_list(i)?;
152    let (i, _) = multispace0(i)?;
153    let (i, _) = char('{')(i)?;
154    let (i, _) = multispace0(i)?;
155
156    let (i, events) = opt_trailing_comma(
157        separated_list0(
158            preceded(multispace0, char(',')),
159            preceded(multispace0, interface_event)
160        ),
161    )(i)?;
162
163    let (i, _) = multispace0(i)?;
164    let (i, _) = char('}')(i)?;
165
166    let interface_events_list = InterfaceEventsList {
167        from_roles,
168        to_roles,
169        events,
170    };
171
172    Ok((i, interface_events_list))
173}
174
175fn interface_event<'a>(i: &'a str) -> IResult<&'a str, InterfaceEvent> {
176    let (i, private) = opt(preceded(tag("private"), multispace0))(i)?;
177    let (i, name) = identifier(i)?;
178    let (i, _) = multispace0(i)?;
179    let (i, _) = tag(":")(i)?;
180    let (i, _) = multispace0(i)?;
181    let (i, ty) = mproto_codegen::parse::ty(i)?;
182
183    let interface_event = InterfaceEvent {
184        name: name.to_string(),
185        ty,
186        is_private: private.is_some(),
187    };
188
189    Ok((i, interface_event))
190}
191
192fn roles_list(i: &str) -> IResult<&str, Vec<String>> {
193    let (i, _) = char('@')(i)?;
194    let (i, _) = char('(')(i)?;
195    let (i, roles) = separated_list0(
196        preceded(multispace0, char(',')),
197        preceded(multispace0, identifier),
198    )(i)?;
199    let (i, _) = multispace0(i)?;
200    let (i, _) = opt(char(','))(i)?;
201    let (i, _) = multispace0(i)?;
202    let (i, _) = char(')')(i)?;
203
204    let roles = roles.into_iter().map(|p| p.into()).collect();
205
206    Ok((i, roles))
207}
208
209fn interface_object(i: &str) -> IResult<&str, InterfaceObject> {
210    let (i, name) = identifier(i)?;
211    let (i, _) = multispace0(i)?;
212    let (i, _) = tag(":")(i)?;
213    let (i, _) = multispace0(i)?;
214    let (i, construct) = qualified_identifier(i)?;
215    let (i, _) = multispace0(i)?;
216    let (i, type_args) = opt(mproto_codegen::parse::type_args_list)(i)?;
217    let (i, _) = multispace0(i)?;
218    let (i, role_args) = roles_list(i)?;
219
220    let interface_object = InterfaceObject {
221        name: name.to_string(),
222        construct,
223        type_args: type_args.unwrap_or_else(|| Vec::new()),
224        role_args,
225    };
226
227    Ok((i, interface_object))
228}
229
230fn interface_objects<'a>(i: &'a str) -> IResult<&'a str, Vec<InterfaceObject>> {
231    preceded(
232        tag("objects"),
233        preceded(
234            preceded(multispace0, char('{')),
235            cut(terminated(
236                opt_trailing_comma(separated_list0(
237                    preceded(multispace0, char(',')),
238                    preceded(multispace0, interface_object),
239                )),
240                preceded(multispace0, char('}')),
241            ))
242        ),
243    )(i)
244}
245
246fn interface_state_list<'a>(i: &'a str) -> IResult<&'a str, Vec<InterfaceState>> {
247    let (i, _) = tag("state")(i)?;
248    let (i, _) = multispace0(i)?;
249    let (i, _) = char('{')(i)?;
250    let (i, _) = multispace0(i)?;
251
252    let (i, state_fields) = opt_trailing_comma(
253        separated_list0(
254            preceded(multispace0, char(',')),
255            preceded(multispace0, interface_state),
256        ),
257    )(i)?;
258
259    let (i, _) = multispace0(i)?;
260    let (i, _) = char('}')(i)?;
261
262    Ok((i, state_fields))
263}
264
265fn interface_state<'a>(i: &'a str) -> IResult<&'a str, InterfaceState> {
266    let (i, name) = identifier(i)?;
267    let (i, _) = multispace0(i)?;
268    let (i, _) = tag(":")(i)?;
269    let (i, _) = multispace0(i)?;
270    let (i, ty) = mproto_codegen::parse::ty(i)?;
271
272    Ok((i, InterfaceState { name: name.to_string(), ty }))
273}
274
275fn interface_config_list<'a>(i: &'a str) -> IResult<&'a str, InterfaceConfigList> {
276    let (i, _) = tag("config")(i)?;
277    let (i, _) = multispace0(i)?;
278    let (i, roles) = roles_list(i)?;
279    let (i, _) = multispace0(i)?;
280    let (i, _) = char('{')(i)?;
281    let (i, _) = multispace0(i)?;
282
283    let (i, items) = opt_trailing_comma(
284        separated_list0(
285            preceded(multispace0, char(',')),
286            preceded(multispace0, interface_config_item)
287        ),
288    )(i)?;
289
290    let (i, _) = multispace0(i)?;
291    let (i, _) = char('}')(i)?;
292
293    let interface_config_list = InterfaceConfigList {
294        roles,
295        items,
296    };
297
298    Ok((i, interface_config_list))
299}
300
301fn interface_config_item<'a>(i: &'a str) -> IResult<&'a str, InterfaceConfigItem> {
302    let (i, name) = identifier(i)?;
303    let (i, _) = multispace0(i)?;
304    let (i, _) = tag(":")(i)?;
305    let (i, _) = multispace0(i)?;
306    let (i, ty) = mproto_codegen::parse::ty(i)?;
307
308    Ok((i, InterfaceConfigItem { name: name.to_string(), ty }))
309}
310
311fn interface_required_impls_list<'a>(i: &'a str) -> IResult<&'a str, InterfaceRequiredImplsList> {
312    let (i, _) = tag("impl")(i)?;
313    let (i, _) = multispace0(i)?;
314    let (i, roles) = roles_list(i)?;
315    let (i, _) = multispace0(i)?;
316    let (i, _) = char('{')(i)?;
317    let (i, _) = multispace0(i)?;
318
319    let (i, required_impls) = opt_trailing_comma(
320        separated_list0(
321            preceded(multispace0, char(',')),
322            preceded(multispace0, interface_required_impl)
323        ),
324    )(i)?;
325
326    let (i, _) = multispace0(i)?;
327    let (i, _) = char('}')(i)?;
328
329    let interface_required_impls_list = InterfaceRequiredImplsList {
330        roles,
331        required_impls,
332    };
333
334    Ok((i, interface_required_impls_list))
335}
336
337fn interface_required_impl<'a>(i: &'a str) -> IResult<&'a str, InterfaceRequiredImpl> {
338    let (i, fn_decl) = fn_decl(i)?;
339
340    let interface_required_impl = InterfaceRequiredImpl {
341        name: fn_decl.name,
342        is_async: fn_decl.is_async,
343        input_ty: fn_decl.input_ty,
344        output_ty: fn_decl.output_ty,
345    };
346
347    Ok((i, interface_required_impl))
348}
349
350fn object_methods_list<'a>(i: &'a str) -> IResult<&'a str, ObjectMethodsList> {
351    let (i, _) = tag("methods")(i)?;
352    let (i, _) = multispace0(i)?;
353    let (i, roles) = roles_list(i)?;
354    let (i, _) = multispace0(i)?;
355    let (i, _) = char('{')(i)?;
356    let (i, _) = multispace0(i)?;
357
358    let (i, methods) = opt_trailing_comma(
359        separated_list0(
360            preceded(multispace0, char(',')),
361            preceded(multispace0, object_method)
362        ),
363    )(i)?;
364
365    let (i, _) = multispace0(i)?;
366    let (i, _) = char('}')(i)?;
367
368    let object_methods_list = ObjectMethodsList {
369        roles,
370        methods,
371    };
372
373    Ok((i, object_methods_list))
374}
375
376fn object_method<'a>(i: &'a str) -> IResult<&'a str, ObjectMethod> {
377    let (i, fn_decl) = fn_decl(i)?;
378
379    let object_method = ObjectMethod {
380        name: fn_decl.name,
381        is_async: fn_decl.is_async,
382        input_ty: fn_decl.input_ty,
383        output_ty: fn_decl.output_ty,
384    };
385
386    Ok((i, object_method))
387}
388
389struct FnDecl {
390    pub name: String,
391    pub is_async: bool,
392    pub input_ty: mproto_codegen::ast::Type,
393    pub output_ty: mproto_codegen::ast::Type,
394}
395
396fn fn_decl<'a>(i: &'a str) -> IResult<&'a str, FnDecl> {
397    let (i, name) = identifier(i)?;
398    let (i, _) = multispace0(i)?;
399    let (i, _) = tag(":")(i)?;
400    let (i, _) = multispace0(i)?;
401
402    let (i, maybe_async): (_, Option<&str>) = opt(|i| {
403        let (i, async_tag) = tag("async")(i)?;
404        let (i, _) = multispace0(i)?;
405        Ok((i, async_tag))
406    })(i)?;
407    let is_async = maybe_async.is_some();
408
409    let (i, input_ty) = mproto_codegen::parse::ty(i)?;
410    let (i, _) = multispace0(i)?;
411    let (i, _) = tag("->")(i)?;
412    let (i, _) = multispace0(i)?;
413    let (i, output_ty) = mproto_codegen::parse::ty(i)?;
414
415    let fn_decl = FnDecl {
416        name: name.to_string(),
417        is_async,
418        input_ty,
419        output_ty,
420    };
421
422    Ok((i, fn_decl))
423}
424
425pub fn root<'a>(i: &'a str) -> IResult<&'a str, Vec<SchemaItem>> {
426    separated_list0(
427        multispace0,
428        alt((
429            map(import, |i| SchemaItem::Import(i)),
430            map(interface_def, |i| SchemaItem::Interface(i)),
431            map(mproto_codegen::parse::type_def, |t| SchemaItem::TypeDef(t)),
432        )),
433    )(i)
434}
435
436fn identifier(i: &str) -> IResult<&str, &str> {
437    take_while1(
438        |x: char| x.is_alphanumeric() || x.is_digit(10) || x == '_'
439    )(i)
440}
441
442fn qualified_identifier(i: &str) -> IResult<&str, QualifiedIdentifier> {
443    let (i, maybe_module): (_, Option<&str>) = opt(|i| {
444        let (i, module) = identifier(i)?;
445        let (i, _) = char('.')(i)?;
446        Ok((i, module))
447    })(i)?;
448
449    let (i, name) = identifier(i)?;
450
451    Ok((i, QualifiedIdentifier {
452        name: name.to_string(),
453        module: maybe_module.map(|x| x.to_string()),
454    }))
455}
456
457// TODO Proper error type
458pub fn parse_schema(i: &str) -> Result<Schema, String> {
459    let (_, schema_items) = root(i).map_err(|e| e.to_string())?;
460
461    let mut schema = Schema {
462        imports: Vec::new(),
463        interfaces: Vec::new(),
464        type_defs: Vec::new(),
465    };
466
467    for item in schema_items {
468        match item {
469            SchemaItem::Import(i) => {
470                schema.imports.push(i);
471            }
472            SchemaItem::Interface(i) => {
473                schema.interfaces.push(i);
474            }
475            SchemaItem::TypeDef(t) => {
476                schema.type_defs.push(t);
477            }
478        }
479    }
480
481    Ok(schema)
482}
483
484pub fn parse_file(path: impl AsRef<std::path::Path>) -> Result<Schema, String> {
485    use std::io::Read;
486
487    // Open input file
488    let Ok(mut file) = std::fs::File::open(path.as_ref()) else {
489        return Err(format!("Failed to open file '{}'", path.as_ref().display()));
490    };
491
492    // Load file to string
493    let mut file_str = String::new();
494    if let Err(e) = file.read_to_string(&mut file_str) {
495        return Err(format!("Failed to read file '{}': {e}", path.as_ref().display()));
496    }
497
498    // Remove comments
499    let mut schema_str = file_str.lines()
500        .map(|line| {
501            if let Some(index) = line.find("//") {
502                // Return the slice from the start of the line up to the comment marker
503                &line[..index]
504            } else {
505                // If no comment is found, return the entire line
506                line
507            }
508        })
509        .collect::<Vec<&str>>()
510        .join("\n");
511    schema_str += "\n";
512
513    parse_schema(&schema_str)
514}
515
516#[cfg(test)]
517mod tests {
518    use super::*;
519
520    #[test]
521    fn interface_events() {
522        use mproto_codegen::ast::PrimitiveType::*;
523        use mproto_codegen::ast::Type;
524
525        let data = "interface Foo<T, U, V, > @(server, client) { events @(server, foo) -> @(client, bar) { bar : u32, private baz : i8, t: T } }";
526        let (_, parsed) = interface_def(data).unwrap();
527
528        assert_eq!(
529            parsed,
530            Interface {
531                name: "Foo".into(),
532                type_params: vec!["T".to_string(), "U".to_string(), "V".to_string()],
533                roles: vec!["server".to_string(), "client".to_string()],
534                events: vec![
535                    InterfaceEventsList {
536                        from_roles: vec!["server".to_string(), "foo".to_string()],
537                        to_roles: vec!["client".to_string(), "bar".to_string()],
538                        events: vec![
539                            InterfaceEvent { name: "bar".into(), ty: Type::Primitive(U32), is_private: false },
540                            InterfaceEvent { name: "baz".into(), ty: Type::Primitive(I8), is_private: true },
541                            InterfaceEvent { name: "t".into(), ty: Type::local("T"), is_private: false },
542                        ],
543                    },
544                ],
545                objects: vec![],
546                config: vec![],
547                required_impls: vec![],
548                methods: vec![],
549                state: vec![],
550            }
551        );
552    }
553
554    #[test]
555    fn interface_objects() {
556        use mproto_codegen::ast::PrimitiveType;
557        use mproto_codegen::ast::Type;
558
559        let data = "interface Foo<T, U, V, > @(Server, Client) { objects { foo_the_bar: Request<T, result<U, V>> @(Server, Client), bar_the_baz: Request<T, result<U, V>> @(Client, Server) } }";
560        let (_, parsed) = interface_def(data).unwrap();
561
562        assert_eq!(
563            parsed,
564            Interface {
565                name: "Foo".into(),
566                type_params: vec!["T".to_string(), "U".to_string(), "V".to_string()],
567                roles: vec!["Server".to_string(), "Client".to_string()],
568                events: vec![],
569                objects: vec![
570                    InterfaceObject {
571                        name: "foo_the_bar".to_string(),
572                        construct: QualifiedIdentifier::local("Request"),
573                        type_args: vec![
574                            Type::local("T"),
575                            Type::Primitive(PrimitiveType::Result(
576                                Box::new(Type::local("U")),
577                                Box::new(Type::local("V")),
578                            )),
579                        ],
580                        role_args: vec!["Server".to_string(), "Client".to_string()],
581                    },
582                    InterfaceObject {
583                        name: "bar_the_baz".to_string(),
584                        construct: QualifiedIdentifier::local("Request"),
585                        type_args: vec![
586                            Type::local("T"),
587                            Type::Primitive(PrimitiveType::Result(
588                                Box::new(Type::local("U")),
589                                Box::new(Type::local("V")),
590                            )),
591                        ],
592                        role_args: vec!["Client".to_string(), "Server".to_string()],
593                    },
594                ],
595                config: vec![],
596                required_impls: vec![],
597                methods: vec![],
598                state: vec![],
599            }
600        );
601    }
602
603    #[test]
604    fn interface_impls() {
605        use mproto_codegen::ast::Type;
606
607        let data = "interface Foo<T, U, V, > @(Server, Client) { impl @(Server) { request_handler: T -> U, foo_bar: async U -> V, } }";
608        let (_, parsed) = interface_def(data).unwrap();
609
610        assert_eq!(
611            parsed,
612            Interface {
613                name: "Foo".into(),
614                type_params: vec!["T".to_string(), "U".to_string(), "V".to_string()],
615                roles: vec!["Server".to_string(), "Client".to_string()],
616                events: vec![],
617                objects: vec![
618                ],
619                config: vec![],
620                required_impls: vec![
621                    InterfaceRequiredImplsList {
622                        roles: vec!["Server".to_string()],
623                        required_impls: vec![
624                            InterfaceRequiredImpl {
625                                name: "request_handler".into(),
626                                is_async: false,
627                                input_ty: Type::local("T"),
628                                output_ty: Type::local("U"),
629                            },
630                            InterfaceRequiredImpl {
631                                name: "foo_bar".into(),
632                                is_async: true,
633                                input_ty: Type::local("U"),
634                                output_ty: Type::local("V"),
635                            },
636                        ],
637                    },
638                ],
639                methods: vec![],
640                state: vec![],
641            }
642        );
643    }
644
645    #[test]
646    fn interface_methods() {
647        use mproto_codegen::ast::Type;
648
649        let data = "interface Foo<T, U, V, > @(Server, Client) { methods @(Server) { get_thing: T -> U, foo_bar: async U -> V, } }";
650        let (_, parsed) = interface_def(data).unwrap();
651
652        assert_eq!(
653            parsed,
654            Interface {
655                name: "Foo".into(),
656                type_params: vec!["T".to_string(), "U".to_string(), "V".to_string()],
657                roles: vec!["Server".to_string(), "Client".to_string()],
658                events: vec![],
659                objects: vec![
660                ],
661                config: vec![],
662                required_impls: vec![],
663                methods: vec![
664                    ObjectMethodsList {
665                        roles: vec!["Server".to_string()],
666                        methods: vec![
667                            ObjectMethod {
668                                name: "get_thing".into(),
669                                is_async: false,
670                                input_ty: Type::local("T"),
671                                output_ty: Type::local("U"),
672                            },
673                            ObjectMethod {
674                                name: "foo_bar".into(),
675                                is_async: true,
676                                input_ty: Type::local("U"),
677                                output_ty: Type::local("V"),
678                            },
679                        ],
680                    },
681                ],
682                state: vec![],
683            }
684        );
685    }
686
687    #[test]
688    fn interface_config() {
689        use mproto_codegen::ast::Type;
690
691        let data = "interface Foo<T, U, V, > @(Server, Client) { config @(Server) { t: T, u: U } }";
692        let (_, parsed) = interface_def(data).unwrap();
693
694        assert_eq!(
695            parsed,
696            Interface {
697                name: "Foo".into(),
698                type_params: vec!["T".to_string(), "U".to_string(), "V".to_string()],
699                roles: vec!["Server".to_string(), "Client".to_string()],
700                events: vec![],
701                objects: vec![
702                ],
703                config: vec![
704                    InterfaceConfigList {
705                        roles: vec!["Server".to_string()],
706                        items: vec![
707                            InterfaceConfigItem { name: "t".into(), ty: Type::local("T") },
708                            InterfaceConfigItem { name: "u".into(), ty: Type::local("U") },
709                        ],
710                    },
711                ],
712                required_impls: vec![],
713                methods: vec![],
714                state: vec![],
715            }
716        );
717    }
718}