use super::bindings::Bindings;
use super::corpus::Corpus;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(super) enum Target {
Definition(usize),
Module(usize),
}
pub(super) enum Outcome {
Found(Box<[Target]>),
External,
Missing,
Unsupported,
}
enum Start {
Targets(Box<[Target]>),
Refused(Outcome),
}
pub(super) fn resolve_path(
corpus: &Corpus<'_>,
bindings: &Bindings,
from: usize,
segments: &[Box<str>],
) -> Outcome {
let Some((first, rest)) = segments.split_first() else {
return Outcome::Unsupported;
};
let mut current = match start(corpus, bindings, from, first) {
Start::Targets(targets) => targets,
Start::Refused(outcome) => return outcome,
};
for segment in rest {
current = descend(corpus, bindings, ¤t, segment);
if current.is_empty() {
return Outcome::Missing;
}
}
Outcome::Found(current)
}
pub(super) fn members(
corpus: &Corpus<'_>,
bindings: &Bindings,
node: usize,
name: &str,
) -> Box<[Target]> {
let mut targets = direct_members(corpus, bindings, node, name);
if targets.is_empty() {
targets.extend(glob_members(corpus, bindings, node, name));
}
sorted(targets)
}
fn start(corpus: &Corpus<'_>, bindings: &Bindings, from: usize, first: &str) -> Start {
match first {
"crate" => at_root(corpus, corpus.unit_of(from)),
"self" => Start::Targets(Box::from([Target::Module(from)])),
"super" => at_module(corpus.parent_of(from)),
"$crate" => Start::Refused(Outcome::External),
name => named_start(corpus, bindings, from, name),
}
}
fn named_start(corpus: &Corpus<'_>, bindings: &Bindings, from: usize, name: &str) -> Start {
let lexical = lexical_lookup(corpus, bindings, from, name);
if !lexical.is_empty() {
return Start::Targets(lexical);
}
let unit = corpus
.unit_of(from)
.and_then(|unit| corpus.units.named(unit, name));
match unit {
Some(found) => at_root(corpus, Some(found)),
None => Start::Refused(Outcome::External),
}
}
fn at_root(corpus: &Corpus<'_>, unit: Option<usize>) -> Start {
at_module(unit.and_then(|unit| corpus.root_of(unit)))
}
fn at_module(node: Option<usize>) -> Start {
match node {
Some(found) => Start::Targets(Box::from([Target::Module(found)])),
None => Start::Refused(Outcome::External),
}
}
fn lexical_lookup(
corpus: &Corpus<'_>,
bindings: &Bindings,
from: usize,
name: &str,
) -> Box<[Target]> {
let mut current = Some(from);
while let Some(node) = current {
let found = members(corpus, bindings, node, name);
if !found.is_empty() {
return found;
}
current = corpus.parent_of(node);
}
Box::from([])
}
fn descend(
corpus: &Corpus<'_>,
bindings: &Bindings,
targets: &[Target],
name: &str,
) -> Box<[Target]> {
let mut next: Vec<Target> = Vec::new();
for target in targets {
match target {
Target::Module(node) => next.extend(members(corpus, bindings, *node, name)),
Target::Definition(slot) => next.extend(associated(corpus, *slot, name)),
}
}
sorted(next)
}
fn associated(corpus: &Corpus<'_>, slot: usize, name: &str) -> Box<[Target]> {
let Some(owner) = corpus.index.slot(slot) else {
return Box::from([]);
};
corpus
.index
.associated(&owner.name, name)
.iter()
.filter(|found| corpus.holds(owner.node, **found))
.map(|found| Target::Definition(*found))
.collect()
}
fn glob_members(corpus: &Corpus<'_>, bindings: &Bindings, node: usize, name: &str) -> Vec<Target> {
bindings
.globs(node)
.iter()
.flat_map(|module| direct_members(corpus, bindings, *module, name))
.collect()
}
fn direct_members(
corpus: &Corpus<'_>,
bindings: &Bindings,
node: usize,
name: &str,
) -> Vec<Target> {
let mut targets: Vec<Target> = corpus
.index
.names_in(node, name)
.iter()
.map(|slot| Target::Definition(*slot))
.collect();
targets.extend(
corpus
.index
.modules_in(node, name)
.iter()
.map(|child| Target::Module(*child)),
);
targets.extend(bindings.named(node, name).iter().copied());
targets
}
fn sorted(mut targets: Vec<Target>) -> Box<[Target]> {
targets.sort_unstable();
targets.dedup();
targets.into_boxed_slice()
}