use crate::adapter::c::{ordered_types, ordered_types_from, ordered_types_from_pruning};
use crate::emit::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
use crate::encode::EncodeError;
use zerodds_ast::{Ast, Bound, CaseLabel, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::{Id, NodeId};
use zerodds_ast_layout::key::{
ElementPadding, LayoutKey, PlCdr1HeaderPolicy, Purpose, WcharPosition, WcharWidth,
WstringCodeUnits, WstringLengthCounts, WstringLengthPolicy, WstringUnitOrder, WstringUnitWidth,
};
use zerodds_ast_layout::key::{FixedDigitCount, MapKeyOrdering};
use zerodds_ast_layout::plan::{Extent, MemberHeader, ScalarWrite, scalar_write};
use zerodds_ast_layout::xcdr::DepCollector;
use zerodds_lang_cst::common::{Doc, Literal};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CodecOptions {
pub map_length_with_delimiter: bool,
}
impl Default for CodecOptions {
fn default() -> Self {
Self {
map_length_with_delimiter: true,
}
}
}
const EXTENDED_PARAM: &str = "zd_extended";
fn per_sample(key: LayoutKey) -> bool {
key.pl_cdr1_headers.decided_per_sample() && key.encoding.is_v1()
}
fn body_name(name: &str, key: LayoutKey) -> String {
if per_sample(key) {
format!("{name}_zd_extended")
} else {
name.to_string()
}
}
fn nested_call<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
key: LayoutKey,
args: Vec<T::Expr>,
) -> T::Expr {
nested_call_with::<T>(ast, id, key, args, T::name(EXTENDED_PARAM))
}
fn nested_call_with<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
key: LayoutKey,
mut args: Vec<T::Expr>,
flag: T::Expr,
) -> T::Expr {
let name = body_name(&member_codec_name::<T>(ast, id, key), key);
if per_sample(key) {
args.push(flag);
}
T::call(&name, args)
}
fn sample_past_step(ast: &Ast, root: Id<TypeNode>, key: LayoutKey) -> Result<bool, EncodeError> {
let resolved = key
.for_sample(ast, root)
.map_err(crate::encode::plan_refusal)?;
Ok(resolved.pl_cdr1_headers == PlCdr1HeaderPolicy::ExtendedForConstructed)
}
pub(crate) fn sample_types(ast: &Ast) -> Vec<Id<TypeNode>> {
ast.types()
.filter(|(_, node)| {
!node.name.is_empty()
&& matches!(
node.kind,
TypeKind::Structure { .. }
| TypeKind::Union { .. }
| TypeKind::Valuetype { .. }
)
&& !node
.facets
.xtypes
.as_ref()
.is_some_and(|xtypes| xtypes.is_nested)
})
.map(|(id, _)| id)
.collect()
}
fn with_extended_used<T: CodecTarget>(body: T::Block) -> T::Block {
let mut statements = T::mark_used(EXTENDED_PARAM);
statements.extend(T::into_statements(body));
T::block(statements)
}
const fn prefix(purpose: Purpose) -> &'static str {
match purpose {
Purpose::Data => "zd_enc_",
Purpose::Key => "zd_kenc_",
}
}
fn member_codec_name<T: CodecTarget>(
ast: &Ast,
member_type: Id<TypeNode>,
key: LayoutKey,
) -> String {
codec_name::<T>(
ast,
member_type,
zerodds_ast_layout::keyhash::member_key(ast, member_type, key).purpose,
)
}
#[must_use]
pub fn codec_name<T: CodecTarget>(ast: &Ast, id: Id<TypeNode>, purpose: Purpose) -> String {
let head = prefix(purpose);
let name = match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Primitive(kind)) => {
format!("{head}p_{}", kind.as_str().replace(' ', "_"))
}
_ => format!("{head}{}", T::type_name(ast, id)),
};
T::published_name(name)
}
pub fn codec<T: CodecTarget>(
ast: &Ast,
key: LayoutKey,
options: CodecOptions,
) -> Result<Vec<T::Decl>, EncodeError> {
codec_over::<T>(ast, &ordered_types(ast), key, options, &sample_types(ast))
}
pub fn codec_excluding<T: CodecTarget>(
ast: &Ast,
key: LayoutKey,
options: CodecOptions,
excluded: &[Id<TypeNode>],
also_reaching: &[Id<TypeNode>],
) -> Result<Vec<T::Decl>, EncodeError> {
if excluded.is_empty() {
return codec::<T>(ast, key, options);
}
let (types, entries) = kept_after_excluding(ast, excluded, also_reaching);
codec_over::<T>(ast, &types, key, options, &entries)
}
pub(crate) fn kept_after_excluding(
ast: &Ast,
excluded: &[Id<TypeNode>],
also_reaching: &[Id<TypeNode>],
) -> (Vec<Id<TypeNode>>, Vec<Id<TypeNode>>) {
let mut entries: Vec<Id<TypeNode>> = sample_types(ast)
.into_iter()
.filter(|id| !excluded.contains(id))
.collect();
for id in also_reaching {
if !entries.contains(id) {
entries.push(*id);
}
}
let types = ordered_types_from_pruning(ast, &entries, excluded);
(types, entries)
}
pub fn codec_from<T: CodecTarget>(
ast: &Ast,
root: Id<TypeNode>,
key: LayoutKey,
options: CodecOptions,
) -> Result<Vec<T::Decl>, EncodeError> {
codec_over::<T>(
ast,
&ordered_types_from(ast, &[root]),
key,
options,
&[root],
)
}
pub fn codec_from_many<T: CodecTarget>(
ast: &Ast,
roots: &[Id<TypeNode>],
key: LayoutKey,
options: CodecOptions,
) -> Result<Vec<T::Decl>, EncodeError> {
codec_over::<T>(ast, &ordered_types_from(ast, roots), key, options, roots)
}
pub fn codec_from_many_excluding<T: CodecTarget>(
ast: &Ast,
roots: &[Id<TypeNode>],
key: LayoutKey,
options: CodecOptions,
excluded: &[Id<TypeNode>],
) -> Result<Vec<T::Decl>, EncodeError> {
let types = ordered_types_from_pruning(ast, roots, excluded);
codec_over::<T>(ast, &types, key, options, roots)
}
fn codec_over<T: CodecTarget>(
ast: &Ast,
types: &[Id<TypeNode>],
key: LayoutKey,
options: CodecOptions,
entries: &[Id<TypeNode>],
) -> Result<Vec<T::Decl>, EncodeError> {
crate::encode::axes_the_codecs_carry_for::<T>(key)?;
let mut prototypes = Vec::new();
let mut bodies = Vec::new();
let mut emitted: Vec<String> = Vec::new();
let framed = per_sample(key);
let uncalled = called_only_in_bulk(
ast,
types,
entries,
T::bulk_octets_write("v", T::name("zd_n")).is_some(),
);
for id in types.iter().copied() {
if uncalled.contains(&id) {
continue;
}
let name = codec_name::<T>(ast, id, key.purpose);
if emitted.contains(&name) {
continue;
}
let body = body_name(&name, key);
let Some(statements) = body_of::<T>(ast, id, key, options, &body)? else {
continue;
};
emitted.push(name.clone());
if !framed {
prototypes.extend(function::<T>(ast, id, key, &name, None, false));
bodies.extend(function::<T>(ast, id, key, &name, Some(statements), false));
continue;
}
prototypes.extend(function::<T>(ast, id, key, &body, None, true));
bodies.extend(function::<T>(ast, id, key, &body, Some(statements), true));
if entries.contains(&id) {
let past = sample_past_step(ast, id, key)?;
let call = T::perform(T::call(
&body,
vec![T::name("w"), T::name("v"), T::literal(Literal::Bool(past))],
));
let entry = T::block(vec![call]);
prototypes.extend(entry_function::<T>(ast, id, &name, None));
bodies.extend(entry_function::<T>(ast, id, &name, Some(entry)));
}
}
prototypes.extend(bodies);
Ok(prototypes)
}
pub fn key_holder<T: CodecTarget>(
ast: &Ast,
root: Id<TypeNode>,
key: LayoutKey,
) -> Result<Option<T::Decl>, EncodeError> {
let keys = zerodds_ast_layout::keyhash::key_members_with(
ast,
root,
key.key_holder_nesting,
key.key_holder_order,
)
.map_err(crate::encode::plan_refusal)?;
if keys.is_empty() {
let Some(discriminator) = zerodds_ast_layout::keyhash::union_key_discriminator(ast, root)
else {
return Ok(None);
};
let statements = vec![T::perform(nested_call_with::<T>(
ast,
discriminator,
key,
vec![
T::name("w"),
argument::<T>(ast, discriminator, T::discriminant("v")),
],
T::literal(Literal::Bool(false)),
))];
return Ok(key_holder_routine::<T>(
ast,
root,
&[
"The key holder: the discriminator of a union keyed by it.",
"Big-endian whatever the sample is — DDSI-RTPS 2.5 9.6.3.8.",
],
statements,
));
}
let mut statements = Vec::new();
for node in &keys {
let Some((name, member_type)) = member_by_node(ast, root, *node) else {
continue;
};
let reach = super::reach::<T>(ast, root, *node, &name);
statements.push(T::perform(nested_call_with::<T>(
ast,
member_type,
key,
vec![
T::name("w"),
argument::<T>(ast, member_type, arrow::<T>("v", &reach.field)),
],
T::literal(Literal::Bool(false)),
)));
}
Ok(key_holder_routine::<T>(
ast,
root,
&[
"The key holder: the @key members alone, in member-id order.",
"Big-endian whatever the sample is — DDSI-RTPS 2.5 9.6.3.8.",
],
statements,
))
}
fn key_holder_routine<T: CodecTarget>(
ast: &Ast,
root: Id<TypeNode>,
doc: &[&str],
statements: Vec<T::Stmt>,
) -> Option<T::Decl> {
T::routine(Routine {
doc: Doc::lines(doc),
name: format!("zd_keyholder_{}", T::type_name(ast, root)),
sample: T::value_type(ast, root),
held: Handoff::Value,
writing: true,
tolerate_unused: false,
extra: Vec::new(),
body: Some(T::block(statements)),
})
}
fn function<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
key: LayoutKey,
name: &str,
body: Option<T::Block>,
framed: bool,
) -> Option<T::Decl> {
let _ = key.purpose;
T::routine(Routine {
doc: Doc::none(),
name: name.to_string(),
sample: T::value_type(ast, id),
held: if is_array(ast, id, 0) {
Handoff::Array
} else {
Handoff::Value
},
writing: true,
tolerate_unused: maybe_unused(ast, id, key),
extra: if framed {
vec![(T::scalar(Scalar::Bool), EXTENDED_PARAM.to_string())]
} else {
Vec::new()
},
body: if framed {
body.map(with_extended_used::<T>)
} else {
body
},
})
}
fn entry_function<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
name: &str,
body: Option<T::Block>,
) -> Option<T::Decl> {
T::routine(Routine {
doc: Doc::none(),
name: name.to_string(),
sample: T::value_type(ast, id),
held: if is_array(ast, id, 0) {
Handoff::Array
} else {
Handoff::Value
},
writing: true,
tolerate_unused: false,
extra: Vec::new(),
body,
})
}
pub(crate) fn maybe_unused(ast: &Ast, id: Id<TypeNode>, key: LayoutKey) -> bool {
if ast.interfaces().next().is_some() || crate::emit::type_pair_cpp::has_pair_types(ast) {
return true;
}
if is_a_base(ast, id) {
return true;
}
key.wchar_framing.has_length_octet(WcharPosition::Member)
!= key.wchar_framing.has_length_octet(WcharPosition::Element)
&& matches!(resolve_primitive(ast, id, 0), Some(PrimitiveKind::WChar))
}
fn is_a_base(ast: &Ast, id: Id<TypeNode>) -> bool {
ast.all_nodes().any(|any| match any {
zerodds_ast::AnyId::Type(other, _) => {
crate::adapter::c::struct_base(ast, other) == Some(id)
}
_ => false,
})
}
fn is_array(ast: &Ast, id: Id<TypeNode>, depth: usize) -> bool {
if depth > 64 {
return false;
}
match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Array { .. }) => true,
Ok(TypeKind::Alias { aliased }) => aliased
.resolved()
.is_some_and(|target| is_array(ast, target, depth + 1)),
_ => false,
}
}
fn argument<T: CodecTarget>(ast: &Ast, id: Id<TypeNode>, value: T::Expr) -> T::Expr {
if is_array(ast, id, 0) {
value
} else {
T::handoff(value, Handoff::Value)
}
}
#[allow(clippy::too_many_lines)]
fn body_of<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
key: LayoutKey,
options: CodecOptions,
own_name: &str,
) -> Result<Option<T::Block>, EncodeError> {
let Ok(node) = ast.type_(id) else {
return Ok(None);
};
let statements = match &node.kind {
TypeKind::Primitive(_) => {
let Some(write) = scalar_write(ast, id, key, WcharPosition::Member) else {
return Err(EncodeError::NoWriter {
member: T::type_name(ast, id),
kind: "primitive the layout plan has no scalar write for",
});
};
vec![primitive_write_at::<T>(write, deref::<T>("v"))]
}
TypeKind::String { bound, .. } => {
let Some(write) = scalar_write(ast, id, key, WcharPosition::Member) else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "value the layout plan has no scalar write for",
});
};
vec![match bound_limit(ast, bound) {
Some(limit) => bounded_string_write::<T>(own_name, write, deref::<T>("v"), limit)?,
None => primitive_write_at::<T>(write, deref::<T>("v")),
}]
}
TypeKind::Fixed { .. } => {
let Some(write) = scalar_write(ast, id, key, WcharPosition::Member) else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "value the layout plan has no scalar write for",
});
};
vec![primitive_write_at::<T>(write, deref::<T>("v"))]
}
TypeKind::Enumeration { .. } => enumeration_write::<T>(ast, id, key)?,
TypeKind::Bitmask { .. } => {
let Some(write) = scalar_write(ast, id, key, WcharPosition::Member) else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "bitmask the layout plan has no scalar write for",
});
};
vec![primitive_write_at::<T>(write, deref::<T>("v"))]
}
TypeKind::Bitset { .. } => {
let fields = &ast.all_bitfields_of(id);
let mut deps = DepCollector::default();
let layout = zerodds_ast_layout::xcdr::plan(ast, id, key, &mut deps)
.map_err(crate::encode::plan_refusal)?;
let Extent::Sized(anchor) = layout.extent else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "bitset whose anchor the planner does not size",
});
};
let mut statements = vec![T::declare(
T::scalar(Scalar::UInt64),
"bits",
Some(int::<T>(0)),
)];
let mut position: u32 = 0;
for field in fields {
let Ok(field_node) = ast.bitfield(*field) else {
continue;
};
let width = u32::from(field_node.bits);
if let Some(field_name) = field_node.name.as_ref() {
statements.push(T::perform(T::binary(
"=",
T::name("bits"),
T::binary(
"|",
T::name("bits"),
call_expr::<T>(
"bitfield",
vec![
cast::<T>(
T::scalar(Scalar::UInt64),
arrow::<T>("v", &T::escape(field_name)),
),
int::<T>(i128::from(width)),
int::<T>(i128::from(position)),
],
),
),
)));
}
position = position.saturating_add(width);
}
statements.push(call::<T>(
"write_bitset",
vec![int::<T>(i128::from(anchor)), T::name("bits")],
));
statements
}
TypeKind::Alias { aliased } => {
let Some(target) = aliased.resolved() else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "typedef with an unresolved target",
});
};
let callee = body_name(&member_codec_name::<T>(ast, target, key), key);
if callee == own_name {
return Ok(None);
}
let mut args = vec![T::name("w"), T::name("v")];
if per_sample(key) {
args.push(T::name(EXTENDED_PARAM));
}
vec![T::perform(T::call(&callee, args))]
}
TypeKind::Sequence { element, bound } => {
let Some(element) = element.resolved() else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "sequence with an unresolved element",
});
};
let delimited = zerodds_ast_layout::collection_is_delimited(ast, element, key);
let mut statements = Vec::new();
if let Some(limit) = bound_limit(ast, bound) {
statements.push(bound_check::<T>(
"check_sequence_bound",
T::count_of("v", None),
limit,
));
}
statements.push(dheader_declaration::<T>(delimited));
statements.push(call::<T>("write_u32", vec![T::count_of("v", None)]));
if let Some(bulk) = bulk_write::<T>(ast, element, T::count_of("v", None)) {
statements.push(bulk);
statements.push(finish_dheader::<T>());
return Ok(Some(T::block(statements)));
}
let packed = key.element_padding == ElementPadding::Packed;
let carries_length = sequence_element_carries_length(ast, element, key)?;
let at = T::index(T::elements("v"), T::name("i"));
let mut body: Vec<T::Stmt> = Vec::new();
if packed {
body.push(begin_packed::<T>());
}
if carries_length {
body.push(element_length_declaration::<T>());
}
body.push(
element_write::<T>(ast, element, key, at.clone()).unwrap_or_else(|| {
T::perform(nested_call::<T>(
ast,
element,
key,
vec![T::name("w"), argument::<T>(ast, element, at)],
))
}),
);
if carries_length {
body.push(finish_element_length::<T>());
}
if packed {
body.push(finish_packed::<T>());
}
statements.push(T::count_up("i", T::count_of("v", None), T::block(body)));
statements.push(finish_dheader::<T>());
statements
}
TypeKind::Array { element, dims } => {
let Some(element) = element.resolved() else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "array with an unresolved element",
});
};
let mut lengths = Vec::new();
for dim in dims {
let length = ast.eval_integral(*dim).map_err(|_| EncodeError::NoWriter {
member: own_name.to_string(),
kind: "array whose length does not evaluate",
})?;
lengths.push(length);
}
let delimited = zerodds_ast_layout::array_is_delimited(ast, element, key);
let mut access = T::name("v");
for level in 0..lengths.len() {
access = T::index(access, T::name(&format!("i{level}")));
}
if let [length] = lengths.as_slice() {
if let Some(bulk) = bulk_write::<T>(ast, element, int::<T>(*length)) {
return Ok(Some(T::block(vec![
dheader_declaration::<T>(delimited),
bulk,
finish_dheader::<T>(),
])));
}
}
let mut inner = T::block(vec![
element_write::<T>(ast, element, key, access.clone()).unwrap_or_else(|| {
T::perform(nested_call::<T>(
ast,
element,
key,
vec![T::name("w"), argument::<T>(ast, element, access)],
))
}),
]);
let packed = key.element_padding == ElementPadding::Packed;
for (level, length) in lengths.iter().enumerate().rev() {
let index = format!("i{level}");
let mut body: Vec<T::Stmt> = Vec::new();
if packed {
body.push(begin_packed::<T>());
}
body.extend(T::into_statements(inner));
if packed {
body.push(finish_packed::<T>());
}
inner = T::block(vec![T::count_up(&index, int::<T>(*length), T::block(body))]);
}
let mut statements = vec![dheader_declaration::<T>(delimited)];
statements.extend(T::into_statements(inner));
statements.push(finish_dheader::<T>());
statements
}
TypeKind::Map {
key: k,
value,
bound,
} => {
let (Some(k), Some(value)) = (k.resolved(), value.resolved()) else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "map with an unresolved key or value",
});
};
let mut deps = DepCollector::default();
let layout = zerodds_ast_layout::xcdr::plan(ast, id, key, &mut deps)
.map_err(crate::encode::plan_refusal)?;
let entry = T::map_entry_name(ast, id);
let _ = &entry;
let Ok(ordering) = zerodds_ast_layout::plan::map_entry_ordering(ast, id, key) else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "a map whose key type has no value order, under \
--map-entries ascending-key",
});
};
let mut statements = Vec::new();
if let Some(limit) = bound_limit(ast, bound) {
statements.push(bound_check::<T>(
"check_map_bound",
T::count_of("v", None),
limit,
));
}
statements.push(dheader_declaration::<T>(layout.needs_dheader));
if !layout.needs_dheader || options.map_length_with_delimiter {
statements.push(call::<T>("write_u32", vec![T::count_of("v", None)]));
}
let element = |key: bool| T::entry_half(T::index(T::elements("v"), T::name("i")), key);
let pair = T::block(vec![
T::perform(nested_call::<T>(
ast,
k,
key,
vec![T::name("w"), argument::<T>(ast, k, element(true))],
)),
T::perform(nested_call::<T>(
ast,
value,
key,
vec![T::name("w"), argument::<T>(ast, value, element(false))],
)),
]);
statements.push(match ordering {
None => over_the_entries::<T>("i", pair),
Some(ordering) => ascending_by_key::<T>(ast, k, ordering, pair),
});
statements.push(finish_dheader::<T>());
statements
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
aggregate_body::<T>(ast, id, key)?
}
TypeKind::Union { discriminator, .. } => {
union_body::<T>(ast, id, key, discriminator.resolved(), own_name)?
}
TypeKind::Native | TypeKind::ObjectRef { .. } | TypeKind::AnnotationType { .. } => {
return Ok(None);
}
TypeKind::ValueBox { .. } => {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "boxed value, whose presence flag has no settled wire form",
});
}
TypeKind::Any => {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "`any`, which DDS-XTypes has no encoding for; only CORBA CDR writes one, \
as a TypeCode ahead of the value",
});
}
TypeKind::ValueBase => {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "`ValueBase`, which DDS-XTypes has no encoding for; only CORBA CDR writes \
one, as a chunked value with its repository id",
});
}
};
Ok(Some(T::block(statements)))
}
fn aggregate_body<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
key: LayoutKey,
) -> Result<Vec<T::Stmt>, EncodeError> {
let mut deps = DepCollector::default();
let layout = zerodds_ast_layout::xcdr::plan(ast, id, key, &mut deps)
.map_err(crate::encode::plan_refusal)?;
let packed = key.element_padding == ElementPadding::Packed;
let mut statements = Vec::new();
if packed {
statements.push(begin_packed::<T>());
}
statements.push(dheader_declaration::<T>(layout.needs_dheader));
for (index, member) in layout.members.iter().enumerate() {
let Some((name, member_type)) = member_by_node(ast, id, member.member) else {
continue;
};
let reach = super::reach::<T>(ast, id, member.member, &name);
let field = reach.field.clone();
let node = ast
.all_members_of(id)
.into_iter()
.filter_map(|m| ast.member(m).ok())
.find(|m| m.node_id == member.member);
let optional = node.as_ref().is_some_and(|m| m.facets.is_optional());
let external = node
.as_ref()
.is_some_and(|m| super::reached_externally::<T>(ast, m));
let indirect =
super::behind_pointer::<T>(ast, reach.holder, member_type, external, optional);
let by_reference =
T::holds_by_reference(ast, reach.holder, member_type, optional, external);
if external && !optional && !by_reference {
statements.extend(T::refuse_unset_external(T::external_present("v", &field)));
}
statements.extend(framed_member::<T>(
ast,
member,
index,
member_type,
&field,
key,
optional,
external,
indirect,
by_reference,
)?);
}
if layout.needs_sentinel {
statements.push(call::<T>("write_sentinel", Vec::new()));
}
statements.push(finish_dheader::<T>());
if packed {
statements.push(finish_packed::<T>());
}
Ok(statements)
}
#[allow(clippy::too_many_arguments)]
fn framed_member<T: CodecTarget>(
ast: &Ast,
member: &zerodds_ast_layout::plan::MemberPlan,
index: usize,
member_type: Id<TypeNode>,
field: &str,
key: LayoutKey,
optional: bool,
external: bool,
indirect: bool,
by_reference: bool,
) -> Result<Vec<T::Stmt>, EncodeError> {
let present = if by_reference {
T::reference_present("v", field)
} else if external {
T::external_present("v", field)
} else {
T::present("v", field)
};
fn guarded<B: CodecTarget>(present: B::Expr, statements: Vec<B::Stmt>) -> B::Stmt {
B::when(present, B::block(statements), None)
}
let held = if by_reference {
T::reference_value(ast, member_type, "v", field)
} else if external {
T::external_value(ast, member_type, "v", field, optional)
} else if optional {
T::optional_value(ast, member_type, "v", field)
} else {
arrow::<T>("v", field)
};
let held = if indirect && !by_reference {
T::behind_indirection(ast, member_type, held)
} else {
held
};
let value = T::perform(nested_call::<T>(
ast,
member_type,
key,
vec![T::name("w"), argument::<T>(ast, member_type, held)],
));
let handle = format!("h{index}");
Ok(match member.header {
MemberHeader::NoHeader => vec![value],
MemberHeader::PidCdr1 {
pid,
extended,
must_understand,
} => {
let mut body = open_pid_header::<T>(
ast,
key,
&handle,
pid,
member.member_id,
extended,
Constant::ParameterMember,
must_understand,
member_type,
)?;
body.push(value);
body.push(call::<T>("finish_pid", vec![T::name(&handle)]));
if optional {
vec![guarded::<T>(present, body)]
} else {
body
}
}
MemberHeader::EmHeader {
lc,
must_understand,
} => {
let body = vec![
T::declare(
T::handle(Handle::EmHeader),
&handle,
Some(call_expr::<T>(
"write_emheader",
vec![
int::<T>(i128::from(member.member_id)),
int::<T>(i128::from(lc.bits())),
T::literal(Literal::Bool(must_understand)),
],
)),
),
value,
call::<T>("finish_emheader", vec![T::name(&handle)]),
];
if optional {
vec![guarded::<T>(present, body)]
} else {
body
}
}
MemberHeader::OptionalCdr1 {
pid,
extended,
must_understand,
} => {
let mut body = open_pid_header::<T>(
ast,
key,
&handle,
pid,
member.member_id,
extended,
Constant::ParameterOptional,
must_understand,
member_type,
)?;
body.push(guarded::<T>(present, vec![value]));
body.push(call::<T>("finish_pid", vec![T::name(&handle)]));
body
}
MemberHeader::OptionalFlag => vec![
call::<T>("write_bool", vec![present.clone()]),
guarded::<T>(present, vec![value]),
],
})
}
fn union_body<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
key: LayoutKey,
discriminator: Option<Id<TypeNode>>,
own_name: &str,
) -> Result<Vec<T::Stmt>, EncodeError> {
let Some(discriminator) = discriminator else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "union with an unresolved discriminator",
});
};
let mut deps = DepCollector::default();
let layout = zerodds_ast_layout::xcdr::plan(ast, id, key, &mut deps)
.map_err(crate::encode::plan_refusal)?;
let suppress_negative = zerodds_ast_layout::plan::union_negative_selects_nothing(ast, id, key);
let packed = key.element_padding == ElementPadding::Packed;
let mut statements = Vec::new();
if packed {
statements.push(begin_packed::<T>());
}
statements.push(dheader_declaration::<T>(layout.needs_dheader));
let write_discriminator = T::perform(nested_call::<T>(
ast,
discriminator,
key,
vec![
T::name("w"),
argument::<T>(ast, discriminator, T::discriminant("v")),
],
));
statements.extend(mmember::<T>(
ast,
key,
layout.discriminator,
discriminator,
0,
"hd",
write_discriminator,
)?);
let mut arms: Vec<(Option<T::Expr>, T::Block)> = Vec::new();
let mut fallback: Option<T::Block> = None;
let mut suppressed: Vec<(i128, Id<TypeNode>, String, bool)> = Vec::new();
let mut default_case: Option<(Id<TypeNode>, NodeId)> = None;
for case in ast.cases_of(id) {
let Ok(case_node) = ast.case(*case) else {
continue;
};
let Ok(member) = ast.member(case_node.member) else {
continue;
};
let Some(member_type) = member.type_.resolved() else {
return Err(EncodeError::NoWriter {
member: member.name.clone(),
kind: "union case with an unresolved type",
});
};
let field = T::escape(&member.name);
let external = super::reached_externally::<T>(ast, member);
let held = if external {
T::external_case_value(ast, member_type, "v", &field)
} else {
T::case_member("v", &field)
};
let held = if super::behind_pointer::<T>(ast, id, member_type, external, false) {
T::behind_indirection(ast, member_type, held)
} else {
held
};
let value = T::perform(nested_call::<T>(
ast,
member_type,
key,
vec![T::name("w"), argument::<T>(ast, member_type, held)],
));
let planned = layout
.members
.iter()
.find(|plan| plan.member == member.node_id);
let write = T::block(mmember::<T>(
ast,
key,
planned.map(|plan| plan.header),
member_type,
planned.map_or(0, |plan| plan.member_id),
&format!("hu{}", planned.map_or(0, |plan| plan.member_id)),
value,
)?);
let mut condition: Option<T::Expr> = None;
let mut is_default = false;
for case_label in &case_node.labels {
match case_label {
CaseLabel::Default => is_default = true,
CaseLabel::Value(expr) => {
let value = ast
.eval_integral(*expr)
.map_err(|_| EncodeError::NoWriter {
member: member.name.clone(),
kind: "union label that does not evaluate",
})?;
if suppress_negative && value < 0 {
suppressed.push((value, member_type, field.clone(), external));
continue;
}
let test = T::binary(
"==",
T::discriminant("v"),
T::case_label(ast, discriminator, value),
);
condition = Some(match condition {
None => test,
Some(previous) => T::binary("||", previous, test),
});
}
}
}
if is_default {
fallback = Some(write.clone());
default_case = Some((member_type, member.node_id));
}
if let Some(condition) = condition {
arms.push((Some(condition), write));
}
}
if let (Some((default_type, default_node)), false) = (default_case, suppressed.is_empty()) {
let width = overlay_width(ast, default_type, key, "default:")?;
let planned = layout
.members
.iter()
.find(|plan| plan.member == default_node);
let mut chain = fallback;
for (value, source_type, source_field, source_external) in suppressed.into_iter().rev() {
let source = bit_pattern_source(ast, source_type, key, &source_field)?;
let member = if source_external {
T::external_case_value(ast, source_type, "v", &source_field)
} else {
T::case_member("v", &source_field)
};
let member = if super::behind_pointer::<T>(ast, id, source_type, source_external, false)
{
T::behind_indirection(ast, source_type, member)
} else {
member
};
let member = match source {
crate::emit::BitPatternSource::Int { .. } => cast::<T>(
T::scalar(Scalar::Int64),
T::enumerator_ordinal(ast, source_type, member),
),
_ => member,
};
let pattern = T::bit_pattern(source, member);
let write = T::perform(T::runtime(
"w",
"write_union_overlay",
vec![pattern, int::<T>(i128::from(width))],
));
let overlay = T::block(mmember::<T>(
ast,
key,
planned.map(|plan| plan.header),
source_type,
planned.map_or(0, |plan| plan.member_id),
&format!("ho{}", planned.map_or(0, |plan| plan.member_id)),
write,
)?);
let test = T::binary(
"==",
T::discriminant("v"),
T::case_label(ast, discriminator, value),
);
chain = Some(T::block(vec![T::when(test, overlay, chain)]));
}
fallback = chain;
}
let mut chain: Option<T::Block> = fallback;
for (condition, block) in arms.into_iter().rev() {
let Some(condition) = condition else {
continue;
};
chain = Some(T::block(vec![T::when(condition, block, chain)]));
}
if let Some(chain) = chain {
statements.extend(T::into_statements(chain));
}
if layout.needs_sentinel {
statements.push(call::<T>("write_sentinel", Vec::new()));
}
statements.push(finish_dheader::<T>());
if packed {
statements.push(finish_packed::<T>());
}
Ok(statements)
}
fn overlay_width(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
member: &str,
) -> Result<u8, EncodeError> {
let mut deps = DepCollector::default();
let width = zerodds_ast_layout::xcdr::plan(ast, type_, key, &mut deps)
.ok()
.and_then(|layout| layout.extent.sized())
.filter(|width| matches!(width, 1 | 2 | 4 | 8));
match width {
#[allow(clippy::cast_possible_truncation)]
Some(width) => Ok(width as u8),
None => Err(EncodeError::NoWriter {
member: member.to_string(),
kind: "a union branch with no fixed-width storage to overlay",
}),
}
}
fn bit_pattern_source(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
member: &str,
) -> Result<crate::emit::BitPatternSource, EncodeError> {
use crate::emit::BitPatternSource as Source;
let no_pattern = || EncodeError::NoWriter {
member: member.to_string(),
kind: "a union branch with no fixed-width storage to overlay",
};
let octets_of = |bits: u16| match bits {
1..=8 => 1,
9..=16 => 2,
17..=32 => 4,
_ => 8,
};
match scalar_write(ast, type_, key, WcharPosition::Member).ok_or_else(no_pattern)? {
ScalarWrite::Bool => Ok(Source::Bool),
ScalarWrite::U8 | ScalarWrite::I8 | ScalarWrite::Char => Ok(Source::Int { octets: 1 }),
ScalarWrite::I16 | ScalarWrite::U16 => Ok(Source::Int { octets: 2 }),
ScalarWrite::I32 | ScalarWrite::U32 => Ok(Source::Int { octets: 4 }),
ScalarWrite::I64 | ScalarWrite::U64 => Ok(Source::Int { octets: 8 }),
ScalarWrite::F32 => Ok(Source::Float32),
ScalarWrite::F64 => Ok(Source::Float64),
ScalarWrite::Wchar {
width,
length_octet: false,
..
} => Ok(Source::Int {
octets: match width {
WcharWidth::Two => 2,
WcharWidth::Four => 4,
},
}),
ScalarWrite::Enum { bits } | ScalarWrite::Bitmask { bits } => Ok(Source::Int {
octets: octets_of(bits),
}),
_ => Err(no_pattern()),
}
}
#[allow(clippy::too_many_arguments)]
fn mmember<T: CodecTarget>(
ast: &Ast,
key: LayoutKey,
header: Option<MemberHeader>,
member_type: Id<TypeNode>,
member_id: u32,
handle: &str,
value: T::Stmt,
) -> Result<Vec<T::Stmt>, EncodeError> {
Ok(match header {
None | Some(MemberHeader::NoHeader) => vec![value],
Some(MemberHeader::PidCdr1 {
pid,
extended,
must_understand,
}) => {
let mut statements = open_pid_header::<T>(
ast,
key,
handle,
pid,
member_id,
extended,
Constant::ParameterMember,
must_understand,
member_type,
)?;
statements.push(value);
statements.push(call::<T>("finish_pid", vec![T::name(handle)]));
statements
}
Some(MemberHeader::EmHeader {
lc,
must_understand,
}) => vec![
T::declare(
T::handle(Handle::EmHeader),
handle,
Some(call_expr::<T>(
"write_emheader",
vec![
int::<T>(i128::from(member_id)),
int::<T>(i128::from(lc.bits())),
T::literal(Literal::Bool(must_understand)),
],
)),
),
value,
call::<T>("finish_emheader", vec![T::name(handle)]),
],
Some(MemberHeader::OptionalCdr1 { .. } | MemberHeader::OptionalFlag) => vec![value],
})
}
#[allow(clippy::too_many_arguments)]
fn open_pid_header<T: CodecTarget>(
ast: &Ast,
key: LayoutKey,
handle: &str,
pid: u16,
member_id: u32,
extended: bool,
role: Constant,
must_understand: bool,
member_type: Id<TypeNode>,
) -> Result<Vec<T::Stmt>, EncodeError> {
let flag = T::literal(Literal::Bool(must_understand));
let extended_call = call_expr::<T>(
"write_pid_extended",
vec![
int::<T>(i128::from(member_id)),
T::constant(role),
flag.clone(),
],
);
let short_call = call_expr::<T>(
"write_pid",
vec![int::<T>(i128::from(pid)), T::constant(role), flag],
);
if !per_sample(key) || extended {
return Ok(vec![T::declare(
T::handle(Handle::Pid),
handle,
Some(if extended { extended_call } else { short_call }),
)]);
}
if zerodds_ast_layout::keeps_short_header(ast, member_type)
.map_err(crate::encode::plan_refusal)?
{
return Ok(vec![T::declare(
T::handle(Handle::Pid),
handle,
Some(short_call),
)]);
}
let choose = |call: T::Expr| T::block(vec![T::perform(T::binary("=", T::name(handle), call))]);
Ok(vec![
T::declare(T::handle(Handle::Pid), handle, None),
T::when(
T::name(EXTENDED_PARAM),
choose(extended_call),
Some(choose(short_call)),
),
])
}
fn member_by_node(
ast: &Ast,
aggregate: Id<TypeNode>,
node: NodeId,
) -> Option<(String, Id<TypeNode>)> {
let id = ast
.all_members_of(aggregate)
.into_iter()
.find(|id| ast.member(*id).is_ok_and(|m| m.node_id == node))?;
let member = ast.member(id).ok()?;
Some((member.name.clone(), member.type_.resolved()?))
}
fn dheader_declaration<T: CodecTarget>(present: bool) -> T::Stmt {
T::declare(
T::handle(Handle::DHeader),
"d",
Some(if present {
T::runtime("w", "write_dheader", Vec::new())
} else {
T::runtime_static("no_dheader", Vec::new())
}),
)
}
fn finish_dheader<T: CodecTarget>() -> T::Stmt {
call::<T>("finish_dheader", vec![T::name("d")])
}
const PACKED_SAVED: &str = "zd_saved";
fn begin_packed<T: CodecTarget>() -> T::Stmt {
T::declare(
T::scalar(Scalar::Size),
PACKED_SAVED,
Some(T::runtime("w", "begin_packed_element", Vec::new())),
)
}
fn finish_packed<T: CodecTarget>() -> T::Stmt {
T::perform(T::runtime(
"w",
"finish_packed_element",
vec![T::name(PACKED_SAVED)],
))
}
fn element_length_declaration<T: CodecTarget>() -> T::Stmt {
T::declare(
T::handle(Handle::DHeader),
"zd_el",
Some(T::runtime("w", "write_dheader", Vec::new())),
)
}
fn finish_element_length<T: CodecTarget>() -> T::Stmt {
T::perform(T::runtime("w", "finish_dheader", vec![T::name("zd_el")]))
}
pub(crate) fn sequence_element_carries_length(
ast: &Ast,
element: Id<TypeNode>,
key: LayoutKey,
) -> Result<bool, EncodeError> {
zerodds_ast_layout::xcdr::sequence_element_carries_length(ast, element, key).ok_or_else(|| {
EncodeError::NoLayout {
detail: format!(
"the sequence element {element:?} is not a type the model holds, so whether it \
carries its own length cannot be told"
),
}
})
}
fn primitive_write_at<T: CodecTarget>(write: ScalarWrite, value: T::Expr) -> T::Stmt {
match write {
ScalarWrite::Bool => call::<T>("write_bool", vec![value]),
ScalarWrite::U8 => call::<T>("write_u8", vec![value]),
ScalarWrite::I8 => call::<T>("write_i8", vec![value]),
ScalarWrite::Char => call::<T>("write_char", vec![T::code_point(value, true)]),
ScalarWrite::Wchar {
width,
length_octet,
order,
} => call::<T>(
"write_wchar",
vec![
T::code_point(value, false),
T::constant(match width {
WcharWidth::Two => Constant::WcharWidth2,
WcharWidth::Four => Constant::WcharWidth4,
}),
T::constant(if length_octet {
Constant::WcharGiopLengthPrefixed
} else {
Constant::WcharFixed
}),
T::constant(wstring_unit_order(order)),
],
),
ScalarWrite::I16 => call::<T>("write_i16", vec![value]),
ScalarWrite::U16 => call::<T>("write_u16", vec![value]),
ScalarWrite::I32 => call::<T>("write_i32", vec![value]),
ScalarWrite::U32 => call::<T>("write_u32", vec![value]),
ScalarWrite::I64 => call::<T>("write_i64", vec![value]),
ScalarWrite::U64 => call::<T>("write_u64", vec![value]),
ScalarWrite::F32 => call::<T>("write_f32", vec![value]),
ScalarWrite::F64 => call::<T>("write_f64", vec![value]),
ScalarWrite::F128 => call::<T>("write_f128", vec![value]),
ScalarWrite::Str => call::<T>("write_string", vec![value]),
ScalarWrite::Wstr {
policy,
order,
code_units,
} => match code_units {
WstringCodeUnits::Utf8 => call::<T>(
"write_wstring_utf8_octets",
vec![value, T::constant(wstring_policy(policy))],
),
WstringCodeUnits::Utf16 => call::<T>(
"write_wstring_utf8",
vec![
value,
T::constant(wstring_policy(policy)),
T::constant(wstring_unit_order(order)),
],
),
},
ScalarWrite::Fixed {
digits,
scale,
count,
} => call::<T>(
"write_fixed",
vec![
value,
int::<T>(i128::from(digits)),
int::<T>(i128::from(scale)),
T::constant(fixed_digit_count(count)),
],
),
ScalarWrite::Enum { bits } => {
call::<T>("write_enum", vec![int::<T>(i128::from(bits)), value])
}
ScalarWrite::Bitmask { bits } => call::<T>(
"write_bitmask",
vec![
int::<T>(i128::from(bits)),
cast::<T>(T::scalar(Scalar::UInt64), value),
],
),
}
}
const fn wstring_policy(policy: WstringLengthPolicy) -> Constant {
match policy {
WstringLengthPolicy::SPECIFICATION => Constant::WstringSpecification,
WstringLengthPolicy::CHARACTER_COUNT => Constant::WstringCharacterCount,
WstringLengthPolicy::CHARACTER_COUNT_TERMINATED => {
Constant::WstringCharacterCountTerminated
}
WstringLengthPolicy::SPECIFICATION_WIDE_UNITS => Constant::WstringSpecificationWideUnits,
WstringLengthPolicy::ROS_TYPE_SUPPORT => Constant::WstringRosTypeSupport,
_ => match (policy.counts, policy.terminator, policy.unit) {
(WstringLengthCounts::Octets, _, WstringUnitWidth::Two) => {
Constant::WstringOctetsTerminated2
}
(WstringLengthCounts::Octets, _, WstringUnitWidth::Four) => {
Constant::WstringOctetsTerminated4
}
_ => Constant::WstringCharactersTerminated2,
},
}
}
fn element_write<T: CodecTarget>(
ast: &Ast,
element: Id<TypeNode>,
key: LayoutKey,
value: T::Expr,
) -> Option<T::Stmt> {
if key.wchar_framing.has_length_octet(WcharPosition::Member)
== key.wchar_framing.has_length_octet(WcharPosition::Element)
{
return None;
}
if !matches!(
resolve_primitive(ast, element, 0),
Some(PrimitiveKind::WChar)
) {
return None;
}
let write = scalar_write(ast, element, key, WcharPosition::Element)?;
Some(primitive_write_at::<T>(write, value))
}
pub(crate) fn octet_elements(ast: &Ast, element: Id<TypeNode>) -> bool {
matches!(
resolve_primitive(ast, element, 0),
Some(PrimitiveKind::Octet | PrimitiveKind::UInt8)
)
}
fn bulk_write<T: CodecTarget>(ast: &Ast, element: Id<TypeNode>, count: T::Expr) -> Option<T::Stmt> {
if octet_elements(ast, element) {
T::bulk_octets_write("v", count)
} else {
None
}
}
pub(crate) fn bulk_read<T: CodecTarget>(
ast: &Ast,
element: Id<TypeNode>,
count: T::Expr,
) -> Option<T::Stmt> {
if octet_elements(ast, element) {
T::bulk_octets_read("v", count)
} else {
None
}
}
pub(crate) fn called_only_in_bulk(
ast: &Ast,
types: &[Id<TypeNode>],
entries: &[Id<TypeNode>],
bulk: bool,
) -> Vec<Id<TypeNode>> {
if !bulk {
return Vec::new();
}
let octets: Vec<Id<TypeNode>> = types
.iter()
.copied()
.filter(|id| octet_elements(ast, *id))
.collect();
if octets.is_empty() {
return Vec::new();
}
let mut called: Vec<Id<TypeNode>> = octets
.iter()
.copied()
.filter(|id| entries.contains(id))
.collect();
let mut pending: Vec<Id<TypeNode>> = types
.iter()
.copied()
.filter(|id| !octets.contains(id))
.collect();
while let Some(caller) = pending.pop() {
let copied_in_bulk = match ast.type_(caller).map(|node| &node.kind) {
Ok(TypeKind::Sequence { element, .. }) => element
.resolved()
.is_some_and(|element| octet_elements(ast, element)),
Ok(TypeKind::Array { element, dims }) => {
dims.len() == 1
&& element
.resolved()
.is_some_and(|element| octet_elements(ast, element))
}
_ => false,
};
if copied_in_bulk {
continue;
}
for callee in crate::adapter::c::dependencies(ast, caller) {
if octets.contains(&callee) && !called.contains(&callee) {
called.push(callee);
pending.push(callee);
}
}
}
octets
.into_iter()
.filter(|id| !called.contains(id))
.collect()
}
pub(crate) fn resolve_primitive(
ast: &Ast,
id: Id<TypeNode>,
depth: usize,
) -> Option<PrimitiveKind> {
if depth > 32 {
return None;
}
match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Primitive(kind)) => Some(kind),
Ok(TypeKind::Alias { aliased }) => aliased
.resolved()
.and_then(|target| resolve_primitive(ast, target, depth + 1)),
_ => None,
}
}
const fn wstring_unit_order(order: WstringUnitOrder) -> Constant {
match order {
WstringUnitOrder::Stream => Constant::WstringUnitsStream,
WstringUnitOrder::BigEndianAlways => Constant::WstringUnitsBigEndianAlways,
WstringUnitOrder::ByteOrderMark => Constant::WstringUnitsByteOrderMark,
WstringUnitOrder::HostUnswapped => Constant::WstringUnitsHostUnswapped,
}
}
const fn fixed_digit_count(policy: FixedDigitCount) -> Constant {
match policy {
FixedDigitCount::Declared => Constant::FixedDeclared,
FixedDigitCount::ValuePrecision => Constant::FixedValuePrecision,
FixedDigitCount::ValueStringDigits => Constant::FixedValueStringDigits,
}
}
fn over_the_entries<T: CodecTarget>(index: &str, body: T::Block) -> T::Stmt {
T::count_up(index, T::count_of("v", None), body)
}
fn ascending_by_key<T: CodecTarget>(
ast: &Ast,
key_type: Id<TypeNode>,
ordering: MapKeyOrdering,
pair: T::Block,
) -> T::Stmt {
let before = map_key_before::<T>(ast, key_type, ordering, "zd_k", "i");
let count = over_the_entries::<T>(
"zd_k",
T::block(vec![T::when(
before,
T::block(vec![T::perform(increment::<T>("zd_before"))]),
None,
)]),
);
let select = over_the_entries::<T>(
"i",
T::block(vec![
T::declare(T::scalar(Scalar::UInt32), "zd_before", Some(int::<T>(0))),
count,
T::when(
T::binary("==", T::name("zd_before"), T::name("zd_rank")),
pair,
None,
),
]),
);
over_the_entries::<T>("zd_rank", T::block(vec![select]))
}
fn map_key_before<T: CodecTarget>(
ast: &Ast,
key_type: Id<TypeNode>,
ordering: MapKeyOrdering,
left: &str,
right: &str,
) -> T::Expr {
let less = |a: &str, b: &str| map_key_less::<T>(ast, key_type, ordering, a, b);
T::binary(
"||",
T::paren(less(left, right)),
T::paren(T::binary(
"&&",
T::unary("!", T::paren(less(right, left))),
T::paren(T::binary("<", T::name(left), T::name(right))),
)),
)
}
fn map_key_less<T: CodecTarget>(
ast: &Ast,
key_type: Id<TypeNode>,
ordering: MapKeyOrdering,
left: &str,
right: &str,
) -> T::Expr {
let key_at = |index: &str| {
let half = T::entry_half(T::index(T::elements("v"), T::name(index)), true);
if ordering == MapKeyOrdering::EnumeratorValue {
return T::enumerator_ordinal(ast, key_type, half);
}
half
};
if ordering == MapKeyOrdering::CodeUnits {
return T::binary(
"<",
T::call("strcmp", vec![key_at(left), key_at(right)]),
int::<T>(0),
);
}
let signed_char = ordering == MapKeyOrdering::SignedInteger
&& matches!(
resolve_primitive(ast, key_type, 0),
Some(PrimitiveKind::Char)
);
let read = |index: &str| {
if signed_char {
T::paren(cast::<T>(T::named("signed char"), key_at(index)))
} else {
key_at(index)
}
};
if ordering == MapKeyOrdering::UnsignedInteger
&& matches!(
resolve_primitive(ast, key_type, 0),
Some(PrimitiveKind::UInt64)
)
{
return T::unsigned_less(8, read(left), read(right));
}
if matches!(
resolve_primitive(ast, key_type, 0),
Some(PrimitiveKind::Boolean)
) {
return T::bool_less(read(left), read(right));
}
T::binary("<", read(left), read(right))
}
fn enumeration_write<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
key: LayoutKey,
) -> Result<Vec<T::Stmt>, EncodeError> {
let Some(write) = scalar_write(ast, id, key, WcharPosition::Member) else {
return Err(EncodeError::NoWriter {
member: T::type_name(ast, id),
kind: "enumeration the layout plan has no scalar write for",
});
};
let holder = cast::<T>(
T::scalar(Scalar::Int64),
T::enumerator_ordinal(ast, id, deref::<T>("v")),
);
if zerodds_ast_layout::plan::enumerators_are_declared(key) {
return Ok(vec![primitive_write_at::<T>(write, holder)]);
}
let enumeration = ast
.type_(id)
.map_err(|error| EncodeError::NoLayout {
detail: format!("{error}"),
})?
.name
.clone();
let arms = zerodds_ast_layout::plan::enumerator_writes(ast, id, key).map_err(|enumerator| {
EncodeError::NoEnumeratorValue {
enumeration: enumeration.clone(),
enumerator,
detail: "its wire value does not evaluate under this --enumerator-value setting",
}
})?;
let mut statements = vec![T::declare(
T::scalar(Scalar::Int64),
"zd_wire",
Some(holder),
)];
let mut chain: Option<T::Block> = None;
for (name, on_wire) in arms.into_iter().rev() {
let constant = T::enumerator_constant(ast, id, &name);
chain = Some(T::block(vec![T::when(
T::binary("==", deref::<T>("v"), constant),
T::block(vec![T::perform(T::binary(
"=",
T::name("zd_wire"),
T::wide_int(on_wire),
))]),
chain,
)]));
}
if let Some(chain) = chain {
statements.extend(T::into_statements(chain));
}
statements.push(primitive_write_at::<T>(write, T::name("zd_wire")));
Ok(statements)
}
fn call<T: CodecTarget>(name: &str, args: Vec<T::Expr>) -> T::Stmt {
T::perform(T::runtime("w", name, args))
}
fn call_expr<T: CodecTarget>(name: &str, args: Vec<T::Expr>) -> T::Expr {
T::runtime("w", name, args)
}
fn bound_limit(ast: &Ast, bound: &Bound) -> Option<i128> {
bound
.expr()
.and_then(|expr| ast.eval_integral(expr).ok())
.filter(|limit| *limit >= 0)
}
fn bounded_string_write<T: CodecTarget>(
own_name: &str,
write: ScalarWrite,
value: T::Expr,
limit: i128,
) -> Result<T::Stmt, EncodeError> {
match write {
ScalarWrite::Str => Ok(call::<T>(
"write_string_bounded",
vec![value, T::unsigned_int(limit)],
)),
ScalarWrite::Wstr {
policy,
order,
code_units,
} => Ok(match code_units {
WstringCodeUnits::Utf8 => call::<T>(
"write_wstring_utf8_octets_bounded",
vec![
value,
T::unsigned_int(limit),
T::constant(wstring_policy(policy)),
],
),
WstringCodeUnits::Utf16 => call::<T>(
"write_wstring_bounded",
vec![
value,
T::unsigned_int(limit),
T::constant(wstring_policy(policy)),
T::constant(wstring_unit_order(order)),
],
),
}),
_ => Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "a bound on a scalar write that is not a string or a wstring",
}),
}
}
fn bound_check<T: CodecTarget>(method: &str, count: T::Expr, limit: i128) -> T::Stmt {
call::<T>(method, vec![count, T::unsigned_int(limit)])
}
fn deref<T: CodecTarget>(name: &str) -> T::Expr {
T::whole(name)
}
fn arrow<T: CodecTarget>(value: &str, field: &str) -> T::Expr {
T::member(value, field)
}
fn cast<T: CodecTarget>(type_: T::Type, value: T::Expr) -> T::Expr {
T::cast(type_, value)
}
fn int<T: CodecTarget>(value: i128) -> T::Expr {
T::int(value)
}
fn increment<T: CodecTarget>(name: &str) -> T::Expr {
T::binary(
"=",
T::name(name),
T::binary("+", T::name(name), int::<T>(1)),
)
}