use zerodds_ast::{Ast, CharWidth, PrimitiveKind, TryConstruct, TypeKind, TypeNode};
use zerodds_ast_core::{Id, NodeId};
use zerodds_ast_layout::key::{
ElementPadding, FixedDigitCount, LayoutKey, PlCdr1HeaderPolicy, Purpose, WcharPosition,
WirePosition, WstringCodeUnits, WstringLengthCounts, WstringLengthPolicy, WstringTerminator,
WstringUnitOrder, WstringUnitWidth,
};
use zerodds_ast_layout::plan::{
Extent, LayoutError, MemberHeader, MemberPlan, ScalarWrite, TypeLayout,
};
use zerodds_ast_layout::xcdr::DepCollector;
use zerodds_lang_cst::common::{Doc, Ident, Literal, Path, Visibility};
use zerodds_lang_cst::rust;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EncodeError {
NoLayout {
detail: String,
},
SpecRefuses {
detail: String,
},
NoWriter {
member: String,
kind: &'static str,
},
NoEnumeratorValue {
enumeration: String,
enumerator: String,
detail: &'static str,
},
AmbiguousHeader {
member: String,
detail: String,
},
}
impl EncodeError {
#[must_use]
pub const fn is_spec_refusal(&self) -> bool {
matches!(self, Self::SpecRefuses { .. })
}
}
pub fn axes_the_codecs_carry(key: LayoutKey) -> Result<(), EncodeError> {
axes_the_codecs_carry_named(key, false)
}
pub fn axes_the_codecs_carry_for<T: crate::emit::CodecTarget>(
key: LayoutKey,
) -> Result<(), EncodeError> {
axes_the_codecs_carry_named(key, T::carries_hdds_axes())
}
fn axes_the_codecs_carry_named(key: LayoutKey, carries: bool) -> Result<(), EncodeError> {
if carries {
return Ok(());
}
let mut missing: Vec<&str> = Vec::new();
if !key.element_padding.is_default() {
missing.push(key.element_padding.as_str());
}
if !key.sequence_element_delimiter.is_default() {
missing.push(key.sequence_element_delimiter.as_str());
}
if !key.wstring_code_units.is_default() {
missing.push(key.wstring_code_units.as_str());
}
if missing.is_empty() {
Ok(())
} else {
Err(EncodeError::NoLayout {
detail: format!(
"the generated codecs do not carry {} yet",
missing.join(", ")
),
})
}
}
pub fn axes_the_rust_codec_carries(_key: LayoutKey) -> Result<(), EncodeError> {
Ok(())
}
impl core::fmt::Display for EncodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NoLayout { detail } => write!(f, "the layout could not be planned: {detail}"),
Self::SpecRefuses { detail } => write!(f, "{detail}"),
Self::NoWriter { member, kind } => {
write!(f, "`{member}` is a {kind}, which has no writer yet")
}
Self::NoEnumeratorValue {
enumeration,
enumerator,
detail,
} => write!(
f,
"`{enumeration}::{enumerator}` has no wire value: {detail}"
),
Self::AmbiguousHeader { member, detail } => write!(
f,
"`{member}` has no single PL_CDR1 header form to choose between: {detail}"
),
}
}
}
impl core::error::Error for EncodeError {}
pub const SECONDARY_SUFFIX: &str = "_xcdr1";
const EXTENDED_PARAM: &str = "zd_extended";
const EXTENDED_SUFFIX: &str = "_zd_extended";
fn per_sample(key: LayoutKey) -> bool {
key.pl_cdr1_headers.decided_per_sample() && key.encoding.is_v1()
}
fn sample_past_step(ast: &Ast, root: Id<TypeNode>, key: LayoutKey) -> Result<bool, EncodeError> {
let resolved = key.for_sample(ast, root).map_err(plan_refusal)?;
Ok(resolved.pl_cdr1_headers == PlCdr1HeaderPolicy::ExtendedForConstructed)
}
fn body_name(codec: &str, key: LayoutKey) -> String {
if per_sample(key) {
framed_body(codec)
} else {
format!("encode{codec}")
}
}
fn nested_method(codec: &str, extended: Extended) -> String {
match extended {
Extended::Fixed => format!("encode{codec}"),
Extended::Inherited | Extended::Constant(_) => framed_body(codec),
}
}
fn framed_body(codec: &str) -> String {
format!("encode{codec}{EXTENDED_SUFFIX}")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Extended {
Fixed,
Inherited,
Constant(bool),
}
impl Extended {
#[must_use]
fn of(key: LayoutKey) -> Self {
if !per_sample(key) {
Self::Fixed
} else if key.purpose == Purpose::Data {
Self::Inherited
} else {
Self::Constant(false)
}
}
#[must_use]
fn argument(self) -> Option<rust::Expr> {
match self {
Self::Fixed => None,
Self::Inherited => Some(rust::Expr::Path(Path::single(EXTENDED_PARAM))),
Self::Constant(value) => Some(rust::Expr::Literal(Literal::Bool(value))),
}
}
}
struct StepHeaders {
per_sample: bool,
members: Vec<(NodeId, bool)>,
discriminator: bool,
}
impl StepHeaders {
fn none() -> Self {
Self {
per_sample: false,
members: Vec::new(),
discriminator: false,
}
}
fn member(&self, plan: &MemberPlan, name: &str) -> Result<bool, EncodeError> {
if !self.per_sample {
return Ok(false);
}
self.members
.iter()
.find(|(id, _)| *id == plan.member)
.map(|(_, chosen)| *chosen)
.ok_or_else(|| EncodeError::NoLayout {
detail: format!(
"the PL_CDR1 plans for a sample past RTI's step name no member `{name}`, \
and its header form would be left unwritten"
),
})
}
}
fn plan_under(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
policy: PlCdr1HeaderPolicy,
) -> Result<TypeLayout, EncodeError> {
zerodds_ast_layout::xcdr::plan(
ast,
type_,
key.with_pl_cdr1_headers(policy),
&mut DepCollector::default(),
)
.map_err(plan_refusal)
}
fn header_is_chosen(
member: &str,
framed: MemberHeader,
below: MemberHeader,
past: MemberHeader,
) -> Result<bool, EncodeError> {
let refuse = || ambiguous(member, framed, below, past);
if below == past {
if framed == below {
return Ok(false);
}
return Err(refuse());
}
match (below, past) {
(
MemberHeader::PidCdr1 {
extended: false, ..
},
MemberHeader::PidCdr1 { extended: true, .. },
)
| (
MemberHeader::OptionalCdr1 {
extended: false, ..
},
MemberHeader::OptionalCdr1 { extended: true, .. },
) if framed == below => Ok(true),
_ => Err(refuse()),
}
}
fn ambiguous(
member: &str,
framed: MemberHeader,
below: MemberHeader,
past: MemberHeader,
) -> EncodeError {
EncodeError::AmbiguousHeader {
member: member.to_string(),
detail: format!(
"the codec's own plan frames it {framed:?}, a sample within RTI's short-header step \
{below:?} and a sample past it {past:?}, and one flag cannot choose between them"
),
}
}
fn step_headers(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
framed: &TypeLayout,
) -> Result<StepHeaders, EncodeError> {
if !per_sample(key) {
return Ok(StepHeaders::none());
}
let name = ast
.type_(type_)
.map_or_else(|_| "<unresolved>".to_string(), |node| node.name.clone());
let below = plan_under(ast, type_, key, PlCdr1HeaderPolicy::Specification)?;
let past = plan_under(ast, type_, key, PlCdr1HeaderPolicy::ExtendedForConstructed)?;
if below.members.len() != framed.members.len() || past.members.len() != framed.members.len() {
return Err(EncodeError::NoLayout {
detail: format!(
"`{name}`: the PL_CDR1 plans for the two sides of RTI's step name {} and {} \
members where the codec's own plan names {}",
below.members.len(),
past.members.len(),
framed.members.len()
),
});
}
let mut members = Vec::with_capacity(framed.members.len());
for ((own, below), past) in framed.members.iter().zip(&below.members).zip(&past.members) {
let member = member_name(ast, type_, own.member);
members.push((
own.member,
header_is_chosen(&member, own.header, below.header, past.header)?,
));
}
let discriminator = match (
framed.discriminator,
below.discriminator,
past.discriminator,
) {
(None, None, None) => false,
(Some(framed), Some(below), Some(past)) => {
header_is_chosen(&format!("{name}::discriminator"), framed, below, past)?
}
_ => {
return Err(EncodeError::NoLayout {
detail: format!(
"`{name}`: the PL_CDR1 plans for the two sides of RTI's step disagree about \
whether its discriminator carries a header at all"
),
});
}
};
Ok(StepHeaders {
per_sample: true,
members,
discriminator,
})
}
fn member_name(ast: &Ast, type_: Id<TypeNode>, member: NodeId) -> String {
ast.all_members_of(type_)
.into_iter()
.find(|id| ast.member(*id).is_ok_and(|node| node.node_id == member))
.and_then(|id| ast.member(id).ok())
.map_or_else(|| alloc_format(&member), |node| node.name.clone())
}
pub fn rust_codec(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
) -> Result<Vec<rust::Item>, EncodeError> {
codec_items(ast, type_, key, Companions::Emit)
}
pub fn rust_codec_pair(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
) -> Result<Vec<rust::Item>, EncodeError> {
codec_items(ast, type_, key, Companions::Omit)
}
#[must_use]
pub const fn has_codec(kind: &TypeKind) -> bool {
matches!(
kind,
TypeKind::Structure { .. }
| TypeKind::Union { .. }
| TypeKind::Valuetype { .. }
| TypeKind::Enumeration { .. }
| TypeKind::Bitmask { .. }
| TypeKind::Bitset { .. }
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Companions {
Emit,
Omit,
}
fn codec_items(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
companions: Companions,
) -> Result<Vec<rust::Item>, EncodeError> {
axes_the_rust_codec_carries(key)?;
let codec = match companions {
Companions::Emit => "",
Companions::Omit => SECONDARY_SUFFIX,
};
let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
detail: alloc_format(&error),
})?;
if !has_codec(&node.kind) {
return Err(EncodeError::NoWriter {
member: node.name.clone(),
kind: "a type with no codec of its own; it is written where it is used",
});
}
let mut deps = DepCollector::default();
let layout =
zerodds_ast_layout::xcdr::plan(ast, type_, key, &mut deps).map_err(plan_refusal)?;
let steps = step_headers(ast, type_, key, &layout)?;
if let TypeKind::Enumeration { enumerators } = &node.kind {
let mut items = enumeration_encode_fns(ast, type_, enumerators, key, codec)?;
items.push(enumeration_decode_fn(ast, type_, enumerators, key, codec)?);
return Ok(vec![rust::Item::Impl(rust::Impl {
doc: Doc::none(),
attrs: Vec::new(),
generics: Vec::new(),
trait_: None,
self_type: rust::Type::named(Path::single(&node.name)),
items,
})]);
}
if matches!(node.kind, TypeKind::Bitmask { .. }) {
return Ok(Vec::new());
}
if matches!(node.kind, TypeKind::Bitset { .. }) {
let mut items = bitset_encode_fns(ast, type_, &layout, key, codec)?;
items.push(bitset_decode_fn(ast, type_, &layout, codec)?);
let mut out = vec![rust::Item::Impl(rust::Impl {
doc: Doc::none(),
attrs: Vec::new(),
generics: Vec::new(),
trait_: None,
self_type: rust::Type::named(Path::single(&node.name)),
items,
})];
if companions == Companions::Emit
&& let Some(default) = default_impl(ast, type_)?
{
out.push(default);
}
return Ok(out);
}
if let TypeKind::Union {
discriminator,
cases,
..
} = &node.kind
{
let items = union_codec(
ast,
type_,
&layout,
&steps,
discriminator,
cases,
key,
codec,
)?;
let mut out = vec![rust::Item::Impl(rust::Impl {
doc: Doc::none(),
attrs: Vec::new(),
generics: Vec::new(),
trait_: None,
self_type: rust::Type::named(Path::single(&node.name)),
items,
})];
if companions == Companions::Emit
&& let Some(default) = default_impl(ast, type_)?
{
out.push(default);
}
return Ok(out);
}
let mut items = encode_fns(ast, type_, &layout, &steps, key, codec)?;
items.push(decode_fn(ast, type_, &layout, key, codec)?);
if companions == Companions::Emit {
items.append(&mut key_holder_fns(ast, type_, key)?);
}
let mut out = vec![rust::Item::Impl(rust::Impl {
doc: Doc::none(),
attrs: Vec::new(),
generics: Vec::new(),
trait_: None,
self_type: rust::Type::named(Path::single(&node.name)),
items,
})];
if companions == Companions::Emit
&& let Some(default) = default_impl(ast, type_)?
{
out.push(default);
}
Ok(out)
}
#[allow(clippy::too_many_arguments)]
fn union_codec(
ast: &Ast,
type_: Id<TypeNode>,
layout: &TypeLayout,
steps: &StepHeaders,
discriminator: &zerodds_ast_core::Ref<TypeNode>,
cases: &[Id<zerodds_ast::CaseNode>],
key: LayoutKey,
codec: &'static str,
) -> Result<Vec<rust::Item>, EncodeError> {
let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
detail: alloc_format(&error),
})?;
let name = node.name.clone();
let discriminator = discriminator.resolved().ok_or(EncodeError::NoWriter {
member: name.clone(),
kind: "a union with an unresolved discriminator",
})?;
let discriminator_write = match ast.type_(discriminator).map(|d| &d.kind) {
Ok(TypeKind::Enumeration { .. }) => MemberWrite::Enumeration {
bits: zerodds_ast_layout::enumeration_bit_bound(ast, discriminator, key.enum_holder),
},
_ => write_of(
ast,
node.module,
discriminator,
key,
codec,
Position {
behaviour: zerodds_ast::TryConstruct::Discard,
written: None,
},
),
};
let shape = crate::rust_union::shape(ast, type_).map_err(|kind| EncodeError::NoWriter {
member: name.clone(),
kind,
})?;
let refuse_label = |_| EncodeError::NoWriter {
member: name.clone(),
kind: "a union label with no source form in this column",
};
let written_label = |value: i128| -> Result<rust::Expr, EncodeError> {
Ok(match shape.kind {
crate::rust_union::DiscriminatorKind::Enumeration(_) => {
rust::Expr::Literal(Literal::int(value))
}
kind => crate::rust_union::label_literal(ast, node.module, kind, value)
.map_err(refuse_label)?
.expr(),
})
};
let enumeration_name = |enumeration: Id<TypeNode>| {
ast.type_(enumeration)
.map_or_else(|_| name.clone(), |node| node.name.clone())
};
let label_pattern = |value: i128| -> Result<rust::Pattern, EncodeError> {
Ok(
crate::rust_union::label_literal(ast, node.module, shape.kind, value)
.map_err(refuse_label)?
.pattern(),
)
};
let refused =
|what: String, sink: &str| rust::Expr::Return(Some(Box::new(refused_err(sink, &what))));
let carried_scrutinee = || match shape.kind {
crate::rust_union::DiscriminatorKind::Enumeration(_) => {
rust::Expr::Path(Path::single("discriminator"))
}
_ => rust::Expr::Deref(Box::new(rust::Expr::Path(Path::single("discriminator")))),
};
let write_carried = || -> Result<rust::Expr, EncodeError> {
match shape.kind {
crate::rust_union::DiscriminatorKind::Enumeration(enumeration) => {
let mut arms = Vec::new();
for enumerator in ast.enumerators_of(enumeration) {
let (_, value) = enumerator_variant_and_value(
ast,
&enumeration_name(enumeration),
*enumerator,
key,
)?;
arms.push(rust::MatchArm {
pattern: rust::Pattern::Path(
crate::rust_union::enumerator_path_of(
ast,
node.module,
enumeration,
*enumerator,
)
.map_err(refuse_label)?,
),
guard: None,
body: rust::Expr::Literal(Literal::int(value)),
});
}
write_expr_for(
&discriminator_write,
rust::Expr::Match {
scrutinee: Box::new(rust::Expr::Path(Path::single("discriminator"))),
arms,
},
&name,
false,
)
}
_ => write_expr_for(
&discriminator_write,
rust::Expr::Path(Path::single("discriminator")),
&name,
true,
),
}
};
let check_carried =
|own: Option<&[i128]>, what: String| -> Result<rust::Statement, EncodeError> {
let mut arms = Vec::new();
match own {
Some(labels) => {
for value in labels {
arms.push(rust::MatchArm {
pattern: label_pattern(*value)?,
guard: None,
body: rust::Expr::Block(rust::Block {
statements: Vec::new(),
}),
});
}
arms.push(rust::MatchArm {
pattern: rust::Pattern::Wildcard,
guard: None,
body: refused(what, "w"),
});
}
None => {
for value in shape.all_labels() {
arms.push(rust::MatchArm {
pattern: label_pattern(value)?,
guard: None,
body: refused(what.clone(), "w"),
});
}
arms.push(rust::MatchArm {
pattern: rust::Pattern::Wildcard,
guard: None,
body: rust::Expr::Block(rust::Block {
statements: Vec::new(),
}),
});
}
}
Ok(rust::Statement::Semi(rust::Expr::Match {
scrutinee: Box::new(carried_scrutinee()),
arms,
}))
};
let discriminator_plan = layout.discriminator.map(|header| MemberPlan {
member: layout.type_,
member_id: 0,
header,
offset: zerodds_ast_layout::plan::Offset::Dynamic,
align: 1,
});
let mut encode_arms = Vec::new();
let mut decode_arms = Vec::new();
let mut default_arm = None;
for case in cases {
let case_node = ast.case(*case).map_err(|error| EncodeError::NoLayout {
detail: alloc_format(&error),
})?;
let member = ast
.member(case_node.member)
.map_err(|error| EncodeError::NoLayout {
detail: alloc_format(&error),
})?;
let rust_case = shape
.cases
.iter()
.find(|c| c.member.node_id == member.node_id)
.ok_or(EncodeError::NoWriter {
member: member.name.clone(),
kind: "a union case the shape does not hold",
})?;
let variant = rust_case.variant.clone();
let carries = rust_case.carries_discriminator();
let member_type = member.type_.resolved().ok_or(EncodeError::NoWriter {
member: member.name.clone(),
kind: "a union case with an unresolved type",
})?;
let member_write = write_of(
ast,
node.module,
member_type,
key,
codec,
Position {
behaviour: ast.effective_try_construct(None, Some(member_type)),
written: None,
},
);
let member_write = external_write(ast, member, member_write);
let label_values = rust_case.labels.clone();
let is_default = rust_case.is_default;
let case_plan = layout
.members
.iter()
.find(|plan| plan.member == member.node_id)
.copied();
let construct = |value: rust::Expr| {
let mut args = Vec::new();
if carries {
args.push(rust::Expr::Path(Path::single("discriminator")));
}
args.push(value);
rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&["Self", &variant]))),
args,
}],
}
};
let read_member = read_for(&member.name, &member_write)?;
let build = match case_plan {
None => construct(rust::Expr::Try(Box::new(read_member))),
Some(plan) => {
let mut statements = read_header(&plan);
statements.push(let_statement(
"value",
rust::Expr::Try(Box::new(read_member)),
));
statements.extend(finish_read_header(&plan));
statements.push(rust::Statement::Tail(construct(rust::Expr::Path(
Path::single("value"),
))));
rust::Expr::Block(rust::Block { statements })
}
};
if is_default {
default_arm = Some(build.clone());
} else {
for value in &label_values {
decode_arms.push(rust::MatchArm {
pattern: label_pattern(*value)?,
guard: None,
body: build.clone(),
});
}
}
let mut statements = Vec::new();
if carries {
let own = (!is_default).then_some(label_values.as_slice());
let what = if is_default {
format!("a discriminator no label of `{name}` takes, for its `default:` member")
} else {
format!("a label of the member `{}` of `{name}`", member.name)
};
statements.push(check_carried(own, what)?);
}
if let Some(plan) = &discriminator_plan {
statements.extend(open_header(plan, steps.discriminator));
}
let discriminator_write_expr = if carries {
write_carried()?
} else {
let label = label_values.first().copied().ok_or(EncodeError::NoWriter {
member: member.name.clone(),
kind: "a union case carrying no case label",
})?;
write_expr_for(&discriminator_write, written_label(label)?, &name, false)?
};
statements.push(rust::Statement::Semi(discriminator_write_expr));
if let Some(plan) = &discriminator_plan {
statements.extend(close_header(plan));
}
if let Some(plan) = &case_plan {
statements.extend(open_header(plan, steps.member(plan, &member.name)?));
}
statements.push(rust::Statement::Semi(write_expr_for(
&member_write,
rust::Expr::Path(Path::single("value")),
&member.name,
true,
)?));
if let Some(plan) = &case_plan {
statements.extend(close_header(plan));
}
let mut elements = Vec::new();
if carries {
elements.push(rust::Pattern::Binding(Ident::new("discriminator")));
}
elements.push(rust::Pattern::Binding(Ident::new("value")));
encode_arms.push(rust::MatchArm {
pattern: rust::Pattern::TupleStruct {
path: Path::new(&["Self", &variant]),
elements,
},
guard: None,
body: rust::Expr::Block(rust::Block { statements }),
});
}
if shape.no_member {
let mut statements = vec![check_carried(
None,
format!("a discriminator no label of `{name}` takes, for no member"),
)?];
if let Some(plan) = &discriminator_plan {
statements.extend(open_header(plan, steps.discriminator));
}
statements.push(rust::Statement::Semi(write_carried()?));
if let Some(plan) = &discriminator_plan {
statements.extend(close_header(plan));
}
encode_arms.push(rust::MatchArm {
pattern: rust::Pattern::TupleStruct {
path: Path::new(&["Self", crate::rust_union::NO_MEMBER]),
elements: vec![rust::Pattern::Binding(Ident::new("discriminator"))],
},
guard: None,
body: rust::Expr::Block(rust::Block { statements }),
});
}
let rest = default_arm.or_else(|| {
shape.no_member.then(|| rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&[
"Self",
crate::rust_union::NO_MEMBER,
]))),
args: vec![rust::Expr::Path(Path::single("discriminator"))],
}],
})
});
match rest {
Some(body) => decode_arms.push(rust::MatchArm {
pattern: rust::Pattern::Wildcard,
guard: None,
body,
}),
None if shape.kind == crate::rust_union::DiscriminatorKind::Character => {
decode_arms.push(refusal_arm(&format!(
"a discriminator selecting a case of `{name}`"
)));
}
None => {}
}
let to_enumeration = match shape.kind {
crate::rust_union::DiscriminatorKind::Enumeration(enumeration) => {
let mut arms = Vec::new();
for enumerator in ast.enumerators_of(enumeration) {
let (_, value) = enumerator_variant_and_value(
ast,
&enumeration_name(enumeration),
*enumerator,
key,
)?;
arms.push(rust::MatchArm {
pattern: rust::Pattern::Literal(Literal::int(value)),
guard: None,
body: rust::Expr::Path(
crate::rust_union::enumerator_path_of(
ast,
node.module,
enumeration,
*enumerator,
)
.map_err(refuse_label)?,
),
});
}
arms.push(rust::MatchArm {
pattern: rust::Pattern::Wildcard,
guard: None,
body: refused(
format!("an enumerator of the discriminator of `{name}`"),
"r",
),
});
Some(rust::Statement::Let {
name: Ident::new("discriminator"),
type_: None,
value: Some(rust::Expr::Match {
scrutinee: Box::new(rust::Expr::Path(Path::single("discriminator"))),
arms,
}),
mutable: false,
})
}
_ => None,
};
let mut encode_statements = Vec::new();
if per_sample(key) {
encode_statements.push(rust::Statement::Let {
name: Ident::new("_"),
type_: None,
value: Some(rust::Expr::Path(Path::single(EXTENDED_PARAM))),
mutable: false,
});
}
if layout.needs_dheader {
encode_statements.push(let_statement("dheader", {
rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("w"))),
method: Ident::new("write_dheader"),
args: Vec::new(),
}
}));
}
encode_statements.push(rust::Statement::Semi(rust::Expr::Match {
scrutinee: Box::new(rust::Expr::Path(Path::single("self"))),
arms: encode_arms,
}));
if layout.needs_sentinel {
encode_statements.push(call_statement("write_sentinel", Vec::new()));
}
if layout.needs_dheader {
encode_statements.push(call_statement(
"finish_dheader",
vec![rust::Expr::Path(Path::single("dheader"))],
));
}
encode_statements.push(rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Tuple(Vec::new())],
}));
let encode_statements = wrap_packed_body(
encode_statements,
"w",
key.element_padding == ElementPadding::Packed,
);
let encode = encode_function(&body_name(codec, key), &encode_statements, per_sample(key));
let decode = rust::Item::Function(rust::Function {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&format!("decode{codec}")),
generics: Vec::new(),
params: vec![rust::Param {
name: Some(Ident::new("r")),
type_: rust::Type::Ref {
target: Box::new(rust::Type::named(Path::single("Reader"))),
mutable: true,
lifetime: None,
},
receiver: None,
}],
returns: Some(rust::Type::generic(
Path::single("Result"),
vec![
rust::Type::named(Path::single("Self")),
rust::Type::named(Path::single("WireError")),
],
)),
body: Some(rust::Block {
statements: wrap_packed_body(
union_decode_body(
layout,
discriminator_plan,
&discriminator_write,
to_enumeration,
decode_arms,
)?,
"r",
key.element_padding == ElementPadding::Packed,
),
}),
});
let mut out = with_entry(encode, encode_entry(ast, type_, key, codec)?);
out.push(decode);
Ok(out)
}
fn union_decode_body(
layout: &TypeLayout,
discriminator_plan: Option<MemberPlan>,
discriminator_write: &MemberWrite,
to_enumeration: Option<rust::Statement>,
decode_arms: Vec<rust::MatchArm>,
) -> Result<Vec<rust::Statement>, EncodeError> {
let read_discriminator = rust::Statement::Let {
name: Ident::new("discriminator"),
type_: None,
value: Some(rust::Expr::Try(Box::new(read_for(
"discriminator",
discriminator_write,
)?))),
mutable: false,
};
let branch = rust::Expr::Match {
scrutinee: Box::new(rust::Expr::Path(Path::single("discriminator"))),
arms: decode_arms,
};
if !layout.needs_dheader && !layout.needs_sentinel && discriminator_plan.is_none() {
let mut statements = vec![read_discriminator];
statements.extend(to_enumeration);
statements.push(rust::Statement::Tail(branch));
return Ok(statements);
}
let mut statements = Vec::new();
if layout.needs_dheader {
statements.push(let_statement(
"end",
rust::Expr::Binary {
op: rust::BinaryOp::Add,
lhs: Box::new(rust::Expr::Try(Box::new(call("read_dheader", Vec::new())))),
rhs: Box::new(call("position", Vec::new())),
},
));
}
if let Some(plan) = &discriminator_plan {
statements.extend(read_header(plan));
}
statements.push(read_discriminator);
if let Some(plan) = &discriminator_plan {
statements.extend(finish_read_header(plan));
}
statements.extend(to_enumeration);
statements.push(let_statement("value", rust::Expr::Try(Box::new(branch))));
if layout.needs_sentinel {
statements.extend(read_sentinel());
}
if layout.needs_dheader {
statements.push(rust::Statement::Semi(rust::Expr::Try(Box::new(call(
"skip_to",
vec![rust::Expr::Path(Path::single("end"))],
)))));
}
statements.push(rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Path(Path::single("value"))],
}));
Ok(statements)
}
fn read_sentinel() -> Vec<rust::Statement> {
vec![
let_statement(
"sentinel",
rust::Expr::Try(Box::new(call("read_pid", Vec::new()))),
),
rust::Statement::Semi(rust::Expr::If {
condition: Box::new(rust::Expr::Not(Box::new(rust::Expr::Field {
receiver: Box::new(rust::Expr::Path(Path::single("sentinel"))),
name: Ident::new("end"),
}))),
then: rust::Block {
statements: vec![rust::Statement::Semi(rust::Expr::Return(Some(Box::new(
rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Err"))),
args: vec![rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&[
"WireError",
"refused",
]))),
args: vec![
call("position", Vec::new()),
rust::Expr::Literal(Literal::string(
"PID_SENTINEL closing the parameter list, per rule (23)",
)),
],
}],
},
))))],
},
otherwise: None,
}),
]
}
fn read_header(plan: &MemberPlan) -> Vec<rust::Statement> {
match plan.header {
MemberHeader::NoHeader | MemberHeader::OptionalFlag => Vec::new(),
MemberHeader::PidCdr1 { .. } | MemberHeader::OptionalCdr1 { .. } => vec![let_statement(
"pid",
rust::Expr::Try(Box::new(call("read_pid", Vec::new()))),
)],
MemberHeader::EmHeader { .. } => vec![let_statement(
"_emheader",
rust::Expr::Try(Box::new(call("read_emheader", Vec::new()))),
)],
}
}
fn finish_read_header(plan: &MemberPlan) -> Vec<rust::Statement> {
match plan.header {
MemberHeader::PidCdr1 { .. } | MemberHeader::OptionalCdr1 { .. } => {
vec![rust::Statement::Semi(rust::Expr::Try(Box::new(call(
"finish_pid",
vec![rust::Expr::Path(Path::single("pid"))],
))))]
}
MemberHeader::NoHeader | MemberHeader::OptionalFlag | MemberHeader::EmHeader { .. } => {
Vec::new()
}
}
}
fn write_expr_for(
kind: &MemberWrite,
value: rust::Expr,
at: &str,
borrowed: bool,
) -> Result<rust::Expr, EncodeError> {
let (expr, fallible) = write_call("w", SinkShape::Reference, at, kind, value, borrowed)?;
Ok(if fallible {
rust::Expr::Try(Box::new(expr))
} else {
expr
})
}
const fn enum_holder(bits: u16) -> (rust::Primitive, &'static str, &'static str) {
match bits {
0..=8 => (rust::Primitive::U8, "write_u8", "read_u8"),
9..=16 => (rust::Primitive::U16, "write_u16", "read_u16"),
17..=32 => (rust::Primitive::U32, "write_u32", "read_u32"),
_ => (rust::Primitive::U64, "write_u64", "read_u64"),
}
}
fn enumeration_encode_fns(
ast: &Ast,
type_: Id<TypeNode>,
enumerators: &[Id<zerodds_ast::EnumeratorNode>],
key: LayoutKey,
codec: &'static str,
) -> Result<Vec<rust::Item>, EncodeError> {
let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
detail: alloc_format(&error),
})?;
let bits = zerodds_ast_layout::enumeration_bit_bound(ast, type_, key.enum_holder);
let (primitive, writer, _) = enum_holder(bits);
let mut arms = Vec::new();
for id in enumerators {
let (name, value) = enumerator_variant_and_value(ast, &node.name, *id, key)?;
arms.push(rust::MatchArm {
pattern: rust::Pattern::Path(Path::new(&["Self", &name])),
guard: None,
body: rust::Expr::Literal(Literal::int(value)),
});
}
let mut statements = Vec::new();
if per_sample(key) {
statements.push(rust::Statement::Let {
name: Ident::new("_"),
type_: None,
value: Some(rust::Expr::Path(Path::single(EXTENDED_PARAM))),
mutable: false,
});
}
statements.extend([
rust::Statement::Let {
name: Ident::new("value"),
type_: Some(rust::Type::Primitive(primitive)),
value: Some(rust::Expr::Match {
scrutinee: Box::new(rust::Expr::Path(Path::single("self"))),
arms,
}),
mutable: false,
},
rust::Statement::Semi(rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("w"))),
method: Ident::new(writer),
args: vec![rust::Expr::Path(Path::single("value"))],
}),
rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Tuple(Vec::new())],
}),
]);
Ok(with_entry(
encode_function(&body_name(codec, key), &statements, per_sample(key)),
encode_entry(ast, type_, key, codec)?,
))
}
fn enumerator_variant_and_value(
ast: &Ast,
enumeration: &str,
id: Id<zerodds_ast::EnumeratorNode>,
key: LayoutKey,
) -> Result<(String, i128), EncodeError> {
let enumerator = ast.enumerator(id).map_err(|error| EncodeError::NoLayout {
detail: alloc_format(&error),
})?;
let value = zerodds_ast_layout::enumerator_wire_value(ast, id, key.enumerator_value)
.ok_or_else(|| EncodeError::NoEnumeratorValue {
enumeration: enumeration.to_string(),
enumerator: enumerator.name.clone(),
detail: "the enumerator has no computable wire value",
})?;
Ok((rust::escape_keyword(&enumerator.name), value))
}
fn enumeration_decode_fn(
ast: &Ast,
type_: Id<TypeNode>,
enumerators: &[Id<zerodds_ast::EnumeratorNode>],
key: LayoutKey,
codec: &'static str,
) -> Result<rust::Item, EncodeError> {
let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
detail: alloc_format(&error),
})?;
let bits = zerodds_ast_layout::enumeration_bit_bound(ast, type_, key.enum_holder);
let (_, _, reader) = enum_holder(bits);
let mut arms = Vec::new();
for id in enumerators {
let (name, value) = enumerator_variant_and_value(ast, &node.name, *id, key)?;
arms.push(rust::MatchArm {
pattern: rust::Pattern::Literal(Literal::int(value)),
guard: None,
body: rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Path(Path::new(&["Self", &name]))],
},
});
}
arms.push(refusal_arm(&format!("an enumerator of `{}`", node.name)));
let statements = vec![
rust::Statement::Let {
name: Ident::new("value"),
type_: None,
value: Some(rust::Expr::Try(Box::new(rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("r"))),
method: Ident::new(reader),
args: Vec::new(),
}))),
mutable: false,
},
rust::Statement::Tail(rust::Expr::Match {
scrutinee: Box::new(rust::Expr::Path(Path::single("value"))),
arms,
}),
];
Ok(rust::Item::Function(rust::Function {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&format!("decode{codec}")),
generics: Vec::new(),
params: vec![rust::Param {
name: Some(Ident::new("r")),
type_: rust::Type::Ref {
target: Box::new(rust::Type::named(Path::single("Reader"))),
mutable: true,
lifetime: None,
},
receiver: None,
}],
returns: Some(rust::Type::generic(
Path::single("Result"),
vec![
rust::Type::named(Path::single("Self")),
rust::Type::named(Path::single("WireError")),
],
)),
body: Some(rust::Block { statements }),
}))
}
fn bitset_anchor(layout: &TypeLayout, name: &str) -> Result<u32, EncodeError> {
match layout.extent {
Extent::Sized(anchor) => Ok(anchor),
Extent::Unbounded => Err(EncodeError::NoWriter {
member: name.to_string(),
kind: "bitset whose anchor the planner does not size",
}),
}
}
fn bitset_fields(
ast: &Ast,
type_: Id<TypeNode>,
name: &str,
) -> Result<Vec<(String, u16, u32)>, EncodeError> {
let mut out = Vec::new();
let mut position: u32 = 0;
for field in &ast.all_bitfields_of(type_) {
let Ok(field_node) = ast.bitfield(*field) else {
return Err(EncodeError::NoWriter {
member: name.to_string(),
kind: "bitset with a stale bitfield handle",
});
};
let width = u32::from(field_node.bits);
if position.saturating_add(width) > 64 {
return Err(EncodeError::NoWriter {
member: name.to_string(),
kind: "bitset wider than the sixty-four bits an anchor holds",
});
}
if let Some(field_name) = field_node.name.as_ref() {
out.push((
rust::escape_keyword(field_name).to_string(),
field_node.bits,
position,
));
}
position = position.saturating_add(width);
}
Ok(out)
}
fn bitset_encode_fns(
ast: &Ast,
type_: Id<TypeNode>,
layout: &TypeLayout,
key: LayoutKey,
codec: &'static str,
) -> Result<Vec<rust::Item>, EncodeError> {
let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
detail: alloc_format(&error),
})?;
let anchor = bitset_anchor(layout, &node.name)?;
let mut statements = Vec::new();
if per_sample(key) {
statements.push(rust::Statement::Let {
name: Ident::new("_"),
type_: None,
value: Some(rust::Expr::Path(Path::single(EXTENDED_PARAM))),
mutable: false,
});
}
statements.push(rust::Statement::Let {
name: Ident::new("bits"),
type_: Some(rust::Type::Primitive(rust::Primitive::U64)),
value: Some(rust::Expr::Literal(Literal::int(0))),
mutable: true,
});
for (field, bits, position) in bitset_fields(ast, type_, &node.name)? {
let value = rust::Expr::Field {
receiver: Box::new(rust::Expr::Path(Path::single("self"))),
name: Ident::new(&field),
};
statements.push(rust::Statement::Semi(rust::Expr::Assign {
target: Box::new(rust::Expr::Path(Path::single("bits"))),
value: Box::new(rust::Expr::Binary {
op: rust::BinaryOp::BitOr,
lhs: Box::new(rust::Expr::Path(Path::single("bits"))),
rhs: Box::new(rust::Expr::Try(Box::new(rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("w"))),
method: Ident::new("bitfield"),
args: vec![
widen_to_u64(value, bits),
rust::Expr::Literal(Literal::int(i128::from(bits))),
rust::Expr::Literal(Literal::int(i128::from(position))),
],
}))),
}),
}));
}
statements.push(rust::Statement::Semi(rust::Expr::Try(Box::new(
rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("w"))),
method: Ident::new("write_bitset"),
args: vec![
rust::Expr::Literal(Literal::int(i128::from(anchor))),
rust::Expr::Path(Path::single("bits")),
],
},
))));
statements.push(rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Tuple(Vec::new())],
}));
Ok(with_entry(
encode_function(&body_name(codec, key), &statements, per_sample(key)),
encode_entry(ast, type_, key, codec)?,
))
}
fn bitset_decode_fn(
ast: &Ast,
type_: Id<TypeNode>,
layout: &TypeLayout,
codec: &'static str,
) -> Result<rust::Item, EncodeError> {
let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
detail: alloc_format(&error),
})?;
let anchor = bitset_anchor(layout, &node.name)?;
let mut statements = vec![rust::Statement::Let {
name: Ident::new("bits"),
type_: None,
value: Some(rust::Expr::Try(Box::new(rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("r"))),
method: Ident::new("read_bitset"),
args: vec![rust::Expr::Literal(Literal::int(i128::from(anchor)))],
}))),
mutable: false,
}];
let mut fields = Vec::new();
for (field, bits, position) in bitset_fields(ast, type_, &node.name)? {
let mask: u64 = if bits >= 64 {
u64::MAX
} else {
(1u64 << bits) - 1
};
let masked = rust::Expr::Binary {
op: rust::BinaryOp::BitAnd,
lhs: Box::new(rust::Expr::Binary {
op: rust::BinaryOp::Shr,
lhs: Box::new(rust::Expr::Path(Path::single("bits"))),
rhs: Box::new(rust::Expr::Literal(Literal::int(i128::from(position)))),
}),
rhs: Box::new(rust::Expr::Literal(Literal::int(i128::from(mask)))),
};
let holder = crate::adapter::cst::bitmask_primitive(bits);
let value = if holder == rust::Primitive::U64 {
masked
} else {
rust::Expr::Cast {
value: Box::new(masked),
type_: Box::new(rust::Type::Primitive(holder)),
}
};
fields.push((Ident::new(&field), value));
}
statements.push(rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::StructLit {
path: Path::single("Self"),
fields,
base: None,
}],
}));
Ok(rust::Item::Function(rust::Function {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&format!("decode{codec}")),
generics: Vec::new(),
params: vec![rust::Param {
name: Some(Ident::new("r")),
type_: rust::Type::Ref {
target: Box::new(rust::Type::named(Path::single("Reader"))),
mutable: true,
lifetime: None,
},
receiver: None,
}],
returns: Some(rust::Type::generic(
Path::single("Result"),
vec![
rust::Type::named(Path::single("Self")),
rust::Type::named(Path::single("WireError")),
],
)),
body: Some(rust::Block { statements }),
}))
}
fn encode_fns(
ast: &Ast,
type_: Id<TypeNode>,
layout: &TypeLayout,
steps: &StepHeaders,
key: LayoutKey,
codec: &'static str,
) -> Result<Vec<rust::Item>, EncodeError> {
let mut statements = Vec::new();
if per_sample(key) {
statements.push(rust::Statement::Let {
name: Ident::new("_"),
type_: None,
value: Some(rust::Expr::Path(Path::single(EXTENDED_PARAM))),
mutable: false,
});
}
if layout.needs_dheader {
statements.push(rust::Statement::Let {
name: Ident::new("dheader"),
type_: None,
value: Some(rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("w"))),
method: Ident::new("write_dheader"),
args: Vec::new(),
}),
mutable: false,
});
}
for plan in &layout.members {
let Some(member) = member_of(ast, type_, plan.member, key, codec) else {
continue;
};
let name = member.0;
let kind = member.1;
let optional = member_node_of(ast, type_, plan.member)
.is_some_and(|member| member.facets.is_optional());
let receiver = holder_of(ast, type_, plan.member);
if optional {
statements.extend(write_optional_member(
receiver,
plan,
&name,
&kind,
steps.member(plan, &name)?,
)?);
continue;
}
statements.extend(open_header(plan, steps.member(plan, &name)?));
statements.push(write_for(receiver, &name, &kind)?);
statements.extend(close_header(plan));
}
if layout.needs_sentinel {
statements.push(call_statement("write_sentinel", Vec::new()));
}
if layout.needs_dheader {
statements.push(call_statement(
"finish_dheader",
vec![rust::Expr::Path(Path::single("dheader"))],
));
}
statements.push(rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Tuple(Vec::new())],
}));
let statements = wrap_packed_body(
statements,
"w",
key.element_padding == ElementPadding::Packed,
);
Ok(with_entry(
encode_function(&body_name(codec, key), &statements, per_sample(key)),
encode_entry(ast, type_, key, codec)?,
))
}
fn encode_function(name: &str, statements: &[rust::Statement], flagged: bool) -> rust::Item {
let mut params = vec![
rust::Param {
name: None,
type_: rust::Type::unit(),
receiver: Some(rust::Receiver::Ref),
},
rust::Param {
name: Some(Ident::new("w")),
type_: rust::Type::Ref {
target: Box::new(rust::Type::named(Path::single("Writer"))),
mutable: true,
lifetime: None,
},
receiver: None,
},
];
if flagged {
params.push(rust::Param {
name: Some(Ident::new(EXTENDED_PARAM)),
type_: rust::Type::Primitive(rust::Primitive::Bool),
receiver: None,
});
}
rust::Item::Function(rust::Function {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(name),
generics: Vec::new(),
params,
returns: Some(rust::Type::generic(
Path::single("Result"),
vec![
rust::Type::unit(),
rust::Type::named(Path::single("WireError")),
],
)),
body: Some(rust::Block {
statements: statements.to_vec(),
}),
})
}
fn with_entry(body: rust::Item, entry: Option<rust::Item>) -> Vec<rust::Item> {
match entry {
Some(entry) => vec![body, entry],
None => vec![body],
}
}
fn encode_entry(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
codec: &'static str,
) -> Result<Option<rust::Item>, EncodeError> {
if !per_sample(key) {
return Ok(None);
}
let past = sample_past_step(ast, type_, key)?;
let statements = vec![rust::Statement::Tail(rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("self"))),
method: Ident::new(&body_name(codec, key)),
args: vec![
rust::Expr::Path(Path::single("w")),
rust::Expr::Literal(Literal::Bool(past)),
],
})];
Ok(Some(encode_function(
&format!("encode{codec}"),
&statements,
false,
)))
}
fn open_header(plan: &zerodds_ast_layout::plan::MemberPlan, chosen: bool) -> Vec<rust::Statement> {
match plan.header {
MemberHeader::NoHeader => Vec::new(),
MemberHeader::PidCdr1 {
pid,
extended,
must_understand,
} => vec![bind_pid_header(
chosen,
(pid, plan.member_id),
extended,
"Member",
must_understand,
)],
MemberHeader::OptionalCdr1 {
pid,
extended,
must_understand,
} => vec![bind_pid_header(
chosen,
(pid, plan.member_id),
extended,
"Optional",
must_understand,
)],
MemberHeader::OptionalFlag => Vec::new(),
MemberHeader::EmHeader {
lc,
must_understand,
} => {
vec![bind_header(
"emheader",
"write_emheader",
vec![
rust::Expr::Literal(Literal::int(i128::from(plan.member_id))),
rust::Expr::Literal(Literal::int(i128::from(lc as u8))),
rust::Expr::Literal(Literal::Bool(must_understand)),
],
)]
}
}
}
fn bind_pid_header(
chosen: bool,
(pid, member_id): (u16, u32),
extended: bool,
role: &str,
must_understand: bool,
) -> rust::Statement {
let writer = |method: &str| {
let id = if method == "write_pid_extended" {
i128::from(member_id)
} else {
i128::from(pid)
};
rust::Expr::Try(Box::new(rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("w"))),
method: Ident::new(method),
args: vec![
rust::Expr::Literal(Literal::int(id)),
parameter_role(role),
rust::Expr::Literal(Literal::Bool(must_understand)),
],
}))
};
let value = if !chosen {
writer(if extended {
"write_pid_extended"
} else {
"write_pid"
})
} else {
rust::Expr::If {
condition: Box::new(rust::Expr::Path(Path::single(EXTENDED_PARAM))),
then: rust::Block {
statements: vec![rust::Statement::Tail(writer("write_pid_extended"))],
},
otherwise: Some(rust::Block {
statements: vec![rust::Statement::Tail(writer("write_pid"))],
}),
}
};
rust::Statement::Let {
name: Ident::new("pid"),
type_: None,
value: Some(value),
mutable: false,
}
}
fn close_header(plan: &zerodds_ast_layout::plan::MemberPlan) -> Vec<rust::Statement> {
match plan.header {
MemberHeader::NoHeader | MemberHeader::OptionalFlag => Vec::new(),
MemberHeader::PidCdr1 { .. } | MemberHeader::OptionalCdr1 { .. } => vec![call_statement(
"finish_pid",
vec![rust::Expr::Path(Path::single("pid"))],
)],
MemberHeader::EmHeader { .. } => vec![try_call_statement(
"finish_emheader",
vec![rust::Expr::Path(Path::single("emheader"))],
)],
}
}
fn write_optional_member(
receiver: rust::Expr,
plan: &zerodds_ast_layout::plan::MemberPlan,
name: &str,
kind: &MemberWrite,
chosen: bool,
) -> Result<Vec<rust::Statement>, EncodeError> {
let bound = rust::escape_keyword(name);
let value = rust::Expr::Path(Path::single(&bound));
let (mut present, absent) = match plan.header {
MemberHeader::OptionalFlag => (
vec![call_statement(
"write_bool",
vec![rust::Expr::Literal(Literal::Bool(true))],
)],
vec![call_statement(
"write_bool",
vec![rust::Expr::Literal(Literal::Bool(false))],
)],
),
MemberHeader::OptionalCdr1 { .. } => (Vec::new(), Vec::new()),
_ => (open_header(plan, chosen), Vec::new()),
};
present.push(write_value_to("w", name, kind, value, true)?);
if !matches!(plan.header, MemberHeader::OptionalCdr1 { .. }) {
present.extend(close_header(plan));
}
let matched = rust::Statement::Semi(rust::Expr::Match {
scrutinee: Box::new(rust::Expr::Ref {
target: Box::new(rust::Expr::Field {
receiver: Box::new(receiver),
name: Ident::new(&bound),
}),
mutable: false,
}),
arms: vec![
rust::MatchArm {
pattern: rust::Pattern::TupleStruct {
path: Path::single("Some"),
elements: vec![rust::Pattern::Binding(Ident::new(&bound))],
},
guard: None,
body: rust::Expr::Block(rust::Block {
statements: present,
}),
},
rust::MatchArm {
pattern: rust::Pattern::Path(Path::single("None")),
guard: None,
body: rust::Expr::Block(rust::Block { statements: absent }),
},
],
});
if matches!(plan.header, MemberHeader::OptionalCdr1 { .. }) {
let mut all = open_header(plan, chosen);
all.push(matched);
all.extend(close_header(plan));
return Ok(all);
}
Ok(vec![matched])
}
fn key_holder_fns(
ast: &Ast,
type_: Id<TypeNode>,
data_key: LayoutKey,
) -> Result<Vec<rust::Item>, EncodeError> {
let key = LayoutKey {
purpose: zerodds_ast_layout::Purpose::Key,
endianness: zerodds_ast_layout::Endianness::Big,
..data_key
};
let keys = zerodds_ast_layout::keyhash::key_members_with(
ast,
type_,
key.key_holder_nesting,
key.key_holder_order,
)
.map_err(|error| EncodeError::NoLayout {
detail: alloc_format(&error),
})?;
if keys.is_empty() {
return Ok(Vec::new());
}
let form =
zerodds_ast_layout::keyhash::key_hash_form_for(ast, type_, key).map_err(|error| {
EncodeError::NoLayout {
detail: alloc_format(&error),
}
})?;
let mut statements = vec![rust::Statement::Let {
name: Ident::new("holder"),
type_: None,
value: Some(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&["Writer", "with_max_align"]))),
args: vec![
rust::Expr::Path(Path::new(&["zerodds_wire", "Endianness", "Big"])),
rust::Expr::Literal(Literal::int(i128::from(
key.key_holder_alignment.max_alignment(key.encoding),
))),
],
}),
mutable: true,
}];
let codec = "";
for node in &keys {
let Some((name, kind)) = member_of(ast, type_, *node, key, codec) else {
continue;
};
statements.push(write_value_to_sink(
"holder",
SinkShape::Owned,
&name,
&kind,
rust::Expr::Field {
receiver: Box::new(holder_of(ast, type_, *node)),
name: Ident::new(&rust::escape_keyword(&name)),
},
false,
)?);
}
let mut holder_statements = statements.clone();
holder_statements.push(rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("holder"))),
method: Ident::new("finish"),
args: Vec::new(),
}],
}));
let key_holder_be = rust::Item::Function(rust::Function {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new("key_holder_be"),
generics: Vec::new(),
params: vec![rust::Param {
name: None,
type_: rust::Type::unit(),
receiver: Some(rust::Receiver::Ref),
}],
returns: Some(rust::Type::generic(
Path::single("Result"),
vec![
rust::Type::generic(
Path::single("Vec"),
vec![rust::Type::Primitive(rust::Primitive::U8)],
),
rust::Type::named(Path::single("WireError")),
],
)),
body: Some(rust::Block {
statements: holder_statements,
}),
});
let mut from_holder = Vec::new();
let mut key_fields = Vec::new();
let mut undescribed = None;
for node in &keys {
match member_of(ast, type_, *node, key, codec) {
Some((name, kind)) => {
let field = rust::escape_keyword(&name);
from_holder.push(let_statement(
&field,
rust::Expr::Try(Box::new(read_for(&name, &kind)?)),
));
key_fields.push((
*node,
(Ident::new(&field), rust::Expr::Path(Path::single(&field))),
));
}
None => undescribed = Some(*node),
}
}
if let Some(node) = undescribed {
from_holder = vec![rust::Statement::Tail(refused_err(
"r",
&format!("a key holder with a member this emitter cannot read ({node:?})"),
))];
} else {
from_holder.push(rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![nested_struct_literal(ast, type_, key_fields, &|| {
Some(Box::new(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&["Default", "default"]))),
args: Vec::new(),
}))
})],
}));
}
let key_from_holder_be = rust::Item::Function(rust::Function {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new("key_from_holder_be"),
generics: Vec::new(),
params: vec![rust::Param {
name: Some(Ident::new("r")),
type_: rust::Type::Ref {
target: Box::new(rust::Type::named(Path::single("Reader"))),
mutable: true,
lifetime: None,
},
receiver: None,
}],
returns: Some(rust::Type::generic(
Path::single("Result"),
vec![
rust::Type::named(Path::single("Self")),
rust::Type::named(Path::single("WireError")),
],
)),
body: Some(rust::Block {
statements: from_holder,
}),
});
let finish = match form {
zerodds_ast_layout::keyhash::KeyHashForm::ZeroPadded => "zero_padded",
zerodds_ast_layout::keyhash::KeyHashForm::Md5 => "md5",
};
statements.push(rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("holder"))),
method: Ident::new(finish),
args: Vec::new(),
}],
}));
Ok(vec![
key_holder_be,
key_from_holder_be,
rust::Item::Function(rust::Function {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new("key_hash"),
generics: Vec::new(),
params: vec![rust::Param {
name: None,
type_: rust::Type::unit(),
receiver: Some(rust::Receiver::Ref),
}],
returns: Some(rust::Type::generic(
Path::single("Result"),
vec![
rust::Type::Array {
element: Box::new(rust::Type::Primitive(rust::Primitive::U8)),
length: Box::new(rust::Expr::Literal(Literal::int(16))),
},
rust::Type::named(Path::single("WireError")),
],
)),
body: Some(rust::Block { statements }),
}),
])
}
fn decode_fn(
ast: &Ast,
type_: Id<TypeNode>,
layout: &TypeLayout,
key: LayoutKey,
codec: &'static str,
) -> Result<rust::Item, EncodeError> {
let statements = if ast.effective_extensibility(type_) == zerodds_ast::Extensibility::Mutable {
decode_by_member_id(ast, type_, layout, key, codec)?
} else {
decode_in_plan_order(ast, type_, layout, key, codec)?
};
let statements = wrap_packed_body(
statements,
"r",
key.element_padding == ElementPadding::Packed,
);
Ok(rust::Item::Function(rust::Function {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&format!("decode{codec}")),
generics: Vec::new(),
params: vec![rust::Param {
name: Some(Ident::new("r")),
type_: rust::Type::Ref {
target: Box::new(rust::Type::named(Path::single("Reader"))),
mutable: true,
lifetime: None,
},
receiver: None,
}],
returns: Some(rust::Type::generic(
Path::single("Result"),
vec![
rust::Type::named(Path::single("Self")),
rust::Type::named(Path::single("WireError")),
],
)),
body: Some(rust::Block { statements }),
}))
}
fn decode_in_plan_order(
ast: &Ast,
type_: Id<TypeNode>,
layout: &TypeLayout,
key: LayoutKey,
codec: &'static str,
) -> Result<Vec<rust::Statement>, EncodeError> {
let appendable = ast.effective_extensibility(type_) == zerodds_ast::Extensibility::Appendable;
let mut statements = Vec::new();
if layout.needs_dheader {
if appendable {
statements.push(let_statement(
"end",
rust::Expr::Binary {
op: rust::BinaryOp::Add,
lhs: Box::new(rust::Expr::Try(Box::new(call("read_dheader", Vec::new())))),
rhs: Box::new(call("position", Vec::new())),
},
));
} else {
statements.push(let_statement(
"_dheader",
rust::Expr::Try(Box::new(call("read_dheader", Vec::new()))),
));
}
}
let mut fields = Vec::new();
for plan in &layout.members {
let Some((name, kind)) = member_of(ast, type_, plan.member, key, codec) else {
continue;
};
if appendable && matches!(plan.header, MemberHeader::NoHeader) {
let Some(member) = member_node_of(ast, type_, plan.member) else {
continue;
};
let Some(member_type) = member.type_.resolved() else {
continue;
};
let reached = if layout.needs_dheader {
rust::Expr::Binary {
op: rust::BinaryOp::Ge,
lhs: Box::new(call("position", Vec::new())),
rhs: Box::new(rust::Expr::Path(Path::single("end"))),
}
} else {
rust::Expr::Binary {
op: rust::BinaryOp::Eq,
lhs: Box::new(call("remaining", Vec::new())),
rhs: Box::new(rust::Expr::Literal(Literal::int(0))),
}
};
statements.push(let_statement(
&name,
rust::Expr::If {
condition: Box::new(reached),
then: rust::Block {
statements: vec![rust::Statement::Tail(
crate::default_rust::member_default(
ast,
module_of(ast, type_),
member,
member_type,
)?,
)],
},
otherwise: Some(rust::Block {
statements: vec![rust::Statement::Tail(rust::Expr::Try(Box::new(
read_for(&name, &kind)?,
)))],
}),
},
));
fields.push((plan.member, field_binding(&name)));
continue;
}
if member_node_of(ast, type_, plan.member).is_some_and(|member| member.facets.is_optional())
{
statements.extend(read_optional_member(plan, &name, &kind)?);
fields.push((plan.member, field_binding(&name)));
continue;
}
match plan.header {
MemberHeader::NoHeader | MemberHeader::OptionalFlag => {}
MemberHeader::PidCdr1 { .. } | MemberHeader::OptionalCdr1 { .. } => {
statements.push(let_statement(
"pid",
rust::Expr::Try(Box::new(call("read_pid", Vec::new()))),
));
}
MemberHeader::EmHeader { .. } => {
statements.push(let_statement(
"emheader",
rust::Expr::Try(Box::new(call("read_emheader", Vec::new()))),
));
}
}
statements.push(let_statement(
&name,
rust::Expr::Try(Box::new(read_for(&name, &kind)?)),
));
match plan.header {
MemberHeader::NoHeader | MemberHeader::OptionalFlag | MemberHeader::EmHeader { .. } => {
}
MemberHeader::PidCdr1 { .. } | MemberHeader::OptionalCdr1 { .. } => {
statements.push(rust::Statement::Semi(rust::Expr::Try(Box::new(call(
"finish_pid",
vec![rust::Expr::Path(Path::single("pid"))],
)))));
}
}
fields.push((plan.member, field_binding(&name)));
}
if layout.needs_sentinel {
statements.extend(read_sentinel());
}
statements.push(rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![nested_struct_literal(ast, type_, fields, &|| None)],
}));
Ok(statements)
}
fn decode_by_member_id(
ast: &Ast,
type_: Id<TypeNode>,
layout: &TypeLayout,
key: LayoutKey,
codec: &'static str,
) -> Result<Vec<rust::Statement>, EncodeError> {
let mut slots = Vec::new();
for plan in &layout.members {
let Some((name, kind)) = member_of(ast, type_, plan.member, key, codec) else {
continue;
};
slots.push(MutableSlot {
name,
kind,
member_id: plan.member_id,
});
}
let mut statements = Vec::new();
if layout.needs_dheader {
statements.push(let_statement(
"end",
rust::Expr::Binary {
op: rust::BinaryOp::Add,
lhs: Box::new(rust::Expr::Try(Box::new(call("read_dheader", Vec::new())))),
rhs: Box::new(call("position", Vec::new())),
},
));
}
for slot in &slots {
statements.push(rust::Statement::Let {
name: Ident::new(&rust::escape_keyword(&slot.name)),
type_: None,
value: Some(rust::Expr::Path(Path::single("None"))),
mutable: true,
});
}
if key.encoding.is_v1() {
statements.extend(parameter_list_loop(&slots)?);
} else {
statements.extend(member_header_loop(&slots)?);
}
let mut fields = Vec::new();
for plan in &layout.members {
let Some((name, _)) = member_of(ast, type_, plan.member, key, codec) else {
continue;
};
let bound = rust::escape_keyword(&name);
let optional = member_node_of(ast, type_, plan.member)
.is_some_and(|member| member.facets.is_optional());
if !optional {
let Some(member) = member_node_of(ast, type_, plan.member) else {
continue;
};
let Some(member_type) = member.type_.resolved() else {
continue;
};
statements.push(let_statement(
&name,
or_default(
&bound,
crate::default_rust::member_default(
ast,
module_of(ast, type_),
member,
member_type,
)?,
),
));
}
fields.push((
plan.member,
(Ident::new(&bound), rust::Expr::Path(Path::single(&bound))),
));
}
statements.push(rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![nested_struct_literal(ast, type_, fields, &|| None)],
}));
Ok(statements)
}
fn or_default(bound: &str, value: rust::Expr) -> rust::Expr {
let receiver = Box::new(rust::Expr::Path(Path::single(bound)));
if is_rust_default(&value) {
return rust::Expr::MethodCall {
receiver,
method: Ident::new("unwrap_or_default"),
args: Vec::new(),
};
}
if is_constant(&value) {
return rust::Expr::MethodCall {
receiver,
method: Ident::new("unwrap_or"),
args: vec![value],
};
}
rust::Expr::MethodCall {
receiver,
method: Ident::new("unwrap_or_else"),
args: vec![rust::Expr::Closure {
params: Vec::new(),
body: Box::new(value),
}],
}
}
fn is_rust_default(value: &rust::Expr) -> bool {
match value {
rust::Expr::Literal(Literal::Int(text)) => text == "0",
rust::Expr::Literal(Literal::Float(text)) => {
matches!(text.as_str(), "0.0" | "0.0f32" | "0.0f64")
}
rust::Expr::Literal(Literal::Bool(flag)) => !flag,
rust::Expr::Literal(Literal::Char(ch)) => *ch == '\0',
rust::Expr::Call { callee, args } if args.is_empty() => {
let rust::Expr::Path(path) = callee.as_ref() else {
return false;
};
let names: Vec<&str> = path.segments().iter().map(Ident::as_str).collect();
matches!(
names.as_slice(),
["String", "new"] | ["Vec", "new"] | ["BTreeMap", "new"]
)
}
_ => false,
}
}
fn is_constant(value: &rust::Expr) -> bool {
match value {
rust::Expr::Literal(_) | rust::Expr::Path(_) => true,
rust::Expr::Array(elements) | rust::Expr::Tuple(elements) => {
elements.iter().all(is_constant)
}
rust::Expr::Repeat { value, .. } => is_constant(value),
rust::Expr::StructLit { fields, base, .. } => {
base.is_none() && fields.iter().all(|(_, field)| is_constant(field))
}
rust::Expr::Cast { value, .. } => is_constant(value),
_ => false,
}
}
fn refuse_statement(why: &str) -> rust::Statement {
rust::Statement::Semi(rust::Expr::Return(Some(Box::new(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Err"))),
args: vec![rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&["WireError", "refused"]))),
args: vec![
call("position", Vec::new()),
rust::Expr::Literal(Literal::string(why)),
],
}],
}))))
}
fn refuse_a_second_header(slot: &str, member_id: u32) -> rust::Statement {
rust::Statement::Semi(rust::Expr::If {
condition: Box::new(rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single(slot))),
method: Ident::new("is_some"),
args: Vec::new(),
}),
then: rust::Block {
statements: vec![refuse_statement(&format!(
"one header per member, and member id {member_id} has two"
))],
},
otherwise: None,
})
}
fn member_header_loop(slots: &[MutableSlot]) -> Result<Vec<rust::Statement>, EncodeError> {
let mut body = vec![
let_statement(
"header",
rust::Expr::Try(Box::new(call("read_emheader", Vec::new()))),
),
let_statement(
"ends",
rust::Expr::Binary {
op: rust::BinaryOp::Add,
lhs: Box::new(call("position", Vec::new())),
rhs: Box::new(rust::Expr::Field {
receiver: Box::new(rust::Expr::Path(Path::single("header"))),
name: Ident::new("extent"),
}),
},
),
];
let mut chain = Some(rust::Block {
statements: vec![rust::Statement::Semi(rust::Expr::If {
condition: Box::new(rust::Expr::Field {
receiver: Box::new(rust::Expr::Path(Path::single("header"))),
name: Ident::new("must_understand"),
}),
then: rust::Block {
statements: vec![refuse_statement(
"a must-understand member this type does not declare, per §7.2.2.4.4.4.6",
)],
},
otherwise: Some(rust::Block {
statements: vec![rust::Statement::Semi(rust::Expr::Try(Box::new(call(
"skip_to",
vec![rust::Expr::Path(Path::single("ends"))],
))))],
}),
})],
});
for slot in slots.iter().rev() {
let bound = rust::escape_keyword(&slot.name);
let mut arm = vec![refuse_a_second_header(&bound, slot.member_id)];
arm.push(rust::Statement::Semi(rust::Expr::Assign {
target: Box::new(rust::Expr::Path(Path::single(&bound))),
value: Box::new(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Some"))),
args: vec![rust::Expr::Try(Box::new(read_for(&slot.name, &slot.kind)?))],
}),
}));
arm.push(rust::Statement::Semi(rust::Expr::If {
condition: Box::new(rust::Expr::Binary {
op: rust::BinaryOp::Ne,
lhs: Box::new(call("position", Vec::new())),
rhs: Box::new(rust::Expr::Path(Path::single("ends"))),
}),
then: rust::Block {
statements: vec![refuse_statement(
"a member filling the extent its EMHEADER declared",
)],
},
otherwise: None,
}));
chain = Some(rust::Block {
statements: vec![rust::Statement::Semi(rust::Expr::If {
condition: Box::new(rust::Expr::Binary {
op: rust::BinaryOp::Eq,
lhs: Box::new(rust::Expr::Field {
receiver: Box::new(rust::Expr::Path(Path::single("header"))),
name: Ident::new("member_id"),
}),
rhs: Box::new(rust::Expr::Literal(Literal::int(i128::from(
slot.member_id,
)))),
}),
then: rust::Block { statements: arm },
otherwise: chain,
})],
});
}
if let Some(chain) = chain {
body.extend(chain.statements);
}
Ok(vec![rust::Statement::Semi(rust::Expr::While {
condition: Box::new(rust::Expr::Binary {
op: rust::BinaryOp::Lt,
lhs: Box::new(call("position", Vec::new())),
rhs: Box::new(rust::Expr::Path(Path::single("end"))),
}),
body: rust::Block { statements: body },
})])
}
fn parameter_list_loop(slots: &[MutableSlot]) -> Result<Vec<rust::Statement>, EncodeError> {
let mut statements = vec![rust::Statement::Let {
name: Ident::new("ended"),
type_: None,
value: Some(rust::Expr::Literal(Literal::Bool(false))),
mutable: true,
}];
let skip_parameter_block = rust::Block {
statements: vec![rust::Statement::Semi(rust::Expr::Try(Box::new(call(
"skip_parameter",
vec![rust::Expr::Path(Path::single("pid"))],
))))],
};
let unknown = rust::Block {
statements: vec![rust::Statement::Semi(rust::Expr::If {
condition: Box::new(rust::Expr::Field {
receiver: Box::new(rust::Expr::Path(Path::single("pid"))),
name: Ident::new("must_understand"),
}),
then: rust::Block {
statements: vec![refuse_statement(
"a must-understand parameter this type does not declare, per §7.2.2.4.4.4.6",
)],
},
otherwise: Some(skip_parameter_block.clone()),
})],
};
let mut chain = Some(unknown.clone());
for slot in slots.iter().rev() {
let bound = rust::escape_keyword(&slot.name);
let mut arm = vec![refuse_a_second_header(&bound, slot.member_id)];
arm.push(rust::Statement::Semi(rust::Expr::Assign {
target: Box::new(rust::Expr::Path(Path::single(&bound))),
value: Box::new(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Some"))),
args: vec![rust::Expr::Try(Box::new(read_for(&slot.name, &slot.kind)?))],
}),
}));
arm.push(rust::Statement::Semi(rust::Expr::Try(Box::new(call(
"finish_pid",
vec![rust::Expr::Path(Path::single("pid"))],
)))));
chain = Some(rust::Block {
statements: vec![rust::Statement::Semi(rust::Expr::If {
condition: Box::new(rust::Expr::Binary {
op: rust::BinaryOp::Eq,
lhs: Box::new(rust::Expr::Field {
receiver: Box::new(rust::Expr::Path(Path::single("pid"))),
name: Ident::new("pid"),
}),
rhs: Box::new(rust::Expr::Literal(Literal::int(i128::from(
slot.member_id,
)))),
}),
then: rust::Block { statements: arm },
otherwise: chain,
})],
});
}
let matched = rust::Block {
statements: chain.map_or_else(Vec::new, |chain| chain.statements),
};
let tail = rust::Block {
statements: vec![rust::Statement::Semi(rust::Expr::If {
condition: Box::new(rust::Expr::Field {
receiver: Box::new(rust::Expr::Path(Path::single("pid"))),
name: Ident::new("ignore"),
}),
then: skip_parameter_block,
otherwise: Some(rust::Block {
statements: vec![rust::Statement::Semi(rust::Expr::If {
condition: Box::new(rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("pid"))),
method: Ident::new("is_user_member"),
args: Vec::new(),
}),
then: matched,
otherwise: Some(unknown),
})],
}),
})],
};
let body = vec![
let_statement(
"pid",
rust::Expr::Try(Box::new(call("read_pid", Vec::new()))),
),
rust::Statement::Semi(rust::Expr::If {
condition: Box::new(rust::Expr::Field {
receiver: Box::new(rust::Expr::Path(Path::single("pid"))),
name: Ident::new("end"),
}),
then: rust::Block {
statements: vec![rust::Statement::Semi(rust::Expr::Assign {
target: Box::new(rust::Expr::Path(Path::single("ended"))),
value: Box::new(rust::Expr::Literal(Literal::Bool(true))),
})],
},
otherwise: Some(tail),
}),
];
statements.push(rust::Statement::Semi(rust::Expr::While {
condition: Box::new(rust::Expr::Not(Box::new(rust::Expr::Path(Path::single(
"ended",
))))),
body: rust::Block { statements: body },
}));
Ok(statements)
}
struct MutableSlot {
name: String,
kind: MemberWrite,
member_id: u32,
}
fn write_for(
receiver: rust::Expr,
name: &str,
kind: &MemberWrite,
) -> Result<rust::Statement, EncodeError> {
let value = rust::Expr::Field {
receiver: Box::new(receiver),
name: Ident::new(&rust::escape_keyword(name)),
};
write_value_to("w", name, kind, value, false)
}
pub(crate) fn write_value_to(
sink: &str,
name: &str,
kind: &MemberWrite,
value: rust::Expr,
borrowed: bool,
) -> Result<rust::Statement, EncodeError> {
write_value_to_sink(sink, SinkShape::Reference, name, kind, value, borrowed)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SinkShape {
Reference,
Owned,
}
fn write_value_to_sink(
sink: &str,
shape: SinkShape,
name: &str,
kind: &MemberWrite,
value: rust::Expr,
borrowed: bool,
) -> Result<rust::Statement, EncodeError> {
let (call, fallible) = write_call(sink, shape, name, kind, value, borrowed)?;
Ok(rust::Statement::Semi(if fallible {
rust::Expr::Try(Box::new(call))
} else {
call
}))
}
fn write_call(
sink: &str,
shape: SinkShape,
name: &str,
kind: &MemberWrite,
value: rust::Expr,
borrowed: bool,
) -> Result<(rust::Expr, bool), EncodeError> {
let sink_expr = || rust::Expr::Path(Path::single(sink));
let deref = |value: rust::Expr| {
if borrowed {
rust::Expr::Deref(Box::new(value))
} else {
value
}
};
let by_ref = |value: rust::Expr| {
if borrowed {
value
} else {
rust::Expr::Ref {
target: Box::new(value),
mutable: false,
}
}
};
Ok(match kind {
MemberWrite::External(inner) => {
return write_call(
sink,
shape,
name,
inner,
rust::Expr::MethodCall {
receiver: Box::new(value),
method: Ident::new("as_ref"),
args: Vec::new(),
},
true,
);
}
MemberWrite::Primitive(primitive) => (
rust::Expr::MethodCall {
receiver: Box::new(sink_expr()),
method: Ident::new(writer_name(*primitive)),
args: vec![deref(value)],
},
matches!(primitive, PrimitiveKind::Char | PrimitiveKind::WChar),
),
MemberWrite::String { bound, .. } => {
let (method, mut args, fallible) = match bound {
Some(bound) => (
"write_string_bounded",
vec![rust::Expr::Literal(Literal::int(*bound))],
true,
),
None => ("write_string", Vec::new(), false),
};
let mut all = vec![by_ref(value)];
all.append(&mut args);
(
rust::Expr::MethodCall {
receiver: Box::new(sink_expr()),
method: Ident::new(method),
args: all,
},
fallible,
)
}
MemberWrite::Fixed {
digits,
scale,
count,
} => (
rust::Expr::MethodCall {
receiver: Box::new(sink_expr()),
method: Ident::new("write_fixed"),
args: vec![
by_ref(value),
rust::Expr::Literal(Literal::int(i128::from(*digits))),
rust::Expr::Literal(Literal::int(i128::from(*scale))),
fixed_digits_expr(*count),
],
},
true,
),
MemberWrite::WString {
bound,
policy,
units,
order,
position,
..
} => {
let (method, mut args) = match (units, bound) {
(WstringCodeUnits::Utf8, Some(bound)) => (
"write_wstring_utf8_bounded",
vec![rust::Expr::Literal(Literal::int(*bound))],
),
(WstringCodeUnits::Utf8, None) => ("write_wstring_utf8", Vec::new()),
(WstringCodeUnits::Utf16, Some(bound)) => (
"write_wstring_bounded",
vec![rust::Expr::Literal(Literal::int(*bound))],
),
(WstringCodeUnits::Utf16, None) => ("write_wstring", Vec::new()),
};
let mut all = vec![by_ref(value)];
all.append(&mut args);
all.push(wstring_policy_expr(*policy));
if *units == WstringCodeUnits::Utf16 {
all.push(wstring_order_expr(*order));
}
all.push(wire_position_expr(name, *position)?);
(
rust::Expr::MethodCall {
receiver: Box::new(sink_expr()),
method: Ident::new(method),
args: all,
},
true,
)
}
MemberWrite::Enumeration { bits } => (
rust::Expr::MethodCall {
receiver: Box::new(sink_expr()),
method: Ident::new(enum_writer(*bits)),
args: vec![deref(value)],
},
false,
),
MemberWrite::Bitmask { bits, .. } => (
rust::Expr::MethodCall {
receiver: Box::new(sink_expr()),
method: Ident::new("write_bitmask"),
args: vec![
rust::Expr::Literal(Literal::int(i128::from(*bits))),
widen_to_u64(deref(value), *bits),
],
},
true,
),
MemberWrite::Nested {
codec, extended, ..
} => {
let mut args = vec![match shape {
SinkShape::Reference => sink_expr(),
SinkShape::Owned => rust::Expr::Ref {
target: Box::new(sink_expr()),
mutable: true,
},
}];
args.extend(extended.argument());
(
rust::Expr::MethodCall {
receiver: Box::new(value),
method: Ident::new(&nested_method(codec, *extended)),
args,
},
true,
)
}
MemberWrite::Sequence {
element,
bound,
delimited,
packed,
carries_length,
..
} => {
let method = match (bound, delimited) {
(Some(_), true) => "write_delimited_sequence_bounded",
(Some(_), false) => "write_sequence_bounded",
(None, true) => "write_delimited_sequence",
(None, false) => "write_sequence",
};
let mut args = vec![by_ref(value)];
if let Some(bound) = bound {
args.push(rust::Expr::Literal(Literal::int(*bound)));
}
args.push(sequence_element_writer(
name,
element,
*packed,
*carries_length,
)?);
(
rust::Expr::MethodCall {
receiver: Box::new(sink_expr()),
method: Ident::new(method),
args,
},
true,
)
}
MemberWrite::Map {
key,
value: entry_value,
bound,
delimited,
ascending,
..
} => {
let method = match (bound, delimited) {
(Some(_), true) => "write_delimited_map_bounded",
(Some(_), false) => "write_map_bounded",
(None, true) => "write_delimited_map",
(None, false) => "write_map",
};
let held = match crate::adapter::cst::rust_map_form() {
crate::adapter::cst::RustMapForm::Entries if *ascending => rust::Expr::Ref {
target: Box::new(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&[
"zerodds_wire",
"AscendingKey",
]))),
args: vec![by_ref(value)],
}),
mutable: false,
},
_ => by_ref(value),
};
let mut args = vec![held];
if let Some(bound) = bound {
args.push(rust::Expr::Literal(Literal::int(*bound)));
}
args.push(element_writer(name, key)?);
args.push(element_writer(name, entry_value)?);
(
rust::Expr::MethodCall {
receiver: Box::new(sink_expr()),
method: Ident::new(method),
args,
},
true,
)
}
MemberWrite::Array {
element,
dims,
delimited,
packed,
} => {
(
rust::Expr::MethodCall {
receiver: Box::new(sink_expr()),
method: Ident::new(if *delimited {
"write_delimited_array"
} else {
"write_array"
}),
args: vec![
by_ref(value),
array_element_writer(name, element, dims.len().saturating_sub(1), *packed)?,
],
},
true,
)
}
MemberWrite::Unsupported(kind) => {
return Err(EncodeError::NoWriter {
member: name.to_string(),
kind,
});
}
})
}
fn fixed_digits_expr(count: FixedDigitCount) -> rust::Expr {
rust::Expr::Path(Path::new(&[
"zerodds_wire",
"FixedDigits",
match count {
FixedDigitCount::Declared => "Declared",
FixedDigitCount::ValuePrecision => "ValuePrecision",
FixedDigitCount::ValueStringDigits => "ValueStringDigits",
},
]))
}
fn element_writer(at: &str, element: &MemberWrite) -> Result<rust::Expr, EncodeError> {
let substituted = as_element(element);
let element = substituted.as_ref().unwrap_or(element);
if matches!(element, MemberWrite::Primitive(PrimitiveKind::WChar)) {
return Ok(rust::Expr::Path(Path::new(&[
"Writer",
"write_wchar_element",
])));
}
let (call, fallible) = write_call(
"w",
SinkShape::Reference,
at,
element,
rust::Expr::Path(Path::single("v")),
true,
)?;
Ok(rust::Expr::Closure {
params: vec![Ident::new("w"), Ident::new("v")],
body: Box::new(if fallible {
call
} else {
rust::Expr::Block(rust::Block {
statements: vec![
rust::Statement::Semi(call),
rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Tuple(Vec::new())],
}),
],
})
}),
})
}
fn array_element_writer(
at: &str,
element: &MemberWrite,
remaining: usize,
packed: bool,
) -> Result<rust::Expr, EncodeError> {
if remaining == 0 {
return sequence_element_writer(at, element, packed, false);
}
let inner = array_element_writer(at, element, remaining - 1, packed)?;
let call = rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("w"))),
method: Ident::new("write_array"),
args: vec![rust::Expr::Path(Path::single("v")), inner],
};
if !packed {
return Ok(rust::Expr::Closure {
params: vec![Ident::new("w"), Ident::new("v")],
body: Box::new(call),
});
}
Ok(rust::Expr::Closure {
params: vec![Ident::new("w"), Ident::new("v")],
body: Box::new(rust::Expr::Block(rust::Block {
statements: vec![
let_statement("zd_origin", begin_packed("w")),
rust::Statement::Semi(rust::Expr::Try(Box::new(call))),
rust::Statement::Semi(finish_packed("w", "zd_origin")),
rust::Statement::Tail(ok_unit()),
],
})),
})
}
fn sequence_element_writer(
at: &str,
element: &MemberWrite,
packed: bool,
carries_length: bool,
) -> Result<rust::Expr, EncodeError> {
if !packed && !carries_length {
return element_writer(at, element);
}
let (call, fallible) = element_write_call(at, element)?;
let mut statements = Vec::new();
if packed {
statements.push(let_statement("zd_origin", begin_packed("w")));
}
if carries_length {
statements.push(let_statement(
"zd_length",
rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("w"))),
method: Ident::new("write_dheader"),
args: Vec::new(),
},
));
}
statements.push(rust::Statement::Semi(if fallible {
rust::Expr::Try(Box::new(call))
} else {
call
}));
if carries_length {
statements.push(rust::Statement::Semi(finish_dheader("w", "zd_length")));
}
if packed {
statements.push(rust::Statement::Semi(finish_packed("w", "zd_origin")));
}
statements.push(rust::Statement::Tail(ok_unit()));
Ok(rust::Expr::Closure {
params: vec![Ident::new("w"), Ident::new("v")],
body: Box::new(rust::Expr::Block(rust::Block { statements })),
})
}
fn element_write_call(at: &str, element: &MemberWrite) -> Result<(rust::Expr, bool), EncodeError> {
let substituted = as_element(element);
let element = substituted.as_ref().unwrap_or(element);
if matches!(element, MemberWrite::Primitive(PrimitiveKind::WChar)) {
return Ok((
rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&[
"Writer",
"write_wchar_element",
]))),
args: vec![
rust::Expr::Path(Path::single("w")),
rust::Expr::Path(Path::single("v")),
],
},
true,
));
}
write_call(
"w",
SinkShape::Reference,
at,
element,
rust::Expr::Path(Path::single("v")),
true,
)
}
fn begin_packed(sink: &str) -> rust::Expr {
rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single(sink))),
method: Ident::new("begin_packed_element"),
args: Vec::new(),
}
}
fn finish_packed(sink: &str, saved: &str) -> rust::Expr {
rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single(sink))),
method: Ident::new("finish_packed_element"),
args: vec![rust::Expr::Path(Path::single(saved))],
}
}
fn finish_dheader(sink: &str, header: &str) -> rust::Expr {
rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single(sink))),
method: Ident::new("finish_dheader"),
args: vec![rust::Expr::Path(Path::single(header))],
}
}
fn ok_unit() -> rust::Expr {
rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Tuple(Vec::new())],
}
}
fn wrap_packed_body(
mut statements: Vec<rust::Statement>,
sink: &str,
packed: bool,
) -> Vec<rust::Statement> {
if !packed {
return statements;
}
let tail = statements.pop();
let mut out = Vec::with_capacity(statements.len() + 3);
out.push(let_statement("zd_origin", begin_packed(sink)));
out.extend(statements);
out.push(rust::Statement::Semi(finish_packed(sink, "zd_origin")));
if let Some(tail) = tail {
out.push(tail);
}
out
}
fn as_element(element: &MemberWrite) -> Option<MemberWrite> {
match element {
MemberWrite::WString {
bound,
narrow,
policy,
units,
order,
position: _,
} => Some(MemberWrite::WString {
bound: *bound,
narrow: narrow.clone(),
policy: *policy,
units: *units,
order: *order,
position: WirePosition::Element,
}),
_ => None,
}
}
fn wstring_policy_expr(policy: WstringLengthPolicy) -> rust::Expr {
let counts = match policy.counts {
WstringLengthCounts::Octets => "Octets",
WstringLengthCounts::Characters => "Characters",
};
let terminator = match policy.terminator {
WstringTerminator::Absent => "Absent",
WstringTerminator::Present => "Present",
};
let unit = match policy.unit {
WstringUnitWidth::Two => "Two",
WstringUnitWidth::Four => "Four",
};
rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&[
"zerodds_wire",
"WstringLengthPolicy",
"new",
]))),
args: vec![
rust::Expr::Path(Path::new(&["zerodds_wire", "WstringLengthCounts", counts])),
rust::Expr::Path(Path::new(&[
"zerodds_wire",
"WstringTerminator",
terminator,
])),
rust::Expr::Path(Path::new(&["zerodds_wire", "WstringUnitWidth", unit])),
],
}
}
fn wstring_order_expr(order: WstringUnitOrder) -> rust::Expr {
rust::Expr::Path(Path::new(&[
"zerodds_wire",
"WstringUnitOrder",
match order {
WstringUnitOrder::Stream => "Stream",
WstringUnitOrder::BigEndianAlways => "BigEndianAlways",
WstringUnitOrder::ByteOrderMark => "ByteOrderMark",
WstringUnitOrder::HostUnswapped => "HostUnswapped",
},
]))
}
fn wire_position_expr(member: &str, position: WirePosition) -> Result<rust::Expr, EncodeError> {
let name = match position {
WirePosition::Member => "Member",
WirePosition::Element => "Element",
WirePosition::Discriminator
| WirePosition::KeyHolderMember
| WirePosition::Sentinel
| WirePosition::ExtendedMarker => {
return Err(EncodeError::NoWriter {
member: member.to_string(),
kind: "a wstring at a position the wire runtime has no name for",
});
}
};
Ok(rust::Expr::Path(Path::new(&[
"zerodds_wire",
"WirePosition",
name,
])))
}
const fn enum_writer(bits: u16) -> &'static str {
match bits {
0..=8 => "write_u8",
9..=16 => "write_u16",
17..=32 => "write_u32",
_ => "write_u64",
}
}
pub(crate) fn read_for(name: &str, kind: &MemberWrite) -> Result<rust::Expr, EncodeError> {
let read = read_whole(name, kind)?;
match narrowing_of(kind) {
Some(narrow) => narrowed(read, kind, narrow, name),
None => Ok(read),
}
}
const fn narrowing_of(kind: &MemberWrite) -> Option<&Narrowing> {
match kind {
MemberWrite::String { narrow, .. }
| MemberWrite::WString { narrow, .. }
| MemberWrite::Sequence { narrow, .. }
| MemberWrite::Map { narrow, .. } => narrow.as_ref(),
_ => None,
}
}
fn narrowed(
read: rust::Expr,
kind: &MemberWrite,
narrow: &Narrowing,
name: &str,
) -> Result<rust::Expr, EncodeError> {
let fit = match kind {
MemberWrite::String { .. } => "fit_string",
MemberWrite::WString { .. } => "fit_wstring",
MemberWrite::Sequence { .. } => "fit_sequence",
MemberWrite::Map { .. } => match crate::adapter::cst::rust_map_form() {
crate::adapter::cst::RustMapForm::Entries => "fit_map_entries",
crate::adapter::cst::RustMapForm::BTreeMap => "fit_map",
},
_ => {
return Err(EncodeError::NoWriter {
member: name.to_string(),
kind: "a bound on a type that cannot have one",
});
}
};
let bound = rust::escape_keyword(name);
let default = match (&narrow.default, narrow.behaviour) {
(Some(default), _) => default.clone(),
(None, TryConstruct::UseDefault) => {
return Err(EncodeError::NoWriter {
member: name.to_string(),
kind: "a `@try_construct(USE_DEFAULT)` position whose default this column \
cannot spell",
});
}
(None, _) => rust::Expr::Path(Path::single("__unreachable")),
};
Ok(rust::Expr::Block(rust::Block {
statements: vec![
let_statement(name, rust::Expr::Try(Box::new(read))),
rust::Statement::Tail(rust::Expr::Match {
scrutinee: Box::new(rust::Expr::Try(Box::new(call(
fit,
vec![
rust::Expr::Path(Path::single(&bound)),
rust::Expr::Literal(Literal::int(narrow.bound)),
rust::Expr::Path(Path::new(&[
"TryConstruct",
try_construct_variant(narrow.behaviour),
])),
],
)))),
arms: vec![
rust::MatchArm {
pattern: rust::Pattern::TupleStruct {
path: Path::new(&["Narrowed", "Value"]),
elements: vec![rust::Pattern::Binding(Ident::new(&bound))],
},
guard: None,
body: rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Path(Path::single(&bound))],
},
},
rust::MatchArm {
pattern: rust::Pattern::Path(Path::new(&["Narrowed", "Default"])),
guard: None,
body: if narrow.behaviour == TryConstruct::UseDefault {
rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![default],
}
} else {
rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Err"))),
args: vec![rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&[
"WireError",
"refused",
]))),
args: vec![
call("position", Vec::new()),
rust::Expr::Literal(Literal::string(
"a narrowing that asked for no default and got one",
)),
],
}],
}
},
},
],
}),
],
}))
}
const fn try_construct_variant(behaviour: TryConstruct) -> &'static str {
match behaviour {
TryConstruct::Discard => "Discard",
TryConstruct::UseDefault => "UseDefault",
TryConstruct::Trim => "Trim",
}
}
fn read_whole(name: &str, kind: &MemberWrite) -> Result<rust::Expr, EncodeError> {
Ok(match kind {
MemberWrite::External(inner) => rust::Expr::MethodCall {
receiver: Box::new(read_for(name, inner)?),
method: Ident::new("map"),
args: vec![rust::Expr::Path(Path::new(&["Box", "new"]))],
},
MemberWrite::Primitive(primitive) => call(reader_name(*primitive), Vec::new()),
MemberWrite::String { .. } => call("read_string", Vec::new()),
MemberWrite::Fixed {
digits,
scale,
count,
} => call(
"read_fixed",
vec![
rust::Expr::Literal(Literal::int(i128::from(*digits))),
rust::Expr::Literal(Literal::int(i128::from(*scale))),
fixed_digits_expr(*count),
],
),
MemberWrite::WString {
policy,
units,
order,
position,
..
} => {
if *units == WstringCodeUnits::Utf8 {
call(
"read_wstring_utf8",
vec![
wstring_policy_expr(*policy),
wire_position_expr(name, *position)?,
],
)
} else {
call(
"read_wstring",
vec![
wstring_policy_expr(*policy),
wstring_order_expr(*order),
wire_position_expr(name, *position)?,
],
)
}
}
MemberWrite::Enumeration { bits } => call(enum_reader(*bits), Vec::new()),
MemberWrite::Bitmask { bits, declared } => narrow_from_u64(
call(
"read_bitmask",
vec![
rust::Expr::Literal(Literal::int(i128::from(*bits))),
rust::Expr::Literal(Literal::int(i128::from(*declared))),
],
),
*bits,
),
MemberWrite::Nested {
type_name, codec, ..
} => rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&[
type_name.as_str(),
&format!("decode{codec}"),
]))),
args: vec![rust::Expr::Path(Path::single("r"))],
},
MemberWrite::Sequence {
element,
delimited,
packed,
carries_length,
..
} => call(
if *delimited {
"read_delimited_sequence"
} else {
"read_sequence"
},
vec![sequence_element_reader(
name,
element,
*packed,
*carries_length,
)?],
),
MemberWrite::Map {
key,
value,
delimited,
..
} => call(
match (crate::adapter::cst::rust_map_form(), *delimited) {
(crate::adapter::cst::RustMapForm::Entries, true) => "read_delimited_map_entries",
(crate::adapter::cst::RustMapForm::Entries, false) => "read_map_entries",
(crate::adapter::cst::RustMapForm::BTreeMap, true) => "read_delimited_map",
(crate::adapter::cst::RustMapForm::BTreeMap, false) => "read_map",
},
vec![element_reader(name, key)?, element_reader(name, value)?],
),
MemberWrite::Array {
element,
dims,
delimited,
packed,
} => call(
if *delimited {
"read_delimited_array_n"
} else {
"read_array_n"
},
vec![array_element_reader(
name,
element,
dims.len().saturating_sub(1),
*packed,
)?],
),
MemberWrite::Unsupported(kind) => {
return Err(EncodeError::NoWriter {
member: name.to_string(),
kind,
});
}
})
}
fn element_reader(at: &str, element: &MemberWrite) -> Result<rust::Expr, EncodeError> {
let substituted = as_element(element);
let element = substituted.as_ref().unwrap_or(element);
if narrowing_of(element).is_some() {
return Ok(rust::Expr::Closure {
params: vec![Ident::new("r")],
body: Box::new(read_for(at, element)?),
});
}
Ok(match element {
MemberWrite::Primitive(PrimitiveKind::WChar) => {
rust::Expr::Path(Path::new(&["Reader", "read_wchar_element"]))
}
MemberWrite::Primitive(primitive) => {
rust::Expr::Path(Path::new(&["Reader", reader_name(*primitive)]))
}
MemberWrite::String { .. } => rust::Expr::Path(Path::new(&["Reader", "read_string"])),
MemberWrite::Fixed { .. } => rust::Expr::Closure {
params: vec![Ident::new("r")],
body: Box::new(read_for(at, element)?),
},
MemberWrite::WString { .. } => rust::Expr::Closure {
params: vec![Ident::new("r")],
body: Box::new(read_for(at, element)?),
},
MemberWrite::Enumeration { bits } => {
rust::Expr::Path(Path::new(&["Reader", enum_reader(*bits)]))
}
MemberWrite::Nested {
type_name, codec, ..
} => rust::Expr::Path(Path::new(&[type_name.as_str(), &format!("decode{codec}")])),
MemberWrite::Bitmask { .. }
| MemberWrite::External(_)
| MemberWrite::Sequence { .. }
| MemberWrite::Array { .. }
| MemberWrite::Map { .. } => rust::Expr::Closure {
params: vec![Ident::new("r")],
body: Box::new(read_for(at, element)?),
},
MemberWrite::Unsupported(kind) => {
return Err(EncodeError::NoWriter {
member: at.to_string(),
kind,
});
}
})
}
fn widen_to_u64(value: rust::Expr, bits: u16) -> rust::Expr {
if crate::adapter::cst::bitmask_primitive(bits) == rust::Primitive::U64 {
return value;
}
rust::Expr::Cast {
value: Box::new(value),
type_: Box::new(rust::Type::Primitive(rust::Primitive::U64)),
}
}
fn narrow_from_u64(read: rust::Expr, bits: u16) -> rust::Expr {
let holder = crate::adapter::cst::bitmask_primitive(bits);
if holder == rust::Primitive::U64 {
return read;
}
rust::Expr::MethodCall {
receiver: Box::new(read),
method: Ident::new("map"),
args: vec![rust::Expr::Closure {
params: vec![Ident::new("v")],
body: Box::new(rust::Expr::Cast {
value: Box::new(rust::Expr::Path(Path::single("v"))),
type_: Box::new(rust::Type::Primitive(holder)),
}),
}],
}
}
fn array_element_reader(
at: &str,
element: &MemberWrite,
remaining: usize,
packed: bool,
) -> Result<rust::Expr, EncodeError> {
if remaining == 0 {
return sequence_element_reader(at, element, packed, false);
}
let inner = array_element_reader(at, element, remaining - 1, packed)?;
let call = rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("r"))),
method: Ident::new("read_array_n"),
args: vec![inner],
};
if !packed {
return Ok(rust::Expr::Closure {
params: vec![Ident::new("r")],
body: Box::new(call),
});
}
Ok(rust::Expr::Closure {
params: vec![Ident::new("r")],
body: Box::new(rust::Expr::Block(rust::Block {
statements: vec![
let_statement("zd_origin", begin_packed("r")),
let_statement("value", rust::Expr::Try(Box::new(call))),
rust::Statement::Semi(finish_packed("r", "zd_origin")),
rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Path(Path::single("value"))],
}),
],
})),
})
}
fn sequence_element_reader(
at: &str,
element: &MemberWrite,
packed: bool,
carries_length: bool,
) -> Result<rust::Expr, EncodeError> {
if !packed && !carries_length {
return element_reader(at, element);
}
let read = element_read_expr(at, element)?;
let mut statements = Vec::new();
if packed {
statements.push(let_statement("zd_origin", begin_packed("r")));
}
if carries_length {
statements.push(let_statement(
"zd_declared",
rust::Expr::Cast {
value: Box::new(rust::Expr::Try(Box::new(call("read_u32", Vec::new())))),
type_: Box::new(rust::Type::named(Path::single("usize"))),
},
));
statements.push(let_statement("zd_before", call("position", Vec::new())));
}
statements.push(let_statement("value", rust::Expr::Try(Box::new(read))));
if carries_length {
statements.push(rust::Statement::Semi(rust::Expr::If {
condition: Box::new(rust::Expr::Binary {
op: rust::BinaryOp::Ne,
lhs: Box::new(rust::Expr::Binary {
op: rust::BinaryOp::Sub,
lhs: Box::new(call("position", Vec::new())),
rhs: Box::new(rust::Expr::Path(Path::single("zd_before"))),
}),
rhs: Box::new(rust::Expr::Path(Path::single("zd_declared"))),
}),
then: rust::Block {
statements: vec![rust::Statement::Semi(rust::Expr::Return(Some(Box::new(
refused_err(
"r",
"a sequence element of the octet count its own length declared",
),
))))],
},
otherwise: None,
}));
}
if packed {
statements.push(rust::Statement::Semi(finish_packed("r", "zd_origin")));
}
statements.push(rust::Statement::Tail(rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Ok"))),
args: vec![rust::Expr::Path(Path::single("value"))],
}));
Ok(rust::Expr::Closure {
params: vec![Ident::new("r")],
body: Box::new(rust::Expr::Block(rust::Block { statements })),
})
}
fn element_read_expr(at: &str, element: &MemberWrite) -> Result<rust::Expr, EncodeError> {
let substituted = as_element(element);
let element = substituted.as_ref().unwrap_or(element);
if matches!(element, MemberWrite::Primitive(PrimitiveKind::WChar)) {
return Ok(call("read_wchar_element", Vec::new()));
}
read_for(at, element)
}
const fn enum_reader(bits: u16) -> &'static str {
match bits {
0..=8 => "read_u8",
9..=16 => "read_u16",
17..=32 => "read_u32",
_ => "read_u64",
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum MemberWrite {
External(Box<MemberWrite>),
Primitive(PrimitiveKind),
String {
bound: Option<i128>,
narrow: Option<Narrowing>,
},
Fixed {
digits: u8,
scale: u8,
count: FixedDigitCount,
},
WString {
bound: Option<i128>,
narrow: Option<Narrowing>,
policy: WstringLengthPolicy,
units: WstringCodeUnits,
order: WstringUnitOrder,
position: WirePosition,
},
Sequence {
element: Box<MemberWrite>,
bound: Option<i128>,
narrow: Option<Narrowing>,
delimited: bool,
packed: bool,
carries_length: bool,
},
Map {
key: Box<MemberWrite>,
value: Box<MemberWrite>,
bound: Option<i128>,
narrow: Option<Narrowing>,
delimited: bool,
ascending: bool,
},
Array {
element: Box<MemberWrite>,
dims: Vec<i128>,
delimited: bool,
packed: bool,
},
Enumeration {
bits: u16,
},
Bitmask {
bits: u16,
declared: u64,
},
Nested {
type_name: String,
codec: &'static str,
extended: Extended,
},
Unsupported(&'static str),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Narrowing {
bound: i128,
behaviour: TryConstruct,
default: Option<rust::Expr>,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Position {
pub(crate) behaviour: TryConstruct,
pub(crate) written: Option<TryConstruct>,
}
fn member_of(
ast: &Ast,
type_: Id<TypeNode>,
node: zerodds_ast_core::NodeId,
key: LayoutKey,
codec: &'static str,
) -> Option<(String, MemberWrite)> {
let member = ast
.all_members_of(type_)
.into_iter()
.find(|id| ast.member(*id).is_ok_and(|m| m.node_id == node))?;
let member = ast.member(member).ok()?;
let written = member.facets.xtypes.as_ref().and_then(|x| x.try_construct);
let position = Position {
behaviour: ast.effective_try_construct(written, member.type_.resolved()),
written,
};
let from = module_of(ast, type_);
let mut write = member.type_.resolved().map_or(
MemberWrite::Unsupported("member with an unresolved type"),
|target| write_of(ast, from, target, key, codec, position),
);
if let Some(target) = member.type_.resolved() {
if let Some(narrow) = narrowing_of_mut(&mut write) {
if narrow.behaviour == TryConstruct::UseDefault {
narrow.default = crate::default_rust::member_value_default(
ast,
module_of(ast, type_),
member,
target,
)
.ok();
}
}
}
Some((member.name.clone(), external_write(ast, member, write)))
}
fn external_write(ast: &Ast, member: &zerodds_ast::MemberNode, write: MemberWrite) -> MemberWrite {
if crate::adapter::cst::is_external(ast, member) {
MemberWrite::External(Box::new(write))
} else {
write
}
}
fn narrowing_of_mut(write: &mut MemberWrite) -> Option<&mut Narrowing> {
match write {
MemberWrite::String { narrow, .. }
| MemberWrite::WString { narrow, .. }
| MemberWrite::Sequence { narrow, .. }
| MemberWrite::Map { narrow, .. } => narrow.as_mut(),
_ => None,
}
}
fn member_node_of(
ast: &Ast,
type_: Id<TypeNode>,
node: zerodds_ast_core::NodeId,
) -> Option<&zerodds_ast::MemberNode> {
let member = ast
.all_members_of(type_)
.into_iter()
.find(|id| ast.member(*id).is_ok_and(|m| m.node_id == node))?;
ast.member(member).ok()
}
pub(crate) fn write_of(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
target: Id<TypeNode>,
key: LayoutKey,
codec: &'static str,
at: Position,
) -> MemberWrite {
write_of_at(ast, from, target, key, codec, at, 0)
}
const MAX_WRITE_DEPTH: usize = 64;
fn write_of_at(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
target: Id<TypeNode>,
key: LayoutKey,
codec: &'static str,
at: Position,
depth: usize,
) -> MemberWrite {
if depth > MAX_WRITE_DEPTH {
return MemberWrite::Unsupported("a type whose writer nests past the recursion bound");
}
let Ok(node) = ast.type_(target) else {
return MemberWrite::Unsupported("member with a stale type handle");
};
let narrowing = |bound: Option<i128>| {
bound.map(|bound| Narrowing {
bound,
behaviour: at.behaviour,
default: if at.behaviour == TryConstruct::UseDefault {
crate::default_rust::default_of(ast, from, target, &node.name).ok()
} else {
None
},
})
};
match &node.kind {
TypeKind::Primitive(kind) => MemberWrite::Primitive(*kind),
TypeKind::String {
width: CharWidth::WChar,
bound,
} => {
let bound = bound.expr().and_then(|expr| ast.eval_integral(expr).ok());
MemberWrite::WString {
bound,
narrow: narrowing(bound),
policy: key.wstring_length,
units: key.wstring_code_units,
order: key.wstring_unit_order,
position: WirePosition::Member,
}
}
TypeKind::String { bound, .. } => {
let bound = bound.expr().and_then(|expr| ast.eval_integral(expr).ok());
MemberWrite::String {
bound,
narrow: narrowing(bound),
}
}
TypeKind::Fixed { digits, scale } => MemberWrite::Fixed {
digits: *digits,
scale: *scale,
count: key.fixed_digits,
},
TypeKind::Sequence { element, bound } => element.resolved().map_or_else(
|| MemberWrite::Unsupported("sequence with an unresolved element"),
|element| {
let bound = bound.expr().and_then(|expr| ast.eval_integral(expr).ok());
let Some(carries_length) =
zerodds_ast_layout::xcdr::sequence_element_carries_length(ast, element, key)
else {
return MemberWrite::Unsupported(
"a sequence element whose own length cannot be told",
);
};
MemberWrite::Sequence {
element: Box::new(write_of_at(
ast,
from,
element,
key,
codec,
inside(ast, target, at),
depth + 1,
)),
bound,
narrow: narrowing(bound),
delimited: zerodds_ast_layout::collection_is_delimited(ast, element, key),
packed: key.element_padding == ElementPadding::Packed,
carries_length,
}
},
),
TypeKind::Array { element, dims } => element.resolved().map_or_else(
|| MemberWrite::Unsupported("array with an unresolved element"),
|element| {
let Ok(dims) = dims
.iter()
.map(|dim| ast.eval_integral(*dim))
.collect::<Result<Vec<_>, _>>()
else {
return MemberWrite::Unsupported("array whose length does not evaluate");
};
MemberWrite::Array {
element: Box::new(write_of_at(
ast,
from,
element,
key,
codec,
inside(ast, target, at),
depth + 1,
)),
delimited: zerodds_ast_layout::array_is_delimited(ast, element, key),
dims,
packed: key.element_padding == ElementPadding::Packed,
}
},
),
TypeKind::Map {
key: k,
value,
bound,
} => {
let (Some(k), Some(value)) = (k.resolved(), value.resolved()) else {
return MemberWrite::Unsupported("map with an unresolved key or value");
};
if zerodds_ast_layout::plan::map_entry_ordering(ast, target, key).is_err() {
return MemberWrite::Unsupported(
"a map whose key type has no value order, under --map-entries ascending-key",
);
}
let bound = bound.expr().and_then(|expr| ast.eval_integral(expr).ok());
let mut deps = zerodds_ast_layout::xcdr::DepCollector::default();
let Ok(layout) = zerodds_ast_layout::xcdr::plan(ast, target, key, &mut deps) else {
return MemberWrite::Unsupported("map whose layout does not plan");
};
let inside = inside(ast, target, at);
let ascending = matches!(
zerodds_ast_layout::plan::map_entry_ordering(ast, target, key),
Ok(Some(_))
);
MemberWrite::Map {
key: Box::new(write_of_at(ast, from, k, key, codec, inside, depth + 1)),
value: Box::new(write_of_at(ast, from, value, key, codec, inside, depth + 1)),
bound,
ascending,
narrow: narrowing(bound),
delimited: layout.needs_dheader,
}
}
TypeKind::Bitmask { .. } => {
let Some(ScalarWrite::Bitmask { bits }) =
zerodds_ast_layout::plan::scalar_write(ast, target, key, WcharPosition::Member)
else {
return MemberWrite::Unsupported("a bitmask the layout plan gives no holder for");
};
let Some(declared) = declared_bits(ast, target, bits) else {
return MemberWrite::Unsupported(
"a bitmask with a bit position that does not evaluate",
);
};
MemberWrite::Bitmask { bits, declared }
}
TypeKind::Enumeration { .. } => match crate::adapter::cst::relative_path(ast, from, node) {
Ok(path) => MemberWrite::Nested {
type_name: path
.segments()
.iter()
.map(|s| s.as_str().to_string())
.collect::<Vec<_>>()
.join("::"),
codec,
extended: Extended::of(key),
},
Err(_) => MemberWrite::Unsupported("an enumeration the model has no module for"),
},
TypeKind::Alias { aliased } => aliased.resolved().map_or_else(
|| MemberWrite::Unsupported("typedef with an unresolved target"),
|aliased| write_of_at(ast, from, aliased, key, codec, at, depth + 1),
),
TypeKind::Structure { .. }
| TypeKind::Valuetype { .. }
| TypeKind::Union { .. }
| TypeKind::Bitset { .. } => {
match crate::adapter::cst::relative_path(ast, from, node) {
Ok(path) => MemberWrite::Nested {
type_name: path
.segments()
.iter()
.map(|s| s.as_str().to_string())
.collect::<Vec<_>>()
.join("::"),
codec,
extended: Extended::of(key),
},
Err(_) => MemberWrite::Unsupported("a type the model has no module for"),
}
}
other => MemberWrite::Unsupported(other.as_str()),
}
}
fn declared_bits(ast: &Ast, type_: Id<TypeNode>, bound: u16) -> Option<u64> {
let mut declared = 0u64;
for bit in ast.bits_of(type_) {
let position = ast.effective_bit_position(*bit)?;
if position < bound && position < 64 {
declared |= 1u64 << position;
}
}
Some(declared)
}
fn inside(ast: &Ast, collection: Id<TypeNode>, at: Position) -> Position {
Position {
behaviour: ast.element_try_construct(collection, at.written),
written: None,
}
}
const fn writer_name(kind: PrimitiveKind) -> &'static str {
match kind {
PrimitiveKind::Boolean => "write_bool",
PrimitiveKind::Octet | PrimitiveKind::UInt8 => "write_u8",
PrimitiveKind::Char => "write_char",
PrimitiveKind::WChar => "write_wchar_member",
PrimitiveKind::Int8 => "write_i8",
PrimitiveKind::Int16 => "write_i16",
PrimitiveKind::UInt16 => "write_u16",
PrimitiveKind::Int32 => "write_i32",
PrimitiveKind::UInt32 => "write_u32",
PrimitiveKind::Int64 => "write_i64",
PrimitiveKind::UInt64 => "write_u64",
PrimitiveKind::Float32 => "write_f32",
PrimitiveKind::Float64 => "write_f64",
PrimitiveKind::Float128 => "write_f128",
}
}
const fn reader_name(kind: PrimitiveKind) -> &'static str {
match kind {
PrimitiveKind::Boolean => "read_bool",
PrimitiveKind::Octet | PrimitiveKind::UInt8 => "read_u8",
PrimitiveKind::Char => "read_char",
PrimitiveKind::WChar => "read_wchar_member",
PrimitiveKind::Int8 => "read_i8",
PrimitiveKind::Int16 => "read_i16",
PrimitiveKind::UInt16 => "read_u16",
PrimitiveKind::Int32 => "read_i32",
PrimitiveKind::UInt32 => "read_u32",
PrimitiveKind::Int64 => "read_i64",
PrimitiveKind::UInt64 => "read_u64",
PrimitiveKind::Float32 => "read_f32",
PrimitiveKind::Float64 => "read_f64",
PrimitiveKind::Float128 => "read_f128",
}
}
fn call(method: &str, args: Vec<rust::Expr>) -> rust::Expr {
rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("r"))),
method: Ident::new(method),
args,
}
}
fn bind_header(name: &str, method: &str, args: Vec<rust::Expr>) -> rust::Statement {
rust::Statement::Let {
name: Ident::new(name),
type_: None,
value: Some(rust::Expr::Try(Box::new(rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("w"))),
method: Ident::new(method),
args,
}))),
mutable: false,
}
}
fn call_statement(method: &str, args: Vec<rust::Expr>) -> rust::Statement {
rust::Statement::Semi(rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("w"))),
method: Ident::new(method),
args,
})
}
fn try_call_statement(method: &str, args: Vec<rust::Expr>) -> rust::Statement {
rust::Statement::Semi(rust::Expr::Try(Box::new(rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single("w"))),
method: Ident::new(method),
args,
})))
}
fn parameter_role(role: &str) -> rust::Expr {
rust::Expr::Path(Path::new(&["ParameterRole", role]))
}
fn read_optional_member(
plan: &zerodds_ast_layout::plan::MemberPlan,
name: &str,
kind: &MemberWrite,
) -> Result<Vec<rust::Statement>, EncodeError> {
let some = |value: rust::Expr| rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Some"))),
args: vec![value],
};
let read = rust::Expr::Try(Box::new(read_for(name, kind)?));
match plan.header {
MemberHeader::OptionalFlag => Ok(vec![let_statement(
name,
rust::Expr::If {
condition: Box::new(rust::Expr::Try(Box::new(call("read_bool", Vec::new())))),
then: rust::Block {
statements: vec![rust::Statement::Tail(some(read))],
},
otherwise: Some(rust::Block {
statements: vec![rust::Statement::Tail(rust::Expr::Path(Path::single(
"None",
)))],
}),
},
)]),
MemberHeader::OptionalCdr1 { .. } => Ok(vec![
let_statement(
"pid",
rust::Expr::Try(Box::new(call(
"read_pid_as",
vec![parameter_role("Optional")],
))),
),
let_statement(
name,
rust::Expr::If {
condition: Box::new(rust::Expr::Binary {
op: rust::BinaryOp::Eq,
lhs: Box::new(rust::Expr::Field {
receiver: Box::new(rust::Expr::Path(Path::single("pid"))),
name: Ident::new("length"),
}),
rhs: Box::new(rust::Expr::Literal(Literal::int(0))),
}),
then: rust::Block {
statements: vec![rust::Statement::Tail(rust::Expr::Path(Path::single(
"None",
)))],
},
otherwise: Some(rust::Block {
statements: vec![rust::Statement::Tail(some(read))],
}),
},
),
rust::Statement::Semi(rust::Expr::Try(Box::new(call(
"finish_pid",
vec![rust::Expr::Path(Path::single("pid"))],
)))),
]),
_ => {
let mut out = match plan.header {
MemberHeader::PidCdr1 { .. } => vec![let_statement(
"pid",
rust::Expr::Try(Box::new(call("read_pid", Vec::new()))),
)],
MemberHeader::EmHeader { .. } => vec![let_statement(
"emheader",
rust::Expr::Try(Box::new(call("read_emheader", Vec::new()))),
)],
_ => Vec::new(),
};
out.push(let_statement(name, some(read)));
if matches!(plan.header, MemberHeader::PidCdr1 { .. }) {
out.push(rust::Statement::Semi(rust::Expr::Try(Box::new(call(
"finish_pid",
vec![rust::Expr::Path(Path::single("pid"))],
)))));
}
Ok(out)
}
}
}
fn field_binding(name: &str) -> (Ident, rust::Expr) {
let escaped = rust::escape_keyword(name);
(
Ident::new(&escaped),
rust::Expr::Path(Path::single(&escaped)),
)
}
fn let_statement(name: &str, value: rust::Expr) -> rust::Statement {
rust::Statement::Let {
name: Ident::new(&rust::escape_keyword(name)),
type_: None,
value: Some(value),
mutable: false,
}
}
fn allow_lint(lint: &str) -> rust::Attr {
rust::Attr {
path: Path::single("allow"),
args: vec![rust::AttrArg::Word(Ident::new(lint))],
}
}
fn default_impl(ast: &Ast, type_: Id<TypeNode>) -> Result<Option<rust::Item>, EncodeError> {
let Ok(node) = ast.type_(type_) else {
return Ok(None);
};
if !matches!(
node.kind,
TypeKind::Structure { .. } | TypeKind::Union { .. } | TypeKind::Bitset { .. }
) {
return Ok(None);
}
let from = node.module;
let self_type = rust::Type::named(Path::single(&rust::escape_keyword(&node.name)));
let value = crate::default_rust::default_of(ast, from, type_, &node.name)?;
Ok(Some(rust::Item::Impl(rust::Impl {
doc: Doc::none(),
attrs: vec![allow_lint("clippy::derivable_impls")],
generics: Vec::new(),
trait_: Some(rust::Type::named(Path::single("Default"))),
self_type: self_type.clone(),
items: vec![rust::Item::Function(rust::Function {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Private,
name: Ident::new("default"),
generics: Vec::new(),
params: Vec::new(),
returns: Some(rust::Type::named(Path::single("Self"))),
body: Some(rust::Block {
statements: vec![rust::Statement::Tail(value)],
}),
})],
})))
}
fn holder_of(ast: &Ast, type_: Id<TypeNode>, member: NodeId) -> rust::Expr {
let mut place = rust::Expr::Path(Path::single("self"));
for _ in 0..crate::emit::inheritance_depth(ast, type_, member) {
place = rust::Expr::Field {
receiver: Box::new(place),
name: Ident::new(crate::adapter::cst::PARENT),
};
}
place
}
fn nested_struct_literal(
ast: &Ast,
type_: Id<TypeNode>,
entries: Vec<(NodeId, (Ident, rust::Expr))>,
rest: &impl Fn() -> Option<Box<rust::Expr>>,
) -> rust::Expr {
let deepest = entries
.iter()
.map(|(node, _)| crate::emit::inheritance_depth(ast, type_, *node))
.max()
.unwrap_or(0);
let mut inner: Option<rust::Expr> = None;
for depth in (0..=deepest).rev() {
let mut fields: Vec<(Ident, rust::Expr)> = Vec::new();
if let Some(parent) = inner.take() {
fields.push((Ident::new(crate::adapter::cst::PARENT), parent));
}
for (node, field) in &entries {
if crate::emit::inheritance_depth(ast, type_, *node) == depth {
fields.push(field.clone());
}
}
let path = if depth == 0 {
Path::single("Self")
} else {
let owner = crate::emit::base_at(ast, type_, depth);
ast.type_(owner)
.ok()
.and_then(|node| {
crate::adapter::cst::relative_path(ast, module_of(ast, type_), node).ok()
})
.unwrap_or_else(|| Path::single("Self"))
};
inner = Some(rust::Expr::StructLit {
path,
fields,
base: rest(),
});
}
inner.unwrap_or(rust::Expr::StructLit {
path: Path::single("Self"),
fields: Vec::new(),
base: rest(),
})
}
fn module_of(ast: &Ast, type_: Id<TypeNode>) -> Id<zerodds_ast::ModuleNode> {
ast.type_(type_)
.map_or_else(|_| ast.root_module(), |node| node.module)
}
fn alloc_format(error: &impl core::fmt::Debug) -> String {
format!("{error:?}")
}
pub(crate) fn plan_refusal(error: zerodds_ast_layout::plan::LayoutError) -> EncodeError {
match error {
LayoutError::RecursionOutsideCollection { .. } => EncodeError::SpecRefuses {
detail: format!("{error}"),
},
other => EncodeError::NoLayout {
detail: alloc_format(&other),
},
}
}
fn refusal_arm(what: &str) -> rust::MatchArm {
rust::MatchArm {
pattern: rust::Pattern::Wildcard,
guard: None,
body: refused_err("r", what),
}
}
fn refused_err(sink: &str, what: &str) -> rust::Expr {
rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::single("Err"))),
args: vec![rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&["WireError", "refused"]))),
args: vec![
rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Path(Path::single(sink))),
method: Ident::new("position"),
args: Vec::new(),
},
rust::Expr::Literal(Literal::string(what)),
],
}],
}
}