mirror of
https://github.com/StepanovPlaton/Nand2Tetris.git
synced 2026-04-03 12:20:47 +04:00
134 lines
5.0 KiB
Python
134 lines
5.0 KiB
Python
from pathlib import Path
|
|
import sys
|
|
import os
|
|
import re
|
|
from typing import Callable, Literal
|
|
|
|
vm_code: list[str] = []
|
|
output_filename: str | None = None
|
|
|
|
|
|
def read_file(path: Path | str):
|
|
with open(path, "r") as asm_file:
|
|
vm_code.extend(list(
|
|
filter(lambda line: len(line) > 0,
|
|
map(lambda line: " ".join(
|
|
list(filter(lambda x: x != "", line.strip().split(" ")))
|
|
).split("//")[0],
|
|
asm_file.readlines()))))
|
|
|
|
|
|
if (len(sys.argv) == 1):
|
|
raise ValueError("At least one .vm file was expected")
|
|
for i in range(1, len(sys.argv)):
|
|
if (not os.path.exists(sys.argv[i])):
|
|
raise ValueError(f"{sys.argv[i]} not found")
|
|
if (output_filename is None):
|
|
output_filename = \
|
|
f"{sys.argv[i].replace(".vm", "").replace(
|
|
"/", "").replace("\\", "").replace(".", "")}.asm"
|
|
if (os.path.isdir(sys.argv[i])):
|
|
for file in os.listdir(sys.argv[i]):
|
|
if (not re.match(r".*\.vm", file)):
|
|
print(f"Missing {file} because it's not a .vm file")
|
|
else:
|
|
read_file(Path() / sys.argv[i] / file)
|
|
else:
|
|
read_file(sys.argv[i])
|
|
|
|
|
|
ram_areas: dict[str, int] = {
|
|
"REGISTERS": 0,
|
|
"STATIC": 16,
|
|
"STACK": 256,
|
|
"HEAP": 2048,
|
|
"I/O": 16384,
|
|
}
|
|
|
|
|
|
class MemorySegment:
|
|
def __init__(self, address: str | int | None, type: Literal["pointer", "value", "constant"]):
|
|
self.address = address
|
|
self.type = type
|
|
self.isPointer = type == "pointer"
|
|
self.isConstant = type == "constant"
|
|
|
|
|
|
memory_segments: dict[str, MemorySegment] = {
|
|
"argument": MemorySegment("ARG", "pointer"),
|
|
"local": MemorySegment("LCL", "pointer"),
|
|
"static": MemorySegment(ram_areas["STATIC"], "value"),
|
|
"constant": MemorySegment(None, "constant"),
|
|
"this": MemorySegment("THIS", "pointer"),
|
|
"that": MemorySegment("THAT", "pointer"),
|
|
"pointer": MemorySegment(3, "value"),
|
|
"temp": MemorySegment(5, "value"),
|
|
}
|
|
|
|
|
|
def create_labels():
|
|
labels = {"eq": 0, "gt": 0, "lt": 0}
|
|
|
|
def label(key: str):
|
|
labels[key] += 1
|
|
return (f"{key}{labels[key]//2}" if (labels[key] % 2 == 1) else f"{key}{labels[key]//2-1}").upper()
|
|
return label
|
|
|
|
|
|
label = create_labels()
|
|
stack_commands: dict[str, Callable[[], list[str]]] = {
|
|
"add": lambda: ["@SP", "AM=M-1", "D=M", "A=A-1", "D=D+M", "M=D"],
|
|
"sub": lambda: ["@SP", "AM=M-1", "D=-M", "A=A-1", "D=D+M", "M=D"],
|
|
"neg": lambda: ["@SP", "A=M-1", "M=-M"],
|
|
"eq": lambda: ["@SP", "AM=M-1", "D=-M", "A=A-1", "D=D+M", "M=-1",
|
|
f"@{label("eq")}", "D;JEQ", "@SP", "A=M-1", "M=0", f"({label("eq")})"],
|
|
"gt": lambda: ["@SP", "AM=M-1", "D=-M", "A=A-1", "D=D+M", "M=-1",
|
|
f"@{label("gt")}", "D;JGT", "@SP", "A=M-1", "M=0", f"({label("gt")})"],
|
|
"lt": lambda: ["@SP", "AM=M-1", "D=-M", "A=A-1", "D=D+M", "M=-1",
|
|
f"@{label("lt")}", "D;JLT", "@SP", "A=M-1", "M=0", f"({label("lt")})"],
|
|
"and": lambda: ["@SP", "AM=M-1", "D=M", "A=A-1", "D=D&M", "M=D"],
|
|
"or": lambda: ["@SP", "AM=M-1", "D=M", "A=A-1", "D=D|M", "M=D"],
|
|
"not": lambda: ["@SP", "A=M-1", "M=!M"]
|
|
}
|
|
memory_access_commands: dict[str, Callable[[str, int], list[str]]] = {
|
|
"push": lambda s, i: [*([f"@{i}", "D=A"]
|
|
if memory_segments[s].isConstant else
|
|
[f"@{memory_segments[s].address}", *(["A=M"] if memory_segments[s].isPointer else []),
|
|
"D=A", f"@{i}", "D=D+A", "A=D", "D=M"]),
|
|
"@SP", "A=M", "M=D", "@SP", "M=M+1"],
|
|
"pop": lambda s, i: [f"@{memory_segments[s].address}", *(["A=M"] if memory_segments[s].isPointer else []),
|
|
"D=A", f"@{i}", "D=D+A", "@SP", "A=M", "M=D",
|
|
"@SP", "AM=M-1", "D=M", "@SP", "A=M+1", "A=M", "M=D"]
|
|
}
|
|
|
|
|
|
asm_code: list[str] = []
|
|
for i, line in enumerate(vm_code):
|
|
command = line.split(" ")[0]
|
|
if (command in memory_access_commands):
|
|
if (len(line.split(" ")) != 3):
|
|
raise Exception(
|
|
"Unexpected number of arguments for memory access command")
|
|
segment = line.split(" ")[1]
|
|
index = line.split(" ")[2]
|
|
if (not index.isdigit()):
|
|
raise Exception("Index must be integer digit")
|
|
else:
|
|
index = int(index)
|
|
if (segment in memory_segments.keys()):
|
|
asm_code.append(f"// {line}")
|
|
asm_code.extend(memory_access_commands[command](segment, index))
|
|
else:
|
|
raise Exception(f"Unknown memory segment '{segment}'")
|
|
elif (command in stack_commands):
|
|
asm_code.append(f"// {line}")
|
|
asm_code.extend(stack_commands[command]())
|
|
else:
|
|
raise Exception(f"unexpected command '{command}'")
|
|
|
|
with open("program.asm" if output_filename is None else output_filename, "w") as asm_file:
|
|
for i, command in enumerate(asm_code):
|
|
asm_file.write(f"{command}")
|
|
if (i != len(asm_code)-1):
|
|
asm_file.write("\n")
|