use super::latin_1::Latin1String;
use parking_lot::RwLock;
use std::sync::Arc;
use self::fnv::FnvHashMap;
use fnv;
#[derive(Clone, Debug, Eq)]
pub struct Symbol {
pub(crate) id: usize,
name: Arc<Latin1String>,
}
impl Symbol {
fn new(id: usize, name: &Arc<Latin1String>) -> Symbol {
Symbol {
id,
name: Arc::clone(name),
}
}
pub fn name(self: &Self) -> &Latin1String {
self.name.as_ref()
}
pub fn name_utf8(self: &Self) -> String {
self.name.to_string()
}
}
impl PartialEq for Symbol {
fn eq(self: &Self, other: &Self) -> bool {
self.id == other.id
}
}
impl std::fmt::Display for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name_utf8())
}
}
impl std::hash::Hash for Symbol {
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
self.id.hash(hasher);
}
}
pub struct SymbolTable {
name_to_symbol: RwLock<FnvHashMap<Arc<Latin1String>, Symbol>>,
}
impl SymbolTable {
pub fn new() -> SymbolTable {
SymbolTable {
name_to_symbol: RwLock::new(FnvHashMap::default()),
}
}
pub fn insert_utf8(&self, name: &str) -> Symbol {
let name = Latin1String::from_utf8(name).unwrap();
self.insert(&name)
}
#[cfg(test)]
pub fn insert_extended_utf8(&self, name: &str) -> Symbol {
let name = Latin1String::from_utf8_unchecked(name);
self.insert_extended(&name)
}
pub fn lookup(&self, name: &Latin1String) -> Option<Symbol> {
let name_to_symbol = self.name_to_symbol.read();
if let Some(sym) = name_to_symbol.get(name) {
return Some(sym.clone());
} else {
return None;
}
}
pub fn insert(&self, name: &Latin1String) -> Symbol {
if let Some(symbol) = self.lookup(name) {
symbol
} else {
self.insert_new(name, false)
}
}
pub fn insert_extended(&self, name: &Latin1String) -> Symbol {
if let Some(symbol) = self.lookup(name) {
symbol
} else {
self.insert_new(name, true)
}
}
fn insert_new(&self, name: &Latin1String, is_extended: bool) -> Symbol {
let mut name_to_symbol = self.name_to_symbol.write();
if let Some(sym) = name_to_symbol.get(name) {
return sym.clone();
}
debug_assert_eq!(name.bytes.get(0) == Some(&b'\\'), is_extended);
let name = Arc::from(name.clone());
if is_extended {
let id = name_to_symbol.len();
let sym = Symbol::new(id, &name);
name_to_symbol.insert(name, sym.clone());
return sym;
}
let normal_name = Arc::from(name.to_lowercase());
match name_to_symbol.get(&normal_name).cloned() {
Some(normal_sym) => {
let id = normal_sym.id;
let sym = Symbol::new(id, &name);
name_to_symbol.insert(name, sym.clone());
sym
}
None => {
let id = name_to_symbol.len();
if normal_name != name {
let sym = Symbol::new(id, &normal_name);
name_to_symbol.insert(normal_name, sym);
}
let sym = Symbol::new(id, &name);
name_to_symbol.insert(name, sym.clone());
sym
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn symbol_table_insert() {
let symtab = SymbolTable::new();
let sym = symtab.insert_utf8("hello");
assert_eq!(sym.name_utf8(), "hello");
}
#[test]
fn symbols_are_equal() {
let symtab = SymbolTable::new();
let sym0 = symtab.insert_utf8("hello");
let sym1 = symtab.insert_utf8("hello");
assert_eq!(sym0, sym1);
assert_eq!(sym0.name_utf8(), "hello");
assert_eq!(sym1.name_utf8(), "hello");
let sym0 = symtab.insert_utf8("Hello");
let sym1 = symtab.insert_utf8("hello");
assert_eq!(sym0, sym1);
assert_eq!(sym0.name_utf8(), "Hello");
assert_eq!(sym1.name_utf8(), "hello");
}
#[test]
fn symbols_are_case_insensitive() {
let symtab = SymbolTable::new();
let sym0 = symtab.insert_utf8("Hello");
let sym1 = symtab.insert_utf8("hello");
let sym2 = symtab.insert_utf8("heLLo");
assert_eq!(sym0, sym1);
assert_eq!(sym0, sym2);
assert_eq!(sym1, sym2);
assert_eq!(sym0.name_utf8(), "Hello");
assert_eq!(sym1.name_utf8(), "hello");
assert_eq!(sym2.name_utf8(), "heLLo");
}
#[test]
fn extended_identifiers_symbols_are_case_sensitive() {
let symtab = SymbolTable::new();
let sym0 = symtab.insert_extended_utf8("\\hello\\");
let sym1 = symtab.insert_extended_utf8("\\HELLO\\");
let sym2 = symtab.insert_extended_utf8("\\hello\\");
assert_ne!(sym0, sym1);
assert_eq!(sym0, sym2);
assert_ne!(sym1, sym2);
assert_eq!(sym0.name_utf8(), "\\hello\\");
assert_eq!(sym1.name_utf8(), "\\HELLO\\");
assert_eq!(sym2.name_utf8(), "\\hello\\");
}
#[test]
fn symbols_are_not_equal() {
let symtab = SymbolTable::new();
let sym0 = symtab.insert_utf8("hello");
let sym1 = symtab.insert_utf8("abc");
assert_ne!(sym0, sym1);
}
}