use crate::encode::ProbeError;
use crate::source::{Fields, Source};
use std::string::String;
use std::vec::Vec;
use zerodds_ast::{Ast, Bound, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::Id;
use zerodds_float128::Float128;
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Consumed(Vec<String>);
impl Consumed {
#[must_use]
pub const fn new() -> Self {
Self(Vec::new())
}
pub fn scalar(&mut self, path: &str) {
self.0.push(path.to_string());
}
#[must_use]
pub fn sorted(&self) -> Vec<String> {
let mut out = self.0.clone();
out.sort();
out
}
#[must_use]
pub fn paths(&self) -> &[String] {
&self.0
}
pub fn absorb(&mut self, other: &Self) {
self.0.extend(other.0.iter().cloned());
}
}
#[derive(Debug, Clone)]
pub struct Member {
pub node: zerodds_ast_core::NodeId,
pub name: String,
pub type_: Id<TypeNode>,
pub optional: bool,
}
#[must_use]
pub fn root_path(ast: &Ast, type_: Id<TypeNode>) -> String {
ast.any_of_type(type_)
.ok()
.and_then(|any| ast.scoped_name_of(any))
.map_or_else(String::new, |scoped| scoped.segments().join("::"))
}
#[must_use]
pub fn members(ast: &Ast, type_: Id<TypeNode>) -> Vec<Member> {
ast.all_members_of(type_)
.into_iter()
.filter_map(|id| {
let member = ast.member(id).ok()?;
Some(Member {
node: member.node_id,
name: member.name.clone(),
type_: member.type_.resolved()?,
optional: member.facets.is_optional(),
})
})
.collect()
}
#[must_use]
pub fn union_members(ast: &Ast, type_: Id<TypeNode>) -> Vec<(String, Id<TypeNode>)> {
union_cases(ast, type_)
.into_iter()
.map(|case| (case.name, case.type_))
.collect()
}
#[derive(Debug, Clone)]
pub struct Case {
pub node: zerodds_ast_core::NodeId,
pub name: String,
pub type_: Id<TypeNode>,
pub try_construct: Option<zerodds_ast::TryConstruct>,
pub labels: Vec<zerodds_ast::CaseLabel>,
}
impl Case {
#[must_use]
pub fn is_default(&self) -> bool {
self.labels.iter().any(|label| label.is_default())
}
}
pub fn union_selection(
ast: &Ast,
cases: &[Case],
discriminator: i128,
suppress_negative: bool,
) -> Result<Option<usize>, UnevaluatedLabel> {
let mut fallback = None;
for (index, case) in cases.iter().enumerate() {
for label in &case.labels {
match label.expr() {
None => fallback = Some(index),
Some(expr) => {
let value = ast
.eval_integral(expr)
.map_err(|_| UnevaluatedLabel { case: index })?;
if suppress_negative && value < 0 {
continue;
}
if value == discriminator {
return Ok(Some(index));
}
}
}
}
}
Ok(fallback)
}
#[derive(Debug, Clone, Copy)]
pub struct UnevaluatedLabel {
pub case: usize,
}
#[must_use]
pub fn union_cases(ast: &Ast, type_: Id<TypeNode>) -> Vec<Case> {
ast.cases_of(type_)
.iter()
.filter_map(|id| ast.case(*id).ok())
.filter_map(|case| {
let member = ast.member(case.member).ok()?;
Some(Case {
node: member.node_id,
name: member.name.clone(),
type_: member.type_.resolved()?,
try_construct: member.facets.xtypes.as_ref().and_then(|x| x.try_construct),
labels: case.labels.clone(),
})
})
.collect()
}
#[must_use]
pub fn bitfield_names(ast: &Ast, fields: &[Id<zerodds_ast::BitfieldNode>]) -> Vec<String> {
fields
.iter()
.filter_map(|id| ast.bitfield(*id).ok())
.filter_map(|field| field.name.clone())
.collect()
}
pub fn reject_unknown_keys<V: Source>(
object: Fields<'_, V>,
declared: &[String],
path: &str,
) -> Result<(), ProbeError> {
for name in object.names() {
if !declared.iter().any(|known| known == name) {
return Err(ProbeError::Unknown {
path: join(path, name),
declared: declared.to_vec(),
});
}
}
Ok(())
}
pub fn member_value<'a, V: Source>(
object: Fields<'a, V>,
member: &Member,
path: &str,
) -> Result<Option<&'a V>, ProbeError> {
let member_path = join(path, &member.name);
match object.get(&member.name) {
Some(value) if value.is_null() => Err(ProbeError::Mismatch {
path: member_path,
expected: "a value, or the key left out for an absent @optional".to_string(),
found: "null".to_string(),
}),
Some(value) => Ok(Some(value)),
None if member.optional => Ok(None),
None => Err(ProbeError::Missing { path: member_path }),
}
}
pub fn reject_null<V: Source>(value: &V, path: &str) -> Result<(), ProbeError> {
if value.is_null() {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a value, or the key left out for an absent @optional".to_string(),
found: "null".to_string(),
});
}
Ok(())
}
pub fn discriminator<'a, V: Source>(
object: Fields<'a, V>,
path: &str,
) -> Result<&'a V, ProbeError> {
object.get("_d").ok_or_else(|| ProbeError::Missing {
path: join(path, "_d"),
})
}
#[must_use]
pub fn join(path: &str, name: &str) -> String {
if path.is_empty() {
name.to_string()
} else {
format!("{path}.{name}")
}
}
pub fn object<'a, V: Source>(value: &'a V, path: &str) -> Result<Fields<'a, V>, ProbeError> {
if value.is_object() {
return Ok(Fields::of(value));
}
Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "an object".to_string(),
found: kind_of(value).to_string(),
})
}
pub fn array<'a, V: Source>(value: &'a V, path: &str) -> Result<&'a [V], ProbeError> {
value.as_items().ok_or_else(|| ProbeError::Mismatch {
path: path.to_string(),
expected: "an array".to_string(),
found: kind_of(value).to_string(),
})
}
pub fn text<'a, V: Source>(value: &'a V, path: &str) -> Result<&'a str, ProbeError> {
value.as_text().ok_or_else(|| ProbeError::Mismatch {
path: path.to_string(),
expected: "a string".to_string(),
found: kind_of(value).to_string(),
})
}
pub fn boolean<V: Source>(value: &V, path: &str) -> Result<bool, ProbeError> {
value.as_bool().ok_or_else(|| ProbeError::Mismatch {
path: path.to_string(),
expected: "true or false".to_string(),
found: kind_of(value).to_string(),
})
}
pub fn float<V: Source>(value: &V, path: &str) -> Result<f64, ProbeError> {
value.as_float().ok_or_else(|| ProbeError::Mismatch {
path: path.to_string(),
expected: "a number".to_string(),
found: kind_of(value).to_string(),
})
}
pub fn float128<V: Source>(value: &V, path: &str) -> Result<Float128, ProbeError> {
value.as_float128(path)
}
pub fn character<V: Source>(value: &V, path: &str) -> Result<char, ProbeError> {
let text = text(value, path)?;
let mut chars = text.chars();
match (chars.next(), chars.next()) {
(Some(ch), None) => Ok(ch),
_ => Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "exactly one character".to_string(),
found: format!("`{text}`"),
}),
}
}
pub fn character_for<V: Source>(
kind: PrimitiveKind,
value: &V,
path: &str,
) -> Result<char, ProbeError> {
let found = character(value, path)?;
let point = u32::from(found);
let (limit, expected) = match kind {
PrimitiveKind::Char => (0xff, "a character below U+0100"),
PrimitiveKind::WChar => (0xffff, "a character in the Basic Multilingual Plane"),
PrimitiveKind::Boolean
| PrimitiveKind::Octet
| PrimitiveKind::UInt8
| PrimitiveKind::Int8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::Int32
| PrimitiveKind::UInt32
| PrimitiveKind::Int64
| PrimitiveKind::UInt64
| PrimitiveKind::Float32
| PrimitiveKind::Float64
| PrimitiveKind::Float128 => {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a character type".to_string(),
found: format!("{kind:?}"),
});
}
};
if point > limit {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: expected.to_string(),
found: format!("U+{point:04X}"),
});
}
Ok(found)
}
pub fn integer<V: Source>(value: &V, path: &str) -> Result<i128, ProbeError> {
value.as_integer(path)
}
pub fn integer_for<V: Source>(
kind: PrimitiveKind,
value: &V,
path: &str,
) -> Result<i128, ProbeError> {
let raw = integer(value, path)?;
let (low, high, name): (i128, i128, &str) = match kind {
PrimitiveKind::Octet | PrimitiveKind::UInt8 => (0, 0xff, "an octet"),
PrimitiveKind::Int8 => (-0x80, 0x7f, "an int8"),
PrimitiveKind::Int16 => (-0x8000, 0x7fff, "a short"),
PrimitiveKind::UInt16 => (0, 0xffff, "an unsigned short"),
PrimitiveKind::Int32 => (-0x8000_0000, 0x7fff_ffff, "a long"),
PrimitiveKind::UInt32 => (0, 0xffff_ffff, "an unsigned long"),
PrimitiveKind::Int64 => (-0x8000_0000_0000_0000, 0x7fff_ffff_ffff_ffff, "a long long"),
PrimitiveKind::UInt64 => (0, 0xffff_ffff_ffff_ffff, "an unsigned long long"),
PrimitiveKind::Boolean
| PrimitiveKind::Char
| PrimitiveKind::WChar
| PrimitiveKind::Float32
| PrimitiveKind::Float64
| PrimitiveKind::Float128 => {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "an integral type".to_string(),
found: format!("{kind:?}"),
});
}
};
if raw < low || raw > high {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("{name} in range {low}..={high}"),
found: format!("{raw}"),
});
}
Ok(raw)
}
pub fn enumerator<V: Source>(
ast: &Ast,
type_: Id<TypeNode>,
value: &V,
path: &str,
) -> Result<(String, i128), ProbeError> {
let name = text(value, path).map_err(|_| ProbeError::Mismatch {
path: path.to_string(),
expected: "an enumerator name, as a string".to_string(),
found: kind_of(value).to_string(),
})?;
let mut declared = Vec::new();
let mut found = None;
for id in ast.enumerators_of(type_) {
let Ok(node) = ast.enumerator(*id) else {
continue;
};
declared.push(node.name.clone());
if node.name == name {
found = ast.effective_enumerator_value(*id);
}
}
let value = found.ok_or_else(|| ProbeError::Unknown {
path: path.to_string(),
declared,
})?;
Ok((name.to_string(), value))
}
pub fn bit_names<V: Source>(
ast: &Ast,
type_: Id<TypeNode>,
value: &V,
path: &str,
) -> Result<Vec<(String, u16)>, ProbeError> {
let bound = ast.effective_bit_bound(type_);
if bound == 0 || bound > 64 {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "an @bit_bound between 1 and 64, as Table 33 tabulates".to_string(),
found: format!("{bound}"),
});
}
let entries = array(value, path)?;
let mut declared = Vec::new();
let mut positions = Vec::new();
for id in ast.bits_of(type_) {
let Ok(node) = ast.bit_value(*id) else {
continue;
};
declared.push(node.name.clone());
positions.push((node.name.clone(), ast.effective_bit_position(*id)));
}
let mut out = Vec::with_capacity(entries.len());
for (index, entry) in entries.iter().enumerate() {
let element = format!("{path}[{index}]");
let name = text(entry, &element).map_err(|_| ProbeError::Mismatch {
path: element.clone(),
expected: "a bit name, as a string".to_string(),
found: kind_of(entry).to_string(),
})?;
let position = positions
.iter()
.find(|(declared_name, _)| declared_name == name)
.and_then(|(_, position)| *position)
.ok_or_else(|| ProbeError::Unknown {
path: element.clone(),
declared: declared.clone(),
})?;
if position >= bound {
return Err(ProbeError::Mismatch {
path: element,
expected: format!("a bit below the @bit_bound({bound})"),
found: format!("`{name}` at position {position}"),
});
}
out.push((name.to_string(), position));
}
Ok(out)
}
pub fn bounded_items<'a, V: Source>(
ast: &Ast,
value: &'a V,
bound: Bound,
noun: &str,
path: &str,
) -> Result<&'a [V], ProbeError> {
let items = array(value, path)?;
if let Some(limit) = bound.expr().and_then(|expr| ast.eval_integral(expr).ok()) {
if i128::try_from(items.len()).unwrap_or(i128::MAX) > limit {
return Err(ProbeError::Mismatch {
path: path.to_string(),
expected: format!("at most {limit} {noun}"),
found: format!("{}", items.len()),
});
}
}
Ok(items)
}
pub fn map_entry<'a, V: Source>(entry: &'a V, path: &str) -> Result<(&'a V, &'a V), ProbeError> {
let pair = array(entry, path)?;
match pair {
[key, value] => Ok((key, value)),
other => Err(ProbeError::Mismatch {
path: path.to_string(),
expected: "a two-element array, `[key, value]`".to_string(),
found: format!("{} elements", other.len()),
}),
}
}
pub fn array_dimensions(
ast: &Ast,
dims: &[Id<zerodds_ast::ConstExprNode>],
path: &str,
) -> Result<Vec<usize>, ProbeError> {
dims.iter()
.map(|dim| {
ast.eval_integral(*dim)
.ok()
.and_then(|value| usize::try_from(value).ok())
.ok_or(ProbeError::Unsupported {
path: path.to_string(),
kind: "an array dimension that does not evaluate to a count",
})
})
.collect()
}
#[must_use]
pub fn discriminator_c_width(
ast: &Ast,
discriminator: Id<TypeNode>,
) -> (&'static str, &'static str) {
let resolved = resolve_alias(ast, discriminator);
let unsigned = matches!(
ast.type_(resolved).map(|node| node.kind.clone()),
Ok(TypeKind::Primitive(
PrimitiveKind::UInt8
| PrimitiveKind::UInt16
| PrimitiveKind::UInt32
| PrimitiveKind::UInt64
| PrimitiveKind::Octet
))
);
if unsigned {
("unsigned long long", "ULL")
} else {
("long long", "LL")
}
}
pub const MAX_ALIAS_HOPS: usize = 64;
pub fn reject_alias_cycle(ast: &Ast) -> Result<(), ProbeError> {
let aliases: Vec<Id<TypeNode>> = ast
.types()
.filter(|(_, node)| matches!(node.kind, TypeKind::Alias { .. }))
.map(|(id, _)| id)
.collect();
for start in aliases {
let mut chain: Vec<Id<TypeNode>> = vec![start];
let mut current = start;
loop {
let Ok(TypeKind::Alias { aliased }) = ast.type_(current).map(|node| node.kind.clone())
else {
break;
};
let Some(next) = aliased.resolved() else {
break;
};
if chain.contains(&next) || chain.len() > MAX_ALIAS_HOPS {
return Err(ProbeError::NoLayout {
detail: format!(
"{}: a typedef chain that returns to itself, which IDL 4.2 §7.4.1.4.4.4 \
has no declaration for and no walk over the type graph can end",
alias_name(ast, start)
),
});
}
chain.push(next);
current = next;
}
}
Ok(())
}
fn alias_name(ast: &Ast, type_: Id<TypeNode>) -> String {
ast.any_of_type(type_)
.ok()
.and_then(|any| ast.scoped_name_of(any))
.map_or_else(
|| String::from("a typedef"),
|scoped| scoped.segments().join("::"),
)
}
#[must_use]
pub fn resolve_alias(ast: &Ast, type_: Id<TypeNode>) -> Id<TypeNode> {
let mut current = type_;
for _ in 0..MAX_ALIAS_HOPS {
match ast.type_(current).map(|node| node.kind.clone()) {
Ok(TypeKind::Alias { aliased }) => match aliased.resolved() {
Some(next) => current = next,
None => return current,
},
_ => return current,
}
}
current
}
#[must_use]
pub fn kind_of<V: Source>(value: &V) -> &'static str {
value.kind()
}