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    parse_schema(&file_str)
499}
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504
505    #[test]
506    fn interface_events() {
507        use mproto_codegen::ast::PrimitiveType::*;
508        use mproto_codegen::ast::Type;
509
510        let data = "interface Foo<T, U, V, > @(server, client) { events @(server, foo) -> @(client, bar) { bar : u32, private baz : i8, t: T } }";
511        let (_, parsed) = interface_def(data).unwrap();
512
513        assert_eq!(
514            parsed,
515            Interface {
516                name: "Foo".into(),
517                type_params: vec!["T".to_string(), "U".to_string(), "V".to_string()],
518                roles: vec!["server".to_string(), "client".to_string()],
519                events: vec![
520                    InterfaceEventsList {
521                        from_roles: vec!["server".to_string(), "foo".to_string()],
522                        to_roles: vec!["client".to_string(), "bar".to_string()],
523                        events: vec![
524                            InterfaceEvent { name: "bar".into(), ty: Type::Primitive(U32), is_private: false },
525                            InterfaceEvent { name: "baz".into(), ty: Type::Primitive(I8), is_private: true },
526                            InterfaceEvent { name: "t".into(), ty: Type::local("T"), is_private: false },
527                        ],
528                    },
529                ],
530                objects: vec![],
531                config: vec![],
532                required_impls: vec![],
533                methods: vec![],
534                state: vec![],
535            }
536        );
537    }
538
539    #[test]
540    fn interface_objects() {
541        use mproto_codegen::ast::PrimitiveType;
542        use mproto_codegen::ast::Type;
543
544        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) } }";
545        let (_, parsed) = interface_def(data).unwrap();
546
547        assert_eq!(
548            parsed,
549            Interface {
550                name: "Foo".into(),
551                type_params: vec!["T".to_string(), "U".to_string(), "V".to_string()],
552                roles: vec!["Server".to_string(), "Client".to_string()],
553                events: vec![],
554                objects: vec![
555                    InterfaceObject {
556                        name: "foo_the_bar".to_string(),
557                        construct: QualifiedIdentifier::local("Request"),
558                        type_args: vec![
559                            Type::local("T"),
560                            Type::Primitive(PrimitiveType::Result(
561                                Box::new(Type::local("U")),
562                                Box::new(Type::local("V")),
563                            )),
564                        ],
565                        role_args: vec!["Server".to_string(), "Client".to_string()],
566                    },
567                    InterfaceObject {
568                        name: "bar_the_baz".to_string(),
569                        construct: QualifiedIdentifier::local("Request"),
570                        type_args: vec![
571                            Type::local("T"),
572                            Type::Primitive(PrimitiveType::Result(
573                                Box::new(Type::local("U")),
574                                Box::new(Type::local("V")),
575                            )),
576                        ],
577                        role_args: vec!["Client".to_string(), "Server".to_string()],
578                    },
579                ],
580                config: vec![],
581                required_impls: vec![],
582                methods: vec![],
583                state: vec![],
584            }
585        );
586    }
587
588    #[test]
589    fn interface_impls() {
590        use mproto_codegen::ast::Type;
591
592        let data = "interface Foo<T, U, V, > @(Server, Client) { impl @(Server) { request_handler: T -> U, foo_bar: async U -> V, } }";
593        let (_, parsed) = interface_def(data).unwrap();
594
595        assert_eq!(
596            parsed,
597            Interface {
598                name: "Foo".into(),
599                type_params: vec!["T".to_string(), "U".to_string(), "V".to_string()],
600                roles: vec!["Server".to_string(), "Client".to_string()],
601                events: vec![],
602                objects: vec![
603                ],
604                config: vec![],
605                required_impls: vec![
606                    InterfaceRequiredImplsList {
607                        roles: vec!["Server".to_string()],
608                        required_impls: vec![
609                            InterfaceRequiredImpl {
610                                name: "request_handler".into(),
611                                is_async: false,
612                                input_ty: Type::local("T"),
613                                output_ty: Type::local("U"),
614                            },
615                            InterfaceRequiredImpl {
616                                name: "foo_bar".into(),
617                                is_async: true,
618                                input_ty: Type::local("U"),
619                                output_ty: Type::local("V"),
620                            },
621                        ],
622                    },
623                ],
624                methods: vec![],
625                state: vec![],
626            }
627        );
628    }
629
630    #[test]
631    fn interface_methods() {
632        use mproto_codegen::ast::Type;
633
634        let data = "interface Foo<T, U, V, > @(Server, Client) { methods @(Server) { get_thing: T -> U, foo_bar: async U -> V, } }";
635        let (_, parsed) = interface_def(data).unwrap();
636
637        assert_eq!(
638            parsed,
639            Interface {
640                name: "Foo".into(),
641                type_params: vec!["T".to_string(), "U".to_string(), "V".to_string()],
642                roles: vec!["Server".to_string(), "Client".to_string()],
643                events: vec![],
644                objects: vec![
645                ],
646                config: vec![],
647                required_impls: vec![],
648                methods: vec![
649                    ObjectMethodsList {
650                        roles: vec!["Server".to_string()],
651                        methods: vec![
652                            ObjectMethod {
653                                name: "get_thing".into(),
654                                is_async: false,
655                                input_ty: Type::local("T"),
656                                output_ty: Type::local("U"),
657                            },
658                            ObjectMethod {
659                                name: "foo_bar".into(),
660                                is_async: true,
661                                input_ty: Type::local("U"),
662                                output_ty: Type::local("V"),
663                            },
664                        ],
665                    },
666                ],
667                state: vec![],
668            }
669        );
670    }
671
672    #[test]
673    fn interface_config() {
674        use mproto_codegen::ast::Type;
675
676        let data = "interface Foo<T, U, V, > @(Server, Client) { config @(Server) { t: T, u: U } }";
677        let (_, parsed) = interface_def(data).unwrap();
678
679        assert_eq!(
680            parsed,
681            Interface {
682                name: "Foo".into(),
683                type_params: vec!["T".to_string(), "U".to_string(), "V".to_string()],
684                roles: vec!["Server".to_string(), "Client".to_string()],
685                events: vec![],
686                objects: vec![
687                ],
688                config: vec![
689                    InterfaceConfigList {
690                        roles: vec!["Server".to_string()],
691                        items: vec![
692                            InterfaceConfigItem { name: "t".into(), ty: Type::local("T") },
693                            InterfaceConfigItem { name: "u".into(), ty: Type::local("U") },
694                        ],
695                    },
696                ],
697                required_impls: vec![],
698                methods: vec![],
699                state: vec![],
700            }
701        );
702    }
703}