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
456pub 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 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 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}