use phf::phf_map;
use std::collections::HashMap;
use thiserror::Error;
use crate::hack_int::HackInt;
#[derive(Error, Debug)]
pub enum SymbolTableGetError {
#[error("symbol \"{0}\" not defined")]
NotDefined(String),
}
#[derive(Error, Debug)]
pub enum SymbolTableSetError {
#[error("tried to redefine the built in symbol \"{0}\"")]
RedefinedBuiltIn(String),
#[error("tried to redefine the symbol \"{0}\"")]
Redefined(String),
}
static BUILT_IN: phf::Map<&'static str, HackInt> = phf_map! {
"R0" => HackInt::new_unchecked(0),
"R1" => HackInt::new_unchecked(1),
"R2" => HackInt::new_unchecked(2),
"R3" => HackInt::new_unchecked(3),
"R4" => HackInt::new_unchecked(4),
"R5" => HackInt::new_unchecked(5),
"R6" => HackInt::new_unchecked(6),
"R7" => HackInt::new_unchecked(7),
"R8" => HackInt::new_unchecked(8),
"R9" => HackInt::new_unchecked(9),
"R10" => HackInt::new_unchecked(10),
"R11" => HackInt::new_unchecked(11),
"R12" => HackInt::new_unchecked(12),
"R13" => HackInt::new_unchecked(13),
"R14" => HackInt::new_unchecked(14),
"R15" => HackInt::new_unchecked(15),
"SCREEN" => HackInt::new_unchecked(16384),
"KBD" => HackInt::new_unchecked(24576),
"SP" => HackInt::new_unchecked(0),
"LCL" => HackInt::new_unchecked(1),
"ARG" => HackInt::new_unchecked(2),
"THIS" => HackInt::new_unchecked(3),
"THAT" => HackInt::new_unchecked(4),
};
pub struct SymbolTable {
table: HashMap<String, HackInt>,
}
impl SymbolTable {
pub fn new() -> Self {
Self {
table: HashMap::default(),
}
}
pub fn set(&mut self, name: &str, value: HackInt) -> Result<(), SymbolTableSetError> {
if BUILT_IN.get(name).is_some() {
return Err(SymbolTableSetError::RedefinedBuiltIn(name.to_string()));
}
let entry = self.table.raw_entry_mut().from_key(name);
match entry {
std::collections::hash_map::RawEntryMut::Occupied(_) => {
Err(SymbolTableSetError::Redefined(name.to_string()))
}
std::collections::hash_map::RawEntryMut::Vacant(entry) => {
entry.insert(name.to_string(), value);
Ok(())
}
}
}
pub fn get(&self, name: &str) -> Result<HackInt, SymbolTableGetError> {
if let Some(&built_in) = BUILT_IN.get(name) {
return Ok(built_in);
}
if let Some(&user_defined) = self.table.get(name) {
return Ok(user_defined);
}
Err(SymbolTableGetError::NotDefined(name.to_string()))
}
}
impl Default for SymbolTable {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_redefine_built_in() {
let mut table = SymbolTable::new();
let error = table.set("R1", HackInt::new_unchecked(42)).unwrap_err();
match error {
SymbolTableSetError::RedefinedBuiltIn(_) => (),
_ => panic!("expected RedefinedBuiltIn error"),
}
}
#[test]
fn test_error_redefine_user_defined() {
let mut table = SymbolTable::new();
table.set("some_var", HackInt::new_unchecked(42)).unwrap();
let error = table
.set("some_var", HackInt::new_unchecked(42))
.unwrap_err();
match error {
SymbolTableSetError::Redefined(_) => (),
_ => panic!("expected RedefinedBuiltIn error"),
}
}
}