use crate::error::Span;
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolKind {
PatternBound,
UnwindBound,
WithAlias,
ReturnAlias,
YieldAlias,
ForeachVar,
ComprehensionVar,
Reference,
}
#[derive(Debug, Clone)]
pub struct SymbolEntry {
pub kind: SymbolKind,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct ScopeStack {
scopes: Vec<HashMap<String, SymbolEntry>>,
barriers: Vec<usize>,
}
impl ScopeStack {
pub fn new() -> Self {
Self {
scopes: vec![HashMap::new()],
barriers: Vec::new(),
}
}
pub fn push_scope(&mut self) {
self.scopes.push(HashMap::new());
}
pub fn pop_scope(&mut self) {
debug_assert!(self.scopes.len() > 1, "attempted to pop the base scope");
if self.scopes.len() > 1 {
self.scopes.pop();
}
while let Some(&barrier) = self.barriers.last() {
if barrier >= self.scopes.len() {
self.barriers.pop();
} else {
break;
}
}
}
pub fn push_barrier(&mut self) {
self.barriers.push(self.scopes.len().saturating_sub(1));
}
pub fn pop_barrier(&mut self) {
self.barriers.pop();
}
pub fn bind(&mut self, name: &str, kind: SymbolKind, span: Span) -> Result<(), Span> {
let current = self.scopes.last_mut().unwrap();
if let Some(existing) = current.get(name) {
return Err(existing.span);
}
current.insert(name.to_string(), SymbolEntry { kind, span });
Ok(())
}
pub fn resolve(&self, name: &str) -> Option<(&SymbolEntry, usize)> {
let start = self.barriers.last().copied().unwrap_or(0);
for (depth, scope) in self.scopes.iter().enumerate().rev() {
if depth < start {
break;
}
if let Some(entry) = scope.get(name) {
return Some((entry, depth));
}
}
None
}
pub fn is_bound(&self, name: &str) -> bool {
self.resolve(name).is_some()
}
pub fn visible_bindings(&self) -> Vec<(String, SymbolEntry)> {
let start = self.barriers.last().copied().unwrap_or(0);
let mut seen = HashSet::new();
let mut bindings = Vec::new();
for (depth, scope) in self.scopes.iter().enumerate().rev() {
if depth < start {
break;
}
for (name, entry) in scope {
if seen.insert(name.clone()) {
bindings.push((name.clone(), entry.clone()));
}
}
}
bindings
}
pub fn bound_names(&self) -> Vec<String> {
let mut names = Vec::new();
for scope in &self.scopes {
for name in scope.keys() {
if !names.contains(name) {
names.push(name.clone());
}
}
}
names
}
}
impl Default for ScopeStack {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::{ScopeStack, SymbolKind};
use crate::error::Span;
#[test]
fn pop_scope_preserves_base_scope() {
let mut scopes = ScopeStack::new();
scopes.push_scope();
scopes.pop_scope();
assert!(
scopes
.bind("n", SymbolKind::PatternBound, Span::new(0, 1))
.is_ok()
);
assert!(scopes.is_bound("n"));
}
}