use alloc::string::{String, ToString};
use alloc::vec::Vec;
use zerodds_ast::{AnyId, ModuleNode};
use zerodds_ast_core::{Id, ScopedName};
type Bindings = Vec<(String, AnyId)>;
#[derive(Debug, Clone)]
struct Scope {
module: Option<Id<ModuleNode>>,
name: String,
container: bool,
bindings: Bindings,
}
#[derive(Debug, Clone)]
pub struct ScopeStack {
scopes: Vec<Scope>,
closed: Vec<(Id<ModuleNode>, Bindings)>,
}
impl ScopeStack {
#[must_use]
pub fn new_placeholder() -> Self {
Self {
scopes: Vec::new(),
closed: Vec::new(),
}
}
#[must_use]
pub fn new(root: Id<ModuleNode>) -> Self {
Self {
scopes: alloc::vec![Scope {
module: Some(root),
name: String::new(),
container: false,
bindings: Vec::new(),
}],
closed: Vec::new(),
}
}
pub fn push(&mut self, name: &str, module: Option<Id<ModuleNode>>) {
let bindings = module
.and_then(|id| self.closed.iter().find(|(closed, _)| *closed == id))
.map_or_else(Vec::new, |(_, bindings)| bindings.clone());
self.scopes.push(Scope {
module,
name: name.to_string(),
container: false,
bindings,
});
}
pub fn push_container(&mut self, name: &str) {
self.scopes.push(Scope {
module: None,
name: name.to_string(),
container: true,
bindings: Vec::new(),
});
}
#[must_use]
pub fn enclosing_container(&self) -> Option<&str> {
self.scopes
.last()
.filter(|scope| scope.container)
.map(|scope| scope.name.as_str())
}
pub fn pop(&mut self) {
if self.scopes.len() <= 1 {
return;
}
let Some(scope) = self.scopes.pop() else {
return;
};
let Some(module) = scope.module else {
return;
};
match self.closed.iter_mut().find(|(closed, _)| *closed == module) {
Some((_, stored)) => *stored = scope.bindings,
None => self.closed.push((module, scope.bindings)),
}
}
#[must_use]
pub fn current_module(&self) -> Option<Id<ModuleNode>> {
self.scopes.iter().rev().find_map(|scope| scope.module)
}
pub fn bind(&mut self, name: &str, node: AnyId) {
if let Some(scope) = self.scopes.last_mut() {
scope.bindings.push((name.to_string(), node));
}
}
#[must_use]
pub fn is_bound_here(&self, name: &str) -> bool {
self.scopes.last().is_some_and(|scope| {
scope
.bindings
.iter()
.any(|(bound, _)| bound.eq_ignore_ascii_case(name))
})
}
#[must_use]
pub fn lookup_here(&self, name: &str) -> Option<AnyId> {
self.scopes.last().and_then(|scope| {
scope
.bindings
.iter()
.find(|(bound, _)| bound == name)
.map(|(_, node)| *node)
})
}
#[must_use]
pub fn lookup(&self, name: &str) -> Option<AnyId> {
self.scopes.iter().rev().find_map(|scope| {
scope
.bindings
.iter()
.find(|(bound, _)| bound == name)
.map(|(_, node)| *node)
})
}
#[must_use]
pub fn qualify(&self, name: &str) -> ScopedName {
let mut segments: Vec<String> = self
.scopes
.iter()
.filter(|scope| !scope.name.is_empty())
.map(|scope| scope.name.clone())
.collect();
segments.push(name.to_string());
ScopedName::absolute(segments)
}
#[must_use]
pub fn qualify_segments(&self, segments: &[String]) -> ScopedName {
let mut path: Vec<String> = self
.scopes
.iter()
.filter(|scope| !scope.name.is_empty())
.map(|scope| scope.name.clone())
.collect();
path.extend_from_slice(segments);
ScopedName::absolute(path)
}
#[must_use]
pub fn lookup_candidates(&self, segments: &[String]) -> Vec<ScopedName> {
let enclosing: Vec<String> = self
.scopes
.iter()
.filter(|scope| !scope.name.is_empty())
.map(|scope| scope.name.clone())
.collect();
let mut out = Vec::with_capacity(enclosing.len() + 1);
for depth in (0..=enclosing.len()).rev() {
let mut path = enclosing[..depth].to_vec();
path.extend_from_slice(segments);
out.push(ScopedName::absolute(path));
}
out
}
#[must_use]
pub fn innermost_name(&self) -> Option<&str> {
self.scopes
.last()
.map(|scope| scope.name.as_str())
.filter(|name| !name.is_empty())
}
#[must_use]
pub fn depth(&self) -> usize {
self.scopes.len()
}
}