use crate::key::Encoding;
use alloc::vec::Vec;
use core::fmt;
use zerodds_ast_core::NodeId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum LengthCode {
Lc0,
Lc1,
Lc2,
Lc3,
Lc4,
Lc5,
Lc6,
Lc7,
}
impl LengthCode {
pub const ALL: [Self; 8] = [
Self::Lc0,
Self::Lc1,
Self::Lc2,
Self::Lc3,
Self::Lc4,
Self::Lc5,
Self::Lc6,
Self::Lc7,
];
#[must_use]
pub const fn bits(self) -> u8 {
match self {
Self::Lc0 => 0,
Self::Lc1 => 1,
Self::Lc2 => 2,
Self::Lc3 => 3,
Self::Lc4 => 4,
Self::Lc5 => 5,
Self::Lc6 => 6,
Self::Lc7 => 7,
}
}
#[must_use]
pub const fn fixed_size(self) -> Option<u32> {
match self {
Self::Lc0 => Some(1),
Self::Lc1 => Some(2),
Self::Lc2 => Some(4),
Self::Lc3 => Some(8),
_ => None,
}
}
#[must_use]
pub const fn reuses_leading_length(self) -> bool {
matches!(self, Self::Lc5)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemberHeader {
NoHeader,
PidCdr1 {
pid: u16,
extended: bool,
must_understand: bool,
},
EmHeader {
lc: LengthCode,
must_understand: bool,
},
OptionalCdr1 {
pid: u16,
extended: bool,
must_understand: bool,
},
OptionalFlag,
}
impl MemberHeader {
#[must_use]
pub const fn writes_absence(self) -> bool {
matches!(self, Self::OptionalCdr1 { .. } | Self::OptionalFlag)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Offset {
Fixed(u32),
Dynamic,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Extent {
Sized(u32),
Unbounded,
}
impl Extent {
#[must_use]
pub const fn sized(self) -> Option<u32> {
match self {
Self::Sized(n) => Some(n),
Self::Unbounded => None,
}
}
#[must_use]
pub const fn is_sized(self) -> bool {
matches!(self, Self::Sized(_))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MemberPlan {
pub member: NodeId,
pub member_id: u32,
pub header: MemberHeader,
pub offset: Offset,
pub align: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeLayout {
pub type_: NodeId,
pub align: u8,
pub extent: Extent,
pub needs_dheader: bool,
pub needs_sentinel: bool,
pub discriminator: Option<MemberHeader>,
pub members: Vec<MemberPlan>,
}
impl TypeLayout {
#[must_use]
pub fn scalar(type_: NodeId, align: u8, extent: Extent) -> Self {
Self {
type_,
align,
extent,
needs_dheader: false,
needs_sentinel: false,
discriminator: None,
members: Vec::new(),
}
}
#[must_use]
pub fn member(&self, node: NodeId) -> Option<&MemberPlan> {
self.members.iter().find(|m| m.member == node)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum LayoutError {
Unresolved {
node: NodeId,
},
UnsupportedForEncoding {
node: NodeId,
encoding: Encoding,
reason: &'static str,
},
KeyMustBeBigEndian,
TooDeep {
node: NodeId,
},
RecursionOutsideCollection {
node: NodeId,
},
Model(zerodds_ast::AstError),
}
impl fmt::Display for LayoutError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unresolved { node } => {
write!(f, "type {node:?} has an unresolved reference")
}
Self::UnsupportedForEncoding {
node,
encoding,
reason,
} => write!(f, "type {node:?} cannot be {encoding:?}: {reason}"),
Self::KeyMustBeBigEndian => {
write!(f, "the key holder is defined big-endian")
}
Self::TooDeep { node } => write!(f, "type graph below {node:?} nests too deeply"),
Self::RecursionOutsideCollection { node } => write!(
f,
"{node:?} closes a type cycle with nothing on it that terminates a value — no \
sequence, no map and no @optional member — which IDL 4.2 \
\u{a7}7.4.1.4.4.4.4 does not admit; XTypes 1.3 \u{a7}7.3.1.2.1.4 refuses only a \
cyclic value, which is a different question and is answered at value time"
),
Self::Model(error) => write!(f, "{error}"),
}
}
}
impl core::error::Error for LayoutError {}
impl From<zerodds_ast::AstError> for LayoutError {
fn from(error: zerodds_ast::AstError) -> Self {
Self::Model(error)
}
}
pub const MAX_DEPTH: usize = 64;
#[must_use]
pub const fn align_to(offset: u32, align: u8) -> u32 {
let align = align as u32;
if align <= 1 {
return offset;
}
let remainder = offset % align;
if remainder == 0 {
offset
} else {
offset.saturating_add(align - remainder)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScalarWrite {
Bool,
U8,
I8,
Char,
Wchar {
width: crate::key::WcharWidth,
length_octet: bool,
order: crate::key::WstringUnitOrder,
},
I16,
U16,
I32,
U32,
I64,
U64,
F32,
F64,
F128,
Str,
Wstr {
policy: crate::key::WstringLengthPolicy,
order: crate::key::WstringUnitOrder,
code_units: crate::key::WstringCodeUnits,
},
Fixed {
digits: u8,
scale: u8,
count: crate::key::FixedDigitCount,
},
Enum {
bits: u16,
},
Bitmask {
bits: u16,
},
}
#[must_use]
pub fn scalar_write(
ast: &zerodds_ast::Ast,
type_: zerodds_ast_core::Id<zerodds_ast::TypeNode>,
key: crate::key::LayoutKey,
position: crate::key::WcharPosition,
) -> Option<ScalarWrite> {
use zerodds_ast::{PrimitiveKind, TypeKind};
let mut current = type_;
for _ in 0..32 {
let node = ast.type_(current).ok()?;
match &node.kind {
TypeKind::Alias { aliased } => current = aliased.resolved()?,
TypeKind::Primitive(kind) => {
return Some(match kind {
PrimitiveKind::Boolean => ScalarWrite::Bool,
PrimitiveKind::Octet | PrimitiveKind::UInt8 => ScalarWrite::U8,
PrimitiveKind::Int8 => ScalarWrite::I8,
PrimitiveKind::Char => ScalarWrite::Char,
PrimitiveKind::WChar => ScalarWrite::Wchar {
width: key.wchar_width,
length_octet: key.wchar_framing.has_length_octet(position),
order: key.wstring_unit_order,
},
PrimitiveKind::Int16 => ScalarWrite::I16,
PrimitiveKind::UInt16 => ScalarWrite::U16,
PrimitiveKind::Int32 => ScalarWrite::I32,
PrimitiveKind::UInt32 => ScalarWrite::U32,
PrimitiveKind::Int64 => ScalarWrite::I64,
PrimitiveKind::UInt64 => ScalarWrite::U64,
PrimitiveKind::Float32 => ScalarWrite::F32,
PrimitiveKind::Float64 => ScalarWrite::F64,
PrimitiveKind::Float128 => ScalarWrite::F128,
});
}
TypeKind::String {
width: zerodds_ast::CharWidth::Char,
..
} => return Some(ScalarWrite::Str),
TypeKind::String {
width: zerodds_ast::CharWidth::WChar,
..
} => {
return Some(ScalarWrite::Wstr {
policy: key.wstring_length,
order: key.wstring_unit_order,
code_units: key.wstring_code_units,
});
}
TypeKind::Fixed { digits, scale } => {
return Some(ScalarWrite::Fixed {
digits: *digits,
scale: *scale,
count: key.fixed_digits,
});
}
TypeKind::Enumeration { .. } => {
return Some(ScalarWrite::Enum {
bits: crate::xcdr::enumeration_bit_bound(ast, current, key.enum_holder),
});
}
TypeKind::Bitmask { .. } => {
return Some(ScalarWrite::Bitmask {
bits: ast.effective_bit_bound(current),
});
}
_ => return None,
}
}
None
}
pub fn enumerator_writes(
ast: &zerodds_ast::Ast,
type_: zerodds_ast_core::Id<zerodds_ast::TypeNode>,
key: crate::key::LayoutKey,
) -> Result<alloc::vec::Vec<(alloc::string::String, i128)>, alloc::string::String> {
let mut out = alloc::vec::Vec::new();
for enumerator in ast.enumerators_of(type_) {
let node = ast.enumerator(*enumerator).map_err(|_| {
alloc::string::String::from("an enumerator handle the model does not hold")
})?;
let on_wire = crate::xcdr::enumerator_wire_value(ast, *enumerator, key.enumerator_value)
.ok_or_else(|| node.name.clone())?;
out.push((node.name.clone(), on_wire));
}
Ok(out)
}
#[must_use]
pub fn enumerators_are_declared(key: crate::key::LayoutKey) -> bool {
key.enumerator_value.is_default()
}
pub fn map_entry_ordering(
ast: &zerodds_ast::Ast,
type_: zerodds_ast_core::Id<zerodds_ast::TypeNode>,
key: crate::key::LayoutKey,
) -> Result<Option<crate::key::MapKeyOrdering>, NoKeyOrder> {
let ordering = crate::map_key_ordering(ast, type_, key.map_entries);
if !key.map_entries.is_default() && ordering.is_none() {
return Err(NoKeyOrder);
}
Ok(ordering)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NoKeyOrder;
#[must_use]
pub fn union_negative_selects_nothing(
ast: &zerodds_ast::Ast,
type_: zerodds_ast_core::Id<zerodds_ast::TypeNode>,
key: crate::key::LayoutKey,
) -> bool {
crate::xcdr::union_negative_matches_nothing(ast, type_, key.union_label)
}
#[must_use]
pub fn enumeration_holder_bits(
ast: &zerodds_ast::Ast,
type_: zerodds_ast_core::Id<zerodds_ast::TypeNode>,
key: crate::key::LayoutKey,
) -> u16 {
crate::xcdr::enumeration_bit_bound(ast, type_, key.enum_holder)
}