use crate::contract;
use crate::encode::ProbeError;
use crate::target_keywords;
use std::fmt::Write as _;
use zerodds_ast::{
Ast, Bound, CaseLabel, CharWidth, Extensibility, PrimitiveKind, TypeKind, TypeNode,
};
use zerodds_ast_core::Id;
fn refuse(path: &str, kind: &'static str) -> ProbeError {
ProbeError::Unsupported {
path: path.to_string(),
kind,
}
}
fn unfilled(path: &str, kind: &'static str) -> ProbeError {
ProbeError::Unfilled {
path: path.to_string(),
kind,
}
}
pub fn corba_idl(ast: &Ast, root: Id<TypeNode>) -> Result<String, ProbeError> {
let mut printer = Printer::default();
printer.declare(ast, root, &contract::root_path(ast, root))?;
Ok(printer.finish())
}
#[must_use]
pub fn corba_scoped_name(ast: &Ast, type_: Id<TypeNode>) -> Option<Vec<String>> {
ast.any_of_type(type_)
.ok()
.and_then(|any| ast.scoped_name_of(any))
.map(|scoped| scoped.segments().to_vec())
}
#[derive(Default)]
struct Printer {
done: Vec<Id<TypeNode>>,
open: Vec<Id<TypeNode>>,
declarations: Vec<(Vec<String>, String)>,
named: Vec<(Id<TypeNode>, String)>,
scope: Vec<String>,
}
impl Printer {
fn finish(self) -> String {
let mut out = String::new();
for (scope, text) in self.declarations {
for segment in &scope {
let _ = writeln!(out, "module {segment} {{");
}
out.push_str(&text);
for _ in &scope {
out.push_str("};\n");
}
}
out
}
fn declare(&mut self, ast: &Ast, type_: Id<TypeNode>, path: &str) -> Result<(), ProbeError> {
if self.done.contains(&type_) {
return Ok(());
}
if self.open.contains(&type_) {
return Err(refuse(
path,
"a recursive type, which CORBA CDR frames its own way",
));
}
self.open.push(type_);
let enclosing = self.scope.clone();
let result = self.declare_now(ast, type_, path);
self.scope = enclosing;
self.open.pop();
result?;
self.done.push(type_);
Ok(())
}
fn declare_now(
&mut self,
ast: &Ast,
type_: Id<TypeNode>,
path: &str,
) -> Result<(), ProbeError> {
let node = ast.type_(type_).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let kind = node.kind.clone();
let Some(scoped) = corba_scoped_name(ast, type_) else {
return self.declare_dependencies(ast, &kind, path);
};
let Some((name, scope)) = scoped.split_last() else {
return self.declare_dependencies(ast, &kind, path);
};
let scope = scope.to_vec();
self.scope = scope.clone();
match &kind {
TypeKind::Primitive(_)
| TypeKind::String { .. }
| TypeKind::Fixed { .. }
| TypeKind::Sequence { .. }
| TypeKind::Array { .. } => self.declare_dependencies(ast, &kind, path),
TypeKind::Alias { aliased } => {
let target = aliased.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the typedef target is unresolved"),
})?;
self.declare(ast, target, path)?;
let text = match ast.type_(target).map(|n| n.kind.clone()) {
Ok(TypeKind::Array { element, dims }) => {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the array element is unresolved"),
})?;
let counts = contract::array_dimensions(ast, &dims, path)?;
let brackets: String =
counts.iter().map(|count| format!("[{count}]")).collect();
format!(
"typedef {} {name}{brackets};\n",
self.spell(ast, element, path)?
)
}
_ => format!("typedef {} {name};\n", self.spell(ast, target, path)?),
};
self.declarations.push((scope, text));
Ok(())
}
TypeKind::Enumeration { .. } => {
let text = self.enumeration(ast, type_, name, path)?;
self.declarations.push((scope, text));
Ok(())
}
TypeKind::Structure { base, members } => {
if base.is_some() {
return Err(refuse(
path,
"a structure with a base, which CORBA IDL has no syntax for",
));
}
match ast.effective_extensibility(type_) {
Extensibility::Final | Extensibility::Appendable => {}
Extensibility::Mutable => {
return Err(refuse(
path,
"a @mutable type, whose PL_CDR1 parameter list is XTypes' and not CORBA's",
));
}
}
let ids: Vec<_> = members.clone();
let mut body = format!("struct {name} {{\n");
for id in ids {
let member = ast.member(id).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let member_path = contract::join(path, &member.name);
if member.facets.is_optional() {
return Err(refuse(
&member_path,
"an @optional member, whose presence marker is XTypes' and not CORBA's",
));
}
if is_non_serialized(&member.facets) {
return Err(refuse(
&member_path,
"a @non_serialized member, which CORBA IDL cannot leave off the wire",
));
}
let type_id = member
.type_
.resolved()
.ok_or_else(|| ProbeError::NoLayout {
detail: format!("{member_path}: the member type is unresolved"),
})?;
self.declare(ast, type_id, &member_path)?;
let _ = writeln!(
body,
" {}",
self.member_declaration(ast, type_id, &member.name, &member_path)?
);
}
body.push_str("};\n");
self.declarations.push((scope, body));
Ok(())
}
TypeKind::Union { discriminator, .. } => {
match ast.effective_extensibility(type_) {
Extensibility::Final | Extensibility::Appendable => {}
Extensibility::Mutable => {
return Err(refuse(
path,
"a @mutable union, whose per-member headers are XTypes' and not CORBA's",
));
}
}
let discriminator =
discriminator
.resolved()
.ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the discriminator is unresolved"),
})?;
self.declare(ast, discriminator, path)?;
let mut body = format!(
"union {name} switch ({}) {{\n",
self.spell(ast, discriminator, path)?
);
for case_id in ast.cases_of(type_) {
let case = ast.case(*case_id).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let member = ast
.member(case.member)
.map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let member_path = contract::join(path, &member.name);
let type_id = member
.type_
.resolved()
.ok_or_else(|| ProbeError::NoLayout {
detail: format!("{member_path}: the case member type is unresolved"),
})?;
self.declare(ast, type_id, &member_path)?;
for label in &case.labels {
match label {
CaseLabel::Default => body.push_str(" default:\n"),
CaseLabel::Value(expr) => {
let value = ast.eval_integral(*expr).map_err(|_| {
refuse(&member_path, "a case label that does not evaluate")
})?;
let _ = writeln!(
body,
" case {}:",
case_label(ast, discriminator, value, &member_path)?
);
}
}
}
let _ = writeln!(
body,
" {}",
self.member_declaration(ast, type_id, &member.name, &member_path)?
);
}
body.push_str("};\n");
self.declarations.push((scope, body));
Ok(())
}
TypeKind::Bitmask { .. } => Err(refuse(
path,
"a bitmask, which is XTypes 1.3 §7.3.1.2.1.4 and not CORBA IDL",
)),
TypeKind::Bitset { .. } => Err(refuse(
path,
"a bitset, which is XTypes 1.3 §7.3.1.2.1.3 and not CORBA IDL",
)),
TypeKind::Map { .. } => self.declare_dependencies(ast, &kind, path),
TypeKind::Valuetype { .. } => Err(refuse(
path,
"a valuetype, whose CDR form is CORBA 3.3 §9.3.4's tagged one and not §9.3.2's",
)),
other => Err(unfilled(path, other.as_str())),
}
}
fn declare_dependencies(
&mut self,
ast: &Ast,
kind: &TypeKind,
path: &str,
) -> Result<(), ProbeError> {
match kind {
TypeKind::Sequence { element, .. } | TypeKind::Array { element, .. } => {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the element type is unresolved"),
})?;
self.declare(ast, element, path)
}
TypeKind::Map { key, value, .. } => {
let key = key.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the map key type is unresolved"),
})?;
let value = value.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the map value type is unresolved"),
})?;
self.declare(ast, key, path)?;
self.declare(ast, value, path)
}
_ => Ok(()),
}
}
fn member_declaration(
&mut self,
ast: &Ast,
type_: Id<TypeNode>,
name: &str,
path: &str,
) -> Result<String, ProbeError> {
if let Ok(TypeKind::Array { element, dims }) = ast.type_(type_).map(|n| n.kind.clone()) {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the array element is unresolved"),
})?;
let counts = contract::array_dimensions(ast, &dims, path)?;
let brackets: String = counts.iter().map(|count| format!("[{count}]")).collect();
return Ok(format!(
"{} {name}{brackets};",
self.spell(ast, element, path)?
));
}
Ok(format!("{} {name};", self.spell(ast, type_, path)?))
}
fn enumeration(
&mut self,
ast: &Ast,
type_: Id<TypeNode>,
name: &str,
path: &str,
) -> Result<String, ProbeError> {
if ast.effective_bit_bound(type_) != 32 {
return Err(refuse(
path,
"an enumeration with a @bit_bound, where CORBA's holder is always a 32-bit unsigned long",
));
}
let mut names = Vec::new();
for (position, id) in ast.enumerators_of(type_).iter().enumerate() {
let enumerator = ast.enumerator(*id).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let value = ast.effective_enumerator_value(*id);
if value != Some(i128::try_from(position).unwrap_or(i128::MAX)) {
return Err(refuse(
path,
"an enumerator with an explicit @value, where CORBA's is its ordinal position",
));
}
names.push(enumerator.name.clone());
}
Ok(format!("enum {name} {{ {} }};\n", names.join(", ")))
}
fn name_it(&mut self, type_: Id<TypeNode>, shape: &str) -> Result<String, ProbeError> {
if let Some((_, name)) = self.named.iter().find(|(known, _)| *known == type_) {
return Ok(name.clone());
}
let name = format!("zd_anon_{}", self.named.len() + 1);
self.declarations.push((
self.scope.clone(),
format!("typedef {};\n", shape.replace("@NAME@", &name)),
));
let mut absolute = String::new();
for segment in &self.scope {
let _ = write!(absolute, "::{segment}");
}
let _ = write!(absolute, "::{name}");
self.named.push((type_, absolute.clone()));
Ok(absolute)
}
fn spell_element(
&mut self,
ast: &Ast,
type_: Id<TypeNode>,
path: &str,
) -> Result<String, ProbeError> {
let spelled = self.spell(ast, type_, path)?;
if spelled.starts_with("::") {
return Ok(spelled);
}
if matches!(
ast.type_(type_).map(|node| node.kind.clone()),
Ok(TypeKind::Sequence { .. } | TypeKind::Map { .. })
) {
return self.name_it(type_, &format!("{spelled} @NAME@"));
}
Ok(spelled)
}
fn spell(&mut self, ast: &Ast, type_: Id<TypeNode>, path: &str) -> Result<String, ProbeError> {
if let Some(scoped) = corba_scoped_name(ast, type_) {
let named = matches!(
ast.type_(type_).map(|n| n.kind.clone()),
Ok(TypeKind::Structure { .. }
| TypeKind::Union { .. }
| TypeKind::Enumeration { .. }
| TypeKind::Alias { .. })
);
if named {
return Ok(format!("::{}", scoped.join("::")));
}
}
let node = ast.type_(type_).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
match &node.kind {
TypeKind::Primitive(kind) => primitive(*kind, path).map(ToString::to_string),
TypeKind::String { width, bound } => {
let keyword = match width {
CharWidth::Char => "string",
CharWidth::WChar => "wstring",
};
match self.bound_value(ast, *bound, path)? {
Some(n) => Ok(format!("{keyword}<{n}>")),
None => Ok(keyword.to_string()),
}
}
TypeKind::Sequence { element, bound } => {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the sequence element is unresolved"),
})?;
let spelled = self.spell_element(ast, element, path)?;
match self.bound_value(ast, *bound, path)? {
Some(n) => Ok(format!("sequence<{spelled}, {n}>")),
None => Ok(format!("sequence<{spelled}>")),
}
}
TypeKind::Fixed { digits, scale } => Ok(format!("fixed<{digits},{scale}>")),
TypeKind::Array { element, dims } => {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the array element is unresolved"),
})?;
let counts = contract::array_dimensions(ast, dims, path)?;
let spelled = self.spell_element(ast, element, path)?;
let brackets: String = counts.iter().map(|count| format!("[{count}]")).collect();
self.name_it(type_, &format!("{spelled} @NAME@{brackets}"))
}
TypeKind::Alias { aliased } => {
let target = aliased.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the typedef target is unresolved"),
})?;
self.spell(ast, target, path)
}
TypeKind::Map { key, value, bound } => {
let key = key.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the map key type is unresolved"),
})?;
let value = value.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the map value type is unresolved"),
})?;
let key_spelled = self.spell_element(ast, key, path)?;
let value_spelled = self.spell_element(ast, value, path)?;
match self.bound_value(ast, *bound, path)? {
Some(n) => Ok(format!("map<{key_spelled}, {value_spelled}, {n}>")),
None => Ok(format!("map<{key_spelled}, {value_spelled}>")),
}
}
TypeKind::Bitmask { .. } => Err(refuse(
path,
"a bitmask, which is XTypes 1.3 §7.3.1.2.1.4 and not CORBA IDL",
)),
TypeKind::Bitset { .. } => Err(refuse(
path,
"a bitset, which is XTypes 1.3 §7.3.1.2.1.3 and not CORBA IDL",
)),
other => Err(unfilled(path, other.as_str())),
}
}
fn bound_value(&self, ast: &Ast, bound: Bound, path: &str) -> Result<Option<i128>, ProbeError> {
match bound.expr() {
None => Ok(None),
Some(expr) => ast
.eval_integral(expr)
.map(Some)
.map_err(|_| refuse(path, "a bound that does not evaluate to a count")),
}
}
}
fn is_non_serialized(facets: &zerodds_ast::Facets) -> bool {
facets
.xtypes
.as_ref()
.is_some_and(|xtypes| xtypes.is_non_serialized)
}
fn primitive(kind: PrimitiveKind, path: &str) -> Result<&'static str, ProbeError> {
Ok(match kind {
PrimitiveKind::Boolean => "boolean",
PrimitiveKind::Octet | PrimitiveKind::Int8 | PrimitiveKind::UInt8 => "octet",
PrimitiveKind::Char => "char",
PrimitiveKind::WChar => "wchar",
PrimitiveKind::Int16 => "short",
PrimitiveKind::UInt16 => "unsigned short",
PrimitiveKind::Int32 => "long",
PrimitiveKind::UInt32 => "unsigned long",
PrimitiveKind::Int64 => "long long",
PrimitiveKind::UInt64 => "unsigned long long",
PrimitiveKind::Float32 => "float",
PrimitiveKind::Float64 => "double",
PrimitiveKind::Float128 => {
return Err(ProbeError::MappingDivergence {
path: path.to_string(),
kind: "a long double, whose C++ mapping is x87 extended and not \
§9.3.1.3's binary128",
});
}
})
}
fn case_label(
ast: &Ast,
discriminator: Id<TypeNode>,
value: i128,
path: &str,
) -> Result<String, ProbeError> {
let resolved = contract::resolve_alias(ast, discriminator);
let node = ast.type_(resolved).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
match &node.kind {
TypeKind::Enumeration { .. } => {
for id in ast.enumerators_of(resolved) {
if ast.effective_enumerator_value(*id) == Some(value) {
let enumerator = ast.enumerator(*id).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let scope = corba_scoped_name(ast, resolved)
.and_then(|segments| {
let (_, scope) = segments.split_last()?;
Some(scope.to_vec())
})
.unwrap_or_default();
let mut spelled = String::from("::");
for segment in scope {
spelled.push_str(&segment);
spelled.push_str("::");
}
spelled.push_str(&enumerator.name);
return Ok(spelled);
}
}
Err(refuse(path, "a case label that names no enumerator"))
}
TypeKind::Primitive(PrimitiveKind::Boolean) => {
Ok(if value == 0 { "FALSE" } else { "TRUE" }.to_string())
}
TypeKind::Primitive(PrimitiveKind::Char) => {
let code = u8::try_from(value)
.map_err(|_| refuse(path, "a char case label outside one octet"))?;
Ok(format!("'\\x{code:02x}'"))
}
TypeKind::Primitive(PrimitiveKind::WChar) => {
let code = u16::try_from(value)
.map_err(|_| refuse(path, "a wchar case label outside sixteen bits"))?;
Ok(format!("L'\\u{code:04x}'"))
}
_ => Ok(format!("{value}")),
}
}
#[must_use]
pub fn java_name(ast: &Ast, type_: Id<TypeNode>) -> String {
corba_scoped_name(ast, type_).map_or_else(
|| String::from("UNKNOWN"),
|scoped| {
scoped
.iter()
.map(|segment| target_keywords::java(segment).into_owned())
.collect::<Vec<String>>()
.join(".")
},
)
}
#[must_use]
pub fn cpp_name(ast: &Ast, type_: Id<TypeNode>) -> String {
corba_scoped_name(ast, type_).map_or_else(
|| String::from("UNKNOWN"),
|scoped| {
scoped
.iter()
.map(|segment| target_keywords::cxx(segment).into_owned())
.collect::<Vec<String>>()
.join("::")
},
)
}