use crate::adapter::c::{ordered_types, ordered_types_from, ordered_types_from_pruning};
use crate::emit::encode::CodecOptions;
use crate::emit::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
use crate::encode::EncodeError;
use zerodds_ast::{
Ast, CaseLabel, CharWidth, Extensibility, PrimitiveKind, TryConstruct, TypeKind, TypeNode,
};
use zerodds_ast_core::Id;
use zerodds_ast_layout::key::{
ElementPadding, FixedDigitCount, LayoutKey, WcharPosition, WcharWidth, WstringCodeUnits,
WstringLengthCounts, WstringLengthPolicy, WstringUnitOrder, WstringUnitWidth,
};
use zerodds_ast_layout::plan::{Extent, MemberHeader, ScalarWrite, scalar_write};
use zerodds_ast_layout::xcdr::DepCollector;
use zerodds_lang_cst::common::{Doc, Literal};
#[must_use]
pub fn decoder_name<T: CodecTarget>(ast: &Ast, id: Id<TypeNode>) -> String {
let name = match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Primitive(kind)) => {
format!("zd_dec_p_{}", kind.as_str().replace(' ', "_"))
}
_ => format!("zd_dec_{}", T::type_name(ast, id)),
};
T::published_name(name)
}
pub fn decoder<T: CodecTarget>(
ast: &Ast,
key: LayoutKey,
options: CodecOptions,
) -> Result<Vec<T::Decl>, EncodeError> {
decoder_over::<T>(
ast,
&ordered_types(ast),
&crate::emit::encode::sample_types(ast),
key,
options,
)
}
pub fn decoder_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 decoder::<T>(ast, key, options);
}
let (types, entries) = crate::emit::encode::kept_after_excluding(ast, excluded, also_reaching);
decoder_over::<T>(ast, &types, &entries, key, options)
}
pub fn decoder_from<T: CodecTarget>(
ast: &Ast,
root: Id<TypeNode>,
key: LayoutKey,
options: CodecOptions,
) -> Result<Vec<T::Decl>, EncodeError> {
decoder_over::<T>(
ast,
&ordered_types_from(ast, &[root]),
&[root],
key,
options,
)
}
pub fn decoder_from_many<T: CodecTarget>(
ast: &Ast,
roots: &[Id<TypeNode>],
key: LayoutKey,
options: CodecOptions,
) -> Result<Vec<T::Decl>, EncodeError> {
decoder_over::<T>(ast, &ordered_types_from(ast, roots), roots, key, options)
}
pub fn decoder_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);
decoder_over::<T>(ast, &types, roots, key, options)
}
#[must_use]
pub fn key_holder_decoder_name<T: CodecTarget>(ast: &Ast, root: Id<TypeNode>) -> String {
T::published_name(format!("zd_keyread_{}", decoder_name::<T>(ast, root)))
}
pub fn key_holder_decoder_from<T: CodecTarget>(
ast: &Ast,
root: Id<TypeNode>,
key: LayoutKey,
options: CodecOptions,
) -> Result<Option<Vec<T::Decl>>, EncodeError> {
let refuse = |detail: &str| EncodeError::NoLayout {
detail: detail.to_string(),
};
if key != LayoutKey::key(zerodds_ast_layout::Encoding::PlainCdr2) {
return Err(refuse(
"key holder inverse supports the default ForcedFinal PLAIN_CDR2-BE key dialect only",
));
}
if !matches!(
ast.type_(root).map(|node| &node.kind),
Ok(TypeKind::Structure { .. })
) {
return Err(refuse(
"key holder inverse currently supports struct roots only",
));
}
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() {
return Ok(None);
}
let layout = zerodds_ast_layout::keyhash::key_holder_plan_with(
ast,
root,
key,
&mut DepCollector::default(),
)
.map_err(crate::encode::plan_refusal)?;
let members = slots::<T>(ast, root, &layout)?;
for slot in &members {
if slot.optional
|| slot.external
|| slot.indirect
|| !matches!(
ast.type_(slot.type_).map(|node| &node.kind),
Ok(TypeKind::Primitive(_) | TypeKind::String { .. } | TypeKind::Enumeration { .. })
)
{
return Err(refuse(&format!(
"key holder inverse member `{}` requires unsupported nested, alias, collection or indirect key context",
slot.field
)));
}
}
if members.len() != keys.len() || layout.needs_dheader {
return Err(refuse(
"key holder inverse could not describe every planned key member",
));
}
let mut declarations = Vec::new();
let mut emitted = Vec::new();
let mut statements = Vec::new();
for slot in &members {
let name = key_holder_decoder_name::<T>(ast, slot.type_);
if !emitted.contains(&name) {
let body = body_of::<T>(ast, slot.type_, key, options, &name)?
.ok_or_else(|| refuse("key holder inverse has no member reader"))?;
declarations.push(initialized_key_reader::<T>(
ast,
slot.type_,
name.clone(),
body,
)?);
emitted.push(name.clone());
}
statements.push(T::read_call(
&name,
arrow::<T>("v", &slot.field),
Handoff::Value,
T::constant(Constant::TryConstructDiscard),
));
}
declarations.push(initialized_key_reader::<T>(
ast,
root,
key_holder_decoder_name::<T>(ast, root),
T::block(statements),
)?);
Ok(Some(declarations))
}
fn initialized_key_reader<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
name: String,
body: T::Block,
) -> Result<T::Decl, EncodeError> {
let initializer = T::default_into(ast, id, deref::<T>("v"))?;
T::initialized_reader(
Routine {
doc: Doc::none(),
name,
sample: T::value_type(ast, id),
held: Handoff::Value,
writing: false,
tolerate_unused: false,
extra: vec![(T::handle(Handle::TryConstruct), "zd_tc".to_string())],
body: Some(with_tc_used::<T>(body)),
},
initializer,
)
.ok_or_else(|| EncodeError::NoLayout {
detail: "target does not support explicit key-holder reader initialization".to_string(),
})
}
fn decoder_over<T: CodecTarget>(
ast: &Ast,
types: &[Id<TypeNode>],
entries: &[Id<TypeNode>],
key: LayoutKey,
options: CodecOptions,
) -> 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 uncalled = crate::emit::encode::called_only_in_bulk(
ast,
types,
entries,
T::bulk_octets_read("v", name::<T>("zd_n")).is_some(),
);
for id in types.iter().copied() {
if uncalled.contains(&id) {
continue;
}
let name = decoder_name::<T>(ast, id);
if emitted.contains(&name) {
continue;
}
let Some(body) = body_of::<T>(ast, id, key, options, &name)? else {
continue;
};
emitted.push(name.clone());
prototypes.extend(function::<T>(ast, id, key, &name, None));
bodies.extend(function::<T>(
ast,
id,
key,
&name,
Some(with_tc_used::<T>(body)),
));
}
prototypes.extend(bodies);
Ok(prototypes)
}
fn with_tc_used<T: CodecTarget>(body: T::Block) -> T::Block {
let mut statements = T::mark_used("zd_tc");
statements.extend(T::into_statements(body));
T::block(statements)
}
fn function<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
key: LayoutKey,
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: false,
tolerate_unused: crate::emit::encode::maybe_unused(ast, id, key),
extra: vec![(T::handle(Handle::TryConstruct), "zd_tc".to_string())],
body,
})
}
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,
}
}
const fn destination_form(is_array: bool) -> Handoff {
if is_array {
Handoff::Array
} else {
Handoff::Value
}
}
fn member_read<T: CodecTarget>(ast: &Ast, slot: &Slot<T>, place: T::Expr) -> Vec<T::Stmt> {
let Some(assign_default) = slot.explicit_default.clone() else {
return vec![decode_into::<T>(ast, slot.type_, place, slot.try_construct)];
};
vec![
decode_into::<T>(ast, slot.type_, place, TryConstruct::Discard),
T::when(
call_expr::<T>("reader_clear_discard", Vec::new()),
T::block(assign_default),
None,
),
]
}
fn decode_element_into<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
place: T::Expr,
behaviour: TryConstruct,
key: LayoutKey,
) -> T::Stmt {
if 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))
{
let Some(write) = scalar_write(ast, id, key, WcharPosition::Element) else {
return decode_into::<T>(ast, id, place, behaviour);
};
return T::store(place, T::value_type(ast, id), primitive_read_at::<T>(write));
}
decode_into::<T>(ast, id, place, behaviour)
}
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,
}
}
fn decode_into<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
place: T::Expr,
behaviour: TryConstruct,
) -> T::Stmt {
T::read_call(
&decoder_name::<T>(ast, id),
place,
destination_form(is_array(ast, id, 0)),
T::constant(try_construct_constant(behaviour)),
)
}
const fn try_construct_constant(behaviour: TryConstruct) -> Constant {
match behaviour {
TryConstruct::Discard => Constant::TryConstructDiscard,
TryConstruct::UseDefault => Constant::TryConstructUseDefault,
TryConstruct::Trim => Constant::TryConstructTrim,
}
}
fn narrow<T: CodecTarget>(trim: Vec<T::Stmt>, use_default: Vec<T::Stmt>) -> T::Stmt {
let mut discard = vec![refuse::<T>(Constant::StatusDiscarded), T::give_back(None)];
let mut default_arm = use_default;
default_arm.push(T::give_back(None));
let mut trim_arm = trim;
if trim_arm.is_empty() {
trim_arm = core::mem::take(&mut discard);
} else {
trim_arm.push(T::give_back(None));
}
T::when(
T::binary(
"==",
name::<T>("zd_tc"),
T::constant(Constant::TryConstructTrim),
),
T::block(trim_arm),
Some(T::block(vec![T::when(
T::binary(
"==",
name::<T>("zd_tc"),
T::constant(Constant::TryConstructUseDefault),
),
T::block(default_arm),
Some(T::block(if discard.is_empty() {
vec![refuse::<T>(Constant::StatusDiscarded), T::give_back(None)]
} else {
discard
})),
)])),
)
}
#[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![T::store(
deref::<T>("v"),
T::value_type(ast, id),
primitive_read_at::<T>(write),
)]
}
TypeKind::String {
width: CharWidth::Char,
bound,
} => {
let mut statements = vec![assign::<T>(
deref::<T>("v"),
call_expr::<T>("read_string_alloc", Vec::new()),
)];
statements.extend(string_fit::<T>(ast, id, bound, CharWidth::Char)?);
statements
}
TypeKind::String {
width: CharWidth::WChar,
bound,
} => {
let mut statements = vec![assign::<T>(
deref::<T>("v"),
match scalar_write(ast, id, key, WcharPosition::Member) {
Some(ScalarWrite::Wstr {
policy,
order,
code_units,
}) => match code_units {
WstringCodeUnits::Utf8 => call_expr::<T>(
"read_wstring_utf8_octets",
vec![T::constant(wstring_policy(policy))],
),
WstringCodeUnits::Utf16 => call_expr::<T>(
"read_wstring_utf8",
vec![
T::constant(wstring_policy(policy)),
T::constant(wstring_unit_order(order)),
],
),
},
_ => {
return Err(EncodeError::NoWriter {
member: T::type_name(ast, id),
kind: "wstring the layout plan has no scalar write for",
});
}
},
)];
statements.extend(string_fit::<T>(ast, id, bound, CharWidth::WChar)?);
statements
}
TypeKind::Fixed { .. } => {
let Some(ScalarWrite::Fixed {
digits,
scale,
count,
}) = scalar_write(ast, id, key, WcharPosition::Member)
else {
return Err(EncodeError::NoWriter {
member: T::type_name(ast, id),
kind: "fixed the layout plan has no scalar write for",
});
};
vec![assign::<T>(
deref::<T>("v"),
call_expr::<T>(
"read_fixed",
vec![
int::<T>(i128::from(digits)),
int::<T>(i128::from(scale)),
T::constant(match count {
FixedDigitCount::Declared => Constant::FixedDeclared,
FixedDigitCount::ValuePrecision => Constant::FixedValuePrecision,
FixedDigitCount::ValueStringDigits => Constant::FixedValueStringDigits,
}),
],
),
)]
}
TypeKind::Enumeration { .. } => enumeration_body::<T>(ast, id, key),
TypeKind::Bitmask { .. } => bitmask_body::<T>(ast, id),
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 read = call_expr::<T>("read_bitset", vec![int::<T>(i128::from(anchor))]);
let named = fields.iter().any(|field| {
ast.bitfield(*field)
.is_ok_and(|field_node| field_node.name.is_some())
});
let mut statements = if named {
vec![T::declare(T::scalar(Scalar::UInt64), "zd_bits", Some(read))]
} else {
vec![T::drop_value(read)]
};
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 position.saturating_add(width) > 64 {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "bitset wider than the sixty-four bits an anchor holds",
});
}
if let Some(field_name) = field_node.name.as_ref() {
let mask: u64 = if width >= 64 {
u64::MAX
} else {
(1u64 << width) - 1
};
statements.push(assign::<T>(
arrow::<T>("v", &T::escape(field_name)),
cast::<T>(
T::unsigned_holder(field_node.bits),
T::paren(T::binary(
"&",
T::paren(T::binary(
">>",
name::<T>("zd_bits"),
T::wide_unsigned(u64::from(position)),
)),
T::wide_unsigned(mask),
)),
),
));
}
position = position.saturating_add(width);
}
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 = decoder_name::<T>(ast, target);
if callee == own_name {
return Ok(None);
}
vec![T::read_forward(&callee)]
}
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",
});
};
sequence_body::<T>(ast, id, element, bound, key)?
}
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 {
lengths.push(ast.eval_integral(*dim).map_err(|_| EncodeError::NoWriter {
member: own_name.to_string(),
kind: "array whose length does not evaluate",
})?);
}
array_body::<T>(
ast,
id,
element,
&lengths,
key,
ast.element_try_construct(id, None),
)
}
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)?;
map_body::<T>(ast, id, k, value, bound, layout.needs_dheader, options)?
}
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 enumeration_body<T: CodecTarget>(ast: &Ast, id: Id<TypeNode>, key: LayoutKey) -> Vec<T::Stmt> {
let bits = zerodds_ast_layout::plan::enumeration_holder_bits(ast, id, key);
let mut statements = vec![T::declare(
T::scalar(Scalar::Int64),
"zd_raw",
Some(call_expr::<T>(
"read_enum",
vec![int::<T>(i128::from(bits))],
)),
)];
let default_arm = crate::emit::default_enumerator_name(ast, id)
.map(|literal| T::enumerator_constant(ast, id, &literal))
.map(|constant| vec![assign::<T>(deref::<T>("v"), constant)])
.unwrap_or_default();
let mut chain: Option<T::Block> = Some(T::block(vec![narrow::<T>(Vec::new(), default_arm)]));
let mut arms = Vec::new();
let resolved = zerodds_ast_layout::plan::enumerator_writes(ast, id, key).unwrap_or_default();
for (name, value) in resolved {
let Some(pattern) = enumerator_pattern(value, bits) else {
continue;
};
arms.push((pattern, T::enumerator_constant(ast, id, &name)));
}
for (pattern, constant) in arms.into_iter().rev() {
chain = Some(T::block(vec![T::when(
T::binary("==", name::<T>("zd_raw"), T::wide_signed(pattern)),
T::block(vec![assign::<T>(deref::<T>("v"), constant)]),
chain,
)]));
}
if let Some(chain) = chain {
statements.extend(T::into_statements(chain));
}
statements
}
fn enumerator_pattern(value: i128, bits: u16) -> Option<i128> {
if bits == 0 || bits > 32 {
return None;
}
let width = i128::from(bits);
if value < -(1i128 << (width - 1)) || value > (1i128 << width) - 1 {
return None;
}
let masked = value & ((1i128 << width) - 1);
Some(if masked >= (1i128 << (width - 1)) {
masked - (1i128 << width)
} else {
masked
})
}
fn bitmask_body<T: CodecTarget>(ast: &Ast, id: Id<TypeNode>) -> Vec<T::Stmt> {
let bound = ast.effective_bit_bound(id);
let mut declared: u64 = 0;
for bit in ast.bits_of(id) {
let Some(position) = ast.effective_bit_position(*bit) else {
continue;
};
if position < bound && position < 64 {
declared |= 1u64 << position;
}
}
vec![
T::declare(
T::scalar(Scalar::UInt64),
"zd_bits",
Some(T::paren(T::binary(
"&",
call_expr::<T>("read_bitmask", vec![int::<T>(i128::from(bound))]),
T::wide_unsigned(declared),
))),
),
assign::<T>(
deref::<T>("v"),
T::bitmask_value(ast, id, name::<T>("zd_bits")),
),
]
}
fn bound_limit(ast: &Ast, bound: &zerodds_ast::Bound) -> Option<i128> {
bound
.expr()
.and_then(|expr| ast.eval_integral(expr).ok())
.filter(|limit| *limit >= 0)
}
fn sequence_body<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
element: Id<TypeNode>,
bound: &zerodds_ast::Bound,
key: LayoutKey,
) -> Result<Vec<T::Stmt>, EncodeError> {
let delimited = zerodds_ast_layout::collection_is_delimited(ast, element, key);
let element_behaviour = ast.element_try_construct(id, None);
let mut statements = Vec::new();
if delimited {
statements.push(discard_dheader::<T>());
}
statements.push(T::declare(
T::scalar(Scalar::UInt32),
"zd_n",
Some(call_expr::<T>("read_u32", Vec::new())),
));
statements.push(T::when(
T::binary(
">",
cast::<T>(T::scalar(Scalar::Size), name::<T>("zd_n")),
remaining::<T>(),
),
T::block(vec![
refuse::<T>(Constant::StatusImpossibleLength),
T::give_back(None),
]),
None,
));
let limit = bound_limit(ast, bound);
if let Some(limit) = limit {
statements.push(T::declare(
T::scalar(Scalar::Bool),
"zd_over",
Some(T::binary(">", name::<T>("zd_n"), T::unsigned_int(limit))),
));
}
let element_type = T::value_type(ast, element);
let packed = key.element_padding == ElementPadding::Packed;
let carries_length = crate::emit::encode::sequence_element_carries_length(ast, element, key)?;
let fill = match crate::emit::encode::bulk_read::<T>(ast, element, name::<T>("zd_n")) {
Some(bulk) => bulk,
None => {
let (before, place, after) =
T::sequence_element("v", name::<T>("zd_i"), element_type.clone());
let mut body: Vec<T::Stmt> = Vec::new();
if packed {
body.push(begin_packed::<T>());
}
if carries_length {
body.extend(read_element_length::<T>());
}
body.extend(before);
body.push(decode_element_into::<T>(
ast,
element,
place,
element_behaviour,
key,
));
body.extend(after);
if carries_length {
body.push(close_element_length::<T>());
}
if packed {
body.push(finish_packed::<T>());
}
counted_loop::<T>("zd_i", name::<T>("zd_n"), T::block(body))
}
};
statements.extend(T::make_room_of(
ast,
element,
"v",
element_type,
name::<T>("zd_n"),
true,
vec![fill],
));
if let Some(limit) = limit {
statements.push(T::when(
name::<T>("zd_over"),
T::block(vec![narrow::<T>(
T::keep_first("v", T::unsigned_int(limit)),
vec![T::default_into(ast, id, deref::<T>("v"))?],
)]),
None,
));
}
Ok(statements)
}
fn string_fit<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
bound: &zerodds_ast::Bound,
width: CharWidth,
) -> Result<Vec<T::Stmt>, EncodeError> {
let Some(limit) = bound_limit(ast, bound) else {
return Ok(Vec::new());
};
let (measure, trim) = match width {
CharWidth::Char => ("utf8_octets", "trim_utf8"),
CharWidth::WChar => ("utf16_units", "trim_utf16"),
};
let fits = || cast::<T>(T::scalar(Scalar::Size), T::unsigned_int(limit));
let too_long = T::binary(
">",
T::runtime_static(measure, vec![deref::<T>("v")]),
fits(),
);
Ok(vec![T::when(
match T::materialised(deref::<T>("v")) {
Some(present) => T::binary("&&", present, too_long),
None => too_long,
},
T::block(vec![narrow::<T>(
vec![assign::<T>(
deref::<T>("v"),
T::runtime("r", trim, vec![deref::<T>("v"), fits()]),
)],
vec![T::default_into(ast, id, deref::<T>("v"))?],
)]),
None,
)])
}
fn array_body<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
element: Id<TypeNode>,
lengths: &[i128],
key: LayoutKey,
element_behaviour: TryConstruct,
) -> Vec<T::Stmt> {
let _ = id;
let delimited = zerodds_ast_layout::array_is_delimited(ast, element, key);
if let [length] = lengths {
if let Some(bulk) =
crate::emit::encode::bulk_read::<T>(ast, element, T::unsigned_int(*length))
{
let mut statements = Vec::new();
if delimited {
statements.push(discard_dheader::<T>());
}
statements.extend(T::open_array("v", lengths));
statements.push(bulk);
statements.extend(T::close_array("v", lengths));
return statements;
}
}
let mut access = name::<T>("v");
for level in 0..lengths.len() {
access = T::index(access, name::<T>(&format!("zd_i{level}")));
}
let mut inner = T::block(vec![decode_element_into::<T>(
ast,
element,
access,
element_behaviour,
key,
)]);
let packed = key.element_padding == ElementPadding::Packed;
for (level, length) in lengths.iter().enumerate().rev() {
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![counted_loop::<T>(
&format!("zd_i{level}"),
T::unsigned_int(*length),
T::block(body),
)]);
}
let mut statements = Vec::new();
if delimited {
statements.push(discard_dheader::<T>());
}
statements.extend(T::open_array("v", lengths));
statements.extend(T::into_statements(inner));
statements.extend(T::close_array("v", lengths));
statements
}
fn map_body<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
key_type: Id<TypeNode>,
value_type_: Id<TypeNode>,
bound: &zerodds_ast::Bound,
delimited: bool,
options: CodecOptions,
) -> Result<Vec<T::Stmt>, EncodeError> {
let key_behaviour = ast.key_try_construct(id);
let value_behaviour = ast.element_try_construct(id, None);
let entry_type = T::map_entry_type(ast, id);
let counted = !delimited || options.map_length_with_delimiter;
let mut statements = Vec::new();
if delimited {
statements.push(T::declare(
T::scalar(Scalar::UInt32),
"zd_dl",
Some(call_expr::<T>("read_dheader", Vec::new())),
));
statements.push(T::declare(
T::scalar(Scalar::Size),
"zd_end",
Some(offset_plus::<T>(position::<T>(), name::<T>("zd_dl"))),
));
}
let read_entry = |index: T::Expr| {
T::block(vec![
decode_into::<T>(
ast,
key_type,
T::entry_half(T::index(T::elements("v"), index.clone()), true),
key_behaviour,
),
decode_into::<T>(
ast,
value_type_,
T::entry_half(T::index(T::elements("v"), index), false),
value_behaviour,
),
])
};
if counted {
statements.push(T::declare(
T::scalar(Scalar::UInt32),
"zd_n",
Some(call_expr::<T>("read_u32", Vec::new())),
));
} else {
statements.push(T::storage(entry_type.clone(), "zd_scratch"));
statements.push(T::declare(
T::handle(Handle::ReaderMark),
"zd_mark",
Some(call_expr::<T>("reader_save", Vec::new())),
));
statements.push(T::declare(
T::scalar(Scalar::UInt32),
"zd_n",
Some(T::unsigned_int(0)),
));
statements.push(T::until(
T::binary(
"&&",
T::binary("<", position::<T>(), name::<T>("zd_end")),
status_is_ok::<T>(),
),
T::block(vec![
decode_into::<T>(
ast,
key_type,
T::entry_half(name::<T>("zd_scratch"), true),
key_behaviour,
),
decode_into::<T>(
ast,
value_type_,
T::entry_half(name::<T>("zd_scratch"), false),
value_behaviour,
),
assign::<T>(
name::<T>("zd_n"),
T::binary("+", name::<T>("zd_n"), T::unsigned_int(1)),
),
]),
));
statements.push(T::perform(call_expr::<T>(
"reader_restore",
vec![name::<T>("zd_mark")],
)));
}
statements.push(T::when(
T::binary(
">",
cast::<T>(T::scalar(Scalar::Size), name::<T>("zd_n")),
remaining::<T>(),
),
T::block(vec![
refuse::<T>(Constant::StatusImpossibleLength),
T::give_back(None),
]),
None,
));
let limit = bound_limit(ast, bound);
if let Some(limit) = limit {
statements.push(T::declare(
T::scalar(Scalar::Bool),
"zd_over",
Some(T::binary(">", name::<T>("zd_n"), T::unsigned_int(limit))),
));
}
statements.extend(T::make_room(
"v",
entry_type,
name::<T>("zd_n"),
false,
vec![counted_loop::<T>(
"zd_i",
name::<T>("zd_n"),
read_entry(name::<T>("zd_i")),
)],
));
if delimited {
statements.push(T::perform(call_expr::<T>(
"reader_skip_to",
vec![name::<T>("zd_end")],
)));
}
if let Some(limit) = limit {
statements.push(T::when(
name::<T>("zd_over"),
T::block(vec![narrow::<T>(
T::keep_first("v", T::unsigned_int(limit)),
vec![T::default_into(ast, id, deref::<T>("v"))?],
)]),
None,
));
}
Ok(statements)
}
struct Slot<T: CodecTarget> {
field: String,
type_: Id<TypeNode>,
optional: bool,
external: bool,
indirect: bool,
by_reference: bool,
member_id: u32,
header: MemberHeader,
member: Id<zerodds_ast::MemberNode>,
try_construct: TryConstruct,
explicit_default: Option<Vec<T::Stmt>>,
}
fn slots<T: CodecTarget>(
ast: &Ast,
id: Id<TypeNode>,
layout: &zerodds_ast_layout::TypeLayout,
) -> Result<Vec<Slot<T>>, EncodeError> {
let all = ast.all_members_of(id);
let mut out = Vec::with_capacity(layout.members.len());
for member in &layout.members {
let mut found = None;
for candidate in &all {
let Ok(node) = ast.member(*candidate) else {
continue;
};
if node.node_id == member.member {
found = Some((*candidate, node));
break;
}
}
let Some((handle, node)) = found else {
continue;
};
let Some(member_type) = node.type_.resolved() else {
continue;
};
let try_construct = ast.effective_try_construct(
node.facets.xtypes.as_ref().and_then(|x| x.try_construct),
Some(member_type),
);
let reach = super::reach::<T>(ast, id, member.member, &node.name);
let explicit_default = if try_construct == TryConstruct::UseDefault
&& node
.facets
.xtypes
.as_ref()
.is_some_and(|x| x.default_value.is_some())
{
Some(T::member_default(
ast,
node,
member_type,
arrow::<T>("v", &reach.field),
)?)
} else {
None
};
let optional = node.facets.is_optional();
let external = super::reached_externally::<T>(ast, node);
out.push(Slot::<T> {
indirect: super::behind_pointer::<T>(
ast,
reach.holder,
member_type,
external,
optional,
),
by_reference: T::holds_by_reference(ast, reach.holder, member_type, optional, external),
field: reach.field,
type_: member_type,
optional,
external,
member_id: member.member_id,
header: member.header,
member: handle,
try_construct,
explicit_default,
});
}
Ok(out)
}
fn slot_destination<T: CodecTarget>(ast: &Ast, slot: &Slot<T>) -> (Vec<T::Stmt>, T::Expr) {
if slot.by_reference {
return T::reference_destination(ast, slot.type_, "v", &slot.field);
}
let (mut setup, place) = if slot.external {
T::external_destination("v", &slot.field, slot.optional)
} else if slot.optional {
T::optional_destination("v", &slot.field)
} else {
(Vec::new(), arrow::<T>("v", &slot.field))
};
if slot.indirect {
let (made, through) = T::make_indirection(ast, slot.type_, place);
setup.extend(made);
return (setup, through);
}
(setup, place)
}
fn slot_default_place<T: CodecTarget>(
ast: &Ast,
slot: &Slot<T>,
place: T::Expr,
) -> (Vec<T::Stmt>, T::Expr) {
if slot.indirect {
return T::make_indirection(ast, slot.type_, place);
}
(Vec::new(), place)
}
fn slot_set_present<T: CodecTarget>(slot: &Slot<T>, present: bool) -> Vec<T::Stmt> {
if slot.by_reference {
return if present {
Vec::new()
} else {
T::reference_absent("v", &slot.field)
};
}
if slot.external {
return T::external_set_present("v", &slot.field, present);
}
T::set_present("v", &slot.field, present)
}
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 members = slots::<T>(ast, id, &layout)?;
let extensibility = ast.effective_extensibility(id);
let mutable = extensibility == Extensibility::Mutable;
let mut statements = Vec::new();
let packed = key.element_padding == ElementPadding::Packed;
if packed {
statements.push(begin_packed::<T>());
}
if layout.needs_dheader {
statements.extend(read_delimiter::<T>());
}
for slot in &members {
if slot.optional {
statements.extend(slot_set_present::<T>(slot, false));
}
}
if mutable && key.encoding.is_v1() {
statements.extend(parameter_list::<T>(ast, &members)?);
} else if mutable {
if !layout.needs_dheader {
return Err(EncodeError::NoLayout {
detail: "a mutable type under XCDR2 with no DHEADER planned".to_string(),
});
}
statements.extend(member_headers::<T>(ast, &members)?);
} else {
let tail = Tail {
delimited: layout.needs_dheader,
appendable: extensibility == Extensibility::Appendable,
};
statements.extend(plan_order::<T>(ast, &members, tail)?);
}
if layout.needs_dheader {
statements.extend(close_delimiter::<T>());
}
if packed {
statements.push(finish_packed::<T>());
}
Ok(statements)
}
fn read_delimiter<T: CodecTarget>() -> Vec<T::Stmt> {
vec![
T::declare(
T::scalar(Scalar::UInt32),
"zd_dl",
Some(call_expr::<T>("read_dheader", Vec::new())),
),
T::declare(
T::scalar(Scalar::Size),
"zd_end",
Some(offset_plus::<T>(position::<T>(), name::<T>("zd_dl"))),
),
]
}
fn close_delimiter<T: CodecTarget>() -> Vec<T::Stmt> {
let statements = vec![T::when(
T::binary(">", position::<T>(), name::<T>("zd_end")),
T::block(vec![refuse::<T>(Constant::StatusOutOfRange)]),
None,
)];
let mut statements = statements;
statements.push(T::perform(call_expr::<T>(
"reader_skip_to",
vec![name::<T>("zd_end")],
)));
statements
}
const PACKED_SAVED: &str = "zd_saved";
fn begin_packed<T: CodecTarget>() -> T::Stmt {
T::declare(
T::scalar(Scalar::Size),
PACKED_SAVED,
Some(call_expr::<T>("reader_begin_packed_element", Vec::new())),
)
}
fn finish_packed<T: CodecTarget>() -> T::Stmt {
T::perform(call_expr::<T>(
"reader_finish_packed_element",
vec![name::<T>(PACKED_SAVED)],
))
}
fn read_element_length<T: CodecTarget>() -> Vec<T::Stmt> {
vec![
T::declare(
T::scalar(Scalar::UInt32),
"zd_ell",
Some(call_expr::<T>("read_u32", Vec::new())),
),
T::declare(
T::scalar(Scalar::Size),
"zd_elend",
Some(offset_plus::<T>(position::<T>(), name::<T>("zd_ell"))),
),
]
}
fn close_element_length<T: CodecTarget>() -> T::Stmt {
ends_where_the_header_said::<T>("zd_elend")
}
fn plan_order<T: CodecTarget>(
ast: &Ast,
members: &[Slot<T>],
tail: Tail,
) -> Result<Vec<T::Stmt>, EncodeError> {
let mut statements = Vec::new();
if tail.possible() && !members.is_empty() {
statements.push(T::declare(
T::scalar(Scalar::Bool),
"zd_tail",
Some(literal::<T>("false")),
));
}
for (index, slot) in members.iter().enumerate() {
let place = arrow::<T>("v", &slot.field);
if tail.possible() {
let reached = if index == 0 {
T::paren(tail.reached::<T>())
} else {
T::binary(
"&&",
T::unary("!", name::<T>("zd_tail")),
T::paren(tail.reached::<T>()),
)
};
statements.push(T::when(
reached,
T::block(vec![assign::<T>(
name::<T>("zd_tail"),
literal::<T>("true"),
)]),
None,
));
let mut defaulted = Vec::new();
if slot.optional {
defaulted.extend(slot_set_present::<T>(slot, false));
} else {
let node = ast
.member(slot.member)
.map_err(|error| EncodeError::NoLayout {
detail: format!("{}: {error}", slot.field),
})?;
let (room, into) = slot_default_place::<T>(ast, slot, place.clone());
defaulted.extend(room);
defaulted.extend(T::member_default(ast, node, slot.type_, into)?);
}
let read = plan_order_member::<T>(ast, slot, index, place)?;
statements.push(T::when(
name::<T>("zd_tail"),
T::block(defaulted),
Some(T::block(read)),
));
continue;
}
statements.extend(plan_order_member::<T>(ast, slot, index, place)?);
}
Ok(statements)
}
#[derive(Debug, Clone, Copy)]
struct Tail {
delimited: bool,
appendable: bool,
}
impl Tail {
const fn possible(self) -> bool {
self.appendable
}
fn reached<T: CodecTarget>(self) -> T::Expr {
if self.delimited {
return T::binary(">=", position::<T>(), name::<T>("zd_end"));
}
T::binary("==", remaining::<T>(), T::int(0))
}
}
fn plan_order_member<T: CodecTarget>(
ast: &Ast,
slot: &Slot<T>,
index: usize,
place: T::Expr,
) -> Result<Vec<T::Stmt>, EncodeError> {
let mut statements = Vec::new();
{
match slot.header {
MemberHeader::NoHeader => {
if slot.optional {
let (room, into) = slot_destination::<T>(ast, slot);
statements.extend(room);
statements.extend(member_read::<T>(ast, slot, into));
statements.extend(slot_set_present::<T>(slot, true));
} else if slot.external || slot.indirect {
let (room, into) = slot_destination::<T>(ast, slot);
statements.extend(room);
statements.extend(member_read::<T>(ast, slot, into));
} else {
statements.extend(member_read::<T>(ast, slot, place));
}
}
MemberHeader::OptionalFlag => statements.extend(present_flag::<T>(ast, slot)),
MemberHeader::OptionalCdr1 { .. } => {
statements.extend(optional_parameter::<T>(ast, slot, index));
}
MemberHeader::EmHeader { .. } | MemberHeader::PidCdr1 { .. } => {
return Err(EncodeError::NoLayout {
detail: format!(
"{}: an MMEMBER header in a type that is not `@mutable`",
slot.field
),
});
}
}
}
Ok(statements)
}
fn present_flag<T: CodecTarget>(ast: &Ast, slot: &Slot<T>) -> Vec<T::Stmt> {
let flag = format!("zd_has_{}", slot.field);
let mut arrived = slot_set_present::<T>(slot, true);
let (room, into) = slot_destination::<T>(ast, slot);
arrived.extend(room);
arrived.extend(member_read::<T>(ast, slot, into));
vec![
T::declare(
T::scalar(Scalar::Bool),
&flag,
Some(call_expr::<T>("read_bool", Vec::new())),
),
T::when(
name::<T>(&flag),
T::block(arrived),
Some(T::block(slot_set_present::<T>(slot, false))),
),
]
}
fn optional_parameter<T: CodecTarget>(ast: &Ast, slot: &Slot<T>, index: usize) -> Vec<T::Stmt> {
let header = format!("zd_p{index}");
let flag = format!("zd_has_{}", slot.field);
let mine = T::binary(
"&&",
T::paren(status_is_ok::<T>()),
T::paren(T::binary(
"&&",
T::paren(T::binary(
"==",
T::header_field(name::<T>(&header), "pid"),
T::unsigned_int(i128::from(slot.member_id)),
)),
T::paren(T::binary(
"==",
T::header_field(name::<T>(&header), "end"),
literal::<T>("false"),
)),
)),
);
let mut arrived = slot_set_present::<T>(slot, true);
let (room, into) = slot_destination::<T>(ast, slot);
arrived.extend(room);
arrived.extend(member_read::<T>(ast, slot, into));
vec![
T::declare(
T::handle(Handle::PidRead),
&header,
Some(call_expr::<T>(
"read_pid_as",
vec![T::constant(Constant::ParameterOptional)],
)),
),
T::when(
mine,
T::block(vec![
T::declare(
T::scalar(Scalar::Bool),
&flag,
Some(T::binary(
"!=",
T::header_field(name::<T>(&header), "length"),
T::unsigned_int(0),
)),
),
T::when(
name::<T>(&flag),
T::block(arrived),
Some(T::block(slot_set_present::<T>(slot, false))),
),
T::perform(call_expr::<T>("finish_pid_read", vec![name::<T>(&header)])),
]),
Some(T::block(vec![
refuse::<T>(Constant::StatusOutOfRange),
T::give_back(None),
])),
),
]
}
fn member_headers<T: CodecTarget>(
ast: &Ast,
members: &[Slot<T>],
) -> Result<Vec<T::Stmt>, EncodeError> {
let mut statements = Vec::new();
for (index, slot) in members.iter().enumerate() {
let _ = slot;
statements.push(T::declare(
T::scalar(Scalar::Bool),
&format!("zd_seen{index}"),
Some(literal::<T>("false")),
));
}
statements.push(T::declare(T::handle(Handle::EmHeaderRead), "zd_h", None));
statements.push(T::declare(
T::scalar(Scalar::Size),
"zd_ends",
Some(literal::<T>("0")),
));
let mut chain = Some(T::block(vec![T::when(
T::header_field(name::<T>("zd_h"), "must_understand"),
T::block(vec![refuse::<T>(Constant::StatusOutOfRange)]),
Some(T::block(vec![T::perform(call_expr::<T>(
"reader_skip_to",
vec![name::<T>("zd_ends")],
))])),
)]));
for (index, slot) in members.iter().enumerate().rev() {
let seen = format!("zd_seen{index}");
let mut arm = vec![T::when(
name::<T>(&seen),
T::block(vec![
refuse::<T>(Constant::StatusOutOfRange),
T::give_back(None),
]),
None,
)];
arm.push(assign::<T>(name::<T>(&seen), literal::<T>("true")));
if slot.optional {
let (room, into) = slot_destination::<T>(ast, slot);
arm.extend(room);
arm.extend(member_read::<T>(ast, slot, into));
arm.extend(slot_set_present::<T>(slot, true));
} else {
let (room, into) = slot_destination::<T>(ast, slot);
arm.extend(room);
arm.extend(member_read::<T>(ast, slot, into));
}
arm.push(ends_where_the_header_said::<T>("zd_ends"));
chain = Some(T::block(vec![T::when(
T::binary(
"==",
T::header_field(name::<T>("zd_h"), "member_id"),
T::unsigned_int(i128::from(slot.member_id)),
),
T::block(arm),
chain,
)]));
}
let mut loop_body = vec![
assign::<T>(
name::<T>("zd_h"),
call_expr::<T>("read_emheader", Vec::new()),
),
T::when(
T::binary(
"!=",
call_expr::<T>("reader_status", Vec::new()),
T::constant(Constant::StatusOk),
),
T::block(vec![T::leave()]),
None,
),
assign::<T>(
name::<T>("zd_ends"),
offset_plus::<T>(
position::<T>(),
T::header_field(name::<T>("zd_h"), "extent"),
),
),
];
if let Some(chain) = chain {
loop_body.extend(T::into_statements(chain));
}
statements.push(T::until(
T::binary(
"&&",
T::paren(T::binary("<", position::<T>(), name::<T>("zd_end"))),
T::paren(status_is_ok::<T>()),
),
T::block(loop_body),
));
statements.extend(defaults_for_absent::<T>(ast, members)?);
Ok(statements)
}
fn parameter_list<T: CodecTarget>(
ast: &Ast,
members: &[Slot<T>],
) -> Result<Vec<T::Stmt>, EncodeError> {
let mut statements = Vec::new();
for index in 0..members.len() {
statements.push(T::declare(
T::scalar(Scalar::Bool),
&format!("zd_seen{index}"),
Some(literal::<T>("false")),
));
}
statements.push(T::declare(T::handle(Handle::PidRead), "zd_p", None));
statements.push(T::declare(
T::scalar(Scalar::Size),
"zd_at",
Some(literal::<T>("0")),
));
let skip_parameter = T::block(vec![T::perform(call_expr::<T>(
"reader_skip_to",
vec![offset_plus::<T>(
name::<T>("zd_at"),
T::header_field(name::<T>("zd_p"), "length"),
)],
))]);
let unknown = T::block(vec![T::when(
T::header_field(name::<T>("zd_p"), "must_understand"),
T::block(vec![
refuse::<T>(Constant::StatusOutOfRange),
T::give_back(None),
]),
Some(skip_parameter.clone()),
)]);
let mut chain = Some(unknown.clone());
for (index, slot) in members.iter().enumerate().rev() {
let seen = format!("zd_seen{index}");
let mut arm = vec![T::when(
name::<T>(&seen),
T::block(vec![
refuse::<T>(Constant::StatusOutOfRange),
T::give_back(None),
]),
None,
)];
arm.push(assign::<T>(name::<T>(&seen), literal::<T>("true")));
if slot.optional {
let (room, into) = slot_destination::<T>(ast, slot);
arm.extend(room);
arm.extend(member_read::<T>(ast, slot, into));
arm.extend(slot_set_present::<T>(slot, true));
} else {
let (room, into) = slot_destination::<T>(ast, slot);
arm.extend(room);
arm.extend(member_read::<T>(ast, slot, into));
}
chain = Some(T::block(vec![T::when(
T::binary(
"==",
T::header_field(name::<T>("zd_p"), "pid"),
T::unsigned_int(i128::from(slot.member_id)),
),
T::block(arm),
chain,
)]));
}
let mut loop_body = vec![
T::when(
T::binary("<", remaining::<T>(), T::int(4)),
T::block(vec![refuse::<T>(Constant::StatusTruncated), T::leave()]),
None,
),
assign::<T>(name::<T>("zd_p"), call_expr::<T>("read_pid", Vec::new())),
T::when(
T::binary(
"!=",
call_expr::<T>("reader_status", Vec::new()),
T::constant(Constant::StatusOk),
),
T::block(vec![T::leave()]),
None,
),
T::when(
T::header_field(name::<T>("zd_p"), "end"),
T::block(vec![T::leave()]),
None,
),
assign::<T>(name::<T>("zd_at"), position::<T>()),
];
if let Some(chain) = chain {
loop_body.push(T::when(
T::header_field(name::<T>("zd_p"), "ignore"),
skip_parameter,
Some(T::block(vec![T::when(
T::runtime_static("pid_is_user_member", vec![name::<T>("zd_p")]),
chain,
Some(unknown),
)])),
));
}
loop_body.push(T::perform(call_expr::<T>(
"finish_pid_read",
vec![name::<T>("zd_p")],
)));
statements.push(T::until(status_is_ok::<T>(), T::block(loop_body)));
statements.extend(defaults_for_absent::<T>(ast, members)?);
Ok(statements)
}
fn defaults_for_absent<T: CodecTarget>(
ast: &Ast,
members: &[Slot<T>],
) -> Result<Vec<T::Stmt>, EncodeError> {
let mut statements = Vec::new();
for (index, slot) in members.iter().enumerate() {
if slot.optional {
continue;
}
let node = ast
.member(slot.member)
.map_err(|error| EncodeError::NoLayout {
detail: format!("{}: {error}", slot.field),
})?;
let (room, into) = slot_default_place::<T>(ast, slot, arrow::<T>("v", &slot.field));
let mut defaulted = room;
defaulted.extend(T::member_default(ast, node, slot.type_, into)?);
statements.push(T::when(
T::binary(
"==",
name::<T>(&format!("zd_seen{index}")),
literal::<T>("false"),
),
T::block(defaulted),
None,
));
}
Ok(statements)
}
fn ends_where_the_header_said<T: CodecTarget>(ends: &str) -> T::Stmt {
T::when(
T::binary("!=", position::<T>(), name::<T>(ends)),
T::block(vec![refuse::<T>(Constant::StatusOutOfRange)]),
None,
)
}
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 mut statements = Vec::new();
let packed = key.element_padding == ElementPadding::Packed;
if packed {
statements.push(begin_packed::<T>());
}
if layout.needs_dheader {
statements.extend(read_delimiter::<T>());
}
statements.extend(read_mmember::<T>(
layout.discriminator,
0,
"zd_hd",
decode_into::<T>(
ast,
discriminator,
T::discriminant("v"),
ast.discriminator_try_construct(id),
),
));
let case_fields: Vec<String> = ast
.cases_of(id)
.iter()
.map(|case| {
ast.case(*case)
.and_then(|case_node| ast.member(case_node.member))
.map(|member| T::escape(&member.name))
.map_err(|_| EncodeError::NoWriter {
member: own_name.to_string(),
kind: "union case the model does not hold",
})
})
.collect::<Result<_, _>>()?;
statements.extend(T::clear_union_cases("v", &case_fields));
let takes_parameter_headers = layout
.members
.iter()
.any(|plan| matches!(plan.header, MemberHeader::PidCdr1 { .. }));
if takes_parameter_headers {
statements.push(T::declare(
T::scalar(Scalar::Bool),
"zd_sent",
Some(literal::<T>("false")),
));
}
let mut arms: Vec<(T::Expr, T::Block)> = Vec::new();
let mut fallback: Option<T::Block> = 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 planned = layout
.members
.iter()
.find(|plan| plan.member == member.node_id);
let external = super::reached_externally::<T>(ast, member);
let (mut setup, place) = if external {
T::external_case_destination("v", &field)
} else {
(Vec::new(), T::case_member("v", &field))
};
let place = if super::behind_pointer::<T>(ast, id, member_type, external, false) {
let (made, through) = T::make_indirection(ast, member_type, place);
setup.extend(made);
through
} else {
place
};
let mut arm_statements = setup;
arm_statements.extend(read_union_member::<T>(
planned.map(|plan| plan.header),
planned.map_or(0, |plan| plan.member_id),
&format!("zd_hu{}", planned.map_or(0, |plan| plan.member_id)),
decode_into::<T>(
ast,
member_type,
place.clone(),
ast.effective_try_construct(
member.facets.xtypes.as_ref().and_then(|x| x.try_construct),
Some(member_type),
),
),
T::default_into(ast, member_type, place)?,
));
let read = T::block(arm_statements);
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 {
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(read.clone());
}
if let Some(condition) = condition {
arms.push((condition, read));
}
}
let mut chain: Option<T::Block> = fallback;
for (condition, block) in arms.into_iter().rev() {
chain = Some(T::block(vec![T::when(condition, block, chain)]));
}
if let Some(chain) = chain {
statements.extend(T::into_statements(chain));
}
if takes_parameter_headers && !layout.needs_sentinel {
statements.extend(T::mark_used("zd_sent"));
}
if layout.needs_sentinel {
statements.push(T::when(
T::unary("!", name::<T>("zd_sent")),
T::block(vec![
T::declare(
T::handle(Handle::PidRead),
"zd_pe",
Some(call_expr::<T>("read_pid", Vec::new())),
),
T::when(
T::binary(
"==",
T::header_field(name::<T>("zd_pe"), "end"),
literal::<T>("false"),
),
T::block(vec![
refuse::<T>(Constant::StatusOutOfRange),
T::give_back(None),
]),
None,
),
]),
None,
));
}
if layout.needs_dheader {
statements.extend(close_delimiter::<T>());
}
if packed {
statements.push(finish_packed::<T>());
}
Ok(statements)
}
fn read_union_member<T: CodecTarget>(
header: Option<MemberHeader>,
member_id: u32,
handle: &str,
value: T::Stmt,
default: T::Stmt,
) -> Vec<T::Stmt> {
match header {
Some(MemberHeader::PidCdr1 { .. }) => vec![
T::declare(
T::handle(Handle::PidRead),
handle,
Some(call_expr::<T>("read_pid", Vec::new())),
),
T::when(
T::header_field(name::<T>(handle), "end"),
T::block(vec![
default.clone(),
assign::<T>(name::<T>("zd_sent"), literal::<T>("true")),
]),
Some(T::block(vec![
T::when(
T::binary(
"!=",
T::header_field(name::<T>(handle), "pid"),
T::unsigned_int(i128::from(member_id)),
),
T::block(vec![
refuse::<T>(Constant::StatusOutOfRange),
T::give_back(None),
]),
None,
),
value.clone(),
T::perform(call_expr::<T>("finish_pid_read", vec![name::<T>(handle)])),
])),
),
],
Some(MemberHeader::EmHeader { .. }) => vec![T::when(
T::binary(">=", position::<T>(), name::<T>("zd_end")),
T::block(vec![default]),
Some(T::block(read_mmember::<T>(
header, member_id, handle, value,
))),
)],
_ => read_mmember::<T>(header, member_id, handle, value),
}
}
fn read_mmember<T: CodecTarget>(
header: Option<MemberHeader>,
member_id: u32,
handle: &str,
value: T::Stmt,
) -> Vec<T::Stmt> {
let wrong_id = |mine: T::Expr| {
T::when(
T::unary("!", T::paren(mine)),
T::block(vec![
refuse::<T>(Constant::StatusOutOfRange),
T::give_back(None),
]),
None,
)
};
match header {
None | Some(MemberHeader::NoHeader) => vec![value],
Some(MemberHeader::PidCdr1 { .. }) => vec![
T::declare(
T::handle(Handle::PidRead),
handle,
Some(call_expr::<T>("read_pid", Vec::new())),
),
wrong_id(T::binary(
"&&",
T::paren(T::binary(
"==",
T::header_field(name::<T>(handle), "pid"),
T::unsigned_int(i128::from(member_id)),
)),
T::paren(T::binary(
"==",
T::header_field(name::<T>(handle), "end"),
literal::<T>("false"),
)),
)),
value,
T::perform(call_expr::<T>("finish_pid_read", vec![name::<T>(handle)])),
],
Some(MemberHeader::EmHeader { .. }) => vec![
T::declare(
T::handle(Handle::EmHeaderRead),
handle,
Some(call_expr::<T>("read_emheader", Vec::new())),
),
wrong_id(T::binary(
"==",
T::header_field(name::<T>(handle), "member_id"),
T::unsigned_int(i128::from(member_id)),
)),
value,
],
Some(MemberHeader::OptionalCdr1 { .. } | MemberHeader::OptionalFlag) => vec![value],
}
}
fn primitive_read_at<T: CodecTarget>(write: ScalarWrite) -> T::Expr {
match write {
ScalarWrite::Bool => call_expr::<T>("read_bool", Vec::new()),
ScalarWrite::U8 => call_expr::<T>("read_u8", Vec::new()),
ScalarWrite::I8 => call_expr::<T>("read_i8", Vec::new()),
ScalarWrite::Char => cast::<T>(
T::scalar(Scalar::Char),
call_expr::<T>("read_char", Vec::new()),
),
ScalarWrite::Wchar {
width,
length_octet,
order,
} => cast::<T>(
T::scalar(Scalar::UInt16),
call_expr::<T>(
"read_wchar",
vec![
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_expr::<T>("read_i16", Vec::new()),
ScalarWrite::U16 => call_expr::<T>("read_u16", Vec::new()),
ScalarWrite::I32 => call_expr::<T>("read_i32", Vec::new()),
ScalarWrite::U32 => call_expr::<T>("read_u32", Vec::new()),
ScalarWrite::I64 => call_expr::<T>("read_i64", Vec::new()),
ScalarWrite::U64 => call_expr::<T>("read_u64", Vec::new()),
ScalarWrite::F32 => call_expr::<T>("read_f32", Vec::new()),
ScalarWrite::F64 => call_expr::<T>("read_f64", Vec::new()),
ScalarWrite::F128 => call_expr::<T>("read_f128", Vec::new()),
ScalarWrite::Str
| ScalarWrite::Wstr { .. }
| ScalarWrite::Fixed { .. }
| ScalarWrite::Enum { .. }
| ScalarWrite::Bitmask { .. } => call_expr::<T>("read_u8", Vec::new()),
}
}
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,
},
}
}
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,
}
}
fn name<T: CodecTarget>(text: &str) -> T::Expr {
T::name(text)
}
fn literal<T: CodecTarget>(text: &str) -> T::Expr {
T::literal(Literal::Int(text.to_string()))
}
fn int<T: CodecTarget>(value: i128) -> T::Expr {
T::int(value)
}
fn deref<T: CodecTarget>(text: &str) -> T::Expr {
T::whole(text)
}
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 assign<T: CodecTarget>(place: T::Expr, value: T::Expr) -> T::Stmt {
T::perform(T::binary("=", place, value))
}
fn call_expr<T: CodecTarget>(function_name: &str, args: Vec<T::Expr>) -> T::Expr {
T::runtime("r", function_name, args)
}
fn position<T: CodecTarget>() -> T::Expr {
call_expr::<T>("reader_position", Vec::new())
}
fn remaining<T: CodecTarget>() -> T::Expr {
call_expr::<T>("reader_remaining", Vec::new())
}
fn offset_plus<T: CodecTarget>(base: T::Expr, count: T::Expr) -> T::Expr {
T::binary("+", base, cast::<T>(T::scalar(Scalar::Size), count))
}
fn refuse<T: CodecTarget>(status: Constant) -> T::Stmt {
T::perform(call_expr::<T>("reader_refuse", vec![T::constant(status)]))
}
fn status_is_ok<T: CodecTarget>() -> T::Expr {
T::binary(
"==",
call_expr::<T>("reader_status", Vec::new()),
T::constant(Constant::StatusOk),
)
}
fn discard_dheader<T: CodecTarget>() -> T::Stmt {
T::drop_value(call_expr::<T>("read_dheader", Vec::new()))
}
fn counted_loop<T: CodecTarget>(index: &str, count: T::Expr, body: T::Block) -> T::Stmt {
T::count_up(index, count, body)
}