use std::collections::HashMap;
use crate::intern::Symbol;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Binding<V> {
depth: u32,
value: V,
}
#[derive(Debug)]
pub struct ScopeMap<V> {
bindings: HashMap<Symbol, Vec<Binding<V>>>,
log: Vec<Symbol>,
marks: Vec<usize>,
}
impl<V> Default for ScopeMap<V> {
fn default() -> Self {
ScopeMap { bindings: HashMap::new(), log: Vec::new(), marks: Vec::new() }
}
}
impl<V: Copy> ScopeMap<V> {
#[must_use]
pub fn new() -> Self {
ScopeMap::default()
}
#[inline]
#[must_use]
pub fn depth(&self) -> u32 {
self.marks.len() as u32 + 1
}
#[inline]
#[must_use]
pub fn at_file_scope(&self) -> bool {
self.marks.is_empty()
}
pub fn push(&mut self) {
self.marks.push(self.log.len());
}
pub fn pop(&mut self) {
let mark = self.marks.pop().expect("the file scope is never closed");
while self.log.len() > mark {
let name = self.log.pop().expect("the log is longer than the mark");
if let Some(stack) = self.bindings.get_mut(&name) {
stack.pop();
}
}
}
pub fn declare(&mut self, name: Symbol, value: V) -> Option<V> {
let depth = self.depth();
let stack = self.bindings.entry(name).or_default();
match stack.last_mut() {
Some(top) if top.depth == depth => {
let was = top.value;
top.value = value;
Some(was)
}
_ => {
stack.push(Binding { depth, value });
self.log.push(name);
None
}
}
}
pub fn declare_at_file_scope(&mut self, name: Symbol, value: V) -> bool {
let stack = self.bindings.entry(name).or_default();
if !stack.is_empty() {
return false;
}
stack.push(Binding { depth: 1, value });
true
}
#[must_use]
pub fn get(&self, name: Symbol) -> Option<V> {
Some(self.bindings.get(&name)?.last()?.value)
}
#[must_use]
pub fn get_here(&self, name: Symbol) -> Option<V> {
let depth = self.depth();
let top = self.bindings.get(&name)?.last()?;
(top.depth == depth).then_some(top.value)
}
}
#[cfg(test)]
mod tests {
use super::*;
const X: Symbol = Symbol::from_raw(1);
#[test]
fn only_the_innermost_scope_counts_as_here() {
let mut names = ScopeMap::new();
names.declare(X, 1);
names.push();
assert_eq!(names.get(X), Some(1));
assert_eq!(names.get_here(X), None);
assert_eq!(names.depth(), 2);
names.pop();
assert_eq!(names.get_here(X), Some(1));
}
#[test]
fn an_inner_binding_hides_an_outer_one_and_gives_it_back() {
let mut names = ScopeMap::new();
names.declare(X, 1);
names.push();
assert_eq!(names.declare(X, 2), None);
assert_eq!(names.get(X), Some(2));
names.pop();
assert_eq!(names.get(X), Some(1));
}
#[test]
fn a_second_binding_in_one_scope_is_a_redeclaration_and_says_what_it_was() {
let mut names = ScopeMap::new();
assert_eq!(names.declare(X, 1), None);
assert_eq!(names.declare(X, 2), Some(1));
assert_eq!(names.get(X), Some(2));
}
#[test]
fn a_file_scope_binding_made_from_inside_outlives_the_block_it_was_made_in() {
let mut names = ScopeMap::new();
names.push();
names.push();
assert!(names.declare_at_file_scope(X, 1));
assert_eq!(names.get(X), Some(1));
names.pop();
names.pop();
assert!(names.at_file_scope());
assert_eq!(names.get(X), Some(1), "the block it was used in is not where it was bound");
assert_eq!(names.get_here(X), Some(1));
}
#[test]
fn a_name_that_already_means_something_is_left_meaning_it() {
let mut names = ScopeMap::new();
names.push();
names.declare(X, 1);
assert!(!names.declare_at_file_scope(X, 2));
assert_eq!(names.get(X), Some(1));
names.pop();
assert_eq!(names.get(X), None);
}
#[test]
fn closing_a_scope_leaves_nothing_behind() {
let mut names = ScopeMap::new();
assert!(names.at_file_scope());
for depth in 0..64 {
names.push();
names.declare(X, depth);
}
assert_eq!(names.depth(), 65);
for _ in 0..64 {
names.pop();
}
assert!(names.at_file_scope());
assert_eq!(names.get(X), None);
}
#[test]
#[should_panic(expected = "the file scope is never closed")]
fn the_outermost_scope_cannot_be_closed() {
ScopeMap::<u32>::new().pop();
}
}