use crate::key::{LayoutKey, WirePosition, WstringTerminator};
use crate::plan::{Extent, LayoutError, MAX_DEPTH, align_to};
use crate::xcdr::{DepCollector, extensibility_of, plan, primitive_layout};
use alloc::vec::Vec;
use zerodds_ast::{Ast, Bound, CharWidth, Extensibility, TypeKind, TypeNode};
use zerodds_ast_core::Id;
const ENCAPSULATION: u32 = 4;
const PARAMETER_HEADER: u32 = 12;
const LIST_END: u32 = 12;
const STRING_BOUND: u32 = 255;
const SEQUENCE_BOUND: u32 = 100;
const SHORT_LENGTH_MAX: u32 = 0xFFFF;
const PAST: u32 = 0x0002_0000;
pub(crate) fn exceeds_short_length(
ast: &Ast,
root: Id<TypeNode>,
key: LayoutKey,
) -> Result<bool, LayoutError> {
let mut walk = Walk {
ast,
key,
stack: Vec::new(),
};
let body = walk.value(root, 0, 0)?;
Ok(add(body, ENCAPSULATION) > SHORT_LENGTH_MAX)
}
pub fn keeps_short_header(ast: &Ast, id: Id<TypeNode>) -> Result<bool, LayoutError> {
let mut current = id;
for _ in 0..MAX_DEPTH {
let node = ast.type_(current)?;
match &node.kind {
TypeKind::Alias { aliased } => {
current = aliased
.resolved()
.ok_or(LayoutError::Unresolved { node: node.node_id })?;
}
TypeKind::Primitive(_) | TypeKind::Enumeration { .. } | TypeKind::Bitmask { .. } => {
return Ok(true);
}
_ => return Ok(false),
}
}
Err(LayoutError::TooDeep {
node: ast.type_(id)?.node_id,
})
}
fn add(a: u32, b: u32) -> u32 {
a.saturating_add(b).min(PAST)
}
fn align(at: u32, origin: u32, alignment: u8) -> u32 {
add(origin, align_to(at.saturating_sub(origin), alignment))
}
struct Walk<'a> {
ast: &'a Ast,
key: LayoutKey,
stack: Vec<Id<TypeNode>>,
}
impl Walk<'_> {
fn value(&mut self, id: Id<TypeNode>, at: u32, origin: u32) -> Result<u32, LayoutError> {
if at >= PAST || self.stack.contains(&id) {
return Ok(PAST);
}
if self.stack.len() >= MAX_DEPTH {
return Err(LayoutError::TooDeep {
node: self.ast.type_(id)?.node_id,
});
}
self.stack.push(id);
let answer = self.inner(id, at, origin);
self.stack.pop();
answer
}
fn inner(&mut self, id: Id<TypeNode>, mut at: u32, origin: u32) -> Result<u32, LayoutError> {
let ast = self.ast;
let key = self.key;
let node = ast.type_(id)?;
let resolved = |reference: &zerodds_ast_core::Ref<TypeNode>| {
reference
.resolved()
.ok_or(LayoutError::Unresolved { node: node.node_id })
};
Ok(match &node.kind {
TypeKind::Alias { aliased } => self.value(resolved(aliased)?, at, origin)?,
TypeKind::Primitive(kind) => {
let (alignment, size) = primitive_layout(*kind, key, WirePosition::Member);
add(align(at, origin, alignment), size)
}
TypeKind::Fixed { digits, .. } => add(at, (u32::from(*digits) + 2) / 2),
TypeKind::String { width, bound } => {
let count = self.bound(*bound, STRING_BOUND, id)?;
let (unit, terminal) = match width {
CharWidth::Char => (1, 1),
CharWidth::WChar => (
key.wstring_length.unit_at(WirePosition::Member).octets() as u32,
u32::from(matches!(
key.wstring_length.terminator_at(WirePosition::Member),
WstringTerminator::Present
)),
),
};
let content = count.saturating_add(terminal).saturating_mul(unit);
add(add(align(at, origin, 4), 4), content)
}
TypeKind::Enumeration { .. } | TypeKind::Bitmask { .. } | TypeKind::Bitset { .. } => {
let layout = plan(ast, id, key, &mut DepCollector::default())?;
let Extent::Sized(size) = layout.extent else {
return Err(unsupported(node, key, "a scalar without a size"));
};
add(align(at, origin, layout.align), size)
}
TypeKind::Sequence { element, bound } => {
let count = self.bound(*bound, SEQUENCE_BOUND, id)?;
at = add(align(at, origin, 4), 4);
self.repeat(&[resolved(element)?], count, at, origin)?
}
TypeKind::Map {
key: map_key,
value,
bound,
} => {
let count = self.bound(*bound, SEQUENCE_BOUND, id)?;
at = add(align(at, origin, 4), 4);
self.repeat(&[resolved(map_key)?, resolved(value)?], count, at, origin)?
}
TypeKind::Array { element, dims } => {
let mut count = 1u32;
for dim in dims {
let n = ast
.eval_integral(*dim)
.ok()
.and_then(|n| u32::try_from(n).ok())
.ok_or_else(|| unsupported(node, key, "an unevaluable array dimension"))?;
count = count.saturating_mul(n);
}
self.repeat(&[resolved(element)?], count, at, origin)?
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let mutable = extensibility_of(ast, node, key) == Extensibility::Mutable;
for member in ast.all_members_of(id) {
let member = ast.member(member)?;
let target = member.type_.resolved().ok_or(LayoutError::Unresolved {
node: member.node_id,
})?;
at = if mutable || member.facets.is_optional() {
self.parameter(target, at, origin)?
} else {
self.value(target, at, origin)?
};
}
if mutable {
add(align(at, origin, 4), LIST_END)
} else {
at
}
}
TypeKind::Union {
discriminator,
cases,
..
} => {
let mutable = extensibility_of(ast, node, key) == Extensibility::Mutable;
let discriminator = resolved(discriminator)?;
at = if mutable {
self.parameter(discriminator, at, origin)?
} else {
self.value(discriminator, at, origin)?
};
let mut widest = at;
for case in cases {
let member = ast.member(ast.case(*case)?.member)?;
let target = member.type_.resolved().ok_or(LayoutError::Unresolved {
node: member.node_id,
})?;
widest = widest.max(if mutable {
self.parameter(target, at, origin)?
} else {
self.value(target, at, origin)?
});
}
if mutable {
add(align(widest, origin, 4), LIST_END)
} else {
widest
}
}
_ => return Err(unsupported(node, key, "a construct no sample holds")),
})
}
fn parameter(&mut self, id: Id<TypeNode>, at: u32, origin: u32) -> Result<u32, LayoutError> {
let start = add(align(at, origin, 4), PARAMETER_HEADER);
self.value(id, start, start)
}
fn repeat(
&mut self,
elements: &[Id<TypeNode>],
count: u32,
mut at: u32,
origin: u32,
) -> Result<u32, LayoutError> {
for _ in 0..count {
let before = at;
for element in elements {
at = self.value(*element, at, origin)?;
}
if at >= PAST || at == before {
break;
}
}
Ok(at)
}
fn bound(&self, bound: Bound, default: u32, id: Id<TypeNode>) -> Result<u32, LayoutError> {
match bound {
Bound::Unbounded => Ok(default),
Bound::Const(expr) => self
.ast
.eval_integral(expr)
.ok()
.and_then(|n| u32::try_from(n).ok())
.ok_or_else(|| match self.ast.type_(id) {
Ok(node) => unsupported(node, self.key, "an unevaluable collection bound"),
Err(error) => LayoutError::Model(error),
}),
}
}
}
fn unsupported(node: &TypeNode, key: LayoutKey, reason: &'static str) -> LayoutError {
LayoutError::UnsupportedForEncoding {
node: node.node_id,
encoding: key.encoding,
reason,
}
}