use indexmap::IndexSet;
use rustc_hash::FxHasher;
use std::hash::BuildHasherDefault;
use thiserror::Error;
use crate::v0::table::{NodeId, VarId};
type FxIndexSet<K> = IndexSet<K, BuildHasherDefault<FxHasher>>;
#[derive(Debug, Clone, Default)]
pub struct VarTable<'a> {
vars: FxIndexSet<(NodeId, &'a str)>,
scopes: Vec<VarScope>,
}
impl<'a> VarTable<'a> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn enter(&mut self, node: NodeId) {
self.scopes.push(VarScope {
node,
var_count: 0,
var_stack: self.vars.len(),
});
}
pub fn exit(&mut self) {
let scope = self.scopes.pop().unwrap();
self.vars.drain(scope.var_stack..);
}
pub fn resolve(&self, name: &'a str) -> Result<VarId, UnknownVarError<'a>> {
let scope = self.scopes.last().ok_or(UnknownVarError::Root(name))?;
let set_index = self
.vars
.get_index_of(&(scope.node, name))
.ok_or(UnknownVarError::WithinNode(scope.node, name))?;
let var_index = (set_index - scope.var_stack) as u16;
Ok(VarId(scope.node, var_index))
}
#[must_use]
pub fn is_visible(&self, var: VarId) -> bool {
let Some(scope) = self.scopes.last() else {
return false;
};
scope.node == var.0 && var.1 < scope.var_count
}
pub fn insert(&mut self, name: &'a str) -> Result<VarId, DuplicateVarError<'a>> {
let scope = self.scopes.last_mut().unwrap();
let inserted = self.vars.insert((scope.node, name));
if !inserted {
return Err(DuplicateVarError(scope.node, name));
}
let var_index = scope.var_count;
scope.var_count += 1;
Ok(VarId(scope.node, var_index))
}
pub fn clear(&mut self) {
self.vars.clear();
self.scopes.clear();
}
}
#[derive(Debug, Clone)]
struct VarScope {
node: NodeId,
var_count: u16,
var_stack: usize,
}
#[derive(Debug, Clone, Error)]
#[error("node {0} already has a variable named `{1}`")]
pub struct DuplicateVarError<'a>(NodeId, &'a str);
#[derive(Debug, Clone, Error)]
pub enum UnknownVarError<'a> {
#[error("can not resolve variable `{1}` in node {0}")]
WithinNode(NodeId, &'a str),
#[error("can not resolve variable `{0}` in the root scope")]
Root(&'a str),
}