use super::parse;
use super::{
ArtifactStructure, ConstantReading, DeclaredArtifact, DeclaredImplementation, DeclaredMember,
ImplementationMember, ImplementationStructure, StructuralDisagreement, StructuralVerdict,
};
pub fn read(rendered: &str, declared: &DeclaredArtifact<'_>) -> StructuralVerdict {
match parse::declarations_in(rendered) {
Some(structure) => compared(&structure, declared),
None => StructuralVerdict::Unparsable,
}
}
pub fn compared(
structure: &ArtifactStructure,
declared: &DeclaredArtifact<'_>,
) -> StructuralVerdict {
match disagreement(structure, declared) {
Some(found) => StructuralVerdict::Deviates(found),
None => StructuralVerdict::Conforms,
}
}
fn disagreement(
structure: &ArtifactStructure,
declared: &DeclaredArtifact<'_>,
) -> Option<StructuralDisagreement> {
if structure.other_items > 0 {
return Some(StructuralDisagreement::UnexpectedItem);
}
if let Some(at) = duplicated(&structure.implementations) {
return Some(StructuralDisagreement::DuplicateImplementation { at });
}
if structure.implementations.len() != declared.implementations.len() {
return Some(StructuralDisagreement::OutputCardinality {
declared: declared.implementations.len(),
read: structure.implementations.len(),
});
}
structure
.implementations
.iter()
.zip(declared.implementations.iter())
.enumerate()
.find_map(|(at, (read, expected))| implementation_disagreement(at, read, expected))
}
fn implementation_disagreement(
at: usize,
read: &ImplementationStructure,
expected: &DeclaredImplementation<'_>,
) -> Option<StructuralDisagreement> {
if &read.target != expected.target {
return Some(StructuralDisagreement::ImplementationTarget { at });
}
if read.trait_path.as_ref() != expected.trait_path {
return Some(StructuralDisagreement::TraitPath { at });
}
if read.postures.as_slice() != expected.postures {
return Some(StructuralDisagreement::ImplPosture { at });
}
let carried = read
.meaning_bearing_attributes
.iter()
.find(|spelled| !expected.attributes.contains(spelled));
if let Some(attribute) = carried {
return Some(StructuralDisagreement::MeaningBearingAttribute {
at,
attribute: attribute.spelling(),
});
}
member_disagreement(at, &read.members, expected.members.members())
}
fn member_disagreement(
at: usize,
read: &[ImplementationMember],
declared: &[DeclaredMember<'_>],
) -> Option<StructuralDisagreement> {
if let Some(member) = undeclared_member(read, declared) {
return Some(StructuralDisagreement::UnexpectedImplMember { at, member });
}
if let Some(member) = restated_member(read) {
return Some(StructuralDisagreement::DuplicateMember { at, member });
}
declared
.iter()
.find_map(|expected| member_value_disagreement(at, read, expected))
}
fn undeclared_member(
read: &[ImplementationMember],
declared: &[DeclaredMember<'_>],
) -> Option<String> {
for member in read {
match member {
ImplementationMember::Other { described } => return Some((*described).to_owned()),
ImplementationMember::Constant { name, .. } => {
if !declared
.iter()
.any(|expected| expected.name == name.as_str())
{
return Some(name.clone());
}
}
}
}
None
}
fn restated_member(read: &[ImplementationMember]) -> Option<String> {
for (position, member) in read.iter().enumerate() {
let ImplementationMember::Constant { name, .. } = member else {
continue;
};
let restated = read
.iter()
.take(position)
.any(|earlier| named_constant(earlier, name.as_str()).is_some());
if restated {
return Some(name.clone());
}
}
None
}
fn member_value_disagreement(
at: usize,
read: &[ImplementationMember],
expected: &DeclaredMember<'_>,
) -> Option<StructuralDisagreement> {
let stated = read
.iter()
.find_map(|member| named_constant(member, expected.name));
let Some(member_reading) = stated else {
return Some(StructuralDisagreement::MissingImplMember {
at,
member: expected.name.to_owned(),
});
};
let Some(value) = member_reading.as_ref() else {
return Some(StructuralDisagreement::MemberValueUnread {
at,
member: expected.name.to_owned(),
});
};
if *value == expected.reading {
return None;
}
Some(StructuralDisagreement::MemberValue {
at,
member: expected.name.to_owned(),
})
}
fn named_constant<'read>(
member: &'read ImplementationMember,
name: &str,
) -> Option<&'read Option<ConstantReading>> {
let ImplementationMember::Constant {
name: stated,
reading,
} = member
else {
return None;
};
if stated.as_str() == name {
Some(reading)
} else {
None
}
}
fn duplicated(implementations: &[ImplementationStructure]) -> Option<usize> {
for (at, found) in implementations.iter().enumerate() {
let earlier = found.trait_path.is_some()
&& implementations
.iter()
.take(at)
.any(|other| other.target == found.target && other.trait_path == found.trait_path);
if earlier {
return Some(at);
}
}
None
}