use alloc::vec::Vec;
use crate::stack::build::StackBuilder;
use crate::stack::{
import_sites, BindingReach, Definition, ImportSite, StackNode, SymbolPop, SymbolPush,
};
use crate::syntax::SyntaxGraph;
use crate::{Language, NodeId};
impl StackBuilder {
pub(super) fn add_imports(
&mut self,
syntax: &SyntaxGraph,
language: Language,
root: NodeId,
path: &str,
) {
let directories = package_directories(path);
for site in import_sites(syntax, language) {
if site.reach == BindingReach::Scope
&& matches!(self.graph.nodes.get(&site.scope), Some(StackNode::Scope(_)))
{
self.add_import(&site, language, root, &directories);
}
}
}
fn add_import(
&mut self,
site: &ImportSite,
language: Language,
root: NodeId,
directories: &[&str],
) {
let binding = &site.binding;
let module = imported_module(
binding.module_stem.as_str(),
binding.relative_level,
language,
directories,
);
let imported = (!binding.binds_module).then(|| {
binding
.source_symbol
.as_deref()
.unwrap_or(site.symbol.as_str())
});
let pushes: Option<Vec<&str>> = module.map(|module| {
imported
.filter(|symbol| *symbol != "*")
.into_iter()
.chain(qualified(&module).into_iter().rev())
.collect()
});
if site.symbol == "*" {
let Some([first, rest @ ..]) = pushes.as_deref() else {
return;
};
self.graph.add_node(site.node, relay(first));
self.graph.add_edge(site.scope, site.node, 0);
self.add_pushes(site.node, rest, root);
return;
}
self.graph.add_node(
site.node,
StackNode::PopSymbol(SymbolPop {
symbol: site.symbol.clone(),
precedence: 1,
definition: Some(Definition {
is_private: language != Language::Python,
visibility_guard: None,
}),
import: Some(binding.clone()),
}),
);
self.graph.add_edge(site.scope, site.node, 1);
if let Some(pushes) = pushes {
self.add_pushes(site.node, &pushes, root);
}
}
fn add_pushes(&mut self, from: NodeId, symbols: &[&str], root: NodeId) {
let last = symbols.iter().fold(from, |previous, symbol| {
let push = self.add(relay(symbol));
self.graph.add_edge(previous, push, 0);
push
});
self.graph.add_edge(last, root, 0);
}
}
fn relay(symbol: &str) -> StackNode {
StackNode::PushSymbol(SymbolPush {
symbol: symbol.into(),
precedence: 0,
is_reference: false,
})
}
fn package_directories(path: &str) -> Vec<&str> {
path.rsplit_once('/')
.map_or("", |(directories, _)| directories)
.split(['/', '.'])
.filter(|segment| !segment.is_empty())
.collect()
}
fn imported_module<'a>(
stem: &'a str,
relative_level: u32,
language: Language,
directories: &[&'a str],
) -> Option<Vec<&'a str>> {
let package = if relative_level == 0 {
&[][..]
} else {
let climbed = usize::try_from(relative_level.saturating_sub(1)).ok()?;
let kept = directories.len().checked_sub(climbed)?;
if language == Language::Python && kept == 0 {
return None;
}
directories.get(..kept)?
};
Some(
package
.iter()
.copied()
.chain(stem.split('.').filter(|segment| !segment.is_empty()))
.collect(),
)
}
fn qualified<'a>(segments: &[&'a str]) -> Vec<&'a str> {
segments
.iter()
.enumerate()
.flat_map(|(index, segment)| (index > 0).then_some(".").into_iter().chain([*segment]))
.collect()
}