use leo_ast::{Composite, Expression, Function, Location, NodeBuilder, NodeID, Type};
use leo_errors::{AstError, Color, Label, LeoError, Result};
use leo_span::{Span, Symbol};
use indexmap::IndexMap;
use itertools::Itertools;
use std::{cell::RefCell, collections::HashMap, rc::Rc};
mod symbols;
pub use symbols::*;
#[derive(Debug, Default)]
pub struct SymbolTable {
functions: IndexMap<Location, FunctionSymbol>,
records: IndexMap<Location, Composite>,
structs: IndexMap<Vec<Symbol>, Composite>,
global_consts: IndexMap<Location, Expression>,
globals: IndexMap<Location, VariableSymbol>,
all_locals: HashMap<NodeID, LocalTable>,
local: Option<LocalTable>,
}
#[derive(Clone, Default, Debug)]
struct LocalTable {
inner: Rc<RefCell<LocalTableInner>>,
}
#[derive(Clone, Default, Debug)]
struct LocalTableInner {
id: NodeID,
parent: Option<NodeID>,
children: Vec<NodeID>,
consts: HashMap<Symbol, Expression>,
variables: HashMap<Symbol, VariableSymbol>,
}
impl LocalTable {
fn new(symbol_table: &mut SymbolTable, id: NodeID, parent: Option<NodeID>) -> Self {
if let Some(parent_id) = parent
&& let Some(parent_table) = symbol_table.all_locals.get_mut(&parent_id)
{
parent_table.inner.borrow_mut().children.push(id);
}
LocalTable {
inner: Rc::new(RefCell::new(LocalTableInner {
id,
parent,
consts: HashMap::new(),
variables: HashMap::new(),
children: vec![], })),
}
}
pub fn dup(
&self,
node_builder: &NodeBuilder,
all_locals: &mut HashMap<NodeID, LocalTable>,
new_parent: Option<NodeID>,
) -> LocalTable {
let inner = self.inner.borrow();
let new_id = node_builder.next_id();
let new_children: Vec<NodeID> = inner
.children
.iter()
.map(|child_id| {
let child_table = all_locals.get(child_id).expect("Child must exist").clone();
let duped_child = child_table.dup(node_builder, all_locals, Some(new_id));
let child_new_id = duped_child.inner.borrow().id;
all_locals.insert(child_new_id, duped_child);
child_new_id
})
.collect();
let new_table = LocalTable {
inner: Rc::new(RefCell::new(LocalTableInner {
id: new_id,
parent: new_parent,
children: new_children,
consts: inner.consts.clone(),
variables: inner.variables.clone(),
})),
};
all_locals.insert(new_id, new_table.clone());
new_table
}
}
impl SymbolTable {
pub fn reset_but_consts(&mut self) {
self.functions.clear();
self.records.clear();
self.structs.clear();
self.globals.clear();
self.all_locals.clear();
self.local = None;
}
pub fn global_scope(&self) -> bool {
self.local.is_none()
}
pub fn iter_structs(&self) -> impl Iterator<Item = (&Vec<Symbol>, &Composite)> {
self.structs.iter()
}
pub fn iter_records(&self) -> impl Iterator<Item = (&Location, &Composite)> {
self.records.iter()
}
pub fn iter_functions(&self) -> impl Iterator<Item = (&Location, &FunctionSymbol)> {
self.functions.iter()
}
pub fn lookup_struct(&self, path: &[Symbol]) -> Option<&Composite> {
self.structs.get(path)
}
pub fn lookup_record(&self, location: &Location) -> Option<&Composite> {
self.records.get(location)
}
pub fn lookup_function(&self, location: &Location) -> Option<&FunctionSymbol> {
self.functions.get(location)
}
pub fn lookup_path(&self, program: Symbol, path: &[Symbol]) -> Option<VariableSymbol> {
self.lookup_global(&Location::new(program, path.to_vec()))
.cloned()
.or_else(|| path.last().copied().and_then(|name| self.lookup_local(name)))
}
pub fn lookup_local(&self, name: Symbol) -> Option<VariableSymbol> {
let mut current = self.local.as_ref();
while let Some(table) = current {
let borrowed = table.inner.borrow();
let value = borrowed.variables.get(&name);
if value.is_some() {
return value.cloned();
}
current = borrowed.parent.and_then(|id| self.all_locals.get(&id));
}
None
}
pub fn enter_scope(&mut self, id: Option<NodeID>) {
self.local = id.map(|id| {
let parent = self.local.as_ref().map(|table| table.inner.borrow().id);
let new_local_table = if let Some(existing) = self.all_locals.get(&id) {
existing.clone()
} else {
let new_table = LocalTable::new(self, id, parent);
self.all_locals.insert(id, new_table.clone());
new_table
};
assert_eq!(parent, new_local_table.inner.borrow().parent, "Entered scopes out of order.");
new_local_table.clone()
});
}
pub fn enter_scope_duped(&mut self, old_id: NodeID, node_builder: &NodeBuilder) -> usize {
let old_local_table = self.all_locals.get(&old_id).expect("Must have an old scope to dup from.").clone();
let new_local_table =
old_local_table.dup(node_builder, &mut self.all_locals, self.local.as_ref().map(|t| t.inner.borrow().id));
let new_id = new_local_table.inner.borrow().id;
self.local = Some(new_local_table);
new_id
}
pub fn enter_parent(&mut self) {
let parent: Option<NodeID> = self.local.as_ref().and_then(|table| table.inner.borrow().parent);
self.local = parent.map(|id| self.all_locals.get(&id).expect("Parent should exist.")).cloned();
}
pub fn is_local_to(&self, scope: NodeID, symbol: Symbol) -> bool {
self.all_locals.get(&scope).map(|locals| locals.inner.borrow().variables.contains_key(&symbol)).unwrap_or(false)
}
pub fn is_defined_in_scope_or_ancestor_until(&self, scope: NodeID, symbol: Symbol) -> bool {
let mut current = self.local.as_ref();
while let Some(table) = current {
let inner = table.inner.borrow();
if inner.variables.contains_key(&symbol) {
return true;
}
if inner.id == scope {
break;
}
current = inner.parent.and_then(|parent_id| self.all_locals.get(&parent_id));
}
false
}
pub fn is_local_to_or_in_child_scope(&self, scope: NodeID, symbol: Symbol) -> bool {
let mut stack = vec![scope];
while let Some(current_id) = stack.pop() {
if let Some(table) = self.all_locals.get(¤t_id) {
let inner = table.inner.borrow();
if inner.variables.contains_key(&symbol) {
return true;
}
stack.extend(&inner.children);
}
}
false
}
pub fn insert_const(&mut self, program: Symbol, path: &[Symbol], value: Expression) {
if let Some(table) = self.local.as_mut() {
let [const_name] = &path else { panic!("Local consts cannot have paths with more than 1 segment.") };
table.inner.borrow_mut().consts.insert(*const_name, value);
} else {
self.global_consts.insert(Location::new(program, path.to_vec()), value);
}
}
pub fn lookup_const(&self, program: Symbol, path: &[Symbol]) -> Option<Expression> {
let mut current = self.local.as_ref();
while let Some(table) = current {
let borrowed = table.inner.borrow();
let value = borrowed.consts.get(path.last().expect("all paths must have at least 1 segment"));
if value.is_some() {
return value.cloned();
}
current = borrowed.parent.and_then(|id| self.all_locals.get(&id));
}
self.global_consts.get(&Location::new(program, path.to_vec())).cloned()
}
pub fn insert_struct(&mut self, program: Symbol, path: &[Symbol], composite: Composite) -> Result<()> {
if let Some(old_composite) = self.structs.get(path) {
if eq_struct(&composite, old_composite) {
Ok(())
} else {
Err(AstError::redefining_external_struct(path.iter().format("::"), old_composite.span).into())
}
} else {
let location = Location::new(program, path.to_vec());
self.check_shadow_global(&location, composite.identifier.span)?;
self.structs.insert(path.to_vec(), composite);
Ok(())
}
}
pub fn insert_record(&mut self, location: Location, composite: Composite) -> Result<()> {
self.check_shadow_global(&location, composite.identifier.span)?;
self.records.insert(location, composite);
Ok(())
}
pub fn insert_function(&mut self, location: Location, function: Function) -> Result<()> {
self.check_shadow_global(&location, function.identifier.span)?;
self.functions.insert(location, FunctionSymbol { function, finalizer: None });
Ok(())
}
pub fn insert_global(&mut self, location: Location, var: VariableSymbol) -> Result<()> {
self.check_shadow_global(&location, var.span)?;
self.globals.insert(location, var);
Ok(())
}
pub fn lookup_global(&self, location: &Location) -> Option<&VariableSymbol> {
self.globals.get(location)
}
pub fn emit_shadow_error(name: Symbol, span: Span, prev_span: Span) -> LeoError {
AstError::name_defined_multiple_times(name, span)
.with_labels(vec![
Label::new(format!("previous definition of `{name}` here"), prev_span).with_color(Color::Blue),
Label::new(format!("`{name}` redefined here"), span),
])
.into()
}
fn check_shadow_global(&self, location: &Location, span: Span) -> Result<()> {
let name = location.path.last().expect("location path must have at least one segment.");
self.functions
.get(location)
.map(|f| f.function.identifier.span)
.or_else(|| self.records.get(location).map(|r| r.identifier.span))
.or_else(|| self.structs.get(&location.path).map(|s| s.identifier.span))
.or_else(|| self.globals.get(location).map(|g| g.span))
.map_or_else(|| Ok(()), |prev_span| Err(Self::emit_shadow_error(*name, span, prev_span)))
}
fn check_shadow_variable(&self, program: Symbol, path: &[Symbol], span: Span) -> Result<()> {
let mut current = self.local.as_ref();
while let Some(table) = current {
if let [name] = &path
&& let Some(var_symbol) = table.inner.borrow().variables.get(name)
{
return Err(Self::emit_shadow_error(*name, span, var_symbol.span));
}
current = table.inner.borrow().parent.map(|id| self.all_locals.get(&id).expect("Parent should exist."));
}
self.check_shadow_global(&Location::new(program, path.to_vec()), span)?;
Ok(())
}
pub fn insert_variable(&mut self, program: Symbol, path: &[Symbol], var: VariableSymbol) -> Result<()> {
self.check_shadow_variable(program, path, var.span)?;
if let Some(table) = self.local.as_mut() {
let [name] = &path else { panic!("Local variables cannot have paths with more than 1 segment.") };
table.inner.borrow_mut().variables.insert(*name, var);
} else {
self.globals.insert(Location::new(program, path.to_vec()), var);
}
Ok(())
}
pub fn attach_finalizer(
&mut self,
caller: Location,
callee: Location,
future_inputs: Vec<Location>,
inferred_inputs: Vec<Type>,
) -> Result<()> {
let callee_location = Location::new(callee.program, callee.path);
if let Some(func) = self.functions.get_mut(&caller) {
func.finalizer = Some(Finalizer { location: callee_location, future_inputs, inferred_inputs });
Ok(())
} else {
Err(AstError::function_not_found(caller.path.iter().format("::")).into())
}
}
}
fn eq_struct(new: &Composite, old: &Composite) -> bool {
if new.members.len() != old.members.len() {
return false;
}
new.members
.iter()
.zip(old.members.iter())
.all(|(member1, member2)| member1.name() == member2.name() && member1.type_.eq_flat_relaxed(&member2.type_))
}