use crate::contract::{self, Consumed};
use crate::encode::ProbeError;
use serde_json::Value;
use std::fmt::Write as _;
use zerodds_ast::{Ast, CharWidth, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::Id;
enum Place {
Accessor {
object: String,
name: String,
},
Element(String),
MapEntry(String),
}
impl Place {
fn assign(&self, literal: &str) -> String {
match self {
Self::Accessor { object, name } => format!("{object}.{name}({literal});"),
Self::Element(expression) | Self::MapEntry(expression) => {
format!("{expression} = {literal};")
}
}
}
fn reference(&self) -> String {
match self {
Self::Accessor { object, name } => format!("{object}.{name}()"),
Self::Element(expression) | Self::MapEntry(expression) => expression.clone(),
}
}
}
#[derive(Debug, Default)]
struct Cpp {
source: String,
seen: Consumed,
}
pub fn fill_fastdds(ast: &Ast, type_: Id<TypeNode>, values: &Value) -> Result<String, ProbeError> {
fill_fastdds_recording(ast, type_, values, &mut Consumed::new())
}
pub fn fill_fastdds_recording(
ast: &Ast,
type_: Id<TypeNode>,
values: &Value,
seen: &mut Consumed,
) -> Result<String, ProbeError> {
contract::reject_alias_cycle(ast)?;
let mut out = Cpp::default();
let root = contract::root_path(ast, type_);
let result = emit(
ast,
type_,
values,
&Place::Element(String::from("sample")),
&mut out,
&root,
);
*seen = core::mem::take(&mut out.seen);
result?;
Ok(out.source)
}
#[must_use]
pub fn cpp_name(ast: &Ast, type_: Id<TypeNode>) -> String {
ast.any_of_type(type_)
.ok()
.and_then(|any| ast.scoped_name_of(any))
.map_or_else(
|| String::from("UNKNOWN"),
|scoped| scoped.segments().join("::"),
)
}
fn emit(
ast: &Ast,
type_: Id<TypeNode>,
value: &Value,
place: &Place,
out: &mut Cpp,
path: &str,
) -> Result<(), ProbeError> {
let before = out.source.len();
emit_value(ast, type_, value, place, out, path)?;
if let Place::MapEntry(subscript) = place {
if out.source.len() == before {
let _ = writeln!(out.source, "(void){subscript};");
}
}
Ok(())
}
#[allow(clippy::too_many_lines)]
fn emit_value(
ast: &Ast,
type_: Id<TypeNode>,
value: &Value,
place: &Place,
out: &mut Cpp,
path: &str,
) -> Result<(), ProbeError> {
let node = ast.type_(type_).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
match &node.kind {
TypeKind::Primitive(PrimitiveKind::Float128) => Err(ProbeError::Unsupported {
path: path.to_string(),
kind: "a long double, whose Fast DDS bytes differ between x86-64 and aarch64",
}),
TypeKind::Primitive(kind) => {
let literal = primitive_literal(*kind, value, path)?;
out.seen.scalar(path);
let _ = writeln!(out.source, "{}", place.assign(&literal));
Ok(())
}
TypeKind::String {
width: CharWidth::Char,
..
} => {
let text = contract::text(value, path)?;
out.seen.scalar(path);
let _ = writeln!(out.source, "{}", place.assign(&cpp_string(text)));
Ok(())
}
TypeKind::String {
width: CharWidth::WChar,
..
} => {
let text = contract::text(value, path)?;
out.seen.scalar(path);
emit_wstring(text, place, out);
Ok(())
}
TypeKind::Bitmask { .. } => {
let set = contract::bit_names(ast, type_, value, path)?;
let literal = bitmask_literal(ast, type_, &set, out, path);
let _ = writeln!(out.source, "{}", place.assign(&literal));
Ok(())
}
TypeKind::Bitset { .. } => {
let object = contract::object(value, path)?;
let declared = contract::bitfield_names(ast, &ast.all_bitfields_of(type_));
contract::reject_unknown_keys(object, &declared, path)?;
let here = place.reference();
for name in &declared {
let field_path = contract::join(path, name);
let Some(field_value) = object.get(name) else {
return Err(ProbeError::Missing { path: field_path });
};
let raw = contract::integer(field_value, &field_path)?;
out.seen.scalar(&field_path);
let _ = writeln!(out.source, "{here}.{name} = {raw};");
}
Ok(())
}
TypeKind::Map {
key,
value: entry,
bound,
} => {
let key_type = key.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the map key type is unresolved"),
})?;
let value_type = entry.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the map value type is unresolved"),
})?;
let entries = contract::bounded_items(ast, value, *bound, "entries", path)?;
let here = place.reference();
for (index, pair) in entries.iter().enumerate() {
let entry_path = format!("{path}[{index}]");
let (entry_key, entry_value) = contract::map_entry(pair, &entry_path)?;
let key_path = contract::join(&entry_path, "key");
let key_literal = scalar_literal(ast, key_type, entry_key, &key_path)?;
out.seen.scalar(&key_path);
emit(
ast,
value_type,
entry_value,
&Place::MapEntry(format!("{here}[{key_literal}]")),
out,
&contract::join(&entry_path, "value"),
)?;
}
Ok(())
}
TypeKind::Fixed { .. } => Err(ProbeError::Unsupported {
path: path.to_string(),
kind: "a fixed, which fastddsgen rejects in the IDL before any \
statement could be written for it",
}),
TypeKind::Enumeration { .. } => {
let (name, _) = contract::enumerator(ast, type_, value, path)?;
out.seen.scalar(path);
let literal = format!("{}::{name}", cpp_name(ast, type_));
let _ = writeln!(out.source, "{}", place.assign(&literal));
Ok(())
}
TypeKind::Alias { aliased } => {
let target = aliased.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the typedef target is unresolved"),
})?;
emit(ast, target, value, place, out, path)
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let object = contract::object(value, path)?;
let here = place.reference();
let members = contract::members(ast, type_);
let declared: Vec<String> = members.iter().map(|member| member.name.clone()).collect();
contract::reject_unknown_keys(object, &declared, path)?;
for member in &members {
let Some(member_value) = contract::member_value(object, member, path)? else {
continue;
};
if crate::dump_cpp::is_external(ast, type_, &member.name) && member.optional {
let getter = format!("{here}.{}()", member.name);
let _ = writeln!(
out.source,
"{{ using zd_t = std::remove_reference_t<decltype(*({getter}.value()))>; \
{getter} = eprosima::fastcdr::external<zd_t>(std::make_shared<zd_t>()); }}"
);
emit(
ast,
member.type_,
member_value,
&Place::Element(format!("(*{getter}.value())")),
out,
&contract::join(path, &member.name),
)?;
continue;
}
if crate::dump_cpp::is_external(ast, type_, &member.name) && !member.optional {
let getter = format!("{here}.{}()", member.name);
let _ = writeln!(
out.source,
"{{ using zd_t = std::remove_reference_t<decltype(*{getter})>; \
{getter} = eprosima::fastcdr::external<zd_t>(std::make_shared<zd_t>()); }}"
);
emit(
ast,
member.type_,
member_value,
&Place::Element(format!("(*{getter})")),
out,
&contract::join(path, &member.name),
)?;
continue;
}
if member.optional && crate::fill::is_string_type(ast, member.type_) {
emit(
ast,
member.type_,
member_value,
&Place::Element(format!("{here}.{}()", member.name)),
out,
&contract::join(path, &member.name),
)?;
continue;
}
emit(
ast,
member.type_,
member_value,
&Place::Accessor {
object: here.clone(),
name: member.name.clone(),
},
out,
&contract::join(path, &member.name),
)?;
}
Ok(())
}
TypeKind::Union { .. } => {
let object = contract::object(value, path)?;
let discriminant = contract::discriminator(object, path)?;
let here = place.reference();
for (name, member_value) in object {
if name == "_d" {
continue;
}
let Some(member_type) = union_member(ast, type_, name) else {
return Err(ProbeError::Unknown {
path: contract::join(path, name),
declared: union_member_names(ast, type_),
});
};
contract::reject_null(member_value, &contract::join(path, name))?;
if crate::dump_cpp::is_external(ast, type_, name) {
let getter = format!("{here}.{name}()");
let _ = writeln!(
out.source,
"{{ using zd_t = std::remove_reference_t<decltype(*{getter})>; \
{here}.{name}(eprosima::fastcdr::external<zd_t>(std::make_shared<zd_t>())); }}"
);
emit(
ast,
member_type,
member_value,
&Place::Element(format!("(*{getter})")),
out,
&contract::join(path, name),
)?;
continue;
}
if reached_through_the_getter(ast, member_type) {
let _ = writeln!(out.source, "{here}.{name}({{}});");
}
emit(
ast,
member_type,
member_value,
&Place::Accessor {
object: here.clone(),
name: name.to_string(),
},
out,
&contract::join(path, name),
)?;
}
let TypeKind::Union { discriminator, .. } = &node.kind else {
return Err(ProbeError::Unsupported {
path: path.to_string(),
kind: "a union whose kind changed under the emitter",
});
};
let discriminator = discriminator
.resolved()
.ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the discriminator is unresolved"),
})?;
emit(
ast,
discriminator,
discriminant,
&Place::Accessor {
object: here,
name: String::from("_d"),
},
out,
&contract::join(path, "_d"),
)
}
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)?;
emit_array(ast, element, value, &place.reference(), out, path, &counts)
}
TypeKind::Sequence { element, bound } => {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the sequence element is unresolved"),
})?;
let items = contract::bounded_items(ast, value, *bound, "elements", path)?;
let here = place.reference();
let _ = writeln!(out.source, "{here}.resize({});", items.len());
for (index, item) in items.iter().enumerate() {
emit(
ast,
element,
item,
&Place::Element(format!("{here}[{index}]")),
out,
&format!("{path}[{index}]"),
)?;
}
Ok(())
}
other => Err(ProbeError::Unfilled {
path: path.to_string(),
kind: other.as_str(),
}),
}
}
#[allow(clippy::too_many_arguments)]
fn emit_array(
ast: &Ast,
element: Id<TypeNode>,
value: &Value,
here: &str,
out: &mut Cpp,
path: &str,
counts: &[usize],
) -> Result<(), ProbeError> {
let Some((&count, rest)) = counts.split_first() else {
return emit(
ast,
element,
value,
&Place::Element(here.to_string()),
out,
path,
);
};
let items = contract::array(value, path)?;
if items.len() != count {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("exactly {count} elements"),
found: format!("{}", items.len()),
});
}
for (index, item) in items.iter().enumerate() {
emit_array(
ast,
element,
item,
&format!("{here}[{index}]"),
out,
&format!("{path}[{index}]"),
rest,
)?;
}
Ok(())
}
fn emit_wstring(text: &str, place: &Place, out: &mut Cpp) {
if text.chars().any(|ch| ch as u32 > 0xFFFF) {
let _ = writeln!(
out.source,
"static_assert(sizeof(wchar_t) >= 4, \
\"this sample has a code point outside the BMP\");"
);
}
let mut units = String::new();
for ch in text.chars() {
let _ = write!(units, "static_cast<wchar_t>(0x{:04x}), ", ch as u32);
}
let _ = units.pop();
let _ = units.pop();
let _ = writeln!(
out.source,
"{}",
place.assign(&format!("std::wstring{{{units}}}"))
);
}
fn bitmask_literal(
ast: &Ast,
type_: Id<TypeNode>,
set: &[(String, u16)],
out: &mut Cpp,
path: &str,
) -> String {
let prefix = namespace_prefix(ast, type_);
let mut flags = Vec::with_capacity(set.len());
for (index, (name, _)) in set.iter().enumerate() {
out.seen.scalar(&format!("{path}[{index}]"));
flags.push(format!("{prefix}{name}"));
}
if flags.is_empty() {
"0".to_string()
} else {
flags.join(" | ")
}
}
fn scalar_literal(
ast: &Ast,
type_: Id<TypeNode>,
value: &Value,
path: &str,
) -> Result<String, ProbeError> {
let node = ast.type_(type_).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
match &node.kind {
TypeKind::Primitive(kind) => primitive_literal(*kind, value, path),
TypeKind::String {
width: CharWidth::Char,
..
} => Ok(cpp_string(contract::text(value, path)?)),
TypeKind::Enumeration { .. } => {
let (name, _) = contract::enumerator(ast, type_, value, path)?;
Ok(format!("{}::{name}", cpp_name(ast, type_)))
}
TypeKind::Alias { aliased } => {
let target = aliased.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the typedef target is unresolved"),
})?;
scalar_literal(ast, target, value, path)
}
other => Err(ProbeError::Unfilled {
path: path.to_string(),
kind: other.as_str(),
}),
}
}
fn namespace_prefix(ast: &Ast, type_: Id<TypeNode>) -> String {
let Some(scoped) = ast
.any_of_type(type_)
.ok()
.and_then(|any| ast.scoped_name_of(any))
else {
return String::new();
};
let segments = scoped.segments();
if segments.len() <= 1 {
return String::new();
}
let mut prefix = segments[..segments.len() - 1].join("::");
prefix.push_str("::");
prefix
}
fn reached_through_the_getter(ast: &Ast, type_: Id<TypeNode>) -> bool {
match ast.type_(type_).map(|node| node.kind.clone()) {
Ok(TypeKind::Alias { aliased }) => aliased
.resolved()
.is_some_and(|target| reached_through_the_getter(ast, target)),
Ok(
TypeKind::Bitset { .. }
| TypeKind::Map { .. }
| TypeKind::Structure { .. }
| TypeKind::Valuetype { .. }
| TypeKind::Union { .. }
| TypeKind::Array { .. }
| TypeKind::Sequence { .. },
) => true,
_ => false,
}
}
fn union_member(ast: &Ast, union: Id<TypeNode>, name: &str) -> Option<Id<TypeNode>> {
ast.cases_of(union)
.iter()
.filter_map(|id| ast.case(*id).ok())
.filter_map(|case| ast.member(case.member).ok())
.find(|member| member.name == name)
.and_then(|member| member.type_.resolved())
}
fn union_member_names(ast: &Ast, union: Id<TypeNode>) -> Vec<String> {
ast.cases_of(union)
.iter()
.filter_map(|id| ast.case(*id).ok())
.filter_map(|case| ast.member(case.member).ok())
.map(|member| member.name.clone())
.collect()
}
fn primitive_literal(kind: PrimitiveKind, value: &Value, path: &str) -> Result<String, ProbeError> {
Ok(match kind {
PrimitiveKind::Boolean => {
if contract::boolean(value, path)? {
"true".to_string()
} else {
"false".to_string()
}
}
PrimitiveKind::Char => character(kind, value, path, 2)?,
PrimitiveKind::WChar => character(kind, value, path, 4)?,
PrimitiveKind::Float32 => {
format!("{:.17e}f", contract::float(value, path)?)
}
PrimitiveKind::Float64 | PrimitiveKind::Float128 => {
format!("{:.17e}", contract::float(value, path)?)
}
PrimitiveKind::Int64 => {
let n = contract::integer_for(kind, value, path)?;
if n == i128::from(i64::MIN) {
"(-9223372036854775807LL - 1)".to_string()
} else {
format!("{n}LL")
}
}
PrimitiveKind::UInt64 => format!("{}ULL", contract::integer_for(kind, value, path)?),
_ => format!("{}", contract::integer_for(kind, value, path)?),
})
}
fn character(
kind: PrimitiveKind,
value: &Value,
path: &str,
digits: usize,
) -> Result<String, ProbeError> {
let code = contract::character_for(kind, value, path)? as u32;
Ok(format!("0x{code:0digits$x}"))
}
fn cpp_string(text: &str) -> String {
let mut out = String::from("\"");
for byte in text.as_bytes() {
match byte {
b'"' => out.push_str("\\\""),
b'\\' => out.push_str("\\\\"),
b'\n' => out.push_str("\\n"),
b'\r' => out.push_str("\\r"),
b'\t' => out.push_str("\\t"),
0x20..=0x7E => out.push(char::from(*byte)),
other => {
let _ = write!(out, "\\{other:03o}");
}
}
}
out.push('"');
out
}