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 let mut schema_str = file_str.lines()
500 .map(|line| {
501 if let Some(index) = line.find("//") {
502 &line[..index]
504 } else {
505 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}