1mod calls;
48mod codec;
49mod enhanced;
50mod enhanced_codec;
51
52use crate::adapter::cpp::{
53 CppMapping, NATIVE_NAMESPACE, cpp_mapping, cpp_primitive, cpp_value_type, module_names,
54 nest_by_module, nest_by_module_placed,
55};
56use crate::adapter::cpp_psm::psm_type;
57use crate::adapter::interface::{
58 self, BindingError, Operation, Origin, Parameter, RETURN_MEMBER, Service, ServiceMapping,
59};
60use zerodds_ast::{Ast, ModuleNode, ParamDirection, PrimitiveKind, TypeKind, TypeNode};
61use zerodds_ast_core::Id;
62use zerodds_lang_cst::common::{Doc, Ident, Path};
63use zerodds_lang_cst::cpp;
64
65const RPC: &str = "dds::rpc";
69
70const MAX_ALIAS_DEPTH: usize = 64;
73
74pub(super) struct Sink {
84 items: Vec<(Id<ModuleNode>, bool, cpp::Decl)>,
85 unseen: Vec<(Id<ModuleNode>, String)>,
88}
89
90impl Sink {
91 fn new() -> Self {
92 Self {
93 items: Vec::new(),
94 unseen: Vec::new(),
95 }
96 }
97
98 pub(super) fn push_topic_unseen(&mut self, item: (Id<ModuleNode>, cpp::Decl)) {
102 if let Some(topic) = topic_name(&item.1) {
103 self.unseen.push((item.0, topic));
104 }
105 self.items.push((item.0, true, item.1));
106 }
107
108 pub(super) fn push(&mut self, item: (Id<ModuleNode>, cpp::Decl)) {
110 self.items.push((item.0, false, item.1));
111 }
112
113 pub(super) fn push_topic(&mut self, item: (Id<ModuleNode>, cpp::Decl)) {
115 self.items.push((item.0, true, item.1));
116 }
117
118 fn nest(self, ast: &Ast) -> Result<Vec<cpp::Decl>, BindingError> {
120 if !is_psm() {
121 let plain = self
122 .items
123 .into_iter()
124 .map(|(module, _, decl)| (module, decl))
125 .collect();
126 return nest_by_module(ast, plain).map_err(BindingError::from);
127 }
128 let mut placed = Vec::with_capacity(self.items.len());
129 let unseen = self.unseen;
130 for (module, native, decl) in self.items {
131 let using = if native {
132 topic_name(&decl)
133 .filter(|topic| {
134 !unseen
135 .iter()
136 .any(|(at, name)| *at == module && name == topic)
137 })
138 .map(|topic| {
139 let scope = module_names(ast, module)?;
140 let mut path = format!("::{NATIVE_NAMESPACE}");
141 for segment in scope {
142 path.push_str("::");
143 path.push_str(&segment);
144 }
145 Ok::<_, crate::adapter::cst::MaterialiseError>(cpp::Decl::Verbatim(
146 format!("using {path}::{topic};"),
147 ))
148 })
149 .transpose()?
150 } else {
151 None
152 };
153 placed.push((module, native, decl));
154 if let Some(using) = using {
155 placed.push((module, false, using));
156 }
157 }
158 nest_by_module_placed(ast, placed).map_err(BindingError::from)
159 }
160}
161
162fn topic_name(decl: &cpp::Decl) -> Option<String> {
164 if let cpp::Decl::Class(class) = decl {
165 return Some(class.name.as_str().to_string());
166 }
167 if let cpp::Decl::Variable(variable) = decl {
168 return Some(variable.name.as_str().to_string());
169 }
170 None
171}
172
173pub fn cpp_interface_items(ast: &Ast) -> Result<Vec<cpp::Decl>, BindingError> {
182 cpp_interface_items_with(ast, ServiceMapping::Basic)
183}
184
185pub fn remote_member_types(ast: &Ast) -> Result<Vec<Id<TypeNode>>, BindingError> {
193 let remote: Vec<Service> = interface::services(ast)?
194 .into_iter()
195 .filter(Service::is_remote)
196 .collect();
197 codec::member_types(ast, &remote)
198}
199
200pub fn cpp_interface_items_with(
212 ast: &Ast,
213 mapping: ServiceMapping,
214) -> Result<Vec<cpp::Decl>, BindingError> {
215 let mut out = Sink::new();
216 let services = interface::services(ast)?;
217 let root = ast.root_module();
218 if services.iter().any(Service::is_remote) {
219 out.push((
223 root,
224 cpp::Decl::Verbatim("#include <dds/rpc.hpp>".to_string()),
225 ));
226 let remote: Vec<Service> = services
227 .iter()
228 .filter(|service| service.is_remote())
229 .cloned()
230 .collect();
231 for decl in codec::member_codecs(ast, &remote)? {
232 out.push((root, decl));
233 }
234 if !mapping.is_enhanced() {
238 let classic = codec::member_codecs_xcdr1(ast, &remote)?;
239 if !classic.is_empty() {
240 out.push((root, codec::in_xcdr1_namespace(classic)));
241 }
242 }
243 }
244 let mut declared_hashes: Vec<(Id<ModuleNode>, String)> = Vec::new();
247 let mut declared_exceptions = Vec::new();
248 for service in services {
249 if mapping.is_enhanced() {
250 enhanced::declare(
251 ast,
252 &Service { mapping, ..service },
253 &mut out,
254 &mut declared_exceptions,
255 )?;
256 } else {
257 declare(ast, &service, &mut out, &mut declared_hashes)?;
258 }
259 }
260 out.nest(ast)
261}
262
263pub fn cpp_service_items(ast: &Ast, service: &Service) -> Result<Vec<cpp::Decl>, BindingError> {
268 let mut out = Sink::new();
269 declare(ast, service, &mut out, &mut Vec::new())?;
270 out.nest(ast)
271}
272
273fn declare(
274 ast: &Ast,
275 service: &Service,
276 out: &mut Sink,
277 declared_hashes: &mut Vec<(Id<ModuleNode>, String)>,
278) -> Result<(), BindingError> {
279 let module = service.module;
280 let name = cxx_escape(&service.name);
281 let operations = service.all_operations();
282
283 for operation in &operations {
287 out.push_topic((
288 module,
289 cpp::Decl::Class(struct_of(
290 &interface::in_struct_name(&name, &cxx_escape(&operation.name)),
291 request_fields(ast, service, operation)?,
292 Doc::line("The `in` and `inout` parameters of one call. DDS-RPC 1.0 §7.5.1.1.4."),
293 )),
294 ));
295 let out_struct = (
296 module,
297 cpp::Decl::Class(struct_of(
298 &interface::out_struct_name(&name, &cxx_escape(&operation.name)),
299 reply_fields(ast, service, operation)?,
300 Doc::line(
301 "The return value and the `out` and `inout` parameters of one call. DDS-RPC 1.0 §7.5.1.1.5.",
302 ),
303 )),
304 );
305 if is_psm() && operation.has_outputs() {
306 out.push_topic_unseen(out_struct);
311 out.push((
312 module,
313 cpp::Decl::Class(struct_of(
314 &interface::out_struct_name(&name, &cxx_escape(&operation.name)),
315 reply_fields_for_application(ast, service, operation)?,
316 Doc::line("The return value and the `out` and `inout` parameters of one call, as the application sees them. DDS-RPC 1.0 §7.5.1.1.5 and §7.11.1.1.2 rule 7.i."),
317 )),
318 ));
319 } else {
320 out.push_topic(out_struct);
321 }
322 }
323
324 for operation in &operations {
329 let operation_name = cxx_escape(&operation.name);
330 out.push_topic((
331 module,
332 hash_constant(
333 &interface::operation_hash_constant(&name, &operation_name),
334 interface::operation_hash(&operation.name),
335 "The case label of one operation. DDS-RPC 1.0 §7.5.1.1.6 rules 4 and 5a.",
336 ),
337 ));
338 }
339 for operation in &operations {
340 for raised in &operation.raises {
341 let key = (module, raised.scoped_name.clone());
342 if declared_hashes.contains(&key) {
343 continue;
344 }
345 declared_hashes.push(key);
346 out.push_topic((
347 module,
348 hash_constant(
349 &interface::exception_hash_constant(&cxx_escape(&raised.name)),
350 interface::exception_hash(&raised.scoped_name),
351 "The case label of one raised exception. DDS-RPC 1.0 §7.5.1.1.5 rule 4.",
352 ),
353 ));
354 }
355 }
356
357 for operation in &operations {
360 out.push_topic((
361 module,
362 cpp::Decl::Class(result_union_of(service, &name, operation)),
363 ));
364 }
365
366 out.push_topic((
367 module,
368 cpp::Decl::Class(union_of(
369 &interface::call_union_name(&name),
370 &operations,
371 &name,
372 false,
373 Doc::line("The request union over every operation. DDS-RPC 1.0 §7.5.1.1.6."),
374 )),
375 ));
376 out.push_topic((
377 module,
378 cpp::Decl::Class(union_of(
379 &interface::return_union_name(&name),
380 &operations,
381 &name,
382 true,
383 Doc::line("The reply union. DDS-RPC 1.0 §7.5.1.1.7."),
384 )),
385 ));
386
387 let request = interface::request_type_name(&name);
388 let reply = interface::reply_type_name(&name);
389 out.push_topic((
390 module,
391 cpp::Decl::Class(struct_of(
392 &request,
393 vec![
394 field(qualified(RPC, "RequestHeader"), "header"),
395 field(
396 cpp::Type::named(Path::single(&interface::call_union_name(&name))),
397 interface::DATA_MEMBER,
398 ),
399 ],
400 Doc::line("The request topic type. DDS-RPC 1.0 §7.5.1.1.6."),
401 )),
402 ));
403 out.push_topic((
404 module,
405 cpp::Decl::Class(struct_of(
406 &reply,
407 vec![
408 field(qualified(RPC, "ReplyHeader"), "header"),
409 field(
410 cpp::Type::named(Path::single(&interface::return_union_name(&name))),
411 interface::DATA_MEMBER,
412 ),
413 ],
414 Doc::line("The reply topic type. DDS-RPC 1.0 §7.5.1.1.7."),
415 )),
416 ));
417 if service.is_remote() {
418 let root = ast.root_module();
421 let synthesized = codec::synthesized_codecs(ast, service, &name, &operations)?;
422 for decl in synthesized.iter().cloned() {
423 out.push((root, decl));
424 }
425 out.push((root, codec::in_xcdr1_namespace(synthesized)));
426 out.push((root, codec::traits(ast, service, &name)?));
427 }
428 out.push((module, cpp::Decl::Class(support_class(service, &name))));
429
430 out.push((
433 module,
434 cpp::Decl::Forward {
435 is_struct: false,
436 name: Ident::new(&format!("{name}Async")),
437 },
438 ));
439 out.push((
440 module,
441 cpp::Decl::Class(sync_interface(ast, service, &name, &operations)?),
442 ));
443 out.push((
444 module,
445 cpp::Decl::Class(async_interface(ast, service, &name, &operations)?),
446 ));
447
448 if service.is_remote() {
449 out.push((module, requester_alias(&name, &request, &reply)));
450 out.push((module, replier_alias(&name, &request, &reply)));
451 out.push((
452 module,
453 cpp::Decl::Class(calls::client_class(
454 ast,
455 service,
456 &name,
457 &operations,
458 calls::Shape::Basic,
459 )?),
460 ));
461 out.push((
462 module,
463 cpp::Decl::Class(calls::service_class(
464 ast,
465 service,
466 &name,
467 &operations,
468 calls::Shape::Basic,
469 )?),
470 ));
471 }
472 Ok(())
473}
474
475fn request_fields(
478 ast: &Ast,
479 service: &Service,
480 operation: &Operation,
481) -> Result<Vec<cpp::ClassMember>, BindingError> {
482 let mut out = Vec::new();
483 for param in operation.inputs() {
484 out.push(field(
485 cpp_value_type(ast, param.type_, &at(service, operation, ¶m.name))?,
486 &cxx_escape(¶m.name),
487 ));
488 }
489 if out.is_empty() {
494 out.push(field(
495 qualified(RPC, "UnusedMember"),
496 interface::DUMMY_MEMBER,
497 ));
498 }
499 Ok(out)
500}
501
502fn reply_fields_for_application(
505 ast: &Ast,
506 service: &Service,
507 operation: &Operation,
508) -> Result<Vec<cpp::ClassMember>, BindingError> {
509 let mut out = Vec::new();
510 for param in operation.outputs() {
511 out.push(field(
512 user_type(ast, param.type_, &at(service, operation, ¶m.name))?,
513 &cxx_escape(¶m.name),
514 ));
515 }
516 if let Some(returns) = operation.returns {
517 let taken: Vec<String> = operation
518 .outputs()
519 .map(|param| cxx_escape(¶m.name))
520 .collect();
521 out.push(field(
522 user_type(ast, returns, &at(service, operation, "return"))?,
523 &return_member_name(&taken),
524 ));
525 }
526 Ok(out)
527}
528
529pub(super) fn native_out_type(
532 ast: &Ast,
533 module: Id<ModuleNode>,
534 name: &str,
535 operation: &Operation,
536) -> Result<cpp::Type, BindingError> {
537 let out = interface::out_struct_name(name, &cxx_escape(&operation.name));
538 if !is_psm() {
539 return Ok(cpp::Type::named(Path::single(&out)));
540 }
541 let mut path = format!("::{NATIVE_NAMESPACE}");
542 for segment in module_names(ast, module).map_err(BindingError::from)? {
543 path.push_str("::");
544 path.push_str(&segment);
545 }
546 path.push_str("::");
547 path.push_str(&out);
548 Ok(cpp::Type::named(Path::single(&path)))
549}
550
551fn reply_fields(
552 ast: &Ast,
553 service: &Service,
554 operation: &Operation,
555) -> Result<Vec<cpp::ClassMember>, BindingError> {
556 let mut out = Vec::new();
560 for param in operation.outputs() {
561 out.push(field(
562 cpp_value_type(ast, param.type_, &at(service, operation, ¶m.name))?,
563 &cxx_escape(¶m.name),
564 ));
565 }
566 if let Some(returns) = operation.returns {
567 let taken: Vec<String> = operation
568 .outputs()
569 .map(|param| cxx_escape(¶m.name))
570 .collect();
571 out.push(field(
572 cpp_value_type(ast, returns, &at(service, operation, "return"))?,
573 &return_member_name(&taken),
574 ));
575 }
576 if out.is_empty() {
578 out.push(field(
579 qualified(RPC, "UnusedMember"),
580 interface::DUMMY_MEMBER,
581 ));
582 }
583 Ok(out)
584}
585
586fn return_member_name(taken: &[String]) -> String {
593 if !taken.iter().any(|name| name == RETURN_MEMBER) {
594 return RETURN_MEMBER.to_string();
595 }
596 for n in 1..=i32::MAX {
597 let candidate = format!("{RETURN_MEMBER}{n}");
598 if !taken.iter().any(|name| name == &candidate) {
599 return candidate;
600 }
601 }
602 format!("{RETURN_MEMBER} ")
605}
606
607fn union_of(
616 name: &str,
617 operations: &[Operation],
618 service: &str,
619 reply: bool,
620 doc: Doc,
621) -> cpp::Class {
622 let mut members = vec![
623 discriminator_field(),
624 case_field(
631 qualified(RPC, "UnknownOperation"),
632 interface::UNKNOWN_OP_MEMBER,
633 Doc::line("Live on the `default` case. DDS-RPC 1.0 §7.5.1.1.6 rule 3."),
634 ),
635 ];
636 for operation in operations {
637 let operation_name = cxx_escape(&operation.name);
638 let case = if reply {
639 interface::result_union_name(service, &operation_name)
640 } else {
641 interface::in_struct_name(service, &operation_name)
642 };
643 members.push(case_field(
644 cpp::Type::named(Path::single(&case)),
645 &operation_name,
646 Doc::line(&format!(
647 "Live when `_d` is `{}`.",
648 interface::operation_hash_constant(service, &operation_name)
649 )),
650 ));
651 }
652 cpp::Class {
653 doc,
654 is_struct: true,
655 name: Ident::new(name),
656 bases: Vec::new(),
657 sections: vec![cpp::Section {
658 access: cpp::Access::Public,
659 members,
660 }],
661 }
662}
663
664fn discriminator_field() -> cpp::ClassMember {
671 cpp::ClassMember::Field(cpp::Field {
672 doc: Doc::line("Which case this sample carries. DDS-RPC 1.0 §7.5.1.1.6 rule 2."),
673 type_: cpp_primitive(PrimitiveKind::Int32),
674 name: Ident::new("_d"),
675 dims: Vec::new(),
676 default: None,
677 is_static: false,
678 is_constexpr: false,
679 bits: None,
680 })
681}
682
683fn case_field(type_: cpp::Type, name: &str, doc: Doc) -> cpp::ClassMember {
685 cpp::ClassMember::Field(cpp::Field {
686 doc,
687 type_,
688 name: Ident::new(name),
689 dims: Vec::new(),
690 default: None,
691 is_static: false,
692 is_constexpr: false,
693 bits: None,
694 })
695}
696
697fn hash_constant(name: &str, value: i32, doc: &str) -> cpp::Decl {
703 cpp::Decl::Variable(cpp::Variable {
704 doc: Doc::line(doc),
705 type_: cpp_primitive(PrimitiveKind::Int32),
706 name: Ident::new(name),
707 value: Some(cpp::Expr::Literal(zerodds_lang_cst::common::Literal::Int(
708 value.to_string(),
709 ))),
710 is_static: false,
711 is_constexpr: true,
712 is_inline: true,
713 })
714}
715
716fn result_union_of(service: &Service, name: &str, operation: &Operation) -> cpp::Class {
722 let operation_name = cxx_escape(&operation.name);
723 let mut members = vec![
724 discriminator_field(),
725 case_field(
726 cpp::Type::named(Path::single(&interface::out_struct_name(
727 name,
728 &operation_name,
729 ))),
730 interface::RESULT_MEMBER,
731 Doc::line(&format!(
732 "Live when `_d` is `dds::RETCODE_OK` ({}). DDS-RPC 1.0 §7.5.1.1.5 rule 3a.",
733 interface::RETCODE_OK
734 )),
735 ),
736 ];
737 for raised in &operation.raises {
738 members.push(case_field(
739 cpp::Type::named(Path::single(&cxx_escape(&raised.name))),
740 &interface::exception_member_name(&raised.name),
743 Doc::line(&format!(
744 "Live when `_d` is `{}`. DDS-RPC 1.0 §7.5.1.1.5 rule 5.",
745 interface::exception_hash_constant(&cxx_escape(&raised.name))
746 )),
747 ));
748 }
749 let _ = service;
750 cpp::Class {
751 doc: Doc::line("The outcome of one operation. DDS-RPC 1.0 §7.5.1.1.5."),
752 is_struct: true,
753 name: Ident::new(&interface::result_union_name(name, &operation_name)),
754 bases: Vec::new(),
755 sections: vec![cpp::Section {
756 access: cpp::Access::Public,
757 members,
758 }],
759 }
760}
761
762fn support_class(service: &Service, name: &str) -> cpp::Class {
770 let style = interface::BindingStyle::FunctionCall;
776 let mut members = vec![
777 alias("RequestType", &interface::request_type_name(name)),
778 alias("ReplyType", &interface::reply_type_name(name)),
779 topic_name_method(
780 "request_topic_name",
781 &service.request_topic_name(style),
782 "The request topic's name. DDS-RPC 1.0 §7.4.1, §7.4.2.",
783 ),
784 topic_name_method(
785 "reply_topic_name",
786 &service.reply_topic_name(style),
787 "The reply topic's name. DDS-RPC 1.0 §7.4.1, §7.4.2.",
788 ),
789 topic_name_method(
790 "service_name",
791 &service.service_name,
792 "The `<service_name>` segment of §7.4.1, `\"Service\"` unless \
793 `@service(name = \"…\")` supplied one.",
794 ),
795 ];
796 members.push(calls::defaults_method(service));
797 for (at, alias_name) in service.topic_aliases(style, false).into_iter().enumerate() {
800 members.push(topic_name_method(
801 &format!("request_topic_alias_{at}"),
802 &alias_name,
803 "A request-topic alias from a parent interface. DDS-RPC 1.0 §7.4.3.",
804 ));
805 }
806 for (at, alias_name) in service.topic_aliases(style, true).into_iter().enumerate() {
807 members.push(topic_name_method(
808 &format!("reply_topic_alias_{at}"),
809 &alias_name,
810 "A reply-topic alias from a parent interface. DDS-RPC 1.0 §7.4.3.",
811 ));
812 }
813 cpp::Class {
814 doc: Doc::line("The service's type support: its topic types and their topic names."),
815 is_struct: true,
816 name: Ident::new(&format!("{name}Support")),
817 bases: Vec::new(),
818 sections: vec![cpp::Section {
819 access: cpp::Access::Public,
820 members,
821 }],
822 }
823}
824
825fn topic_name_method(method: &str, topic: &str, doc: &str) -> cpp::ClassMember {
826 cpp::ClassMember::Method(cpp::Method {
827 doc: Doc::line(doc),
828 returns: cpp::Type::Pointer(Box::new(cpp::Type::Const(Box::new(cpp::Type::Primitive(
829 cpp::Primitive::Char,
830 ))))),
831 name: Ident::new(method),
832 params: Vec::new(),
833 is_const: false,
834 is_static: true,
835 is_virtual: false,
836 is_pure: false,
837 is_constexpr: true,
838 is_noexcept: true,
839 is_override: false,
840 body: Some(cpp::Block {
841 statements: vec![cpp::Statement::Return(Some(cpp::Expr::Literal(
842 zerodds_lang_cst::common::Literal::Str(topic.to_string()),
843 )))],
844 }),
845 })
846}
847
848fn sync_interface(
852 ast: &Ast,
853 service: &Service,
854 name: &str,
855 operations: &[Operation],
856) -> Result<cpp::Class, BindingError> {
857 let mut members = vec![
858 alias("SupportType", &format!("{name}Support")),
859 alias("RequestType", &interface::request_type_name(name)),
860 alias("ReplyType", &interface::reply_type_name(name)),
861 alias("AsyncInterfaceType", &format!("{name}Async")),
863 cpp::ClassMember::Destructor(cpp::Destructor {
865 doc: Doc::line("Rule 2: a public virtual destructor."),
866 name: Ident::new(name),
867 is_virtual: true,
868 body: cpp::FunctionBody::Defaulted,
869 }),
870 ];
871 let _ = operations;
872 for operation in &service.operations {
873 members.push(cpp::ClassMember::Method(sync_method(
874 ast, service, operation,
875 )?));
876 }
877 for attribute in &service.attributes {
883 let at = format!("{}::{}", service.name, attribute.name);
884 let type_ = user_type(ast, attribute.type_, &at)?;
885 let spelled = cxx_escape(&attribute.name);
886 if is_native(ast, attribute.type_) {
887 members.push(cpp::ClassMember::Method(attribute_method(
889 &spelled,
890 type_.clone(),
891 Vec::new(),
892 false,
893 )));
894 if !attribute.readonly {
895 members.push(cpp::ClassMember::Method(attribute_method(
896 &spelled,
897 cpp::Type::Void,
898 vec![cpp::Param {
899 type_,
900 name: Some(Ident::new(&spelled)),
901 default: None,
902 }],
903 false,
904 )));
905 }
906 } else {
907 members.push(cpp::ClassMember::Method(attribute_method(
910 &spelled,
911 cpp::Type::Ref(Box::new(type_.clone())),
912 Vec::new(),
913 false,
914 )));
915 members.push(cpp::ClassMember::Method(attribute_method(
916 &spelled,
917 cpp::Type::Ref(Box::new(cpp::Type::Const(Box::new(type_.clone())))),
918 Vec::new(),
919 true,
920 )));
921 if !attribute.readonly {
922 members.push(cpp::ClassMember::Method(attribute_method(
923 &spelled,
924 cpp::Type::Void,
925 vec![cpp::Param {
926 type_: cpp::Type::Ref(Box::new(cpp::Type::Const(Box::new(type_)))),
927 name: Some(Ident::new(&spelled)),
928 default: None,
929 }],
930 false,
931 )));
932 }
933 }
934 }
935 Ok(cpp::Class {
936 doc: Doc::line(
937 "The service, as the abstract class DDS-RPC 1.0 §7.11.1.1.2 rule 1 requires.",
938 ),
939 is_struct: false,
940 name: Ident::new(name),
941 bases: bases(ast, service, "")?,
942 sections: vec![cpp::Section {
943 access: cpp::Access::Public,
944 members,
945 }],
946 })
947}
948
949fn sync_method(
951 ast: &Ast,
952 service: &Service,
953 operation: &Operation,
954) -> Result<cpp::Method, BindingError> {
955 let mut params = Vec::new();
956 let mut returns = cpp::Type::Void;
957
958 if let Some(target) = operation.returns {
962 let type_ = user_type(ast, target, &at(service, operation, "return"))?;
963 if is_native(ast, target) {
964 returns = type_;
965 } else {
966 params.push(cpp::Param {
967 type_: cpp::Type::Ref(Box::new(type_)),
968 name: Some(Ident::new("cxx_return")),
969 default: None,
970 });
971 }
972 }
973
974 for param in &operation.params {
977 params.push(cpp::Param {
978 type_: passed(ast, param, &at(service, operation, ¶m.name))?,
979 name: Some(Ident::new(&cxx_escape(¶m.name))),
980 default: None,
981 });
982 }
983
984 Ok(cpp::Method {
985 doc: doc_for(operation),
986 returns,
987 name: Ident::new(&cxx_escape(&operation.name)),
988 params,
989 is_const: false,
990 is_static: false,
991 is_virtual: true,
992 is_pure: true,
993 is_constexpr: false,
994 is_noexcept: false,
996 is_override: false,
997 body: None,
998 })
999}
1000
1001fn async_interface(
1003 ast: &Ast,
1004 service: &Service,
1005 name: &str,
1006 operations: &[Operation],
1007) -> Result<cpp::Class, BindingError> {
1008 let mut members = vec![
1009 alias("SupportType", &format!("{name}Support")),
1010 alias("RequestType", &interface::request_type_name(name)),
1011 alias("ReplyType", &interface::reply_type_name(name)),
1012 alias("InterfaceType", name),
1014 cpp::ClassMember::Destructor(cpp::Destructor {
1015 doc: Doc::line("Rule 2: a public virtual destructor."),
1016 name: Ident::new(&format!("{name}Async")),
1017 is_virtual: true,
1018 body: cpp::FunctionBody::Defaulted,
1019 }),
1020 ];
1021 let _ = operations;
1022 for operation in &service.operations {
1023 members.push(cpp::ClassMember::Method(async_method(
1024 ast, service, name, operation,
1025 )?));
1026 }
1027 for attribute in &service.attributes {
1033 let at = format!("{}::{}", service.name, attribute.name);
1034 let payload = user_type(ast, attribute.type_, &at)?;
1037 let type_ = user_type(ast, attribute.type_, &at)?;
1038 let spelled = cxx_escape(&attribute.name);
1039 members.push(cpp::ClassMember::Method(async_attribute_method(
1040 &spelled,
1041 future_of(payload),
1042 Vec::new(),
1043 )));
1044 if !attribute.readonly {
1045 let passed = if is_native(ast, attribute.type_) {
1046 cpp::Type::Const(Box::new(type_))
1047 } else {
1048 cpp::Type::Ref(Box::new(cpp::Type::Const(Box::new(type_))))
1049 };
1050 members.push(cpp::ClassMember::Method(async_attribute_method(
1051 &spelled,
1052 future_of(cpp::Type::Void),
1053 vec![cpp::Param {
1054 type_: passed,
1055 name: Some(Ident::new(&spelled)),
1056 default: None,
1057 }],
1058 )));
1059 }
1060 }
1061 let bases = bases(ast, service, "Async")?;
1062 Ok(cpp::Class {
1063 doc: Doc::line(
1064 "The service's asynchronous half, as DDS-RPC 1.0 §7.11.1.1.2 rule 1 requires.",
1065 ),
1066 is_struct: false,
1067 name: Ident::new(&format!("{name}Async")),
1068 bases,
1069 sections: vec![cpp::Section {
1070 access: cpp::Access::Public,
1071 members,
1072 }],
1073 })
1074}
1075
1076fn async_payload(
1083 ast: &Ast,
1084 service: &Service,
1085 name: &str,
1086 operation: &Operation,
1087) -> Result<cpp::Type, BindingError> {
1088 Ok(if operation.has_outputs() {
1089 cpp::Type::named(Path::single(&interface::out_struct_name(
1090 name,
1091 &cxx_escape(&operation.name),
1092 )))
1093 } else if let Some(target) = operation.returns {
1094 user_type(ast, target, &at(service, operation, "return"))?
1097 } else {
1098 cpp::Type::Void
1099 })
1100}
1101
1102fn async_method(
1104 ast: &Ast,
1105 service: &Service,
1106 name: &str,
1107 operation: &Operation,
1108) -> Result<cpp::Method, BindingError> {
1109 let payload = async_payload(ast, service, name, operation)?;
1110
1111 let mut params = Vec::new();
1114 for param in operation.inputs() {
1115 let type_ = user_type(ast, param.type_, &at(service, operation, ¶m.name))?;
1116 params.push(cpp::Param {
1117 type_: if is_native(ast, param.type_) {
1118 cpp::Type::Const(Box::new(type_))
1119 } else {
1120 cpp::Type::Ref(Box::new(cpp::Type::Const(Box::new(type_))))
1121 },
1122 name: Some(Ident::new(&cxx_escape(¶m.name))),
1123 default: None,
1124 });
1125 }
1126
1127 Ok(cpp::Method {
1128 doc: doc_for(operation),
1129 returns: future_of(payload),
1130 name: Ident::new(&format!("{}_async", cxx_escape(&operation.name))),
1132 params,
1133 is_const: false,
1134 is_static: false,
1135 is_virtual: true,
1136 is_pure: true,
1137 is_constexpr: false,
1138 is_noexcept: false,
1139 is_override: false,
1140 body: None,
1141 })
1142}
1143
1144fn requester_alias(name: &str, request: &str, reply: &str) -> cpp::Decl {
1156 endpoint_alias(&format!("{name}Requester"), "Requester", request, reply)
1157}
1158
1159fn replier_alias(name: &str, request: &str, reply: &str) -> cpp::Decl {
1161 endpoint_alias(&format!("{name}Replier"), "Replier", request, reply)
1162}
1163
1164fn endpoint_alias(name: &str, template: &str, request: &str, reply: &str) -> cpp::Decl {
1165 cpp::Decl::Alias(cpp::Alias {
1166 doc: Doc::line(&format!(
1167 "The service's `{template}`, over its two topic types. DDS-RPC 1.0 §7.11.1.1.1."
1168 )),
1169 name: Ident::new(name),
1170 type_: cpp::Type::template(
1171 Path::new(&["dds", "rpc", "core", template]),
1172 vec![
1173 cpp::Type::named(Path::single(request)),
1174 cpp::Type::named(Path::single(reply)),
1175 ],
1176 ),
1177 })
1178}
1179
1180pub(super) fn is_psm() -> bool {
1184 cpp_mapping() == CppMapping::Psm
1185}
1186
1187pub(super) fn user_type(ast: &Ast, id: Id<TypeNode>, at: &str) -> Result<cpp::Type, BindingError> {
1196 if is_psm() {
1197 return Ok(cpp::Type::named(Path::single(&psm_type(ast, id, at)?)));
1198 }
1199 Ok(cpp_value_type(ast, id, at)?)
1200}
1201
1202pub(super) fn converted(
1206 ast: &Ast,
1207 id: Id<TypeNode>,
1208 at: &str,
1209 value: cpp::Expr,
1210 to_native: bool,
1211) -> Result<cpp::Expr, BindingError> {
1212 if !is_psm() {
1213 return Ok(value);
1214 }
1215 let conv = format!("Conv<{}>", psm_type(ast, id, at)?);
1216 let function = if to_native {
1217 "to_native"
1218 } else {
1219 "from_native"
1220 };
1221 Ok(cpp::Expr::Call {
1222 callee: Box::new(cpp::Expr::Path(Path::new(&[
1223 "zerodds",
1224 "psm",
1225 conv.as_str(),
1226 function,
1227 ]))),
1228 args: vec![value],
1229 })
1230}
1231
1232fn passed(ast: &Ast, param: &Parameter, at: &str) -> Result<cpp::Type, BindingError> {
1237 let type_ = user_type(ast, param.type_, at)?;
1238 Ok(match param.direction {
1239 ParamDirection::In if is_native(ast, param.type_) => type_,
1240 ParamDirection::In => cpp::Type::Ref(Box::new(cpp::Type::Const(Box::new(type_)))),
1241 ParamDirection::Out | ParamDirection::InOut => cpp::Type::Ref(Box::new(type_)),
1242 })
1243}
1244
1245fn is_native(ast: &Ast, id: Id<TypeNode>) -> bool {
1254 native_at(ast, id, 0)
1255}
1256
1257fn native_at(ast: &Ast, id: Id<TypeNode>, depth: usize) -> bool {
1259 if depth > MAX_ALIAS_DEPTH {
1260 return false;
1261 }
1262 let Ok(node) = ast.type_(id) else {
1263 return false;
1264 };
1265 match &node.kind {
1266 TypeKind::Primitive(_) | TypeKind::Enumeration { .. } | TypeKind::Bitmask { .. } => true,
1267 TypeKind::Alias { aliased } => aliased
1268 .resolved()
1269 .is_some_and(|target| native_at(ast, target, depth + 1)),
1270 _ => false,
1271 }
1272}
1273
1274fn bases(ast: &Ast, service: &Service, suffix: &str) -> Result<Vec<cpp::Base>, BindingError> {
1283 service
1284 .bases
1285 .iter()
1286 .map(|base| {
1287 let module = interface::service(ast, base.id)?.module;
1289 Ok(cpp::Base {
1290 access: cpp::Access::Public,
1291 virtual_: false,
1292 path: enhanced::path_in_idl(
1293 ast,
1294 service.module,
1295 module,
1296 &format!("{}{suffix}", cxx_escape(&base.name)),
1297 )?,
1298 })
1299 })
1300 .collect()
1301}
1302
1303fn attribute_method(
1305 name: &str,
1306 returns: cpp::Type,
1307 params: Vec<cpp::Param>,
1308 is_const: bool,
1309) -> cpp::Method {
1310 cpp::Method {
1311 doc: Doc::line("An attribute accessor. DDS-Cxx-PSM 1.0 §7.4.6."),
1312 returns,
1313 name: Ident::new(name),
1314 params,
1315 is_const,
1316 is_static: false,
1317 is_virtual: true,
1318 is_pure: true,
1319 is_constexpr: false,
1320 is_noexcept: false,
1321 is_override: false,
1322 body: None,
1323 }
1324}
1325
1326fn async_attribute_method(name: &str, returns: cpp::Type, params: Vec<cpp::Param>) -> cpp::Method {
1328 cpp::Method {
1329 doc: Doc::line("An attribute accessor. DDS-RPC 1.0 §7.11.1.1.2 rules 7.f and 7.g."),
1330 returns,
1331 name: Ident::new(&format!("{name}_async")),
1332 params,
1333 is_const: false,
1334 is_static: false,
1335 is_virtual: true,
1336 is_pure: true,
1337 is_constexpr: false,
1338 is_noexcept: false,
1339 is_override: false,
1340 body: None,
1341 }
1342}
1343
1344fn future_of(payload: cpp::Type) -> cpp::Type {
1346 cpp::Type::template(Path::new(&["dds", "rpc", "future"]), vec![payload])
1347}
1348
1349fn struct_of(name: &str, members: Vec<cpp::ClassMember>, doc: Doc) -> cpp::Class {
1350 cpp::Class {
1351 doc,
1352 is_struct: true,
1353 name: Ident::new(name),
1354 bases: Vec::new(),
1355 sections: vec![cpp::Section {
1356 access: cpp::Access::Public,
1357 members,
1358 }],
1359 }
1360}
1361
1362fn field(type_: cpp::Type, name: &str) -> cpp::ClassMember {
1363 cpp::ClassMember::Field(cpp::Field {
1364 doc: Doc::none(),
1365 type_,
1366 name: Ident::new(name),
1367 dims: Vec::new(),
1368 default: None,
1369 is_static: false,
1370 is_constexpr: false,
1371 bits: None,
1372 })
1373}
1374
1375fn alias(name: &str, target: &str) -> cpp::ClassMember {
1376 cpp::ClassMember::Alias(cpp::Alias {
1377 doc: Doc::none(),
1378 name: Ident::new(name),
1379 type_: cpp::Type::named(Path::single(target)),
1380 })
1381}
1382
1383fn qualified(namespace: &str, name: &str) -> cpp::Type {
1384 let mut segments: Vec<&str> = namespace.split("::").collect();
1385 segments.push(name);
1386 cpp::Type::named(Path::new(&segments))
1387}
1388
1389fn doc_for(operation: &Operation) -> Doc {
1392 operation
1393 .raises_line()
1394 .map_or_else(Doc::none, |line| Doc::line(&line))
1395}
1396
1397fn at(service: &Service, operation: &Operation, what: &str) -> String {
1398 format!("{}::{}({what})", service.name, operation.name)
1399}
1400
1401#[must_use]
1407pub fn cxx_escape(name: &str) -> String {
1408 crate::adapter::cpp::cxx_name(name)
1411}