156 lines
4.9 KiB
Python
156 lines
4.9 KiB
Python
#import re
|
|
#import sys
|
|
import argparse
|
|
|
|
def convert_assembly_to_binary_file(asm_file, binary_file):
|
|
with open(asm_file, "r") as f:
|
|
result = translate_lines(f.readlines())
|
|
output = "\n".join([l for l in result if l])
|
|
with open(binary_file, "w") as f:
|
|
f.write(output)
|
|
|
|
def translate_lines(lines):
|
|
lines = strip_whitespace_and_comments(lines)
|
|
symbol_table = build_symbol_table(lines)
|
|
translate_instruction = build_instruction_translator(symbol_table)
|
|
return [translate_instruction(x) for x in lines]
|
|
|
|
def strip_whitespace_and_comments(lines):
|
|
instructions = []
|
|
for line in lines:
|
|
stripped_line = line.strip()
|
|
if stripped_line:
|
|
if not stripped_line.startswith("//"):
|
|
if "//" in stripped_line:
|
|
instructions.append(stripped_line.split("//")[0].strip())
|
|
else:
|
|
instructions.append(stripped_line)
|
|
return instructions
|
|
|
|
def build_symbol_table(lines):
|
|
symbols = {
|
|
"R0": "0000000000000000",
|
|
"R1": "0000000000000001",
|
|
"R2": "0000000000000010",
|
|
"R3": "0000000000000011",
|
|
"R4": "0000000000000100",
|
|
"R5": "0000000000000101",
|
|
"R6": "0000000000000110",
|
|
"R7": "0000000000000111",
|
|
"R8": "0000000000001000",
|
|
"R9": "0000000000001001",
|
|
"R10": "0000000000001010",
|
|
"R11": "0000000000001011",
|
|
"R12": "0000000000001100",
|
|
"R13": "0000000000001101",
|
|
"R14": "0000000000001110",
|
|
"R15": "0000000000001111",
|
|
"SP": "0000000000000000",
|
|
"ARG": "0000000000000010",
|
|
"LCL": "0000000000000001",
|
|
"THIS": "0000000000000011",
|
|
"THAT": "0000000000000100",
|
|
"KBD": "0110000000000000",
|
|
"SCREEN": "0100000000000000"
|
|
}
|
|
is_address_instruction = lambda x: x.startswith("@")
|
|
is_compute_instruction = lambda x: "=" in x or ";" in x
|
|
label_value = lambda x: x.replace("(", "").replace(")", "").strip()
|
|
current_line_num = 0
|
|
for line in lines:
|
|
if is_address_instruction(line) or is_compute_instruction(line):
|
|
current_line_num +=1
|
|
elif is_label(line):
|
|
symbols[label_value(line)] = decimal_to_binary(current_line_num)
|
|
base_address = 16
|
|
for line in lines:
|
|
if line.startswith("@"):
|
|
value = line[1:]
|
|
if value not in symbols and not value.isnumeric():
|
|
symbols[value] = decimal_to_binary(base_address)
|
|
base_address += 1
|
|
return symbols
|
|
|
|
def build_instruction_translator(symbol_table):
|
|
COMPUTATIONS = {
|
|
"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",
|
|
"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"
|
|
}
|
|
DESTINATIONS = {
|
|
"": "000",
|
|
"M": "001",
|
|
"D": "010",
|
|
"MD": "011",
|
|
"A": "100",
|
|
"AM": "101",
|
|
"AD": "110",
|
|
"AMD": "111"
|
|
}
|
|
JUMPS = {
|
|
"": "000",
|
|
"JGT": "001",
|
|
"JEQ": "010",
|
|
"JGE": "011",
|
|
"JLT": "100",
|
|
"JNE": "101",
|
|
"JLE": "110",
|
|
"JMP": "111"
|
|
}
|
|
def fn(line):
|
|
if is_label(line):
|
|
return
|
|
if line.startswith("@"):
|
|
value = line[1:]
|
|
if value in symbol_table:
|
|
return symbol_table[value]
|
|
return decimal_to_binary(int(value))
|
|
dest, jump = "", ""
|
|
comp = line.split("=").pop().split(";")[0]
|
|
if "=" in line:
|
|
dest = line.split("=")[0]
|
|
if ";" in line:
|
|
jump = line.split(";").pop()
|
|
return f"111{COMPUTATIONS.get(comp, '0000000')}{DESTINATIONS.get(dest, '000')}{JUMPS.get(jump, '000')}"
|
|
return fn
|
|
|
|
def is_label(line):
|
|
return line.startswith("(") and line.endswith(")")
|
|
|
|
def decimal_to_binary(decimal_value):
|
|
return f"{decimal_value:0>16b}"
|
|
|
|
if __name__ == "__main__":
|
|
#parser = argparse.ArgumentParser(description="Generates a hack binary file from assembly")
|
|
#parser.add_argument("Rect.asm")
|
|
#parser.add_argument("Rect.hack")
|
|
#parser.add_argument("asm_file", help="name of a HACK assembly file, i.e input.asm")
|
|
#parser.add_argument("binary_file", help="name of the HACK file, i.e output.hack")
|
|
#args = parser.parse_args()
|
|
#convert_assembly_to_binary_file(args.asm_file, args.binary_file)
|
|
convert_assembly_to_binary_file('Rect.asm', 'Rect.hack') |