pub mod function_symbol;
pub use function_symbol::*;
pub mod variable_symbol;
pub use variable_symbol::*;
use std::cell::RefCell;
use leo_ast::{Composite, Function, Location, normalize_json_value, remove_key_from_json};
use leo_errors::{AstError, Result};
use leo_span::{Span, Symbol};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json;
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct SymbolTable {
pub(crate) parent: Option<Box<SymbolTable>>,
pub functions: IndexMap<Location, FunctionSymbol>,
pub structs: IndexMap<Location, Composite>,
pub(crate) variables: IndexMap<Location, VariableSymbol>,
pub(crate) scope_index: usize,
pub(crate) scopes: Vec<RefCell<SymbolTable>>,
}
impl SymbolTable {
pub fn check_shadowing(&self, location: &Location, is_struct: bool, span: Span) -> Result<()> {
if self.functions.contains_key(location) {
return Err(AstError::shadowed_function(location.name, span).into());
} else if self.structs.get(location).is_some() && !(location.program.is_none() && is_struct) {
return Err(AstError::shadowed_record(location.name, span).into());
} else if self.structs.get(&Location::new(None, location.name)).is_some() && !is_struct {
return Err(AstError::shadowed_struct(location.name, span).into());
} else if self.variables.contains_key(location) {
return Err(AstError::shadowed_variable(location.name, span).into());
}
if let Some(parent) = self.parent.as_ref() { parent.check_shadowing(location, is_struct, span) } else { Ok(()) }
}
pub fn scope_index(&mut self) -> usize {
let index = self.scope_index;
self.scope_index += 1;
index
}
pub fn insert_fn(&mut self, location: Location, insert: &Function) -> Result<()> {
let id = self.scope_index();
self.check_shadowing(&location, false, insert.span)?;
self.functions.insert(location, Self::new_function_symbol(id, insert));
self.scopes.push(Default::default());
Ok(())
}
pub fn insert_struct(&mut self, location: Location, insert: &Composite) -> Result<()> {
self.check_shadowing(&location, !insert.is_record, insert.span)?;
if insert.is_record {
self.structs.insert(location, insert.clone());
} else {
if let Some(struct_) = self.structs.get(&Location::new(None, location.name)) {
if !self.check_eq_struct(insert, struct_) {
return Err(AstError::redefining_external_struct(location.name, insert.span).into());
}
}
self.structs.insert(Location::new(None, location.name), insert.clone());
}
Ok(())
}
fn check_eq_struct(&self, new: &Composite, old: &Composite) -> bool {
if new.is_record || old.is_record {
return false;
}
if new.members.len() != old.members.len() {
return false;
}
for (member1, member2) in new.members.iter().zip(old.members.iter()) {
if member1.name() != member2.name() || !member1.type_.eq_flat_relaxed(&member2.type_) {
return false;
}
}
true
}
pub fn attach_finalize(&mut self, caller: Location, callee: Location) -> Result<()> {
if let Some(func) = self.functions.get_mut(&caller) {
func.finalize = Some(callee);
Ok(())
} else if let Some(parent) = self.parent.as_mut() {
parent.attach_finalize(caller, callee)
} else {
Err(AstError::function_not_found(caller.name).into())
}
}
pub fn insert_variable(&mut self, location: Location, insert: VariableSymbol) -> Result<()> {
self.check_shadowing(&location, false, insert.span)?;
self.variables.insert(location, insert);
Ok(())
}
pub fn insert_futures(&mut self, program: Symbol, function: Symbol, futures: Vec<Location>) -> Result<()> {
if let Some(func) = self.functions.get_mut(&Location::new(Some(program), function)) {
func.future_inputs = futures;
Ok(())
} else if let Some(parent) = self.parent.as_mut() {
parent.insert_futures(program, function, futures)
} else {
Err(AstError::function_not_found(function).into())
}
}
pub fn remove_variable_from_current_scope(&mut self, location: Location) {
self.variables.remove(&location);
}
pub fn insert_block(&mut self) -> usize {
self.scopes.push(RefCell::new(Default::default()));
self.scope_index()
}
pub fn lookup_fn_symbol(&self, location: Location) -> Option<&FunctionSymbol> {
if let Some(func) = self.functions.get(&location) {
Some(func)
} else if let Some(parent) = self.parent.as_ref() {
parent.lookup_fn_symbol(location)
} else {
None
}
}
pub fn lookup_struct(&self, location: Location, main_program: Option<Symbol>) -> Option<&Composite> {
if let Some(struct_) = self.structs.get(&location) {
return Some(struct_);
} else if location.program == main_program {
if let Some(struct_) = self.structs.get(&Location::new(None, location.name)) {
return Some(struct_);
}
}
if let Some(parent) = self.parent.as_ref() { parent.lookup_struct(location, main_program) } else { None }
}
pub fn lookup_variable(&self, location: Location) -> Option<&VariableSymbol> {
if let Some(var) = self.variables.get(&location) {
Some(var)
} else if let Some(parent) = self.parent.as_ref() {
parent.lookup_variable(location)
} else {
None
}
}
pub fn lookup_variable_in_current_scope(&self, location: Location) -> Option<&VariableSymbol> {
self.variables.get(&location)
}
pub fn lookup_scope_by_index(&self, index: usize) -> Option<&RefCell<Self>> {
self.scopes.get(index)
}
pub fn to_json_string(&self) -> Result<String> {
Ok(serde_json::to_string_pretty(&self)
.map_err(|e| AstError::failed_to_convert_symbol_table_to_json_string(&e))?)
}
pub fn to_json_value(&self) -> Result<serde_json::Value> {
Ok(serde_json::to_value(self).map_err(|e| AstError::failed_to_convert_symbol_table_to_json_value(&e))?)
}
pub fn to_json_file(&self, mut path: std::path::PathBuf, file_name: &str) -> Result<()> {
path.push(file_name);
let file =
std::fs::File::create(&path).map_err(|e| AstError::failed_to_create_symbol_table_json_file(&path, &e))?;
let writer = std::io::BufWriter::new(file);
Ok(serde_json::to_writer_pretty(writer, &self)
.map_err(|e| AstError::failed_to_write_symbol_table_to_json_file(&path, &e))?)
}
pub fn to_json_file_without_keys(
&self,
mut path: std::path::PathBuf,
file_name: &str,
excluded_keys: &[&str],
) -> Result<()> {
path.push(file_name);
let file =
std::fs::File::create(&path).map_err(|e| AstError::failed_to_create_symbol_table_json_file(&path, &e))?;
let writer = std::io::BufWriter::new(file);
let mut value = self.to_json_value().unwrap();
for key in excluded_keys {
value = remove_key_from_json(value, key);
}
value = normalize_json_value(value);
Ok(serde_json::to_writer_pretty(writer, &value)
.map_err(|e| AstError::failed_to_write_symbol_table_to_json_file(&path, &e))?)
}
pub fn from_json_string(json: &str) -> Result<Self> {
let symbol_table: SymbolTable =
serde_json::from_str(json).map_err(|e| AstError::failed_to_read_json_string_to_symbol_table(&e))?;
Ok(symbol_table)
}
pub fn from_json_file(path: std::path::PathBuf) -> Result<Self> {
let data = std::fs::read_to_string(&path).map_err(|e| AstError::failed_to_read_json_file(&path, &e))?;
Self::from_json_string(&data)
}
}
#[cfg(test)]
mod tests {
use super::*;
use leo_ast::{Identifier, Type, Variant};
use leo_span::{Symbol, symbol::create_session_if_not_set_then};
#[test]
fn serialization_test() {
create_session_if_not_set_then(|_| {
let mut symbol_table = SymbolTable::default();
let func_loc = Location::new(Some(Symbol::intern("credits")), Symbol::intern("transfer_public"));
let insert = Function {
annotations: Vec::new(),
id: 0,
output_type: Type::Address,
variant: Variant::Inline,
span: Default::default(),
input: Vec::new(),
identifier: Identifier::new(Symbol::intern("transfer_public"), Default::default()),
output: vec![],
block: Default::default(),
};
symbol_table.insert_fn(func_loc, &insert).unwrap();
symbol_table
.insert_variable(
Location::new(Some(Symbol::intern("credits")), Symbol::intern("accounts")),
VariableSymbol { type_: Type::Address, span: Default::default(), declaration: VariableType::Const },
)
.unwrap();
symbol_table
.insert_struct(Location::new(Some(Symbol::intern("credits")), Symbol::intern("token")), &Composite {
is_record: false,
span: Default::default(),
id: 0,
identifier: Identifier::new(Symbol::intern("token"), Default::default()),
members: Vec::new(),
external: None,
})
.unwrap();
symbol_table
.insert_variable(Location::new(None, Symbol::intern("foo")), VariableSymbol {
type_: Type::Address,
span: Default::default(),
declaration: VariableType::Const,
})
.unwrap();
let json = symbol_table.to_json_string().unwrap();
let deserialized = SymbolTable::from_json_string(&json).unwrap();
assert_eq!(symbol_table, deserialized);
});
}
}