From d51a392c1e9218e4e37d59ec7cbde9513e305ede Mon Sep 17 00:00:00 2001 From: Jonas Date: Wed, 18 Feb 2026 14:58:31 +0100 Subject: [PATCH] feat: class clean up. mneumonics for diff commands. comp, jmp, dest. symbol table start. --- .../assembler/src/assembler/assembler.py | 98 ++++++++++++++----- .../assembler/src/assembler/cli.py | 7 +- .../assembler/src/assembler/code.py | 59 +++++++++++ .../assembler/src/assembler/code_tables.py | 52 ++++++++++ .../assembler/src/assembler/parser.py | 4 +- .../assembler/src/assembler/symbol_table.py | 46 +++++++++ 6 files changed, 239 insertions(+), 27 deletions(-) create mode 100644 project-6-assembler/assembler/src/assembler/code_tables.py diff --git a/project-6-assembler/assembler/src/assembler/assembler.py b/project-6-assembler/assembler/src/assembler/assembler.py index 190af97..5bfb52b 100644 --- a/project-6-assembler/assembler/src/assembler/assembler.py +++ b/project-6-assembler/assembler/src/assembler/assembler.py @@ -1,32 +1,86 @@ from pathlib import Path -from .parser import Parser +from assembler.parser import Parser + +from .symbol_table import SymbolTable -def assemble(input_file: Path, output_file: Path): +class Assembler: """ - Assemble a Hack assembly file into machine code. + Main assembler that coordinates the two-pass assembly process. - Args: - input_file: Path to the .asm file - output_file: Path where the .hack file will be written + Attributes: + input_file: Path to the .asm input file + output_file: Path to the .hack output file + parser: Parser instance for reading commands + symbol_table: SymbolTable instance for managing symbols + binary_instructions: List of translated binary instructions """ - # Read input file - with open(input_file, "r") as f: - lines = f.readlines() - # Parser - parser = Parser(lines) - # while parser.has_more_commands(): # Will raise NotImplementedError - parser.advance() # Does nothing - # cmd_type = parser.command_type() + def __init__(self, input_file: Path, output_file: Path): + """Initialize assembler with input/output paths.""" + # File paths + self.input_file: Path = input_file + self.output_file: Path = output_file - # For now, just write a placeholder - # Later we'll implement: - # 1. First pass: build symbol table - # 2. Second pass: generate machine code + # Components (create now) + self.symbol_table: SymbolTable = SymbolTable() - # Output - with open(output_file, "w") as f: - f.write(f"// Assembled from {input_file.name}\n") - f.write(f"// {len(lines)} lines processed\n") + # Data structures (empty, will fill later) + self.binary_instructions: list[str] = [] + + # Counters + self.next_variable_address = 16 + + # Will be created later + self.parser: Parser + self.lines: list[str] = [] + + with open(input_file, "r") as f: + self.lines = f.readlines() + raise NotImplementedError("Not implemented") + + def assemble(self) -> None: + """ + Main assembly process - coordinates first and second pass. + Public API method that CLI will call. + """ + + def _first_pass(self) -> None: + """ + First pass: Build symbol table with label addresses. + Scans through code to find all (LABEL) declarations. + """ + raise NotImplementedError("Not implemented") + + def _second_pass(self) -> None: + """ + Second pass: Translate instructions to binary. + Handles A-commands, C-commands, and resolves symbols. + """ + raise NotImplementedError("Not implemented") + + def _translate_a_command(self, symbol: str) -> str: + """ + Translate A-command to 16-bit binary. + Handles: numeric constants, predefined symbols, variables. + """ + raise NotImplementedError("Not implemented") + + def _translate_c_command(self) -> str: + """ + Translate C-command to 16-bit binary. + Format: 111 a c1c2c3c4c5c6 d1d2d3 j1j2j3 + """ + raise NotImplementedError("Not implemented") + + def _write_output(self) -> None: + """Write binary instructions to output file.""" + with open(self.output_file, "w") as f: + f.write(f"// Assembled from {self.input_file.name}\n") + f.write(f"// {len(self.lines)} lines processed\n") + raise NotImplementedError("Not implemented") + + def _assembled_success(self) -> None: + """Success Message""" + print(f"Successfully assembled {self.input_file} -> {self.output_file}") diff --git a/project-6-assembler/assembler/src/assembler/cli.py b/project-6-assembler/assembler/src/assembler/cli.py index ee95d84..a411a0d 100644 --- a/project-6-assembler/assembler/src/assembler/cli.py +++ b/project-6-assembler/assembler/src/assembler/cli.py @@ -2,7 +2,7 @@ import argparse import sys from pathlib import Path -from .assembler import assemble +from .assembler import Assembler def main(): @@ -26,8 +26,9 @@ def main(): # Assemble try: - assemble(input_path, output_path) - print(f"Successfully assembled {input_path} -> {output_path}") + # Assemble + assembler = Assembler(input_path, output_path) + assembler.assemble() except Exception as e: print(f"Error during assembly: {e}", file=sys.stderr) sys.exit(1) diff --git a/project-6-assembler/assembler/src/assembler/code.py b/project-6-assembler/assembler/src/assembler/code.py index e69de29..fbd1bc8 100644 --- a/project-6-assembler/assembler/src/assembler/code.py +++ b/project-6-assembler/assembler/src/assembler/code.py @@ -0,0 +1,59 @@ +from .code_tables import DEST_TABLE, COMP_TABLE, JUMP_TABLE + + +class Code: + """Translates Hack assembly mnemonics into binary codes.""" + + @staticmethod + def dest(mnemonic: str) -> str: + """ + Returns the 3-bit binary code for the dest mnemonic. + + Args: + mnemonic: Destination mnemonic (e.g., "M", "D", "MD", "AMD") + + Returns: + 3-bit binary string (e.g., "010", "111") + + Raises: + KeyError: If mnemonic is invalid + """ + # Use dictionary lookup table + print(mnemonic) + raise NotImplementedError("not implemented") + + @staticmethod + def comp(mnemonic: str) -> str: + """ + Returns the 7-bit binary code for the comp mnemonic. + + Args: + mnemonic: Computation mnemonic (e.g., "D+1", "M-1", "D&A") + + Returns: + 7-bit binary string (e.g., "0001111", "1110000") + + Raises: + KeyError: If mnemonic is invalid + """ + print(mnemonic) + raise NotImplementedError("not implemented") + # Use dictionary lookup table + + @staticmethod + def jump(mnemonic: str) -> str: + """ + Returns the 3-bit binary code for the jump mnemonic. + + Args: + mnemonic: Jump mnemonic (e.g., "JGT", "JEQ", "JMP", or "" for no jump) + + Returns: + 3-bit binary string (e.g., "001", "111") + + Raises: + KeyError: If mnemonic is invalid + """ + print(mnemonic) + raise NotImplementedError("not implemented") + # Use dictionary lookup table diff --git a/project-6-assembler/assembler/src/assembler/code_tables.py b/project-6-assembler/assembler/src/assembler/code_tables.py new file mode 100644 index 0000000..604a59d --- /dev/null +++ b/project-6-assembler/assembler/src/assembler/code_tables.py @@ -0,0 +1,52 @@ +DEST_TABLE = { + "": "000", + "M": "001", + "D": "010", + "MD": "011", + "A": "100", + "AM": "101", + "AD": "110", + "AMD": "111", +} +JUMP_TABLE = { + "": "000", + "JGT": "001", + "JEQ": "010", + "JGE": "011", + "JLT": "100", + "JNE": "101", + "JLE": "110", + "JMP": "111", +} +COMP_TABLE = { + # Constant and register operations (a=0) + "0": "0101010", + "1": "0111111", + "-1": "0111010", + "D": "0001100", + "A": "0110000", + "!D": "0001101", + "!A": "0110001", + "-D": "0001111", + "-A": "0110011", + "D+1": "0011111", + "A+1": "0110111", + "D-1": "0001110", + "A-1": "0110010", + "D+A": "0000010", + "D-A": "0010011", + "A-D": "0000111", + "D&A": "0000000", + "D|A": "0010101", + # Memory-based operations (a=1) + "M": "1110000", + "!M": "1110001", + "-M": "1110011", + "M+1": "1110111", + "M-1": "1110010", + "D+M": "1000010", + "D-M": "1010011", + "M-D": "1000111", + "D&M": "1000000", + "D|M": "1010101", +} diff --git a/project-6-assembler/assembler/src/assembler/parser.py b/project-6-assembler/assembler/src/assembler/parser.py index bac421a..5c0867e 100644 --- a/project-6-assembler/assembler/src/assembler/parser.py +++ b/project-6-assembler/assembler/src/assembler/parser.py @@ -41,7 +41,7 @@ class Parser: Raises: NotImplementedError: Not yet implemented """ - raise NotImplementedError("commandType not yet implemented") + raise NotImplementedError("command_type not yet implemented") def symbol(self) -> str: """ @@ -63,7 +63,7 @@ class Parser: """ Returns the dest mnemonic in the current C-command. - Should only be called when commandType() is C_COMMAND. + Should only be called when command_type () is C_COMMAND. Returns: str: The dest mnemonic (e.g., "M", "MD", "AMD") diff --git a/project-6-assembler/assembler/src/assembler/symbol_table.py b/project-6-assembler/assembler/src/assembler/symbol_table.py index e69de29..9697c3b 100644 --- a/project-6-assembler/assembler/src/assembler/symbol_table.py +++ b/project-6-assembler/assembler/src/assembler/symbol_table.py @@ -0,0 +1,46 @@ +class SymbolTable: + def __init__(self): + """ + Initialize dictionary with predefined Hack platform symbols + """ + self.symbols = { + "SP": 0, + "LCL": 1, + "ARG": 2, + "THIS": 3, + "THAT": 4, + "R0": 0, + "R1": 1, + "R2": 2, + "R3": 3, + "R4": 4, + "R5": 5, + "R6": 6, + "R7": 7, + "R8": 8, + "R9": 9, + "R10": 10, + "R11": 11, + "R12": 12, + "R13": 13, + "R14": 14, + "R15": 15, + "SCREEN": 16384, + "KBD": 24576, + } + + def add_entry(self, symbol: str, address: int) -> None: + print(symbol) + print(address) + # Add symbol-address pair to dictionary + raise NotImplementedError("add_entru not yet implemented") + + def contains(self, symbol: str) -> bool: + print(symbol) + raise NotImplementedError("contains not yet implemented") + # Check if symbol exists in dictionary + + def get_address(self, symbol: str) -> int: + print(symbol) + raise NotImplementedError("get_address not yet implemented") + # Return address for symbol (raises KeyError if not found)