mod calls;
mod codec;
mod enhanced;
mod enhanced_codec;
use crate::adapter::cpp::{
CppMapping, NATIVE_NAMESPACE, cpp_mapping, cpp_primitive, cpp_value_type, module_names,
nest_by_module, nest_by_module_placed,
};
use crate::adapter::cpp_psm::psm_type;
use crate::adapter::interface::{
self, BindingError, Operation, Origin, Parameter, RETURN_MEMBER, Service, ServiceMapping,
};
use zerodds_ast::{Ast, ModuleNode, ParamDirection, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::Id;
use zerodds_lang_cst::common::{Doc, Ident, Path};
use zerodds_lang_cst::cpp;
const RPC: &str = "dds::rpc";
const MAX_ALIAS_DEPTH: usize = 64;
pub(super) struct Sink {
items: Vec<(Id<ModuleNode>, bool, cpp::Decl)>,
unseen: Vec<(Id<ModuleNode>, String)>,
}
impl Sink {
fn new() -> Self {
Self {
items: Vec::new(),
unseen: Vec::new(),
}
}
pub(super) fn push_topic_unseen(&mut self, item: (Id<ModuleNode>, cpp::Decl)) {
if let Some(topic) = topic_name(&item.1) {
self.unseen.push((item.0, topic));
}
self.items.push((item.0, true, item.1));
}
pub(super) fn push(&mut self, item: (Id<ModuleNode>, cpp::Decl)) {
self.items.push((item.0, false, item.1));
}
pub(super) fn push_topic(&mut self, item: (Id<ModuleNode>, cpp::Decl)) {
self.items.push((item.0, true, item.1));
}
fn nest(self, ast: &Ast) -> Result<Vec<cpp::Decl>, BindingError> {
if !is_psm() {
let plain = self
.items
.into_iter()
.map(|(module, _, decl)| (module, decl))
.collect();
return nest_by_module(ast, plain).map_err(BindingError::from);
}
let mut placed = Vec::with_capacity(self.items.len());
let unseen = self.unseen;
for (module, native, decl) in self.items {
let using = if native {
topic_name(&decl)
.filter(|topic| {
!unseen
.iter()
.any(|(at, name)| *at == module && name == topic)
})
.map(|topic| {
let scope = module_names(ast, module)?;
let mut path = format!("::{NATIVE_NAMESPACE}");
for segment in scope {
path.push_str("::");
path.push_str(&segment);
}
Ok::<_, crate::adapter::cst::MaterialiseError>(cpp::Decl::Verbatim(
format!("using {path}::{topic};"),
))
})
.transpose()?
} else {
None
};
placed.push((module, native, decl));
if let Some(using) = using {
placed.push((module, false, using));
}
}
nest_by_module_placed(ast, placed).map_err(BindingError::from)
}
}
fn topic_name(decl: &cpp::Decl) -> Option<String> {
if let cpp::Decl::Class(class) = decl {
return Some(class.name.as_str().to_string());
}
if let cpp::Decl::Variable(variable) = decl {
return Some(variable.name.as_str().to_string());
}
None
}
pub fn cpp_interface_items(ast: &Ast) -> Result<Vec<cpp::Decl>, BindingError> {
cpp_interface_items_with(ast, ServiceMapping::Basic)
}
pub fn remote_member_types(ast: &Ast) -> Result<Vec<Id<TypeNode>>, BindingError> {
let remote: Vec<Service> = interface::services(ast)?
.into_iter()
.filter(Service::is_remote)
.collect();
codec::member_types(ast, &remote)
}
pub fn cpp_interface_items_with(
ast: &Ast,
mapping: ServiceMapping,
) -> Result<Vec<cpp::Decl>, BindingError> {
let mut out = Sink::new();
let services = interface::services(ast)?;
let root = ast.root_module();
if services.iter().any(Service::is_remote) {
out.push((
root,
cpp::Decl::Verbatim("#include <dds/rpc.hpp>".to_string()),
));
let remote: Vec<Service> = services
.iter()
.filter(|service| service.is_remote())
.cloned()
.collect();
for decl in codec::member_codecs(ast, &remote)? {
out.push((root, decl));
}
if !mapping.is_enhanced() {
let classic = codec::member_codecs_xcdr1(ast, &remote)?;
if !classic.is_empty() {
out.push((root, codec::in_xcdr1_namespace(classic)));
}
}
}
let mut declared_hashes: Vec<(Id<ModuleNode>, String)> = Vec::new();
let mut declared_exceptions = Vec::new();
for service in services {
if mapping.is_enhanced() {
enhanced::declare(
ast,
&Service { mapping, ..service },
&mut out,
&mut declared_exceptions,
)?;
} else {
declare(ast, &service, &mut out, &mut declared_hashes)?;
}
}
out.nest(ast)
}
pub fn cpp_service_items(ast: &Ast, service: &Service) -> Result<Vec<cpp::Decl>, BindingError> {
let mut out = Sink::new();
declare(ast, service, &mut out, &mut Vec::new())?;
out.nest(ast)
}
fn declare(
ast: &Ast,
service: &Service,
out: &mut Sink,
declared_hashes: &mut Vec<(Id<ModuleNode>, String)>,
) -> Result<(), BindingError> {
let module = service.module;
let name = cxx_escape(&service.name);
let operations = service.all_operations();
for operation in &operations {
out.push_topic((
module,
cpp::Decl::Class(struct_of(
&interface::in_struct_name(&name, &cxx_escape(&operation.name)),
request_fields(ast, service, operation)?,
Doc::line("The `in` and `inout` parameters of one call. DDS-RPC 1.0 §7.5.1.1.4."),
)),
));
let out_struct = (
module,
cpp::Decl::Class(struct_of(
&interface::out_struct_name(&name, &cxx_escape(&operation.name)),
reply_fields(ast, service, operation)?,
Doc::line(
"The return value and the `out` and `inout` parameters of one call. DDS-RPC 1.0 §7.5.1.1.5.",
),
)),
);
if is_psm() && operation.has_outputs() {
out.push_topic_unseen(out_struct);
out.push((
module,
cpp::Decl::Class(struct_of(
&interface::out_struct_name(&name, &cxx_escape(&operation.name)),
reply_fields_for_application(ast, service, operation)?,
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."),
)),
));
} else {
out.push_topic(out_struct);
}
}
for operation in &operations {
let operation_name = cxx_escape(&operation.name);
out.push_topic((
module,
hash_constant(
&interface::operation_hash_constant(&name, &operation_name),
interface::operation_hash(&operation.name),
"The case label of one operation. DDS-RPC 1.0 §7.5.1.1.6 rules 4 and 5a.",
),
));
}
for operation in &operations {
for raised in &operation.raises {
let key = (module, raised.scoped_name.clone());
if declared_hashes.contains(&key) {
continue;
}
declared_hashes.push(key);
out.push_topic((
module,
hash_constant(
&interface::exception_hash_constant(&cxx_escape(&raised.name)),
interface::exception_hash(&raised.scoped_name),
"The case label of one raised exception. DDS-RPC 1.0 §7.5.1.1.5 rule 4.",
),
));
}
}
for operation in &operations {
out.push_topic((
module,
cpp::Decl::Class(result_union_of(service, &name, operation)),
));
}
out.push_topic((
module,
cpp::Decl::Class(union_of(
&interface::call_union_name(&name),
&operations,
&name,
false,
Doc::line("The request union over every operation. DDS-RPC 1.0 §7.5.1.1.6."),
)),
));
out.push_topic((
module,
cpp::Decl::Class(union_of(
&interface::return_union_name(&name),
&operations,
&name,
true,
Doc::line("The reply union. DDS-RPC 1.0 §7.5.1.1.7."),
)),
));
let request = interface::request_type_name(&name);
let reply = interface::reply_type_name(&name);
out.push_topic((
module,
cpp::Decl::Class(struct_of(
&request,
vec![
field(qualified(RPC, "RequestHeader"), "header"),
field(
cpp::Type::named(Path::single(&interface::call_union_name(&name))),
interface::DATA_MEMBER,
),
],
Doc::line("The request topic type. DDS-RPC 1.0 §7.5.1.1.6."),
)),
));
out.push_topic((
module,
cpp::Decl::Class(struct_of(
&reply,
vec![
field(qualified(RPC, "ReplyHeader"), "header"),
field(
cpp::Type::named(Path::single(&interface::return_union_name(&name))),
interface::DATA_MEMBER,
),
],
Doc::line("The reply topic type. DDS-RPC 1.0 §7.5.1.1.7."),
)),
));
if service.is_remote() {
let root = ast.root_module();
let synthesized = codec::synthesized_codecs(ast, service, &name, &operations)?;
for decl in synthesized.iter().cloned() {
out.push((root, decl));
}
out.push((root, codec::in_xcdr1_namespace(synthesized)));
out.push((root, codec::traits(ast, service, &name)?));
}
out.push((module, cpp::Decl::Class(support_class(service, &name))));
out.push((
module,
cpp::Decl::Forward {
is_struct: false,
name: Ident::new(&format!("{name}Async")),
},
));
out.push((
module,
cpp::Decl::Class(sync_interface(ast, service, &name, &operations)?),
));
out.push((
module,
cpp::Decl::Class(async_interface(ast, service, &name, &operations)?),
));
if service.is_remote() {
out.push((module, requester_alias(&name, &request, &reply)));
out.push((module, replier_alias(&name, &request, &reply)));
out.push((
module,
cpp::Decl::Class(calls::client_class(
ast,
service,
&name,
&operations,
calls::Shape::Basic,
)?),
));
out.push((
module,
cpp::Decl::Class(calls::service_class(
ast,
service,
&name,
&operations,
calls::Shape::Basic,
)?),
));
}
Ok(())
}
fn request_fields(
ast: &Ast,
service: &Service,
operation: &Operation,
) -> Result<Vec<cpp::ClassMember>, BindingError> {
let mut out = Vec::new();
for param in operation.inputs() {
out.push(field(
cpp_value_type(ast, param.type_, &at(service, operation, ¶m.name))?,
&cxx_escape(¶m.name),
));
}
if out.is_empty() {
out.push(field(
qualified(RPC, "UnusedMember"),
interface::DUMMY_MEMBER,
));
}
Ok(out)
}
fn reply_fields_for_application(
ast: &Ast,
service: &Service,
operation: &Operation,
) -> Result<Vec<cpp::ClassMember>, BindingError> {
let mut out = Vec::new();
for param in operation.outputs() {
out.push(field(
user_type(ast, param.type_, &at(service, operation, ¶m.name))?,
&cxx_escape(¶m.name),
));
}
if let Some(returns) = operation.returns {
let taken: Vec<String> = operation
.outputs()
.map(|param| cxx_escape(¶m.name))
.collect();
out.push(field(
user_type(ast, returns, &at(service, operation, "return"))?,
&return_member_name(&taken),
));
}
Ok(out)
}
pub(super) fn native_out_type(
ast: &Ast,
module: Id<ModuleNode>,
name: &str,
operation: &Operation,
) -> Result<cpp::Type, BindingError> {
let out = interface::out_struct_name(name, &cxx_escape(&operation.name));
if !is_psm() {
return Ok(cpp::Type::named(Path::single(&out)));
}
let mut path = format!("::{NATIVE_NAMESPACE}");
for segment in module_names(ast, module).map_err(BindingError::from)? {
path.push_str("::");
path.push_str(&segment);
}
path.push_str("::");
path.push_str(&out);
Ok(cpp::Type::named(Path::single(&path)))
}
fn reply_fields(
ast: &Ast,
service: &Service,
operation: &Operation,
) -> Result<Vec<cpp::ClassMember>, BindingError> {
let mut out = Vec::new();
for param in operation.outputs() {
out.push(field(
cpp_value_type(ast, param.type_, &at(service, operation, ¶m.name))?,
&cxx_escape(¶m.name),
));
}
if let Some(returns) = operation.returns {
let taken: Vec<String> = operation
.outputs()
.map(|param| cxx_escape(¶m.name))
.collect();
out.push(field(
cpp_value_type(ast, returns, &at(service, operation, "return"))?,
&return_member_name(&taken),
));
}
if out.is_empty() {
out.push(field(
qualified(RPC, "UnusedMember"),
interface::DUMMY_MEMBER,
));
}
Ok(out)
}
fn return_member_name(taken: &[String]) -> String {
if !taken.iter().any(|name| name == RETURN_MEMBER) {
return RETURN_MEMBER.to_string();
}
for n in 1..=i32::MAX {
let candidate = format!("{RETURN_MEMBER}{n}");
if !taken.iter().any(|name| name == &candidate) {
return candidate;
}
}
format!("{RETURN_MEMBER} ")
}
fn union_of(
name: &str,
operations: &[Operation],
service: &str,
reply: bool,
doc: Doc,
) -> cpp::Class {
let mut members = vec![
discriminator_field(),
case_field(
qualified(RPC, "UnknownOperation"),
interface::UNKNOWN_OP_MEMBER,
Doc::line("Live on the `default` case. DDS-RPC 1.0 §7.5.1.1.6 rule 3."),
),
];
for operation in operations {
let operation_name = cxx_escape(&operation.name);
let case = if reply {
interface::result_union_name(service, &operation_name)
} else {
interface::in_struct_name(service, &operation_name)
};
members.push(case_field(
cpp::Type::named(Path::single(&case)),
&operation_name,
Doc::line(&format!(
"Live when `_d` is `{}`.",
interface::operation_hash_constant(service, &operation_name)
)),
));
}
cpp::Class {
doc,
is_struct: true,
name: Ident::new(name),
bases: Vec::new(),
sections: vec![cpp::Section {
access: cpp::Access::Public,
members,
}],
}
}
fn discriminator_field() -> cpp::ClassMember {
cpp::ClassMember::Field(cpp::Field {
doc: Doc::line("Which case this sample carries. DDS-RPC 1.0 §7.5.1.1.6 rule 2."),
type_: cpp_primitive(PrimitiveKind::Int32),
name: Ident::new("_d"),
dims: Vec::new(),
default: None,
is_static: false,
is_constexpr: false,
bits: None,
})
}
fn case_field(type_: cpp::Type, name: &str, doc: Doc) -> cpp::ClassMember {
cpp::ClassMember::Field(cpp::Field {
doc,
type_,
name: Ident::new(name),
dims: Vec::new(),
default: None,
is_static: false,
is_constexpr: false,
bits: None,
})
}
fn hash_constant(name: &str, value: i32, doc: &str) -> cpp::Decl {
cpp::Decl::Variable(cpp::Variable {
doc: Doc::line(doc),
type_: cpp_primitive(PrimitiveKind::Int32),
name: Ident::new(name),
value: Some(cpp::Expr::Literal(zerodds_lang_cst::common::Literal::Int(
value.to_string(),
))),
is_static: false,
is_constexpr: true,
is_inline: true,
})
}
fn result_union_of(service: &Service, name: &str, operation: &Operation) -> cpp::Class {
let operation_name = cxx_escape(&operation.name);
let mut members = vec![
discriminator_field(),
case_field(
cpp::Type::named(Path::single(&interface::out_struct_name(
name,
&operation_name,
))),
interface::RESULT_MEMBER,
Doc::line(&format!(
"Live when `_d` is `dds::RETCODE_OK` ({}). DDS-RPC 1.0 §7.5.1.1.5 rule 3a.",
interface::RETCODE_OK
)),
),
];
for raised in &operation.raises {
members.push(case_field(
cpp::Type::named(Path::single(&cxx_escape(&raised.name))),
&interface::exception_member_name(&raised.name),
Doc::line(&format!(
"Live when `_d` is `{}`. DDS-RPC 1.0 §7.5.1.1.5 rule 5.",
interface::exception_hash_constant(&cxx_escape(&raised.name))
)),
));
}
let _ = service;
cpp::Class {
doc: Doc::line("The outcome of one operation. DDS-RPC 1.0 §7.5.1.1.5."),
is_struct: true,
name: Ident::new(&interface::result_union_name(name, &operation_name)),
bases: Vec::new(),
sections: vec![cpp::Section {
access: cpp::Access::Public,
members,
}],
}
}
fn support_class(service: &Service, name: &str) -> cpp::Class {
let style = interface::BindingStyle::FunctionCall;
let mut members = vec![
alias("RequestType", &interface::request_type_name(name)),
alias("ReplyType", &interface::reply_type_name(name)),
topic_name_method(
"request_topic_name",
&service.request_topic_name(style),
"The request topic's name. DDS-RPC 1.0 §7.4.1, §7.4.2.",
),
topic_name_method(
"reply_topic_name",
&service.reply_topic_name(style),
"The reply topic's name. DDS-RPC 1.0 §7.4.1, §7.4.2.",
),
topic_name_method(
"service_name",
&service.service_name,
"The `<service_name>` segment of §7.4.1, `\"Service\"` unless \
`@service(name = \"…\")` supplied one.",
),
];
members.push(calls::defaults_method(service));
for (at, alias_name) in service.topic_aliases(style, false).into_iter().enumerate() {
members.push(topic_name_method(
&format!("request_topic_alias_{at}"),
&alias_name,
"A request-topic alias from a parent interface. DDS-RPC 1.0 §7.4.3.",
));
}
for (at, alias_name) in service.topic_aliases(style, true).into_iter().enumerate() {
members.push(topic_name_method(
&format!("reply_topic_alias_{at}"),
&alias_name,
"A reply-topic alias from a parent interface. DDS-RPC 1.0 §7.4.3.",
));
}
cpp::Class {
doc: Doc::line("The service's type support: its topic types and their topic names."),
is_struct: true,
name: Ident::new(&format!("{name}Support")),
bases: Vec::new(),
sections: vec![cpp::Section {
access: cpp::Access::Public,
members,
}],
}
}
fn topic_name_method(method: &str, topic: &str, doc: &str) -> cpp::ClassMember {
cpp::ClassMember::Method(cpp::Method {
doc: Doc::line(doc),
returns: cpp::Type::Pointer(Box::new(cpp::Type::Const(Box::new(cpp::Type::Primitive(
cpp::Primitive::Char,
))))),
name: Ident::new(method),
params: Vec::new(),
is_const: false,
is_static: true,
is_virtual: false,
is_pure: false,
is_constexpr: true,
is_noexcept: true,
is_override: false,
body: Some(cpp::Block {
statements: vec![cpp::Statement::Return(Some(cpp::Expr::Literal(
zerodds_lang_cst::common::Literal::Str(topic.to_string()),
)))],
}),
})
}
fn sync_interface(
ast: &Ast,
service: &Service,
name: &str,
operations: &[Operation],
) -> Result<cpp::Class, BindingError> {
let mut members = vec![
alias("SupportType", &format!("{name}Support")),
alias("RequestType", &interface::request_type_name(name)),
alias("ReplyType", &interface::reply_type_name(name)),
alias("AsyncInterfaceType", &format!("{name}Async")),
cpp::ClassMember::Destructor(cpp::Destructor {
doc: Doc::line("Rule 2: a public virtual destructor."),
name: Ident::new(name),
is_virtual: true,
body: cpp::FunctionBody::Defaulted,
}),
];
let _ = operations;
for operation in &service.operations {
members.push(cpp::ClassMember::Method(sync_method(
ast, service, operation,
)?));
}
for attribute in &service.attributes {
let at = format!("{}::{}", service.name, attribute.name);
let type_ = user_type(ast, attribute.type_, &at)?;
let spelled = cxx_escape(&attribute.name);
if is_native(ast, attribute.type_) {
members.push(cpp::ClassMember::Method(attribute_method(
&spelled,
type_.clone(),
Vec::new(),
false,
)));
if !attribute.readonly {
members.push(cpp::ClassMember::Method(attribute_method(
&spelled,
cpp::Type::Void,
vec![cpp::Param {
type_,
name: Some(Ident::new(&spelled)),
default: None,
}],
false,
)));
}
} else {
members.push(cpp::ClassMember::Method(attribute_method(
&spelled,
cpp::Type::Ref(Box::new(type_.clone())),
Vec::new(),
false,
)));
members.push(cpp::ClassMember::Method(attribute_method(
&spelled,
cpp::Type::Ref(Box::new(cpp::Type::Const(Box::new(type_.clone())))),
Vec::new(),
true,
)));
if !attribute.readonly {
members.push(cpp::ClassMember::Method(attribute_method(
&spelled,
cpp::Type::Void,
vec![cpp::Param {
type_: cpp::Type::Ref(Box::new(cpp::Type::Const(Box::new(type_)))),
name: Some(Ident::new(&spelled)),
default: None,
}],
false,
)));
}
}
}
Ok(cpp::Class {
doc: Doc::line(
"The service, as the abstract class DDS-RPC 1.0 §7.11.1.1.2 rule 1 requires.",
),
is_struct: false,
name: Ident::new(name),
bases: bases(ast, service, "")?,
sections: vec![cpp::Section {
access: cpp::Access::Public,
members,
}],
})
}
fn sync_method(
ast: &Ast,
service: &Service,
operation: &Operation,
) -> Result<cpp::Method, BindingError> {
let mut params = Vec::new();
let mut returns = cpp::Type::Void;
if let Some(target) = operation.returns {
let type_ = user_type(ast, target, &at(service, operation, "return"))?;
if is_native(ast, target) {
returns = type_;
} else {
params.push(cpp::Param {
type_: cpp::Type::Ref(Box::new(type_)),
name: Some(Ident::new("cxx_return")),
default: None,
});
}
}
for param in &operation.params {
params.push(cpp::Param {
type_: passed(ast, param, &at(service, operation, ¶m.name))?,
name: Some(Ident::new(&cxx_escape(¶m.name))),
default: None,
});
}
Ok(cpp::Method {
doc: doc_for(operation),
returns,
name: Ident::new(&cxx_escape(&operation.name)),
params,
is_const: false,
is_static: false,
is_virtual: true,
is_pure: true,
is_constexpr: false,
is_noexcept: false,
is_override: false,
body: None,
})
}
fn async_interface(
ast: &Ast,
service: &Service,
name: &str,
operations: &[Operation],
) -> Result<cpp::Class, BindingError> {
let mut members = vec![
alias("SupportType", &format!("{name}Support")),
alias("RequestType", &interface::request_type_name(name)),
alias("ReplyType", &interface::reply_type_name(name)),
alias("InterfaceType", name),
cpp::ClassMember::Destructor(cpp::Destructor {
doc: Doc::line("Rule 2: a public virtual destructor."),
name: Ident::new(&format!("{name}Async")),
is_virtual: true,
body: cpp::FunctionBody::Defaulted,
}),
];
let _ = operations;
for operation in &service.operations {
members.push(cpp::ClassMember::Method(async_method(
ast, service, name, operation,
)?));
}
for attribute in &service.attributes {
let at = format!("{}::{}", service.name, attribute.name);
let payload = user_type(ast, attribute.type_, &at)?;
let type_ = user_type(ast, attribute.type_, &at)?;
let spelled = cxx_escape(&attribute.name);
members.push(cpp::ClassMember::Method(async_attribute_method(
&spelled,
future_of(payload),
Vec::new(),
)));
if !attribute.readonly {
let passed = if is_native(ast, attribute.type_) {
cpp::Type::Const(Box::new(type_))
} else {
cpp::Type::Ref(Box::new(cpp::Type::Const(Box::new(type_))))
};
members.push(cpp::ClassMember::Method(async_attribute_method(
&spelled,
future_of(cpp::Type::Void),
vec![cpp::Param {
type_: passed,
name: Some(Ident::new(&spelled)),
default: None,
}],
)));
}
}
let bases = bases(ast, service, "Async")?;
Ok(cpp::Class {
doc: Doc::line(
"The service's asynchronous half, as DDS-RPC 1.0 §7.11.1.1.2 rule 1 requires.",
),
is_struct: false,
name: Ident::new(&format!("{name}Async")),
bases,
sections: vec![cpp::Section {
access: cpp::Access::Public,
members,
}],
})
}
fn async_payload(
ast: &Ast,
service: &Service,
name: &str,
operation: &Operation,
) -> Result<cpp::Type, BindingError> {
Ok(if operation.has_outputs() {
cpp::Type::named(Path::single(&interface::out_struct_name(
name,
&cxx_escape(&operation.name),
)))
} else if let Some(target) = operation.returns {
user_type(ast, target, &at(service, operation, "return"))?
} else {
cpp::Type::Void
})
}
fn async_method(
ast: &Ast,
service: &Service,
name: &str,
operation: &Operation,
) -> Result<cpp::Method, BindingError> {
let payload = async_payload(ast, service, name, operation)?;
let mut params = Vec::new();
for param in operation.inputs() {
let type_ = user_type(ast, param.type_, &at(service, operation, ¶m.name))?;
params.push(cpp::Param {
type_: if is_native(ast, param.type_) {
cpp::Type::Const(Box::new(type_))
} else {
cpp::Type::Ref(Box::new(cpp::Type::Const(Box::new(type_))))
},
name: Some(Ident::new(&cxx_escape(¶m.name))),
default: None,
});
}
Ok(cpp::Method {
doc: doc_for(operation),
returns: future_of(payload),
name: Ident::new(&format!("{}_async", cxx_escape(&operation.name))),
params,
is_const: false,
is_static: false,
is_virtual: true,
is_pure: true,
is_constexpr: false,
is_noexcept: false,
is_override: false,
body: None,
})
}
fn requester_alias(name: &str, request: &str, reply: &str) -> cpp::Decl {
endpoint_alias(&format!("{name}Requester"), "Requester", request, reply)
}
fn replier_alias(name: &str, request: &str, reply: &str) -> cpp::Decl {
endpoint_alias(&format!("{name}Replier"), "Replier", request, reply)
}
fn endpoint_alias(name: &str, template: &str, request: &str, reply: &str) -> cpp::Decl {
cpp::Decl::Alias(cpp::Alias {
doc: Doc::line(&format!(
"The service's `{template}`, over its two topic types. DDS-RPC 1.0 §7.11.1.1.1."
)),
name: Ident::new(name),
type_: cpp::Type::template(
Path::new(&["dds", "rpc", "core", template]),
vec![
cpp::Type::named(Path::single(request)),
cpp::Type::named(Path::single(reply)),
],
),
})
}
pub(super) fn is_psm() -> bool {
cpp_mapping() == CppMapping::Psm
}
pub(super) fn user_type(ast: &Ast, id: Id<TypeNode>, at: &str) -> Result<cpp::Type, BindingError> {
if is_psm() {
return Ok(cpp::Type::named(Path::single(&psm_type(ast, id, at)?)));
}
Ok(cpp_value_type(ast, id, at)?)
}
pub(super) fn converted(
ast: &Ast,
id: Id<TypeNode>,
at: &str,
value: cpp::Expr,
to_native: bool,
) -> Result<cpp::Expr, BindingError> {
if !is_psm() {
return Ok(value);
}
let conv = format!("Conv<{}>", psm_type(ast, id, at)?);
let function = if to_native {
"to_native"
} else {
"from_native"
};
Ok(cpp::Expr::Call {
callee: Box::new(cpp::Expr::Path(Path::new(&[
"zerodds",
"psm",
conv.as_str(),
function,
]))),
args: vec![value],
})
}
fn passed(ast: &Ast, param: &Parameter, at: &str) -> Result<cpp::Type, BindingError> {
let type_ = user_type(ast, param.type_, at)?;
Ok(match param.direction {
ParamDirection::In if is_native(ast, param.type_) => type_,
ParamDirection::In => cpp::Type::Ref(Box::new(cpp::Type::Const(Box::new(type_)))),
ParamDirection::Out | ParamDirection::InOut => cpp::Type::Ref(Box::new(type_)),
})
}
fn is_native(ast: &Ast, id: Id<TypeNode>) -> bool {
native_at(ast, id, 0)
}
fn native_at(ast: &Ast, id: Id<TypeNode>, depth: usize) -> bool {
if depth > MAX_ALIAS_DEPTH {
return false;
}
let Ok(node) = ast.type_(id) else {
return false;
};
match &node.kind {
TypeKind::Primitive(_) | TypeKind::Enumeration { .. } | TypeKind::Bitmask { .. } => true,
TypeKind::Alias { aliased } => aliased
.resolved()
.is_some_and(|target| native_at(ast, target, depth + 1)),
_ => false,
}
}
fn bases(ast: &Ast, service: &Service, suffix: &str) -> Result<Vec<cpp::Base>, BindingError> {
service
.bases
.iter()
.map(|base| {
let module = interface::service(ast, base.id)?.module;
Ok(cpp::Base {
access: cpp::Access::Public,
virtual_: false,
path: enhanced::path_in_idl(
ast,
service.module,
module,
&format!("{}{suffix}", cxx_escape(&base.name)),
)?,
})
})
.collect()
}
fn attribute_method(
name: &str,
returns: cpp::Type,
params: Vec<cpp::Param>,
is_const: bool,
) -> cpp::Method {
cpp::Method {
doc: Doc::line("An attribute accessor. DDS-Cxx-PSM 1.0 §7.4.6."),
returns,
name: Ident::new(name),
params,
is_const,
is_static: false,
is_virtual: true,
is_pure: true,
is_constexpr: false,
is_noexcept: false,
is_override: false,
body: None,
}
}
fn async_attribute_method(name: &str, returns: cpp::Type, params: Vec<cpp::Param>) -> cpp::Method {
cpp::Method {
doc: Doc::line("An attribute accessor. DDS-RPC 1.0 §7.11.1.1.2 rules 7.f and 7.g."),
returns,
name: Ident::new(&format!("{name}_async")),
params,
is_const: false,
is_static: false,
is_virtual: true,
is_pure: true,
is_constexpr: false,
is_noexcept: false,
is_override: false,
body: None,
}
}
fn future_of(payload: cpp::Type) -> cpp::Type {
cpp::Type::template(Path::new(&["dds", "rpc", "future"]), vec![payload])
}
fn struct_of(name: &str, members: Vec<cpp::ClassMember>, doc: Doc) -> cpp::Class {
cpp::Class {
doc,
is_struct: true,
name: Ident::new(name),
bases: Vec::new(),
sections: vec![cpp::Section {
access: cpp::Access::Public,
members,
}],
}
}
fn field(type_: cpp::Type, name: &str) -> cpp::ClassMember {
cpp::ClassMember::Field(cpp::Field {
doc: Doc::none(),
type_,
name: Ident::new(name),
dims: Vec::new(),
default: None,
is_static: false,
is_constexpr: false,
bits: None,
})
}
fn alias(name: &str, target: &str) -> cpp::ClassMember {
cpp::ClassMember::Alias(cpp::Alias {
doc: Doc::none(),
name: Ident::new(name),
type_: cpp::Type::named(Path::single(target)),
})
}
fn qualified(namespace: &str, name: &str) -> cpp::Type {
let mut segments: Vec<&str> = namespace.split("::").collect();
segments.push(name);
cpp::Type::named(Path::new(&segments))
}
fn doc_for(operation: &Operation) -> Doc {
operation
.raises_line()
.map_or_else(Doc::none, |line| Doc::line(&line))
}
fn at(service: &Service, operation: &Operation, what: &str) -> String {
format!("{}::{}({what})", service.name, operation.name)
}
#[must_use]
pub fn cxx_escape(name: &str) -> String {
crate::adapter::cpp::cxx_name(name)
}