modrpc_codegen/
parse.rs

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