use crate::canonical;
use crate::encode::ProbeError;
use crate::sink::{Aggregate, Sink};
use crate::source::Source;
use std::string::String;
use std::vec::Vec;
use zerodds_ast::{Ast, ConstExpr, Literal, MemberNode, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::Id;
const MAX_DEPTH: usize = 64;
pub fn member<V: Source + Sink>(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
path: &str,
) -> Result<V, ProbeError> {
member_at_depth(ast, node, type_, path, 0)
}
fn member_at_depth<V: Source + Sink>(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
path: &str,
depth: usize,
) -> Result<V, ProbeError> {
if let Some(expr) = node.facets.xtypes.as_ref().and_then(|x| x.default_value) {
if node.facets.is_optional() {
return Err(ProbeError::Unsupported {
path: path.to_string(),
kind: "an `@optional` member with an explicit `@default`, which \
§7.2.2.4.4.4.9 does not allow",
});
}
return explicit(ast, expr, type_, path);
}
at_depth(ast, type_, path, depth)
}
pub fn of_type<V: Source + Sink>(
ast: &Ast,
type_: Id<TypeNode>,
path: &str,
) -> Result<V, ProbeError> {
at_depth(ast, type_, path, 0)
}
#[allow(clippy::too_many_lines)]
fn at_depth<V: Source + Sink>(
ast: &Ast,
type_: Id<TypeNode>,
path: &str,
depth: usize,
) -> Result<V, ProbeError> {
if depth > MAX_DEPTH {
return Err(ProbeError::Unsupported {
path: path.to_string(),
kind: "a type whose default value nests deeper than a model without a cycle can",
});
}
let node = ast.type_(type_).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
match &node.kind {
TypeKind::Primitive(kind) => primitive(*kind, path),
TypeKind::String { .. } => Ok(V::text(String::new())),
TypeKind::Fixed { digits, scale } => {
Ok(V::fixed(canonical::decimal("0", *digits, *scale, path)?))
}
TypeKind::Alias { aliased } => {
let target = aliased.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the typedef target is unresolved"),
})?;
at_depth(ast, target, path, depth + 1)
}
TypeKind::Enumeration { .. } => first_enumerator(ast, type_, path),
TypeKind::Bitmask { .. } => Ok(V::bit_names(Vec::new())),
TypeKind::Bitset { .. } => {
let mut out = Vec::new();
for id in &ast.all_bitfields_of(type_) {
let Ok(field) = ast.bitfield(*id) else {
continue;
};
let Some(name) = field.name.as_ref() else {
continue;
};
out.push((name.clone(), 0));
}
V::bitfields(out, path)
}
TypeKind::Sequence { .. } | TypeKind::Map { .. } => Ok(V::collection(Vec::new())),
TypeKind::Array { element, dims } => {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the array element is unresolved"),
})?;
let counts: Vec<usize> = dims
.iter()
.map(|dim| {
ast.eval_integral(*dim)
.ok()
.and_then(|value| usize::try_from(value).ok())
.unwrap_or(0)
})
.collect();
array(ast, element, path, &counts, depth + 1)
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
structure(ast, type_, path, depth + 1)
}
TypeKind::Union { .. } => union(ast, type_, path, depth + 1),
other => Err(ProbeError::Unfilled {
path: path.to_string(),
kind: other.as_str(),
}),
}
}
fn primitive<V: Sink>(kind: PrimitiveKind, path: &str) -> Result<V, ProbeError> {
match kind {
PrimitiveKind::Boolean => Ok(V::boolean(false)),
PrimitiveKind::Char | PrimitiveKind::WChar => Ok(V::character(kind, '\0')),
PrimitiveKind::Float128 => Ok(V::float128(zerodds_float128::Float128::ZERO)),
PrimitiveKind::Float32 | PrimitiveKind::Float64 => V::float(kind, 0.0_f64, path),
PrimitiveKind::Octet
| PrimitiveKind::UInt8
| PrimitiveKind::Int8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::Int32
| PrimitiveKind::UInt32
| PrimitiveKind::Int64
| PrimitiveKind::UInt64 => V::integer(kind, 0, path),
}
}
fn first_enumerator<V: Sink>(ast: &Ast, type_: Id<TypeNode>, path: &str) -> Result<V, ProbeError> {
let mut first = None;
for id in ast.enumerators_of(type_) {
let Ok(node) = ast.enumerator(*id) else {
continue;
};
if node
.facets
.xtypes
.as_ref()
.is_some_and(|x| x.is_default_literal)
{
return Ok(V::enumerator(node.name.clone()));
}
if first.is_none() {
first = Some(node.name.clone());
}
}
first
.map(V::enumerator)
.ok_or_else(|| ProbeError::Mismatch {
path: path.to_string(),
expected: "an enumeration with at least one enumerator, whose first is its default"
.to_string(),
found: "an empty enumeration".to_string(),
})
}
fn array<V: Source + Sink>(
ast: &Ast,
element: Id<TypeNode>,
path: &str,
counts: &[usize],
depth: usize,
) -> Result<V, ProbeError> {
let Some((&count, rest)) = counts.split_first() else {
return at_depth(ast, element, path, depth);
};
let one: V = array(ast, element, path, rest, depth)?;
Ok(V::collection(std::iter::repeat_n(one, count).collect()))
}
fn structure<V: Source + Sink>(
ast: &Ast,
type_: Id<TypeNode>,
path: &str,
depth: usize,
) -> Result<V, ProbeError> {
let mut out = Aggregate::new();
for id in &ast.all_members_of(type_) {
let Ok(node) = ast.member(*id) else { continue };
if node.facets.is_optional() {
continue;
}
let Some(member_type) = node.type_.resolved() else {
return Err(ProbeError::NoLayout {
detail: format!("{path}.{}: the member type is unresolved", node.name),
});
};
let member_path = format!("{path}.{}", node.name);
out.insert(
node.name.clone(),
member_at_depth(ast, node, member_type, &member_path, depth)?,
);
}
Ok(V::aggregate(out))
}
fn union<V: Source + Sink>(
ast: &Ast,
type_: Id<TypeNode>,
path: &str,
depth: usize,
) -> Result<V, ProbeError> {
let node = ast.type_(type_).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let TypeKind::Union { discriminator, .. } = &node.kind else {
return Err(ProbeError::Unsupported {
path: path.to_string(),
kind: "a union whose kind changed under the probe",
});
};
let discriminator = discriminator
.resolved()
.ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the discriminator is unresolved"),
})?;
let discriminant_path = format!("{path}._d");
let discriminant: V = at_depth(ast, discriminator, &discriminant_path, depth)?;
let mut out = Aggregate::new();
let selector =
crate::decode::selector_of(ast, discriminator, &discriminant, &discriminant_path)?;
out.insert(String::from("_d"), discriminant);
let selected = crate::decode::select_case(ast, type_, selector, false).map_err(|_| {
ProbeError::Mismatch {
path: path.to_string(),
expected: String::from("a union whose case labels all evaluate"),
found: String::from("a case label the model cannot evaluate"),
}
})?;
if let Some(case) = selected {
let member_path = format!("{path}.{}", case.name);
let value = at_depth(ast, case.type_, &member_path, depth)?;
out.insert(case.name, value);
}
Ok(V::aggregate(out))
}
fn explicit<V: Source + Sink>(
ast: &Ast,
expr: Id<zerodds_ast::ConstExprNode>,
type_: Id<TypeNode>,
path: &str,
) -> Result<V, ProbeError> {
let raw: V = literal_value(ast, expr, path)?;
canonical::canonicalise(ast, type_, &raw, path)
}
fn literal_value<V: Sink>(
ast: &Ast,
expr: Id<zerodds_ast::ConstExprNode>,
path: &str,
) -> Result<V, ProbeError> {
let node = ast.expr(expr).map_err(|error| ProbeError::NoLayout {
detail: format!("{path}: {error}"),
})?;
match &node.expr {
ConstExpr::Literal(Literal::Boolean(flag)) => Ok(V::boolean(*flag)),
ConstExpr::Literal(Literal::Char(ch)) => Ok(V::text(ch.to_string())),
ConstExpr::Literal(Literal::WChar(code)) => match char::from_u32(*code) {
Some(ch) => Ok(V::text(ch.to_string())),
None => Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a `@default` wide character that is a code point".to_string(),
found: format!("{code:#x}"),
}),
},
ConstExpr::Literal(Literal::String(text)) => Ok(V::text(text.clone())),
ConstExpr::Literal(Literal::WString(codes)) => {
let mut text = String::with_capacity(codes.len());
for code in codes {
let Some(ch) = char::from_u32(*code) else {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a `@default` wide string of code points".to_string(),
found: format!("{code:#x}"),
});
};
text.push(ch);
}
Ok(V::text(text))
}
ConstExpr::Literal(Literal::Float {
mantissa,
exponent: None,
}) => number_text(mantissa, path),
ConstExpr::Literal(Literal::Float {
mantissa,
exponent: Some(exponent),
}) => number_text(&format!("{mantissa}e{exponent}"), path),
ConstExpr::Literal(Literal::Fixed { digits, scale }) => {
Ok(V::text(fixed_text(digits, *scale)))
}
ConstExpr::Literal(Literal::Enumerator(reference)) => {
let target = reference.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the `@default` enumerator is unresolved"),
})?;
let node = ast
.enumerator(target)
.map_err(|error| ProbeError::NoLayout {
detail: format!("{path}: {error}"),
})?;
Ok(V::text(node.name.clone()))
}
_ => {
let value = ast
.eval_integral(expr)
.map_err(|error| ProbeError::Mismatch {
path: path.to_string(),
expected: "a `@default` this frontend can evaluate".to_string(),
found: format!("{error:?}"),
})?;
Ok(V::text(format!("{value}")))
}
}
}
fn number_text<V: Sink>(text: &str, path: &str) -> Result<V, ProbeError> {
let parsed: f64 = text.parse().map_err(|_| ProbeError::Mismatch {
path: path.to_string(),
expected: "a `@default` floating-point literal".to_string(),
found: format!("`{text}`"),
})?;
V::number(parsed, path)
}
fn fixed_text(digits: &str, scale: u8) -> String {
let (negative, magnitude) = match digits.strip_prefix('-') {
Some(rest) => (true, rest),
None => (false, digits),
};
let scale = usize::from(scale);
let padded = if magnitude.len() <= scale {
format!("{}{magnitude}", "0".repeat(scale + 1 - magnitude.len()))
} else {
magnitude.to_string()
};
let split = padded.len() - scale;
let mut out = String::with_capacity(padded.len() + 2);
if negative {
out.push('-');
}
out.push_str(&padded[..split]);
if scale > 0 {
out.push('.');
out.push_str(&padded[split..]);
}
out
}