summaryrefslogtreecommitdiff
path: root/projects/hack-vm/__main__.py
blob: b849edbac55294d1e56d4d47496a5fbf8a08f9a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import os
from pathlib import Path
from argparse import ArgumentParser
from .vminit import vminit
from .memory import translate_memory
from .arith_logic import translate_arith_logic
from .compare import translate_compare
from .branching import translate_branching
from .function import translate_function, translate_return
from .utils import *


def vm_translate(input_path, verbose, init):
    # if input_path is a file, translate the file
    # if it is a directory, translate every .vm file inside
    try:
        filenames = os.listdir(input_path)
        files = [Path(input_path / f) for f in filenames]
        vm_files = filter(lambda fn: fn.suffix == ".vm", files)
        output_path = input_path / (input_path.name + ".asm")
    except NotADirectoryError:
        vm_files = [Path(input_path)]
        output_path = input_path.with_suffix(".asm")
    except:
        exit_on_error(f"Cannot open input path: {input_path}", EXIT_CODE_FILE_ERROR)

    # assembly translated from all .vm files will be concatenated here
    asm = vminit(input_path.stem) if init else ""

    for input_fn in vm_files:
        # load all vm commands from file, e.g. program/Main.vm
        try:
            input_file = open(input_fn)
        except:
            exit_on_error(f"Cannot open input file: {input_fn}", EXIT_CODE_FILE_ERROR)

        prog = input_fn.stem  # e.g. Main
        vm_cmds = []
        line = input_file.readline()
        while line:
            cmd = line.rstrip("\n").split("//", maxsplit=1)[0].strip()
            if cmd:
                vm_cmds.append(cmd)
            line = input_file.readline()

        input_file.close()

        for cmd in vm_cmds:
            # tokenize vm_line, hand to sub-translators based on first token
            match cmd.split():
                case []:
                    continue
                case [("push" | "pop") as action, segment, index]:
                    asm += translate_memory(action, segment, index, prog, verbose)
                case [("add" | "sub" | "neg" | "and" | "or" | "not") as command]:
                    asm += translate_arith_logic(command, verbose)
                case [("lt" | "eq" | "gt") as command]:
                    asm += translate_compare(command, verbose)
                case [("label" | "goto" | "if-goto") as action, label]:
                    asm += translate_branching(action, label, prog, verbose)
                case [("call" | "function") as action, function, n]:
                    asm += translate_function(action, function, n, prog, verbose)
                case ["return"]:
                    asm += translate_return(verbose)
                case _:
                    exit_on_error(
                        f"Syntax error: invalid command: {cmd}", EXIT_CODE_SYNTAX_ERROR
                    )

    try:
        output_file = open(output_path, "w")
    except:
        exit_on_error(f"Cannot open output file: {output_path}", EXIT_CODE_FILE_ERROR)

    output_file.write(asm)
    output_file.close()
    print(f"Assembly written to {output_path}")


if __name__ == "__main__":
    parser = ArgumentParser("hack-vm")
    parser.add_argument("-v", "--verbose", action="store_true", help="verbose mode")
    parser.add_argument("-I", "--no-init", action="store_true", help="do not write bootstrap code")
    parser.add_argument("input_path", help="HACK VM file or directory")
    args = parser.parse_args()
    vm_translate(Path(args.input_path), args.verbose, not args.no_init)