Skip to content

Commit

Permalink
Merge pull request #3 from volf52/asm-coder
Browse files Browse the repository at this point in the history
Add `Coder` and `SymbolTable`.

- Add related tests.
- Updated `Parser` to support more `dest` symbols.
  • Loading branch information
volf52 authored Dec 21, 2020
2 parents def996e + 46b3b43 commit 2073406
Show file tree
Hide file tree
Showing 4 changed files with 390 additions and 10 deletions.
182 changes: 182 additions & 0 deletions pyasm/coder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
from typing import Dict, Optional

TranslationTable = Dict[str, str]


class InvalidMnemonicError(LookupError):
def __init__(self, field: str, mnemonic: str):
msg = f"Invalid mnemonic for `{field}`: {mnemonic}"
super(InvalidMnemonicError, self).__init__(msg)


class Coder:
__DEST = {
"": "000",
"M": "001",
"D": "010",
"MD": "011",
"DM": "011",
"A": "100",
"AM": "101",
"MA": "101",
"AD": "110",
"DA": "110",
"AMD": "111",
"ADM": "111",
"MAD": "111",
"MDA": "111",
"DAM": "111",
"DMA": "111",
}
__COMP = {
"0": "0101010",
"1": "0111111",
"-1": "0111010",
"D": "0001100",
"A": "0110000",
"M": "1110000",
"!D": "0001101",
"!A": "0110001",
"!M": "1110001",
"-D": "0001111",
"-A": "0110011",
"-M": "1110011",
"D+1": "0011111",
"A+1": "0110111",
"M+1": "1110111",
"D-1": "0001110",
"A-1": "0110010",
"M-1": "1110010",
"D+A": "0000010",
"A+D": "0000010",
"D+M": "1000010",
"M+D": "1000010",
"D-A": "0010011",
"D-M": "1010011",
"A-D": "0000111",
"M-D": "1000111",
"D&A": "0000000",
"A&D": "0000000",
"D&M": "1000000",
"M&D": "1000000",
"D|A": "0010101",
"A|D": "0010101",
"D|M": "1010101",
"M|D": "1010101",
}

__JMP = {
"": "000",
"JGT": "001",
"JEQ": "010",
"JGE": "011",
"JLT": "100",
"JNE": "101",
"JLE": "110",
"JMP": "111",
}

def __init__(self):
raise RuntimeError("Cannot instantiate this class")

@staticmethod
def get_dest_table() -> TranslationTable:
return Coder.__DEST

@staticmethod
def get_comp_table() -> TranslationTable:
return Coder.__COMP

@staticmethod
def get_jmp_table() -> TranslationTable:
return Coder.__JMP

@staticmethod
def __get_mnemonic(mnemonic: str, table: TranslationTable, field: str):
code = table.get(mnemonic.upper())
if code is None:
raise InvalidMnemonicError(field, mnemonic)

return code

@staticmethod
def translate_dest(mnemonic: str) -> str:
return Coder.__get_mnemonic(mnemonic, Coder.__DEST, "dest")

@staticmethod
def translate_comp(mnemonic: str) -> str:
return Coder.__get_mnemonic(mnemonic, Coder.__COMP, "comp")

@staticmethod
def translate_jmp(mnemonic: str) -> str:
return Coder.__get_mnemonic(mnemonic, Coder.__JMP, "jmp")


class SymbolTable:
__RESERVED = {
"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,
"sp": 0,
"lcl": 1,
"arg": 2,
"this": 3,
"that": 4,
"screen": 16384,
"kbd": 24576,
}

__slots__ = "__lookup_table"

def __init__(self):
self.__lookup_table = {}

def __setitem__(self, key: str, value: int) -> None:
if key.lower() in SymbolTable.__RESERVED:
raise ValueError(f"Cannot set a reserved symbol. {key}")

self.__lookup_table[key] = value

def __getitem__(self, key: str):
reserved = SymbolTable.__RESERVED.get(key.lower())
if reserved is not None:
return reserved

return self.__lookup_table[key]

def get(self, key: str, default=None) -> Optional[int]:
reserved = SymbolTable.__RESERVED.get(key.lower())
if reserved is not None:
return reserved

return self.__lookup_table.get(key, default)

def __len__(self) -> int:
return len(self.__lookup_table)

def clear(self) -> None:
self.__lookup_table.clear()

def delete(self, symbol: str) -> bool:
if symbol.lower() in SymbolTable.__RESERVED:
return False

val = self.__lookup_table.get(symbol)
if val is None:
return False

self.__lookup_table.__delitem__(symbol)
return True
18 changes: 17 additions & 1 deletion pyasm/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,23 @@ def generate_possible_c_commands():
"D|M",
]

possible_dests = ["M", "D", "MD", "A", "AM", "AD", "AMD"]
possible_dests = [
"M",
"D",
"MD",
"DM",
"A",
"AM",
"MA",
"AD",
"DA",
"AMD",
"ADM",
"MAD",
"MDA",
"DAM",
"DMA",
]
possible_jmps = ["JGT", "JEQ", "JGE", "JLT", "JNE", "JLE", "JMP"]

result = []
Expand Down
168 changes: 168 additions & 0 deletions tests/test_coder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import pytest

from pyasm.coder import Coder, InvalidMnemonicError, SymbolTable


def test_coder_cannot_be_instantiated():
with pytest.raises(RuntimeError):
Coder()


def test_dest_code_length():
table = Coder.get_dest_table()
code_values = set(table.values())

assert len(code_values) == 8
for code in code_values:
assert len(code) == 3


def test_comp_code_length():
table = Coder.get_comp_table()
code_values = set(table.values())

assert len(code_values) == 28
for code in code_values:
assert len(code) == 7


def test_jmp_code_length():
table = Coder.get_jmp_table()
code_values = set(table.values())

assert len(code_values) == 8
for code in code_values:
assert len(code) == 3


@pytest.mark.parametrize(
"mnemonic,expected",
[("", "000"), ("MAD", "111"), ("DA", "110"), ("M", "001")],
)
def test_valid_dest_(mnemonic: str, expected: str):
code = Coder.translate_dest(mnemonic)
assert code == expected


@pytest.mark.parametrize("mnemonic", ["l", "shoot", "MDM", "AA"])
def test_invalid_dest_(mnemonic: str):
with pytest.raises(InvalidMnemonicError) as err:
Coder.translate_dest(mnemonic)

assert mnemonic in str(err.value)
assert "dest" in str(err.value)


@pytest.mark.parametrize(
"mnemonic,expected",
[("", "000"), ("jgt", "001"), ("JLe", "110"), ("jMp", "111")],
)
def test_valid_jmp_(mnemonic: str, expected: str):
code = Coder.translate_jmp(mnemonic)
assert code == expected


@pytest.mark.parametrize("mnemonic", ["JA", "jbe", "AD", "bla"])
def test_invalid_jmp_(mnemonic: str):
with pytest.raises(InvalidMnemonicError) as err:
Coder.translate_jmp(mnemonic)

assert mnemonic in str(err.value)
assert "jmp" in str(err.value)


@pytest.mark.parametrize(
"mnemonic,expected",
[
("0", "0101010"),
("D+1", "0011111"),
("D|M", "1010101"),
("m-d", "1000111"),
],
)
def test_valid_comp_(mnemonic: str, expected: str):
code = Coder.translate_comp(mnemonic)
assert code == expected


@pytest.mark.parametrize(
"mnemonic", ["!1", "1-M", "A+M", "A&M", "! D", "D -1", "A + D"]
)
def test_invalid_comp_(mnemonic: str):
with pytest.raises(InvalidMnemonicError) as err:
Coder.translate_comp(mnemonic)

assert mnemonic in str(err.value)
assert "comp" in str(err.value)


@pytest.fixture(scope="module")
def symbol_table() -> SymbolTable:
return SymbolTable()


@pytest.mark.parametrize(
"symbol", ["r0", "R0", "THIS", "SCREEN", "scReEn", "kbd", "R7"]
)
def test_adding_reserved_symbols_to_symbol_table(
symbol_table: SymbolTable, symbol: str
):
with pytest.raises(ValueError):
symbol_table[symbol] = 3

assert len(symbol_table) == 0


def test_getting_reserved_symbols(symbol_table: SymbolTable):
for i in range(1, 15 + 1):
assert symbol_table.get(f"r{i}") == i
assert symbol_table.get(f"R{i}") == i

assert symbol_table.get("sp") == 0
assert symbol_table.get("SP") == 0
assert symbol_table.get("lcl") == 1
assert symbol_table.get("LCL") == 1
assert symbol_table.get("arg") == 2
assert symbol_table.get("ARG") == 2
assert symbol_table.get("this") == 3
assert symbol_table.get("THIS") == 3
assert symbol_table.get("that") == 4
assert symbol_table.get("THAT") == 4


@pytest.mark.integ_test
def test_setting_and_getting_symbol_table_entries_():
symbol_table = SymbolTable()

assert len(symbol_table) == 0

# Get from empty table
with pytest.raises(KeyError):
_ = symbol_table["abc"]

assert symbol_table.get("abc") is None
assert symbol_table.get("abc", default=14) == 14

assert len(symbol_table) == 0

symbol_table["loop"] = 16
assert symbol_table["loop"] == 16
assert symbol_table.get("loop") == 16

assert not symbol_table.delete("r0")
assert not symbol_table.delete("KBD")
assert not symbol_table.delete("abc")
assert symbol_table.delete("loop")
assert symbol_table.get("loop") is None
with pytest.raises(KeyError):
_ = symbol_table["loop"]

symbol_table["end"] = 256
assert symbol_table["end"] == 256
symbol_table["end"] = 147
assert symbol_table["end"] == 147
symbol_table["something_else"] = 123
assert len(symbol_table) == 2

symbol_table.clear()
assert len(symbol_table) == 0
Loading

0 comments on commit 2073406

Please sign in to comment.