use zerodds_ast::{
Ast, AttributeNode, ExceptionNode, InterfaceKind, InterfaceNode, ModuleNode, OperationNode,
ParamDirection, TypeNode,
};
use zerodds_ast_core::{Id, NodeId};
pub const RETURN_MEMBER: &str = "return_";
pub const DUMMY_MEMBER: &str = "dummy";
pub const UNKNOWN_OP_MEMBER: &str = "unknownOp";
pub const RESULT_MEMBER: &str = "result";
pub const DATA_MEMBER: &str = "data";
pub const RETCODE_OK: i32 = 0;
pub const REQUEST_SUFFIX: &str = "_Request";
pub const REPLY_SUFFIX: &str = "_Reply";
pub const DEFAULT_SERVICE_NAME: &str = "Service";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InterfaceError {
ForwardWithoutDefinition {
name: String,
},
NoNode {
kind: &'static str,
at: String,
},
Unresolved {
construct: &'static str,
at: String,
},
TooDeep {
name: String,
limit: usize,
},
OutsideServiceGrammar {
construct: &'static str,
at: String,
},
DuplicateOperation {
interface: String,
name: String,
},
DuplicateParameter {
at: String,
name: String,
},
}
impl core::fmt::Display for InterfaceError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ForwardWithoutDefinition { name } => write!(
f,
"interface `{name}` is forward-declared and never defined"
),
Self::NoNode { kind, at } => {
write!(f, "{at}: the model lists a {kind} it will not hand over")
}
Self::Unresolved { construct, at } => {
write!(f, "{at}: the {construct} does not resolve")
}
Self::TooDeep { name, limit } => write!(
f,
"interface `{name}`: the inheritance chain is deeper than {limit} levels"
),
Self::OutsideServiceGrammar { construct, at } => write!(
f,
"{at}: {construct} is outside the DDS-RPC 1.0 ยง7.3.1.1 service grammar"
),
Self::DuplicateOperation { interface, name } => write!(
f,
"interface `{interface}`: the operation name `{name}` is used twice \
(DDS-RPC 1.0 ยง7.5.1.1.3 rule 1 covers the names an attribute implies)"
),
Self::DuplicateParameter { at, name } => {
write!(f, "{at}: the parameter name `{name}` is used twice")
}
}
}
}
impl core::error::Error for InterfaceError {}
#[must_use]
pub fn interface_error_to_materialise(
error: InterfaceError,
at: &str,
) -> crate::adapter::cst::MaterialiseError {
crate::adapter::cst::MaterialiseError::Unevaluated {
construct: format!("interface binding ({error})"),
at: at.to_string(),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BindingError {
Interface(InterfaceError),
Materialise(crate::adapter::cst::MaterialiseError),
Codec(String),
}
impl BindingError {
#[must_use]
pub const fn diagnostic_code(&self) -> Option<&'static str> {
match self {
Self::Interface(InterfaceError::ForwardWithoutDefinition { .. }) => Some("DDS-TS-E002"),
Self::Interface(_) | Self::Materialise(_) | Self::Codec(_) => None,
}
}
}
impl From<InterfaceError> for BindingError {
fn from(error: InterfaceError) -> Self {
Self::Interface(error)
}
}
impl From<crate::adapter::cst::MaterialiseError> for BindingError {
fn from(error: crate::adapter::cst::MaterialiseError) -> Self {
Self::Materialise(error)
}
}
impl core::fmt::Display for BindingError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Interface(error) => write!(f, "{error}"),
Self::Materialise(error) => write!(f, "{error}"),
Self::Codec(reason) => write!(f, "no codec for the topic types: {reason}"),
}
}
}
impl core::error::Error for BindingError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Raise {
pub id: Id<ExceptionNode>,
pub name: String,
pub scoped_name: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Parameter {
pub name: String,
pub direction: ParamDirection,
pub type_: Id<TypeNode>,
}
impl Parameter {
#[must_use]
pub const fn is_input(&self) -> bool {
matches!(self.direction, ParamDirection::In | ParamDirection::InOut)
}
#[must_use]
pub const fn is_output(&self) -> bool {
matches!(self.direction, ParamDirection::Out | ParamDirection::InOut)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Origin {
Declared,
AttributeGetter,
AttributeSetter,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Operation {
pub node_id: Option<NodeId>,
pub name: String,
pub returns: Option<Id<TypeNode>>,
pub params: Vec<Parameter>,
pub raises: Vec<Raise>,
pub origin: Origin,
pub mutable: bool,
}
impl Operation {
#[must_use]
pub fn attribute_name(&self) -> &str {
match self.origin {
Origin::AttributeGetter => self.name.strip_prefix("get_attribute_"),
Origin::AttributeSetter => self.name.strip_prefix("set_attribute_"),
Origin::Declared => None,
}
.unwrap_or(&self.name)
}
pub fn inputs(&self) -> impl Iterator<Item = &Parameter> {
self.params.iter().filter(|param| param.is_input())
}
pub fn outputs(&self) -> impl Iterator<Item = &Parameter> {
self.params.iter().filter(|param| param.is_output())
}
#[must_use]
pub fn raises_line(&self) -> Option<String> {
raises_line(&self.raises)
}
#[must_use]
pub fn has_outputs(&self) -> bool {
self.params.iter().any(Parameter::is_output)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribute {
pub node_id: NodeId,
pub name: String,
pub type_: Id<TypeNode>,
pub readonly: bool,
pub get_raises: Vec<Raise>,
pub set_raises: Vec<Raise>,
}
impl Attribute {
#[must_use]
pub fn get_raises_line(&self) -> Option<String> {
raises_line(&self.get_raises)
}
#[must_use]
pub fn set_raises_line(&self) -> Option<String> {
raises_line(&self.set_raises)
}
}
#[must_use]
pub fn raises_line(raises: &[Raise]) -> Option<String> {
if raises.is_empty() {
return None;
}
let names: Vec<&str> = raises.iter().map(|raise| raise.name.as_str()).collect();
Some(format!(
"Raises, as the IDL declares it: {}. DDS-RPC 1.0 ยง7.11.1.1.2 rule 6.c keeps it out of \
the signature.",
names.join(", ")
))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Base {
pub id: Id<InterfaceNode>,
pub name: String,
pub qualified_name: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Service {
pub id: Id<InterfaceNode>,
pub node_id: NodeId,
pub name: String,
pub module: Id<ModuleNode>,
pub qualified_name: String,
pub service_name: String,
pub request_topic: Option<String>,
pub reply_topic: Option<String>,
pub qos_request_profile: Option<String>,
pub qos_reply_profile: Option<String>,
pub kind: InterfaceKind,
pub bases: Vec<Base>,
pub operations: Vec<Operation>,
pub attributes: Vec<Attribute>,
pub mapping: ServiceMapping,
}
impl Service {
#[must_use]
pub const fn is_remote(&self) -> bool {
matches!(
self.kind,
InterfaceKind::Interface | InterfaceKind::Abstract
)
}
#[must_use]
pub fn request_topic_name(&self, style: BindingStyle) -> String {
self.request_topic.clone().unwrap_or_else(|| {
compose_topic_name(
style,
&self.qualified_name,
&format!("{}{REQUEST_SUFFIX}", self.service_name),
)
})
}
#[must_use]
pub fn reply_topic_name(&self, style: BindingStyle) -> String {
self.reply_topic.clone().unwrap_or_else(|| {
compose_topic_name(
style,
&self.qualified_name,
&format!("{}{REPLY_SUFFIX}", self.service_name),
)
})
}
#[must_use]
pub fn topic_aliases(&self, style: BindingStyle, reply: bool) -> Vec<String> {
let annotated = if reply {
self.reply_topic.is_some()
} else {
self.request_topic.is_some()
};
if annotated || !style.captures_interface_name() {
return Vec::new();
}
let suffix = format!(
"{}{}",
self.service_name,
if reply { REPLY_SUFFIX } else { REQUEST_SUFFIX }
);
self.bases
.iter()
.map(|base| compose_topic_name(style, &base.qualified_name, &suffix))
.collect()
}
#[must_use]
pub fn all_operations(&self) -> Vec<Operation> {
let mut out = self.operations.clone();
for attribute in &self.attributes {
out.push(Operation {
node_id: Some(attribute.node_id),
name: format!("get_attribute_{}", attribute.name),
returns: Some(attribute.type_),
params: Vec::new(),
raises: attribute.get_raises.clone(),
origin: Origin::AttributeGetter,
mutable: false,
});
if attribute.readonly {
continue;
}
out.push(Operation {
node_id: Some(attribute.node_id),
name: format!("set_attribute_{}", attribute.name),
returns: None,
params: vec![Parameter {
name: attribute.name.clone(),
direction: ParamDirection::In,
type_: attribute.type_,
}],
raises: attribute.set_raises.clone(),
origin: Origin::AttributeSetter,
mutable: false,
});
}
out
}
}
#[must_use]
pub fn request_type_name(service: &str) -> String {
format!("{service}{REQUEST_SUFFIX}")
}
#[must_use]
pub fn reply_type_name(service: &str) -> String {
format!("{service}{REPLY_SUFFIX}")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BindingStyle {
#[default]
FunctionCall,
RequestReply,
}
impl BindingStyle {
pub const ALL: [Self; 2] = [Self::FunctionCall, Self::RequestReply];
#[must_use]
pub const fn captures_interface_name(self) -> bool {
matches!(self, Self::FunctionCall)
}
}
#[must_use]
pub fn qualified_interface_name(modules: &[&str], interface: &str) -> String {
let mut out = String::new();
for module in modules {
if module.is_empty() {
continue;
}
out.push_str(module);
out.push('_');
}
out.push_str(interface);
out
}
#[must_use]
pub fn compose_topic_name(style: BindingStyle, qualified_interface: &str, suffix: &str) -> String {
if style.captures_interface_name() {
format!("{qualified_interface}_{suffix}")
} else {
suffix.to_string()
}
}
#[must_use]
pub fn call_union_name(service: &str) -> String {
format!("{service}_Call")
}
#[must_use]
pub fn return_union_name(service: &str) -> String {
format!("{service}_Return")
}
#[must_use]
pub fn result_union_name(service: &str, operation: &str) -> String {
format!("{service}_{operation}_Result")
}
#[must_use]
pub fn operation_hash_constant(service: &str, operation: &str) -> String {
format!("{service}_{operation}_Hash")
}
#[must_use]
pub fn exception_hash_constant(exception: &str) -> String {
format!("{exception}_Ex_Hash")
}
#[must_use]
pub fn exception_member_name(exception: &str) -> String {
format!("{}_ex", exception.to_lowercase())
}
#[must_use]
pub fn in_struct_name(service: &str, operation: &str) -> String {
format!("{service}_{operation}_In")
}
#[must_use]
pub fn out_struct_name(service: &str, operation: &str) -> String {
format!("{service}_{operation}_Out")
}
#[must_use]
pub fn operation_hash(operation: &str) -> i32 {
let digest = zerodds_types::hash::hash_bytes(operation.as_bytes());
u32::from_le_bytes([digest.0[0], digest.0[1], digest.0[2], digest.0[3]]) as i32
}
#[must_use]
pub fn exception_hash(scoped_name: &str) -> i32 {
operation_hash(scoped_name)
}
pub const MAX_MODULE_DEPTH: usize = 64;
pub fn module_chain(
ast: &Ast,
module: Id<ModuleNode>,
) -> Result<Vec<(Id<ModuleNode>, String)>, InterfaceError> {
let mut out = Vec::new();
let mut current = Some(module);
let mut depth = 0usize;
while let Some(id) = current {
if depth > MAX_MODULE_DEPTH {
return Err(InterfaceError::NoNode {
kind: "module",
at: format!("the parent chain runs past {MAX_MODULE_DEPTH}"),
});
}
depth += 1;
let node = ast.module(id).map_err(|_| InterfaceError::NoNode {
kind: "module",
at: "the enclosing module".to_string(),
})?;
if !node.name.is_empty() {
out.push((id, node.name.clone()));
}
current = node.parent;
}
out.reverse();
Ok(out)
}
pub fn services(ast: &Ast) -> Result<Vec<Service>, InterfaceError> {
let mut out = Vec::new();
for (id, _) in ast.interfaces() {
out.push(service(ast, id)?);
}
Ok(out)
}
pub fn service(ast: &Ast, id: Id<InterfaceNode>) -> Result<Service, InterfaceError> {
let node = ast.interface(id).map_err(|_| InterfaceError::NoNode {
kind: "interface",
at: "the model".to_string(),
})?;
if node.completeness != zerodds_ast::Completeness::Complete {
return Err(InterfaceError::ForwardWithoutDefinition {
name: node.name.clone(),
});
}
reject_outside_service_grammar(ast, node)?;
let mut bases = Vec::with_capacity(node.bases.len());
for base in &node.bases {
let base_id = base.resolved().ok_or_else(|| InterfaceError::Unresolved {
construct: "base interface",
at: node.name.clone(),
})?;
let base_node = ast.interface(base_id).map_err(|_| InterfaceError::NoNode {
kind: "base interface",
at: node.name.clone(),
})?;
bases.push(Base {
id: base_id,
name: base_node.name.clone(),
qualified_name: qualified_name_of(ast, base_node)?,
});
}
let mut operations = Vec::with_capacity(node.operations.len());
for operation in &node.operations {
let op = ast
.operation(*operation)
.map_err(|_| InterfaceError::NoNode {
kind: "operation",
at: node.name.clone(),
})?;
operations.push(one_operation(ast, &node.name, op)?);
}
let mut attributes = Vec::with_capacity(node.attributes.len());
for attribute in &node.attributes {
let attr = ast
.attribute(*attribute)
.map_err(|_| InterfaceError::NoNode {
kind: "attribute",
at: node.name.clone(),
})?;
attributes.push(one_attribute(ast, &node.name, attr)?);
}
let service = Service {
id,
node_id: node.node_id,
qualified_name: qualified_name_of(ast, node)?,
service_name: service_annotation_name(ast, &node.facets)
.unwrap_or_else(|| DEFAULT_SERVICE_NAME.to_string()),
request_topic: annotation_string(ast, &node.facets, "DDSRequestTopic", "name"),
reply_topic: annotation_string(ast, &node.facets, "DDSReplyTopic", "name"),
qos_request_profile: annotation_named_string(ast, &node.facets, "Qos", "RequestProfile"),
qos_reply_profile: annotation_named_string(ast, &node.facets, "Qos", "ReplyProfile"),
name: node.name.clone(),
module: node.module,
kind: node.kind,
bases,
operations,
attributes,
mapping: ServiceMapping::Basic,
};
let every = service.all_operations();
for (index, operation) in every.iter().enumerate() {
if every[..index]
.iter()
.any(|other| other.name == operation.name)
{
return Err(InterfaceError::DuplicateOperation {
interface: service.name.clone(),
name: operation.name.clone(),
});
}
}
Ok(service)
}
fn reject_outside_service_grammar(ast: &Ast, iface: &InterfaceNode) -> Result<(), InterfaceError> {
let refuse = |construct: &'static str, at: String| {
Err(InterfaceError::OutsideServiceGrammar { construct, at })
};
if iface.kind != InterfaceKind::Interface {
return refuse(
"an interface declaration of a form other than `interface`",
format!("{} {}", iface.kind.as_str(), iface.name),
);
}
if let Ok(module) = ast.module(ast.root_module()) {
if let Some(corba) = module.facets.corba.as_ref() {
if let Some(first) = corba.imports.first() {
return refuse("an `import` directive", first.clone());
}
if let Some(first) = corba.repository_directives.first() {
return refuse("a `typeid`/`typeprefix` directive", first.clone());
}
}
}
let nested = |module: Id<ModuleNode>, scope: Option<&str>| {
module == iface.module && scope == Some(iface.name.as_str())
};
for (_, node) in ast.types() {
if nested(node.module, node.scope.as_deref()) {
return refuse(
"a type declaration in an interface body",
format!("{}::{}", iface.name, node.name),
);
}
}
for (_, node) in ast.constants() {
if nested(node.module, node.scope.as_deref()) {
return refuse(
"a constant declaration in an interface body",
format!("{}::{}", iface.name, node.name),
);
}
}
for (_, node) in ast.exceptions() {
if nested(node.module, node.scope.as_deref()) {
return refuse(
"an exception declaration in an interface body",
format!("{}::{}", iface.name, node.name),
);
}
}
Ok(())
}
const MAX_INHERITANCE_DEPTH: usize = 64;
pub fn all_operations_flattened(
ast: &Ast,
interface: Id<InterfaceNode>,
) -> Result<Vec<Operation>, InterfaceError> {
let mut seen_interfaces = Vec::new();
let mut seen_ops = Vec::new();
let mut out = Vec::new();
collect_flattened_operations(
ast,
interface,
&mut seen_interfaces,
&mut seen_ops,
&mut out,
0,
)?;
Ok(out)
}
pub fn all_attributes_flattened(
ast: &Ast,
interface: Id<InterfaceNode>,
) -> Result<Vec<Attribute>, InterfaceError> {
let mut seen_interfaces = Vec::new();
let mut seen_attrs = Vec::new();
let mut out = Vec::new();
collect_flattened_attributes(
ast,
interface,
&mut seen_interfaces,
&mut seen_attrs,
&mut out,
0,
)?;
Ok(out)
}
fn collect_flattened_attributes(
ast: &Ast,
interface: Id<InterfaceNode>,
seen_interfaces: &mut Vec<Id<InterfaceNode>>,
seen_attrs: &mut Vec<NodeId>,
out: &mut Vec<Attribute>,
depth: usize,
) -> Result<(), InterfaceError> {
if seen_interfaces.contains(&interface) {
return Ok(());
}
let this = service(ast, interface)?;
if depth >= MAX_INHERITANCE_DEPTH {
return Err(InterfaceError::TooDeep {
name: this.name.clone(),
limit: MAX_INHERITANCE_DEPTH,
});
}
seen_interfaces.push(interface);
for base in &this.bases {
collect_flattened_attributes(ast, base.id, seen_interfaces, seen_attrs, out, depth + 1)?;
}
for attribute in this.attributes {
if seen_attrs.contains(&attribute.node_id) {
continue;
}
seen_attrs.push(attribute.node_id);
out.push(attribute);
}
Ok(())
}
fn collect_flattened_operations(
ast: &Ast,
interface: Id<InterfaceNode>,
seen_interfaces: &mut Vec<Id<InterfaceNode>>,
seen_ops: &mut Vec<(NodeId, String)>,
out: &mut Vec<Operation>,
depth: usize,
) -> Result<(), InterfaceError> {
if seen_interfaces.contains(&interface) {
return Ok(());
}
let this = service(ast, interface)?;
if depth >= MAX_INHERITANCE_DEPTH {
return Err(InterfaceError::TooDeep {
name: this.name.clone(),
limit: MAX_INHERITANCE_DEPTH,
});
}
seen_interfaces.push(interface);
for base in &this.bases {
collect_flattened_operations(ast, base.id, seen_interfaces, seen_ops, out, depth + 1)?;
}
for operation in this.all_operations() {
if let Some(id) = operation.node_id {
let key = (id, operation.name.clone());
if seen_ops.contains(&key) {
continue;
}
seen_ops.push(key);
}
out.push(operation);
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ServiceMapping {
#[default]
Basic,
Enhanced,
EnhancedEmptyStructures,
}
impl ServiceMapping {
#[must_use]
pub const fn is_enhanced(self) -> bool {
matches!(self, Self::Enhanced | Self::EnhancedEmptyStructures)
}
#[must_use]
pub const fn empty_style(self) -> EmptyOperationStyle {
match self {
Self::EnhancedEmptyStructures => EmptyOperationStyle::EmptyAnnotation,
Self::Basic | Self::Enhanced => EmptyOperationStyle::DummyMember,
}
}
}
pub const REMOTE_EX_MEMBER: &str = "remoteEx";
pub const MEMBER_ID_MASK: u32 = 0x0FFF_FFFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EmptyOperationStyle {
#[default]
DummyMember,
EmptyAnnotation,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EnhancedMemberType {
Idl(Id<TypeNode>),
Dummy,
Struct(String),
Exception(Raise),
RemoteException,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnhancedMember {
pub name: String,
pub type_: EnhancedMemberType,
pub member_id: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnhancedStruct {
pub name: String,
pub choice: bool,
pub autoid: bool,
pub empty: bool,
pub mutable: bool,
pub members: Vec<EnhancedMember>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnhancedOperation {
pub interface: String,
pub module: Id<ModuleNode>,
pub operation: Operation,
pub in_struct: EnhancedStruct,
pub out_struct: EnhancedStruct,
pub result_struct: EnhancedStruct,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnhancedService {
pub operations: Vec<EnhancedOperation>,
pub request: EnhancedStruct,
pub reply: EnhancedStruct,
}
pub type DeclaredOperation = (String, Id<ModuleNode>, Operation);
#[must_use]
pub fn member_id(name: &str) -> u32 {
(operation_hash(name) as u32) & MEMBER_ID_MASK
}
pub fn operations_with_interface_flattened(
ast: &Ast,
service: &Service,
) -> Result<Vec<DeclaredOperation>, InterfaceError> {
let mut out: Vec<DeclaredOperation> = Vec::new();
let mut seen: Vec<Id<InterfaceNode>> = Vec::new();
for base in &service.bases {
collect_inherited(ast, base.id, &mut seen, &mut out, 0)?;
}
out.extend(
service
.all_operations()
.into_iter()
.map(|operation| (service.name.clone(), service.module, operation)),
);
Ok(out)
}
fn collect_inherited(
ast: &Ast,
interface: Id<InterfaceNode>,
seen: &mut Vec<Id<InterfaceNode>>,
out: &mut Vec<DeclaredOperation>,
depth: usize,
) -> Result<(), InterfaceError> {
if seen.contains(&interface) {
return Ok(());
}
let this = service(ast, interface)?;
if depth >= MAX_INHERITANCE_DEPTH {
return Err(InterfaceError::TooDeep {
name: this.name.clone(),
limit: MAX_INHERITANCE_DEPTH,
});
}
seen.push(interface);
for base in &this.bases {
collect_inherited(ast, base.id, seen, out, depth + 1)?;
}
for operation in this.all_operations() {
out.push((this.name.clone(), this.module, operation));
}
Ok(())
}
fn autoid_struct(
name: String,
choice: bool,
mutable: bool,
empty: bool,
members: Vec<(String, EnhancedMemberType)>,
) -> Result<EnhancedStruct, BindingError> {
let mut built: Vec<EnhancedMember> = Vec::with_capacity(members.len());
for (member_name, type_) in members {
let id = member_id(&member_name);
if let Some(clash) = built.iter().find(|member| member.member_id == id) {
return Err(BindingError::Codec(format!(
"members `{}` and `{member_name}` of `{name}` hash to the same member id {id:#010x}",
clash.name
)));
}
built.push(EnhancedMember {
name: member_name,
type_,
member_id: id,
});
}
Ok(EnhancedStruct {
name,
choice,
autoid: true,
empty,
mutable,
members: built,
})
}
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.contains(&candidate) {
return candidate;
}
}
format!("{RETURN_MEMBER} ")
}
fn remote_ex_member_name(operations: &[EnhancedOperation]) -> String {
let taken = |candidate: &str| {
operations
.iter()
.any(|operation| operation.operation.name == candidate)
};
if !taken(REMOTE_EX_MEMBER) {
return REMOTE_EX_MEMBER.to_string();
}
for n in 1..=i32::MAX {
let candidate = format!("{REMOTE_EX_MEMBER}_{n}");
if !taken(&candidate) {
return candidate;
}
}
format!("{REMOTE_EX_MEMBER} ")
}
pub fn enhanced_service(
ast: &Ast,
service: &Service,
empty: EmptyOperationStyle,
) -> Result<EnhancedService, BindingError> {
let empty_annotation = empty == EmptyOperationStyle::EmptyAnnotation;
let mut operations: Vec<EnhancedOperation> = Vec::new();
for (interface, module, operation) in operations_with_interface_flattened(ast, service)? {
if operations
.iter()
.any(|seen| seen.operation.name == operation.name)
{
return Err(BindingError::Codec(format!(
"operation `{}` is declared twice in `{}` and its bases",
operation.name, service.name
)));
}
let mut in_members: Vec<(String, EnhancedMemberType)> = operation
.inputs()
.map(|param| (param.name.clone(), EnhancedMemberType::Idl(param.type_)))
.collect();
let in_empty = in_members.is_empty();
if in_empty && !empty_annotation {
in_members.push((DUMMY_MEMBER.to_string(), EnhancedMemberType::Dummy));
}
let mut out_members: Vec<(String, EnhancedMemberType)> = operation
.outputs()
.map(|param| (param.name.clone(), EnhancedMemberType::Idl(param.type_)))
.collect();
if let Some(returns) = operation.returns {
let taken: Vec<String> = out_members.iter().map(|(name, _)| name.clone()).collect();
out_members.push((return_member_name(&taken), EnhancedMemberType::Idl(returns)));
}
let out_empty = out_members.is_empty();
if out_empty && !empty_annotation {
out_members.push((DUMMY_MEMBER.to_string(), EnhancedMemberType::Dummy));
}
let in_struct = autoid_struct(
in_struct_name(&interface, &operation.name),
false,
operation.mutable,
in_empty && empty_annotation,
in_members,
)?;
let out_struct = autoid_struct(
out_struct_name(&interface, &operation.name),
false,
operation.mutable,
out_empty && empty_annotation,
out_members,
)?;
let mut result_members = vec![(
RESULT_MEMBER.to_string(),
EnhancedMemberType::Struct(out_struct.name.clone()),
)];
for raised in &operation.raises {
result_members.push((
exception_member_name(&raised.name),
EnhancedMemberType::Exception(raised.clone()),
));
}
let result_struct = autoid_struct(
result_union_name(&interface, &operation.name),
true,
operation.mutable,
false,
result_members,
)?;
operations.push(EnhancedOperation {
interface,
module,
operation,
in_struct,
out_struct,
result_struct,
});
}
let request = autoid_struct(
request_type_name(&service.name),
true,
false,
false,
operations
.iter()
.map(|operation| {
(
operation.operation.name.clone(),
EnhancedMemberType::Struct(operation.in_struct.name.clone()),
)
})
.collect(),
)?;
let mut reply_members: Vec<(String, EnhancedMemberType)> = operations
.iter()
.map(|operation| {
(
operation.operation.name.clone(),
EnhancedMemberType::Struct(operation.result_struct.name.clone()),
)
})
.collect();
reply_members.push((
remote_ex_member_name(&operations),
EnhancedMemberType::RemoteException,
));
let reply = autoid_struct(
reply_type_name(&service.name),
true,
false,
false,
reply_members,
)?;
Ok(EnhancedService {
operations,
request,
reply,
})
}
fn qualified_name_of(ast: &Ast, node: &InterfaceNode) -> Result<String, InterfaceError> {
let chain = module_chain(ast, node.module)?;
let segments: Vec<&str> = chain.iter().map(|(_, name)| name.as_str()).collect();
Ok(qualified_interface_name(&segments, &node.name))
}
fn service_annotation_name(ast: &Ast, facets: &zerodds_ast::Facets) -> Option<String> {
if let Some(corba) = facets.corba.as_ref()
&& corba.is_service
&& let Some(name) = named_string_argument(ast, &corba.service_arguments, "name")
{
return Some(name);
}
let applied = applied_annotation(facets, "service")?;
named_string_argument(ast, &applied.arguments, "name")
}
fn applied_annotation<'a>(
facets: &'a zerodds_ast::Facets,
annotation: &str,
) -> Option<&'a zerodds_ast::AppliedAnnotation> {
facets.annotations.iter().find(|candidate| {
let tail = candidate
.name
.rsplit("::")
.next()
.unwrap_or(&candidate.name);
tail.strip_prefix('_').unwrap_or(tail) == annotation
})
}
fn named_string_argument(
ast: &Ast,
arguments: &[zerodds_ast::AppliedArgument],
key: &str,
) -> Option<String> {
let argument = arguments
.iter()
.find(|argument| argument.name.as_deref() == Some(key))?;
literal_string(ast, argument.value)
}
fn literal_string(ast: &Ast, id: Id<zerodds_ast::ConstExprNode>) -> Option<String> {
match &ast.expr(id).ok()?.expr {
zerodds_ast::ConstExpr::Literal(zerodds_ast::Literal::String(text)) => Some(text.clone()),
_ => None,
}
}
fn annotation_string(
ast: &Ast,
facets: &zerodds_ast::Facets,
annotation: &str,
key: &str,
) -> Option<String> {
let applied = applied_annotation(facets, annotation)?;
let argument = applied
.arguments
.iter()
.find(|argument| argument.name.is_none())
.or_else(|| {
applied
.arguments
.iter()
.find(|argument| argument.name.as_deref() == Some(key))
})?;
literal_string(ast, argument.value)
}
fn annotation_named_string(
ast: &Ast,
facets: &zerodds_ast::Facets,
annotation: &str,
key: &str,
) -> Option<String> {
let applied = applied_annotation(facets, annotation)?;
let argument = applied
.arguments
.iter()
.find(|argument| argument.name.as_deref() == Some(key))?;
literal_string(ast, argument.value)
}
fn one_operation(
ast: &Ast,
interface: &str,
op: &OperationNode,
) -> Result<Operation, InterfaceError> {
let at = format!("{interface}::{}", op.name);
if op.oneway || applied_annotation(&op.facets, "oneway").is_some() {
return Err(InterfaceError::OutsideServiceGrammar {
construct: "a `oneway` operation",
at,
});
}
if !op.contexts.is_empty() {
return Err(InterfaceError::OutsideServiceGrammar {
construct: "a `context` clause",
at,
});
}
let returns = match &op.return_type {
None => None,
Some(reference) => {
Some(
reference
.resolved()
.ok_or_else(|| InterfaceError::Unresolved {
construct: "return type",
at: at.clone(),
})?,
)
}
};
let mut params = Vec::with_capacity(op.params.len());
for param in &op.params {
let node = ast.param(*param).map_err(|_| InterfaceError::NoNode {
kind: "parameter",
at: at.clone(),
})?;
let type_ = node
.type_
.resolved()
.ok_or_else(|| InterfaceError::Unresolved {
construct: "parameter type",
at: format!("{at}({})", node.name),
})?;
if params
.iter()
.any(|other: &Parameter| other.name == node.name)
{
return Err(InterfaceError::DuplicateParameter {
at,
name: node.name.clone(),
});
}
params.push(Parameter {
name: node.name.clone(),
direction: node.direction,
type_,
});
}
Ok(Operation {
node_id: Some(op.node_id),
name: op.name.clone(),
returns,
params,
raises: raises(ast, &at, "raises", &op.raises)?,
origin: Origin::Declared,
mutable: applied_annotation(&op.facets, "Mutable").is_some(),
})
}
fn one_attribute(
ast: &Ast,
interface: &str,
attr: &AttributeNode,
) -> Result<Attribute, InterfaceError> {
let at = format!("{interface}::{}", attr.name);
let type_ = attr
.type_
.resolved()
.ok_or_else(|| InterfaceError::Unresolved {
construct: "attribute type",
at: at.clone(),
})?;
Ok(Attribute {
node_id: attr.node_id,
name: attr.name.clone(),
type_,
readonly: attr.readonly,
get_raises: raises(ast, &at, "getraises", &attr.get_raises)?,
set_raises: raises(ast, &at, "setraises", &attr.set_raises)?,
})
}
fn raises(
ast: &Ast,
at: &str,
clause: &'static str,
list: &[zerodds_ast_core::Ref<ExceptionNode>],
) -> Result<Vec<Raise>, InterfaceError> {
let mut out = Vec::with_capacity(list.len());
for reference in list {
let id = reference.resolved().ok_or(InterfaceError::Unresolved {
construct: clause,
at: at.to_string(),
})?;
let node = ast.exception(id).map_err(|_| InterfaceError::NoNode {
kind: "exception",
at: at.to_string(),
})?;
let chain = module_chain(ast, node.module)?;
let mut scoped: Vec<&str> = chain.iter().map(|(_, name)| name.as_str()).collect();
scoped.push(&node.name);
out.push(Raise {
id,
scoped_name: scoped.join("::"),
name: node.name.clone(),
});
}
Ok(out)
}