use vize_carton::{bitflags, FxHashMap, String};
use vize_relief::BindingType;
use crate::{ScopeBinding, ScopeId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SymbolId(u32);
impl SymbolId {
#[inline]
pub const fn new(id: u32) -> Self {
Self(id)
}
#[inline]
pub const fn as_u32(self) -> u32 {
self.0
}
}
#[derive(Debug)]
pub struct Symbol {
pub id: SymbolId,
pub name: String,
pub binding_type: BindingType,
pub scope_id: ScopeId,
pub declaration_offset: u32,
pub references: Vec<u32>,
pub flags: SymbolFlags,
}
impl Symbol {
pub fn new(
id: SymbolId,
name: String,
binding_type: BindingType,
scope_id: ScopeId,
declaration_offset: u32,
) -> Self {
Self {
id,
name,
binding_type,
scope_id,
declaration_offset,
references: Vec::new(),
flags: SymbolFlags::empty(),
}
}
pub fn add_reference(&mut self, offset: u32) {
self.references.push(offset);
}
pub fn is_used(&self) -> bool {
!self.references.is_empty()
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SymbolFlags: u8 {
const EXPORTED = 1 << 0;
const IMPORTED = 1 << 1;
const MUTATED = 1 << 2;
const COMPONENT = 1 << 3;
const DIRECTIVE = 1 << 4;
const FROM_PROPS = 1 << 5;
const FROM_MODEL = 1 << 6;
}
}
#[derive(Debug, Default)]
pub struct SymbolTable {
symbols: Vec<Symbol>,
name_to_id: FxHashMap<String, SymbolId>,
}
impl SymbolTable {
pub fn new() -> Self {
Self::default()
}
pub fn add_symbol(
&mut self,
name: String,
binding_type: BindingType,
scope_id: ScopeId,
declaration_offset: u32,
) -> SymbolId {
let id = SymbolId::new(self.symbols.len() as u32);
let symbol = Symbol::new(id, name.clone(), binding_type, scope_id, declaration_offset);
self.symbols.push(symbol);
self.name_to_id.insert(name, id);
id
}
pub fn get(&self, id: SymbolId) -> Option<&Symbol> {
self.symbols.get(id.as_u32() as usize)
}
pub fn get_mut(&mut self, id: SymbolId) -> Option<&mut Symbol> {
self.symbols.get_mut(id.as_u32() as usize)
}
pub fn lookup(&self, name: &str) -> Option<&Symbol> {
self.name_to_id.get(name).and_then(|id| self.get(*id))
}
pub fn lookup_id(&self, name: &str) -> Option<SymbolId> {
self.name_to_id.get(name).copied()
}
pub fn add_reference(&mut self, name: &str, offset: u32) -> bool {
if let Some(&id) = self.name_to_id.get(name) {
if let Some(symbol) = self.symbols.get_mut(id.as_u32() as usize) {
symbol.add_reference(offset);
return true;
}
}
false
}
pub fn iter(&self) -> impl Iterator<Item = &Symbol> {
self.symbols.iter()
}
pub fn unused_symbols(&self) -> impl Iterator<Item = &Symbol> {
self.symbols.iter().filter(|s| !s.is_used())
}
pub fn len(&self) -> usize {
self.symbols.len()
}
pub fn is_empty(&self) -> bool {
self.symbols.is_empty()
}
}
impl From<&ScopeBinding> for SymbolFlags {
fn from(binding: &ScopeBinding) -> Self {
let mut flags = SymbolFlags::empty();
if binding.is_mutated() {
flags |= SymbolFlags::MUTATED;
}
if matches!(
binding.binding_type,
BindingType::Props | BindingType::PropsAliased
) {
flags |= SymbolFlags::FROM_PROPS;
}
flags
}
}
#[cfg(test)]
mod tests {
use super::SymbolTable;
use crate::ScopeId;
use vize_carton::ToCompactString;
use vize_relief::BindingType;
#[test]
fn test_symbol_table() {
let mut table = SymbolTable::new();
let id = table.add_symbol(
"count".to_compact_string(),
BindingType::SetupRef,
ScopeId::ROOT,
0,
);
assert!(table.lookup("count").is_some());
assert!(table.lookup("unknown").is_none());
table.add_reference("count", 50);
table.add_reference("count", 100);
let symbol = table.get(id).unwrap();
assert_eq!(symbol.references.len(), 2);
assert!(symbol.is_used());
}
#[test]
fn test_unused_symbols() {
let mut table = SymbolTable::new();
table.add_symbol(
"used".to_compact_string(),
BindingType::SetupRef,
ScopeId::ROOT,
0,
);
table.add_symbol(
"unused".to_compact_string(),
BindingType::SetupRef,
ScopeId::ROOT,
10,
);
table.add_reference("used", 50);
let unused: Vec<_> = table.unused_symbols().collect();
assert_eq!(unused.len(), 1);
assert_eq!(unused[0].name, "unused");
}
}