diff --git a/Assignments/7_Virtual_Machine_I_Stack_Arithmetic/vmtranslator.py b/Assignments/7_Virtual_Machine_I_Stack_Arithmetic/vmtranslator.py new file mode 100644 index 0000000..9431170 --- /dev/null +++ b/Assignments/7_Virtual_Machine_I_Stack_Arithmetic/vmtranslator.py @@ -0,0 +1,133 @@ +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") diff --git a/README.md b/README.md index 10eb2f4..eda17f5 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,8 @@ - #### **Software** - **[Project 6: Assembler](./Assignments/6_Assembler/)** > На практике изучаем как язык ассемблера компилируется в двоичный код, [создавая ассемблер для компьютера Hack](./Assignments/6_Assembler/assembler.py) - - Project 7: VM I: Stack Arithmetic + - **[Project 7: VM I: Stack Arithmetic](./Assignments/7_Virtual_Machine_I_Stack_Arithmetic/)** + > Начинаем создание виртуальной машины ([транслятора с языка виртаульной машины на язык ассемблера](./Assignments/7_Virtual_Machine_I_Stack_Arithmetic/vmtranslator.py)). Пишем трансляцию для операций доступа к виртуальным сегментам памяти и стэковую арифметику - Project 8: VM II: Program Control - Project 9: High-Level Language - Project 10: Compiler I: Syntax Analysis