use std::collections::{HashSet, VecDeque};
use prebindgen::SourceLocation;
use crate::registry::{Direction, Registry, TypeKey};
#[derive(Debug)]
pub enum ResolveError {
Unresolved { entries: Vec<UnresolvedEntry> },
}
#[derive(Debug)]
pub struct UnresolvedEntry {
pub key: TypeKey,
pub direction: Direction,
pub location: Option<SourceLocation>,
}
impl std::fmt::Display for ResolveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResolveError::Unresolved { entries } => {
writeln!(
f,
"{} required type(s) could not be resolved:",
entries.len()
)?;
for e in entries {
let dir = match e.direction {
Direction::Input => "input",
Direction::Output => "output",
};
if let Some(loc) = e.location.as_ref() {
writeln!(
f,
"{}:{}:{}: error: unresolved prebindgen {} type `{}`",
loc.file, loc.line, loc.column, dir, e.key
)?;
} else {
writeln!(f, "error: unresolved prebindgen {} type `{}`", dir, e.key)?;
}
}
Ok(())
}
}
}
}
impl std::error::Error for ResolveError {}
fn required_set<M>(registry: &Registry<M>) -> HashSet<(Direction, TypeKey)> {
let mut required: HashSet<(Direction, TypeKey)> = HashSet::new();
let mut queue: VecDeque<(Direction, TypeKey)> = VecDeque::new();
for dir in [Direction::Input, Direction::Output] {
for (key, cell) in registry.type_table(dir) {
if cell.root && required.insert((dir, key.clone())) {
queue.push_back((dir, key.clone()));
}
}
}
while let Some((dir, key)) = queue.pop_front() {
let Some(entry) = registry
.type_table(dir)
.get(&key)
.and_then(|c| c.entry.as_ref())
else {
continue;
};
for sub_key in &entry.subs {
if required.insert((dir, sub_key.clone())) {
queue.push_back((dir, sub_key.clone()));
}
}
}
required
}
fn collect_unresolved_descendants<M>(
registry: &Registry<M>,
seeds: &[(Direction, TypeKey)],
seen: &mut std::collections::HashSet<(Direction, TypeKey)>,
out: &mut Vec<UnresolvedEntry>,
) {
let mut queue: VecDeque<(Direction, TypeKey)> = VecDeque::new();
let enqueue_edges_from =
|dir: Direction,
key: &TypeKey,
queue: &mut VecDeque<(Direction, TypeKey)>,
seen: &mut std::collections::HashSet<(Direction, TypeKey)>| {
for (child_dir, sub) in registry.immediate_edges(dir, key) {
let dep = (child_dir, sub.key());
if seen.insert(dep.clone()) {
queue.push_back(dep);
}
}
};
for (dir, key) in seeds {
enqueue_edges_from(*dir, key, &mut queue, seen);
}
while let Some((dir, key)) = queue.pop_front() {
match registry.type_table(dir).get(&key) {
Some(cell) if cell.entry.is_none() => {
out.push(UnresolvedEntry {
key: key.clone(),
direction: dir,
location: Some(cell.subject.location())
.filter(|l| l.has_position())
.cloned(),
});
enqueue_edges_from(dir, &key, &mut queue, seen);
}
None => {
enqueue_edges_from(dir, &key, &mut queue, seen);
}
Some(_) => {
}
}
}
}
pub(crate) fn check_complete<M>(registry: &Registry<M>) -> Result<(), ResolveError> {
let required = required_set(registry);
let mut entries: Vec<UnresolvedEntry> = Vec::new();
let mut unresolved_required_roots: Vec<(Direction, TypeKey)> = Vec::new();
let mut seen_unresolved: HashSet<(Direction, TypeKey)> = HashSet::new();
for dir in [Direction::Input, Direction::Output] {
let mut keys: Vec<&TypeKey> = registry.type_table(dir).keys().collect();
keys.sort_by(|a, b| a.as_str().cmp(b.as_str()));
for key in keys {
let cell = ®istry.type_table(dir)[key];
if cell.entry.is_some() || !required.contains(&(dir, key.clone())) {
continue;
}
unresolved_required_roots.push((dir, key.clone()));
seen_unresolved.insert((dir, key.clone()));
entries.push(UnresolvedEntry {
key: key.clone(),
direction: dir,
location: Some(cell.subject.location())
.filter(|l| l.has_position())
.cloned(),
});
}
}
collect_unresolved_descendants(
registry,
&unresolved_required_roots,
&mut seen_unresolved,
&mut entries,
);
if entries.is_empty() {
Ok(())
} else {
Err(ResolveError::Unresolved { entries })
}
}
#[cfg(test)]
mod tests;