use std::collections::BTreeSet;
use pedant_types::ResolutionGap;
use super::index::{EmbeddedType, Index, Slot};
use super::signatures::Signatures;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(super) struct Requirement {
name: Box<str>,
signature: Option<Box<str>>,
}
impl Requirement {
pub(super) fn name(&self) -> &str {
&self.name
}
pub(super) fn signature(&self) -> Option<&str> {
self.signature.as_deref()
}
}
pub(super) struct Expansion {
required: Box<[Requirement]>,
gap: Option<ResolutionGap>,
}
impl Expansion {
pub(super) fn required(&self) -> &[Requirement] {
&self.required
}
pub(super) fn gap(&self) -> Option<ResolutionGap> {
self.gap
}
}
#[derive(Default)]
struct Walk {
open: Vec<usize>,
visited: BTreeSet<usize>,
required: Vec<Requirement>,
gap: Option<ResolutionGap>,
}
impl Walk {
fn opening(interface: usize) -> Self {
Self {
open: vec![interface],
..Self::default()
}
}
fn next_open(&mut self) -> Option<usize> {
while let Some(slot) = self.open.pop() {
if self.visited.insert(slot) {
return Some(slot);
}
}
None
}
fn open(&mut self, interface: usize) {
self.open.push(interface);
}
fn require(&mut self, required: Requirement) {
if required.signature.is_none() {
self.refuse(ResolutionGap::UnsupportedSyntax);
}
self.required.push(required);
}
fn refuse(&mut self, gap: ResolutionGap) {
self.gap = self.gap.or(Some(gap));
}
fn seal(self) -> Expansion {
let mut sorted = self.required;
sorted.sort();
sorted.dedup();
let mut gap = self.gap;
let mut merged: Vec<Requirement> = Vec::new();
for entry in sorted {
match merged.last_mut() {
Some(last) if last.name == entry.name => {
last.signature = None;
gap = gap.or(Some(ResolutionGap::Ambiguous));
}
_ => merged.push(entry),
}
}
Expansion {
required: merged.into_boxed_slice(),
gap,
}
}
}
pub(super) fn expand(index: &Index, signatures: &Signatures, interface: usize) -> Expansion {
let mut walk = Walk::opening(interface);
while let Some(slot) = walk.next_open() {
read_interface(index, signatures, slot, &mut walk);
}
walk.seal()
}
fn read_interface(index: &Index, signatures: &Signatures, interface: usize, walk: &mut Walk) {
let Some(found) = index.slot(interface) else {
return;
};
if found.general_terms {
walk.refuse(ResolutionGap::UnsupportedSyntax);
}
let members = index
.unit(found.unit)
.map(|tables| tables.held(&found.name))
.unwrap_or_default();
for member in members {
read_element(index, signatures, (found, *member), walk);
}
}
fn read_element(index: &Index, signatures: &Signatures, stated: (&Slot, usize), walk: &mut Walk) {
let (owner, member) = stated;
let Some(held) = index.slot(member) else {
return;
};
match (held.is_interface_method(), held.is_embedded_field()) {
(true, _) => walk.require(Requirement {
name: Box::from(&*held.name),
signature: signatures.of_slot(member).map(Box::from),
}),
(_, true) => open_embedded(index, (owner, held), walk),
_ => {}
}
}
fn open_embedded(index: &Index, stated: (&Slot, &Slot), walk: &mut Walk) {
let (owner, held) = stated;
let Some(embedded) = embedded_type(index, owner, &held.name) else {
walk.refuse(ResolutionGap::ExternalDefinition);
return;
};
let named = declared_types(index, embedded);
if named.is_empty() {
walk.refuse(ResolutionGap::ExternalDefinition);
}
for slot in named.iter() {
open_interface(index, *slot, walk);
}
}
fn open_interface(index: &Index, slot: usize, walk: &mut Walk) {
match index.slot(slot).is_some_and(Slot::is_interface) {
true => walk.open(slot),
false => walk.refuse(ResolutionGap::UnsupportedSyntax),
}
}
fn embedded_type<'a>(index: &'a Index, owner: &Slot, written: &str) -> Option<&'a EmbeddedType> {
index
.unit(owner.unit)?
.embedded(&owner.name)
.iter()
.find(|embedding| &*embedding.embedded.name == written)
.map(|embedding| &embedding.embedded)
}
fn declared_types(index: &Index, embedded: &EmbeddedType) -> Box<[usize]> {
index
.unit(embedded.unit)
.map(|tables| tables.named(&embedded.name))
.unwrap_or_default()
.iter()
.copied()
.filter(|slot| index.slot(*slot).is_some_and(Slot::is_type))
.collect()
}