use zerodds_idl::ast::types::{Declarator, Member, StructDef, TypeSpec};
use crate::annotations::{StructExtensibility, struct_extensibility};
use crate::error::{Result, RustGenError};
use crate::type_map::escape_keyword;
use crate::type_map::rust_type_for;
fn declarator_ident(decl: &Declarator) -> String {
let raw = match decl {
Declarator::Simple(n) => &n.text,
Declarator::Array(a) => &a.name.text,
};
escape_keyword(raw)
}
pub fn emit_struct(out: &mut String, s: &StructDef, module_path: &[String]) -> Result<()> {
let extensibility = struct_extensibility(&s.annotations);
emit_struct_decl(out, s)?;
out.push('\n');
emit_dds_type_impl(out, s, extensibility, module_path)?;
Ok(())
}
fn emit_struct_decl(out: &mut String, s: &StructDef) -> Result<()> {
out.push_str("/// Generated by `zerodds-idl-rust` from IDL.\n");
out.push_str("#[derive(Debug, Clone, PartialEq, Default)]\n");
out.push_str("pub struct ");
out.push_str(&escape_keyword(&s.name.text));
out.push_str(" {\n");
for member in &s.members {
emit_member_field(out, member)?;
}
out.push_str("}\n");
Ok(())
}
fn emit_member_field(out: &mut String, member: &Member) -> Result<()> {
let rust_ty = rust_type_for(&member.type_spec)?;
let optional = crate::annotations::member_is_optional(&member.annotations);
for declarator in &member.declarators {
match declarator {
Declarator::Simple(name) => {
out.push_str(" pub ");
out.push_str(&escape_keyword(&name.text));
out.push_str(": ");
if optional {
out.push_str(&format!("::core::option::Option<{rust_ty}>"));
} else {
out.push_str(&rust_ty);
}
out.push_str(",\n");
}
Declarator::Array(arr) => {
let mut wrapped = rust_ty.clone();
for size_expr in arr.sizes.iter().rev() {
let size = crate::type_map::const_expr_as_usize(size_expr).ok_or(
RustGenError::InvalidAnnotation {
name: "array-size".to_string(),
reason: "non-integer array dimension",
},
)?;
wrapped = format!("[{wrapped}; {size}]");
}
out.push_str(" pub ");
out.push_str(&escape_keyword(&arr.name.text));
out.push_str(": ");
if optional {
out.push_str(&format!("::core::option::Option<{wrapped}>"));
} else {
out.push_str(&wrapped);
}
out.push_str(",\n");
}
}
}
Ok(())
}
fn emit_dds_type_impl(
out: &mut String,
s: &StructDef,
extensibility: StructExtensibility,
module_path: &[String],
) -> Result<()> {
let key_members: Vec<&Member> = s
.members
.iter()
.filter(|m| crate::annotations::member_is_key(&m.annotations))
.collect();
let has_key = !key_members.is_empty();
let key_holder_max_size = compute_key_holder_max_size(&key_members);
out.push_str("impl zerodds_dcps::DdsType for ");
out.push_str(&escape_keyword(&s.name.text));
out.push_str(" {\n");
out.push_str(" const TYPE_NAME: &'static str = \"");
for module in module_path {
out.push_str(module);
out.push_str("::");
}
out.push_str(&s.name.text);
out.push_str("\";\n");
let ext_variant = match extensibility {
StructExtensibility::Final => "Final",
StructExtensibility::Appendable => "Appendable",
StructExtensibility::Mutable => "Mutable",
};
out.push_str(&format!(
" const EXTENSIBILITY: zerodds_dcps::Extensibility = zerodds_dcps::Extensibility::{ext_variant};\n"
));
if has_key {
out.push_str(" const HAS_KEY: bool = true;\n");
if let Some(size) = key_holder_max_size {
out.push_str(&format!(
" const KEY_HOLDER_MAX_SIZE: ::core::option::Option<usize> = ::core::option::Option::Some({size});\n"
));
}
}
if crate::annotations::struct_is_nested(&s.annotations) {
out.push_str(" const IS_NESTED: bool = true;\n");
}
let type_id_expr = crate::type_identifier::struct_type_identifier_expr(s);
out.push_str(&format!(
" const TYPE_IDENTIFIER: zerodds_types::TypeIdentifier = {type_id_expr};\n"
));
out.push('\n');
out.push_str(" fn encode(&self, out: &mut ::std::vec::Vec<u8>) -> ::core::result::Result<(), zerodds_dcps::EncodeError> {\n");
out.push_str(
" let mut writer = zerodds_cdr::BufferWriter::new(zerodds_cdr::Endianness::Little);\n",
);
emit_encode_body(out, s, extensibility)?;
out.push_str(" out.extend_from_slice(&writer.into_bytes());\n");
out.push_str(" Ok(())\n");
out.push_str(" }\n\n");
out.push_str(
" fn decode(bytes: &[u8]) -> ::core::result::Result<Self, zerodds_dcps::DecodeError> {\n",
);
out.push_str(" let mut reader = zerodds_cdr::BufferReader::new(bytes, zerodds_cdr::Endianness::Little);\n");
emit_decode_body(out, s, extensibility)?;
out.push_str(" }\n");
if has_key {
out.push('\n');
emit_key_holder_be(out, &key_members)?;
}
out.push('\n');
emit_field_value(out, s)?;
out.push_str("}\n");
Ok(())
}
fn emit_field_value(out: &mut String, s: &StructDef) -> Result<()> {
out.push_str(
" fn field_value(&self, path: &str) -> ::core::option::Option<zerodds_sql_filter::Value> {\n",
);
out.push_str(" match path {\n");
for member in &s.members {
let optional = crate::annotations::member_is_optional(&member.annotations);
for declarator in &member.declarators {
let name = declarator_ident(declarator);
emit_field_value_arm(out, &member.type_spec, &name, declarator, optional)?;
}
}
out.push_str(" _ => ::core::option::Option::None,\n");
out.push_str(" }\n");
out.push_str(" }\n");
Ok(())
}
fn emit_field_value_arm(
out: &mut String,
spec: &TypeSpec,
name: &str,
declarator: &Declarator,
optional: bool,
) -> Result<()> {
use zerodds_idl::ast::types::{FloatingType, PrimitiveType};
if matches!(declarator, Declarator::Array(_)) {
return Ok(());
}
let value_expr: Option<String> = match spec {
TypeSpec::Primitive(
PrimitiveType::Integer(_)
| PrimitiveType::Octet
| PrimitiveType::Char
| PrimitiveType::WideChar,
) => Some(if optional {
format!("self.{name}.as_ref().map(|v| zerodds_sql_filter::Value::Int(*v as i64))")
} else {
format!(
"::core::option::Option::Some(zerodds_sql_filter::Value::Int(self.{name} as i64))"
)
}),
TypeSpec::Primitive(PrimitiveType::Floating(FloatingType::Float)) => Some(if optional {
format!("self.{name}.as_ref().map(|v| zerodds_sql_filter::Value::Float(*v as f64))")
} else {
format!(
"::core::option::Option::Some(zerodds_sql_filter::Value::Float(self.{name} as f64))"
)
}),
TypeSpec::Primitive(PrimitiveType::Floating(_)) => Some(if optional {
format!("self.{name}.as_ref().map(|v| zerodds_sql_filter::Value::Float(*v))")
} else {
format!("::core::option::Option::Some(zerodds_sql_filter::Value::Float(self.{name}))")
}),
TypeSpec::Primitive(PrimitiveType::Boolean) => Some(if optional {
format!("self.{name}.as_ref().map(|v| zerodds_sql_filter::Value::Bool(*v))")
} else {
format!("::core::option::Option::Some(zerodds_sql_filter::Value::Bool(self.{name}))")
}),
TypeSpec::String(_) => Some(if optional {
format!("self.{name}.as_ref().map(|v| zerodds_sql_filter::Value::String(v.clone()))")
} else {
format!(
"::core::option::Option::Some(zerodds_sql_filter::Value::String(self.{name}.clone()))"
)
}),
TypeSpec::Scoped(_) => {
out.push_str(&format!(
" p if p.starts_with(\"{name}.\") => self.{name}.field_value(&p[\"{name}.\".len()..]),\n"
));
None
}
TypeSpec::Sequence(_) | TypeSpec::Fixed(_) | TypeSpec::Map(_) | TypeSpec::Any => None,
};
if let Some(expr) = value_expr {
out.push_str(&format!(" \"{name}\" => {expr},\n"));
}
Ok(())
}
fn compute_key_holder_max_size(key_members: &[&Member]) -> Option<usize> {
let mut total = 0usize;
for member in key_members {
let size = crate::type_map::wire_size_bound(&member.type_spec)?;
for declarator in &member.declarators {
let count = match declarator {
Declarator::Simple(_) => 1,
Declarator::Array(arr) => {
let mut n = 1usize;
for size_expr in &arr.sizes {
let dim = crate::type_map::const_expr_as_usize(size_expr)?;
n = n.checked_mul(dim)?;
}
n
}
};
total = total.checked_add(size.checked_mul(count)?)?;
}
}
Some(total)
}
fn emit_key_holder_be(out: &mut String, key_members: &[&Member]) -> Result<()> {
out.push_str(
" fn encode_key_holder_be(&self, holder: &mut zerodds_cdr::PlainCdr2BeKeyHolder) {\n",
);
let mut ordered: Vec<(u32, &Member)> = key_members
.iter()
.enumerate()
.map(|(idx, m)| {
(
crate::annotations::member_id(&m.annotations).unwrap_or(idx as u32),
*m,
)
})
.collect();
ordered.sort_by_key(|(id, _)| *id);
for (_, member) in &ordered {
for declarator in &member.declarators {
let name = declarator_ident(declarator);
emit_key_field_write(out, &member.type_spec, &format!("self.{name}"))?;
}
}
out.push_str(" }\n");
Ok(())
}
fn emit_key_field_write(out: &mut String, spec: &TypeSpec, value_expr: &str) -> Result<()> {
use zerodds_idl::ast::types::{FloatingType, IntegerType, PrimitiveType};
match spec {
TypeSpec::Primitive(p) => {
let method = match p {
PrimitiveType::Integer(IntegerType::Int8) => "write_i8",
PrimitiveType::Integer(IntegerType::UInt8) | PrimitiveType::Octet => "write_u8",
PrimitiveType::Integer(IntegerType::Short | IntegerType::Int16) => "write_i16",
PrimitiveType::Integer(IntegerType::UShort | IntegerType::UInt16) => "write_u16",
PrimitiveType::Integer(IntegerType::Long | IntegerType::Int32) => "write_i32",
PrimitiveType::Integer(IntegerType::ULong | IntegerType::UInt32) => "write_u32",
PrimitiveType::Integer(IntegerType::LongLong | IntegerType::Int64) => "write_i64",
PrimitiveType::Integer(IntegerType::ULongLong | IntegerType::UInt64) => "write_u64",
PrimitiveType::Floating(FloatingType::Float) => "write_f32",
PrimitiveType::Floating(FloatingType::Double | FloatingType::LongDouble) => {
"write_f64"
}
PrimitiveType::Boolean => "write_u8",
PrimitiveType::Char => "write_u8",
PrimitiveType::WideChar => "write_u32",
};
if matches!(
p,
PrimitiveType::Char | PrimitiveType::WideChar | PrimitiveType::Boolean
) {
out.push_str(&format!(" holder.{method}({value_expr} as _);\n"));
} else {
out.push_str(&format!(" holder.{method}({value_expr});\n"));
}
}
TypeSpec::String(_) => {
out.push_str(&format!(" holder.write_string(&{value_expr});\n"));
}
TypeSpec::Sequence(_) | TypeSpec::Scoped(_) => {
return Err(RustGenError::Unsupported {
what: "complex @key field (sequence or nested struct)",
at: 0,
});
}
TypeSpec::Fixed(f) => {
return Err(RustGenError::Unsupported {
what: "fixed @key",
at: f.span.start,
});
}
TypeSpec::Map(m) => {
return Err(RustGenError::Unsupported {
what: "map @key",
at: m.span.start,
});
}
TypeSpec::Any => {
return Err(RustGenError::Unsupported {
what: "any @key",
at: 0,
});
}
}
Ok(())
}
fn emit_encode_body(
out: &mut String,
s: &StructDef,
extensibility: StructExtensibility,
) -> Result<()> {
match extensibility {
StructExtensibility::Final => {
for member in &s.members {
emit_member_encode(out, member, " ")?;
}
}
StructExtensibility::Appendable => {
out.push_str(" zerodds_cdr::struct_enc::encode_appendable(&mut writer, |w| {\n");
for member in &s.members {
emit_member_encode_with_writer(out, member, " ", "w")?;
}
out.push_str(" Ok(())\n");
out.push_str(" })?;\n");
}
StructExtensibility::Mutable => {
let required_ids: Vec<u32> = s
.members
.iter()
.enumerate()
.filter(|(_, m)| !crate::annotations::member_is_optional(&m.annotations))
.map(|(idx, m)| crate::annotations::member_id(&m.annotations).unwrap_or(idx as u32))
.collect();
let required_list = required_ids
.iter()
.map(|id| id.to_string())
.collect::<Vec<_>>()
.join(", ");
out.push_str(" zerodds_cdr::struct_enc::encode_appendable(&mut writer, |w| {\n");
out.push_str(&format!(
" let mut enc = zerodds_cdr::struct_enc::MutableStructEncoder::new(w, ::std::vec![{required_list}]);\n"
));
for (idx, member) in s.members.iter().enumerate() {
emit_mutable_member_encode(out, member, idx, " ")?;
}
out.push_str(" enc.finish()?;\n");
out.push_str(" Ok(())\n");
out.push_str(" })?;\n");
}
}
Ok(())
}
fn emit_member_encode(out: &mut String, member: &Member, indent: &str) -> Result<()> {
emit_member_encode_with_writer(out, member, indent, "&mut writer")
}
fn emit_member_encode_with_writer(
out: &mut String,
member: &Member,
indent: &str,
writer_expr: &str,
) -> Result<()> {
for declarator in &member.declarators {
let name = declarator_ident(declarator);
out.push_str(indent);
emit_field_encode(out, &member.type_spec, &format!("self.{name}"), writer_expr)?;
out.push('\n');
}
Ok(())
}
pub fn emit_field_encode(
out: &mut String,
spec: &TypeSpec,
value_expr: &str,
writer_expr: &str,
) -> Result<()> {
let _ = spec;
out.push_str(&format!(
"<_ as zerodds_cdr::CdrEncode>::encode(&{value_expr}, {writer_expr})?;"
));
Ok(())
}
fn emit_mutable_member_encode(
out: &mut String,
member: &Member,
fallback_id: usize,
indent: &str,
) -> Result<()> {
let id = crate::annotations::member_id(&member.annotations).unwrap_or(fallback_id as u32);
let must_understand = crate::annotations::member_must_understand(&member.annotations);
for declarator in &member.declarators {
let name = declarator_ident(declarator);
out.push_str(indent);
out.push_str(&format!(
"enc.encode_member({id}, {must_understand}, |w| {{ "
));
emit_field_encode(out, &member.type_spec, &format!("self.{name}"), "w")?;
out.push_str(" Ok(()) })?;\n");
}
Ok(())
}
fn emit_decode_body(
out: &mut String,
s: &StructDef,
extensibility: StructExtensibility,
) -> Result<()> {
match extensibility {
StructExtensibility::Final => {
for member in &s.members {
emit_member_decode_let(out, member, " ")?;
}
out.push_str(" Ok(Self {\n");
for member in &s.members {
for declarator in &member.declarators {
let name = declarator_ident(declarator);
out.push_str(&format!(" {name},\n"));
}
}
out.push_str(" })\n");
}
StructExtensibility::Appendable => {
out.push_str(" zerodds_cdr::struct_enc::decode_appendable(&mut reader, |r| {\n");
for member in &s.members {
emit_member_decode_let_with_reader(out, member, " ", "r")?;
}
out.push_str(" Ok(Self {\n");
for member in &s.members {
for declarator in &member.declarators {
let name = declarator_ident(declarator);
out.push_str(&format!(" {name},\n"));
}
}
out.push_str(" })\n");
out.push_str(" }).map_err(::core::convert::Into::into)\n");
}
StructExtensibility::Mutable => {
out.push_str(" zerodds_cdr::struct_enc::decode_appendable(&mut reader, |r| {\n");
for (idx, member) in s.members.iter().enumerate() {
let id = crate::annotations::member_id(&member.annotations).unwrap_or(idx as u32);
let optional = crate::annotations::member_is_optional(&member.annotations);
let base_type = rust_type_for(&member.type_spec)?;
let target = if optional {
format!("::core::option::Option<{base_type}>")
} else {
base_type
};
for declarator in &member.declarators {
let name = declarator_ident(declarator);
out.push_str(&format!(
" // member-id {id}\n let mut {name}: ::core::option::Option<{target}> = ::core::option::Option::None;\n"
));
}
}
out.push_str(" loop {\n");
out.push_str(
" match zerodds_cdr::struct_enc::read_mutable_member(r)? {\n",
);
out.push_str(" ::core::option::Option::Some(member) => {\n");
out.push_str(" let mut body_reader = zerodds_cdr::BufferReader::new(member.body, zerodds_cdr::Endianness::Little);\n");
out.push_str(" match member.member_id {\n");
for (idx, member) in s.members.iter().enumerate() {
let id = crate::annotations::member_id(&member.annotations).unwrap_or(idx as u32);
let optional = crate::annotations::member_is_optional(&member.annotations);
for declarator in &member.declarators {
let name = declarator_ident(declarator);
out.push_str(&format!(" {id} => {{\n"));
out.push_str(&format!(
" {name} = ::core::option::Option::Some("
));
emit_field_decode_with_optional(
out,
&member.type_spec,
"&mut body_reader",
optional,
)?;
out.push_str(");\n");
out.push_str(" }\n");
}
}
out.push_str(" _ => {\n");
out.push_str(" if member.must_understand {\n");
out.push_str(" return ::core::result::Result::Err(zerodds_cdr::DecodeError::UnknownMustUnderstandMember {\n");
out.push_str(" member_id: member.member_id,\n");
out.push_str(" });\n");
out.push_str(" }\n");
out.push_str(" // unknown optional member: skip body\n");
out.push_str(" }\n");
out.push_str(" }\n");
out.push_str(" }\n");
out.push_str(" ::core::option::Option::None => break,\n");
out.push_str(" }\n");
out.push_str(" }\n");
out.push_str(" ::core::result::Result::Ok(Self {\n");
for (idx, member) in s.members.iter().enumerate() {
let id = crate::annotations::member_id(&member.annotations).unwrap_or(idx as u32);
let optional = crate::annotations::member_is_optional(&member.annotations);
for declarator in &member.declarators {
let name = declarator_ident(declarator);
if optional {
out.push_str(&format!(
" {name}: {name}.unwrap_or(::core::option::Option::None),\n"
));
} else {
out.push_str(&format!(
" {name}: {name}.ok_or(zerodds_cdr::DecodeError::MissingNonOptionalMember {{ member_id: {id} }})?,\n"
));
}
}
}
out.push_str(" })\n");
out.push_str(" }).map_err(::core::convert::Into::into)\n");
}
}
Ok(())
}
fn emit_member_decode_let(out: &mut String, member: &Member, indent: &str) -> Result<()> {
emit_member_decode_let_with_reader(out, member, indent, "&mut reader")
}
fn emit_member_decode_let_with_reader(
out: &mut String,
member: &Member,
indent: &str,
reader_expr: &str,
) -> Result<()> {
let optional = crate::annotations::member_is_optional(&member.annotations);
for declarator in &member.declarators {
let name = declarator_ident(declarator);
out.push_str(indent);
out.push_str(&format!("let {name} = "));
emit_field_decode_with_optional(out, &member.type_spec, reader_expr, optional)?;
out.push_str(";\n");
}
Ok(())
}
fn emit_field_decode_with_optional(
out: &mut String,
spec: &TypeSpec,
reader_expr: &str,
optional: bool,
) -> Result<()> {
let target = rust_type_for(spec)?;
let final_target = if optional {
format!("::core::option::Option<{target}>")
} else {
target
};
out.push_str(&format!(
"<{final_target} as zerodds_cdr::CdrDecode>::decode({reader_expr})?"
));
Ok(())
}