# Kyler Olsen # Feb 2024 from collections import namedtuple from typing import TypeVar, Generic, Iterable, Sequence INSTRUCTIONS_COUNT = 0x700 MAX_IMMEDIATE = 0x40 class AssemblerError(Exception): pass class LinkerError(Exception): pass KEY = TypeVar("KEY") ANTIKEY = TypeVar("ANTIKEY") class TwoWayDictionary(Generic[KEY, ANTIKEY]): _dict: dict[KEY, ANTIKEY] _antidict: dict[ANTIKEY, KEY] def __init__( self, keys: Iterable[KEY] | None = None, antikeys: Iterable[ANTIKEY] | None = None, ): if (keys is None) and (antikeys is None): self._dict = {} self._antidict = {} elif ( (keys is None) or (antikeys is None) or len(keys) != len(antikeys) # type: ignore ): raise ValueError else: self._dict = dict(zip(keys, antikeys)) # type: ignore self._antidict = dict(zip(antikeys, keys)) # type: ignore def get_forwards(self, key: KEY, default: None | ANTIKEY = None) -> ANTIKEY: if key not in self._dict: if default is None: raise KeyError("Key not in dictionary") else: return default else: return self._dict[key] def get_backwards( self, antikey: ANTIKEY, default: None | KEY = None, ) -> KEY: if antikey not in self._antidict: if default is None: raise KeyError("Anti-Key not in dictionary") else: return default else: return self._antidict[antikey] def set_forwards(self, key: KEY, antikey: ANTIKEY): if antikey in self._antidict: raise KeyError("Anti-Key already in dictionary.") if key in self._dict: del self._antidict[self._dict[key]] self._dict[key] = antikey self._antidict[antikey] = key def set_backwards(self, antikey: ANTIKEY, key: KEY): if key in self._dict: raise KeyError("Key already in dictionary.") if antikey in self._antidict: del self._dict[self._antidict[antikey]] self._antidict[antikey] = key self._dict[key] = antikey def del_forwards(self, key: KEY): if key not in self._dict: raise KeyError("Key not in dictionary.") del self._antidict[self._dict[key]] del self._dict[key] def del_backwards(self, antikey: ANTIKEY): if antikey not in self._antidict: raise KeyError("Anti-Key not in dictionary.") del self._dict[self._antidict[antikey]] del self._antidict[antikey] def keys(self) -> Iterable[KEY]: return self._dict.keys() def antikeys(self) -> Iterable[ANTIKEY]: return self._antidict.keys() def items(self) -> Iterable[tuple[KEY, ANTIKEY]]: return self._dict.items() def antiitems(self) -> Iterable[tuple[ANTIKEY, KEY]]: return self._antidict.items() class Instruction(namedtuple('Instruction', ['bb', 'bl', 'lb', 'll'])): def __int__(self) -> int: return ( (self.bb << 9) + (self.bl << 6) + (self.lb << 3) + (self.ll) ) def __bytes__(self) -> bytes: value = int(self) u = (value & 0xf00) >> 8 l = value & 0xff return bytes((u, l)) def hex_tuple(self) -> tuple[int, int, int]: value = int(self) u = (value & 0xf00) >> 8 m = (value & 0xf0) >> 4 l = value & 0xf return (u, m, l) def oct_str(self) -> str: return ( str(self.bb) + str(self.bl) + str(self.lb) + str(self.ll) ) class Directive: @staticmethod def directive(line: str, line_number: int) -> "Directive": value = int(line[1:], base=0) return MemoryLocation(value) class MemoryLocation(Directive): _value: int def __init__(self, value: int): self._value = value @property def value(self) -> int: return self._value class Label(Directive): _value: str def __init__(self, value: str): self._value = value @property def value(self) -> str: return self._value class Immediate(Directive): _value: str def __init__(self, value: str): self._value = value @property def value(self) -> str: return self._value class Program: instruction_set = { "NOP": lambda *_: Instruction(0, 0, 0, 0), "HLT": lambda *_: Instruction(0, 0, 0, 1), "BNZ": lambda *_: Instruction(0, 0, 0, 2), "BNA": lambda *_: Instruction(0, 0, 0, 3), "BNP": lambda *_: Instruction(0, 0, 0, 4), "BNN": lambda *_: Instruction(0, 0, 0, 5), "GLA": lambda l, i: Instruction(0, 0, 2, reg1(l, i)), "GET": lambda l, i: Instruction(0, 0, 3, reg1(l, i)), "LOD": lambda l, i: Instruction(0, 0, 4, reg1(l, i)), "STR": lambda l, i: Instruction(0, 0, 5, reg1(l, i)), "POP": lambda l, i: Instruction(0, 0, 6, reg1(l, i)), "PSH": lambda l, i: Instruction(0, 0, 7, reg1(l, i)), "LIU": lambda l, i: Instruction(0, 1, *immediate_value(l, i)), "LDI": lambda l, i: immediate(l, i), "LIL": lambda l, i: Instruction(0, 3, *immediate_value(l, i)), "LSH": lambda l, i: Instruction(0, 4, *reg2(l, i)), "RSH": lambda l, i: Instruction(0, 5, *reg2(l, i)), "INC": lambda l, i: Instruction(0, 6, *reg2(l, i)), "DEC": lambda l, i: Instruction(0, 7, *reg2(l, i)), "AND": lambda l, i: Instruction(1, *reg3(l, i)), "OR": lambda l, i: Instruction(2, *reg3(l, i)), "SUB": lambda l, i: Instruction(3, *reg3(l, i)), "XOR": lambda l, i: Instruction(4, *reg3(l, i)), "NOR": lambda l, i: Instruction(5, *reg3(l, i)), "NAD": lambda l, i: Instruction(6, *reg3(l, i)), "ADD": lambda l, i: Instruction(7, *reg3(l, i)), } _program: str _instructions: list[tuple[int, Instruction | Directive]] _immediate: list[tuple[int, Immediate]] _instruction_map: TwoWayDictionary[int, int] _label_map: dict[str, int] def __init__(self, program: str): self._program = program self._instructions = self.parse(self._program) self._immediate = [] self._label_map = {} self._instruction_map = TwoWayDictionary() self._link() def __bytes__(self) -> bytes: output = bytearray() for i in range(0, INSTRUCTIONS_COUNT, 2): instruction = self._get_instruction(i) u1, m1, l1 = instruction.hex_tuple() output += bytes(((u1 << 4) | m1, )) if i + 1 < INSTRUCTIONS_COUNT: instruction = self._get_instruction(i + 1) u2, m2, l2 = instruction.hex_tuple() output += bytes(((l1 << 4) | u2, )) output += bytes(((m2 << 4) | l2, )) else: output += bytes((l1 << 4, )) return bytes(output) def hex_str(self) -> str: output = "" for i in range(INSTRUCTIONS_COUNT): instruction = self._get_instruction(i) output += f"{hex(int(instruction))}\n" return output def labels(self) -> dict[str, int]: return self._label_map.copy() def _get_instruction(self, index: int) -> Instruction: new_index = self._instruction_map.get_forwards(index, -1) if new_index == -1: value = self.instruction_set["NOP"]("NOP", -1) else: _, value = self._instructions[new_index] if isinstance(value, Immediate): return self.instruction_set["LDI"]( f"LDI {self._label_map[value.value] -1}", -1) elif isinstance(value, Directive): raise LinkerError("Unexpected directive!") else: return value def _get_instruction_line(self, index: int) -> int: return self._instruction_map.get_backwards(index) def _link(self): instruction_line = 0 for index, (line_number, item) in enumerate(self._instructions): if isinstance(item, MemoryLocation): instruction_line = item.value elif isinstance(item, Label): if item.value in self._label_map: raise LinkerError( "Label already declared on line" f" {line_number}: {item.value}" ) self._label_map[item.value] = instruction_line elif isinstance(item, Immediate): self._immediate.append((index, item)) self._instruction_map.set_forwards(instruction_line, index) instruction_line += 1 elif isinstance(item, Directive): raise LinkerError( f"Unknown or Invalid Directive! on line {line_number}.") else: self._instruction_map.set_forwards(instruction_line, index) instruction_line += 1 @classmethod def parse(cls, s: str) -> list[tuple[int, Instruction | Directive]]: instructions: list[tuple[int, Instruction | Directive]] = [] last_error = None for raw_line_number, raw_line in enumerate(s.splitlines(False)): try: line_number = raw_line_number + 1 line = raw_line.strip().upper() if len(line) == 0 or line[0] == ";": pass elif line[:3] in cls.instruction_set: instructions.append(( line_number, cls.instruction_set[line[:3]](line, line_number), )) elif line[:2] in cls.instruction_set: instructions.append(( line_number, cls.instruction_set[line[:2]](line, line_number), )) elif line[-1] == ":": instructions.append(( line_number, Label(line[:-1]), )) elif line[0] == ".": instructions.append(( line_number, Directive.directive(line, line_number), )) elif line: raise AssemblerError( f"Invalid Instruction on line {line_number}: '{line}'") except (AssemblerError, LinkerError) as e: last_error = e print(f"Error:\n\t{e}") if last_error is not None: # raise last_error exit() return instructions def reg(reg: str, line_number: int) -> int: register_names = [ "ZR", "PC", "SP", "MP", "D0", "D1", "D2", "D3", ] register_numbers = [ "0", "1", "2", "3", "4", "5", "6", "7", ] if reg.upper() in register_names: return register_names.index(reg.upper()) elif reg in register_numbers: return register_numbers.index(reg) else: raise AssemblerError( f"Invalid Register on line {line_number}: {reg}") def reg1(line: str, line_number: int) -> int: args = line.split(' ') if len(args) == 2: return reg(args[1], line_number) else: raise AssemblerError( f"Invalid number of arguments on line {line_number}: {args[0]}") def reg2(line: str, line_number: int) -> tuple[int, int]: args = line.split(' ') if len(args) == 3: return reg(args[2], line_number), reg(args[1], line_number) else: raise AssemblerError( f"Invalid number of arguments on line {line_number}: {args[0]}") def reg3(line: str, line_number: int) -> tuple[int, int, int]: args = line.split(' ') if len(args) == 4: return ( reg(args[2], line_number), reg(args[3], line_number), reg(args[1], line_number), ) else: raise AssemblerError( f"Invalid number of arguments on line {line_number}: {args[0]}") def immediate_value(line: str, line_number: int) -> tuple[int, int]: args = line.split(' ') if len(args) == 2: value = int(args[1], base=0) if value >= MAX_IMMEDIATE: raise AssemblerError( f"Immediate value too large {line_number}: {args[1]}") return ((value & 0x038) >> 3, value & 0x007) else: raise AssemblerError( f"Invalid number of arguments on line {line_number}: {args[0]}") def immediate(line: str, line_number: int) -> Instruction | Immediate: args = line.split(' ') if len(args) == 2: if ':' in args[1]: return Immediate(args[1][1:]) else: value = int(args[1], base=0) if value >= MAX_IMMEDIATE: raise AssemblerError( f"Immediate value too large {line_number}: {args[1]}") return Instruction( 0, 2 + ((value & 0x040) >> 6), (value & 0x038) >> 3, value & 0x007, ) else: raise AssemblerError( f"Invalid number of arguments on line {line_number}: {args[0]}")