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