use std::collections::HashMap;
use crate::error::{AssemblerError, SourceLocation};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Symbol {
name: String,
address: u32,
defined_at_line: Option<usize>,
}
impl Symbol {
pub fn new(name: String, address: u32, defined_at_line: Option<usize>) -> Self {
Symbol {
name,
address,
defined_at_line,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn address(&self) -> u32 {
self.address
}
pub fn defined_at_line(&self) -> Option<usize> {
self.defined_at_line
}
}
#[derive(Debug, Default)]
pub struct SymbolTable {
table: HashMap<String, Symbol>,
unresolved: HashMap<String, Vec<usize>>, }
impl SymbolTable {
pub fn new() -> Self {
SymbolTable::default()
}
pub fn define(
&mut self,
name: String,
address: u32,
line_number: Option<usize>,
) -> Result<(), AssemblerError> {
if self.table.contains_key(&name) {
Err(AssemblerError::SymbolError {
message: format!("Symbol '{}' already defined", name),
loc: SourceLocation {
line: line_number.unwrap_or(0),
col: 0,
},
})
} else {
let symbol = Symbol::new(name.clone(), address, line_number);
self.table.insert(name, symbol);
Ok(())
}
}
pub fn lookup(&self, name: &str) -> Option<&Symbol> {
self.table.get(name)
}
pub fn is_defined(&self, name: &str) -> bool {
self.table.contains_key(name)
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &Symbol)> {
self.table.iter()
}
pub fn len(&self) -> usize {
self.table.len()
}
pub fn is_empty(&self) -> bool {
self.table.is_empty()
}
pub fn add_unresolved_reference(&mut self, name: &str, line_number: usize) {
self.unresolved
.entry(name.to_string())
.or_default()
.push(line_number);
}
pub fn check_unresolved(&self) -> Vec<(&String, &Vec<usize>)> {
self.unresolved
.iter()
.filter(|(name, _)| !self.table.contains_key(*name))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_symbol_table() {
let st = SymbolTable::new();
assert!(st.is_empty());
assert_eq!(st.len(), 0);
}
#[test]
fn test_define_and_lookup() {
let mut st = SymbolTable::new();
let name = "loop_start".to_string();
let address = 0x1000;
assert!(st.define(name.clone(), address, Some(5)).is_ok());
assert_eq!(st.len(), 1);
assert!(st.is_defined(&name));
let symbol = st.lookup(&name);
assert!(symbol.is_some());
let symbol = symbol.unwrap();
assert_eq!(symbol.name(), name);
assert_eq!(symbol.address(), address);
assert_eq!(symbol.defined_at_line(), Some(5));
}
#[test]
fn test_lookup_undefined() {
let st = SymbolTable::new();
assert!(st.lookup("undefined_label").is_none());
assert!(!st.is_defined("undefined_label"));
}
#[test]
fn test_redefinition_error() {
let mut st = SymbolTable::new();
let name = "label1".to_string();
assert!(st.define(name.clone(), 0x200, Some(1)).is_ok());
let result = st.define(name.clone(), 0x300, Some(10));
assert!(result.is_err());
assert_eq!(
result.err().unwrap(),
AssemblerError::SymbolError {
message: format!("Symbol '{}' already defined", name),
loc: SourceLocation { line: 10, col: 0 }
}
);
assert_eq!(st.len(), 1);
let symbol = st.lookup("label1").unwrap();
assert_eq!(symbol.address(), 0x200);
assert_eq!(symbol.defined_at_line(), Some(1));
}
#[test]
fn test_iter() {
let mut st = SymbolTable::new();
st.define("lab_a".to_string(), 10, Some(1)).unwrap();
st.define("lab_b".to_string(), 20, Some(2)).unwrap();
let mut count = 0;
let mut found_a = false;
let mut found_b = false;
for (name, symbol) in st.iter() {
count += 1;
if name == "lab_a" {
assert_eq!(symbol.address(), 10);
found_a = true;
} else if name == "lab_b" {
assert_eq!(symbol.address(), 20);
found_b = true;
}
}
assert_eq!(count, 2);
assert!(found_a);
assert!(found_b);
}
}