hack_asm 1.0.1

A Nand2Tetris Hack Assembly Language Assembler written in Rust
Documentation
use thiserror::Error;

use crate::{
    hack_int::HackInt,
    instructions::Label,
    parsing::{ParseError, ParsedInstruction},
    symbol_table::{SymbolTable, SymbolTableGetError, SymbolTableSetError},
};

pub struct AssemblerContext {
    symbol_table: SymbolTable,
    current_variable_address: HackInt,
    current_label_address: HackInt,
    output: Vec<u16>,
}

#[derive(Error, Debug)]
pub enum AssemblerError {
    #[error("exceeded maximum number of variables")]
    TooManyVariables,
    #[error("exceeded maximum number of instructions")]
    TooManyInstructions,
    #[error(transparent)]
    SymbolTableSetError(#[from] SymbolTableSetError),
    #[error(transparent)]
    ParseError(#[from] ParseError),
}

impl AssemblerContext {
    fn set_symbol(&mut self, name: &str, value: HackInt) -> Result<(), SymbolTableSetError> {
        self.symbol_table.set(name, value)?;
        Ok(())
    }

    fn get_symbol(&self, name: &str) -> Result<HackInt, SymbolTableGetError> {
        self.symbol_table.get(name)
    }

    pub fn register_label(&mut self, label: Label, address: usize) -> Result<(), AssemblerError> {
        let address = HackInt::new_unchecked(address as u16);
        self.symbol_table.set(label.name, address)?;

        Ok(())
    }

    fn push_instruction(&mut self, bits: u16) -> Result<(), AssemblerError> {
        if self.output.len() >= crate::constants::ROM_SIZE {
            return Err(AssemblerError::TooManyInstructions);
        }

        self.output.push(bits);
        self.current_label_address.inc_unchecked();

        Ok(())
    }

    pub fn feed_instruction(&mut self, instr: ParsedInstruction) -> Result<(), AssemblerError> {
        match instr {
            ParsedInstruction::AInstruction(i) => {
                let bits = i.to_u16(self)?;
                self.push_instruction(bits)
            }
            ParsedInstruction::CInstruction(i) => self.push_instruction(i.to_u16()),
        }
    }

    pub fn get_or_create_variable(&mut self, name: &str) -> Result<u16, AssemblerError> {
        if let Ok(value) = self.get_symbol(name) {
            return Ok(value.into());
        }

        if self.current_variable_address >= crate::constants::MEMORY_SIZE {
            return Err(AssemblerError::TooManyVariables);
        }

        self.set_symbol(name, self.current_variable_address)?;
        let result = self.current_variable_address.into();
        self.current_variable_address.inc_unchecked();

        Ok(result)
    }

    pub fn into_output(self) -> Vec<u16> {
        self.output
    }
}

impl Default for AssemblerContext {
    fn default() -> Self {
        Self {
            symbol_table: SymbolTable::new(),
            current_variable_address: HackInt::new_unchecked(16),
            current_label_address: HackInt::new_unchecked(0),
            output: Vec::new(),
        }
    }
}