Skip to content

Commit 59ad03b

Browse files
committed
Chapter 7 - Virtual Machine I: Stack Arithmetic
Python3 for Virtual Machine Translator. Python3 for VMT modules CodeWriter, Parser, and Command.
1 parent 1d9700f commit 59ad03b

File tree

5 files changed

+360
-2
lines changed

5 files changed

+360
-2
lines changed

07/vmt.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env python3
2+
3+
4+
"""Virtual Machine Translator
5+
Translates the VM code (either a single .vm file
6+
or a directory of .vm files) into its Hack ASM
7+
equivalent, which can then be assembled and run
8+
on Hack.
9+
"""
10+
11+
12+
import sys
13+
from pathlib import Path
14+
from vmt_modules.parser import Parser
15+
from vmt_modules.command import Command
16+
from vmt_modules.code_writer import CodeWriter
17+
18+
19+
def main():
20+
vm_path = Path(sys.argv[-1])
21+
vm_glob = []
22+
asm_path = str(vm_path.parent) + '\\' + (vm_path.name[:vm_path.name.find('.')] + '.asm')
23+
24+
if vm_path.name == 'vmt.py':
25+
raise IOError('Please supply a .vm source file, or a directory of .vm source files.')
26+
if vm_path.is_dir():
27+
vm_glob = list(vm_path.glob('*.vm'))
28+
if not vm_glob:
29+
raise RuntimeError('No *.vm files found in supplied directory.')
30+
asm_path = str(vm_path.parent) + '\\' + vm_path.name + '\\' + vm_path.name + '.asm'
31+
elif vm_path.is_file():
32+
if vm_path.suffix != '.vm':
33+
raise IOError('Valid VM source file not supplied.')
34+
vm_glob.append(vm_path)
35+
36+
code_writer = CodeWriter(asm_path)
37+
for vm_file in vm_glob:
38+
vm_parser = Parser(vm_file)
39+
while vm_parser.has_more_commands():
40+
if vm_parser.command_type() is Command.C_ARITHMETIC:
41+
code_writer.write_arithmetic(vm_parser.arg1())
42+
elif vm_parser.command_type() is Command.C_PUSH:
43+
code_writer.write_push_pop(Command.C_PUSH, vm_parser.arg1(), vm_parser.arg2())
44+
elif vm_parser.command_type() is Command.C_POP:
45+
code_writer.write_push_pop(Command.C_POP, vm_parser.arg1(), vm_parser.arg2())
46+
else:
47+
raise NotImplementedError()
48+
code_writer.close()
49+
50+
print('[=>] IN .vm file(s):')
51+
print(*vm_glob, sep='\n')
52+
print('[<=] OUT .asm file:\n' + asm_path)
53+
54+
55+
if __name__ == "__main__":
56+
main()

07/vmt_modules/code_writer.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/usr/bin/env python3
2+
3+
4+
"""The CodeWriter Module
5+
Translates given VM commands into Hack ASM code.
6+
"""
7+
8+
9+
from .command import Command
10+
11+
12+
class CodeWriter:
13+
def __init__(self, out_path):
14+
self.out_path = out_path
15+
self._dynamic_labels = {'lt':0, 'eq':0, 'gt':0}
16+
self._bool_jmp_logic_symbol = ''
17+
self._asm_file = open(out_path, 'w+')
18+
19+
20+
def set_file_name(self, file_name):
21+
"""Informs the code writer that the
22+
translation of a new VM file has started.
23+
(Possibly for dynamic label making)?
24+
"""
25+
26+
27+
def write_arithmetic(self, command):
28+
"""Writes the assembly code that is the
29+
translation of the given arithmetic
30+
command.
31+
"""
32+
self._asm_file.write('@SP\n')
33+
if command in ['neg', 'not']:
34+
self._asm_file.write('A=M-1\n')
35+
if command == 'neg':
36+
self._asm_file.write('M=-M\n')
37+
elif command == 'not':
38+
self._asm_file.write('M=!M\n')
39+
else:
40+
self._asm_file.write('AM=M-1\n')
41+
self._asm_file.write('D=M\n')
42+
self._asm_file.write('A=A-1\n')
43+
if command == 'add':
44+
self._asm_file.write('M=M+D\n')
45+
elif command == 'sub':
46+
self._asm_file.write('M=M-D\n')
47+
elif command in ['lt', 'eq', 'gt']:
48+
self._asm_file.write('D=M-D\n')
49+
self._asm_file.write('M=0\n')
50+
if command == 'lt':
51+
self._bool_jmp_logic_symbol = 'LTJGE$' + str(self._dynamic_labels['lt'])
52+
self._dynamic_labels['lt'] += 1
53+
self._asm_file.write('@'+self._bool_jmp_logic_symbol+'\n')
54+
self._asm_file.write('D;JGE\n')
55+
elif command == 'eq':
56+
self._bool_jmp_logic_symbol = 'EQJNE$' + str(self._dynamic_labels['eq'])
57+
self._dynamic_labels['eq'] += 1
58+
self._asm_file.write('@'+self._bool_jmp_logic_symbol+'\n')
59+
self._asm_file.write('D;JNE\n')
60+
elif command == 'gt':
61+
self._bool_jmp_logic_symbol = 'GTJLE$' + str(self._dynamic_labels['gt'])
62+
self._dynamic_labels['gt'] += 1
63+
self._asm_file.write('@'+self._bool_jmp_logic_symbol+'\n')
64+
self._asm_file.write('D;JLE\n')
65+
self._asm_file.write('@SP\n')
66+
self._asm_file.write('A=M-1\n')
67+
self._asm_file.write('M=-1\n')
68+
self._asm_file.write('('+self._bool_jmp_logic_symbol+')\n')
69+
elif command == 'and':
70+
self._asm_file.write('M=M&D\n')
71+
elif command == 'or':
72+
self._asm_file.write('M=M|D\n')
73+
74+
75+
def write_push_pop(self, command, segment, index):
76+
"""Writes the assembly code that is the
77+
translation of the given command, where
78+
command is either C_PUSH or C_POP.
79+
"""
80+
self._asm_file.write('@'+str(index)+'\n')
81+
self._asm_file.write('D=A\n')
82+
if command is Command.C_PUSH:
83+
if segment == 'constant':
84+
self._asm_file.write('@SP\n')
85+
self._asm_file.write('AM=M+1\n')
86+
self._asm_file.write('A=A-1\n')
87+
self._asm_file.write('M=D\n')
88+
elif segment in ['local', 'argument', 'this', 'that', 'pointer', 'temp', 'static']:
89+
if segment == 'local':
90+
self._asm_file.write('@LCL\n')
91+
self._asm_file.write('A=D+M\n')
92+
elif segment == 'argument':
93+
self._asm_file.write('@ARG\n')
94+
self._asm_file.write('A=D+M\n')
95+
elif segment == 'this':
96+
self._asm_file.write('@THIS\n')
97+
self._asm_file.write('A=D+M\n')
98+
elif segment == 'that':
99+
self._asm_file.write('@THAT\n')
100+
self._asm_file.write('A=D+M\n')
101+
elif segment == 'pointer':
102+
self._asm_file.write('@THIS\n')
103+
self._asm_file.write('A=D+A\n')
104+
elif segment == 'temp':
105+
self._asm_file.write('@5\n')
106+
self._asm_file.write('A=D+A\n')
107+
elif segment == 'static':
108+
self._asm_file.write('@16\n')
109+
self._asm_file.write('A=D+A\n')
110+
self._asm_file.write('D=M\n')
111+
self._asm_file.write('@SP\n')
112+
self._asm_file.write('AM=M+1\n')
113+
self._asm_file.write('A=A-1\n')
114+
self._asm_file.write('M=D\n')
115+
else:
116+
raise ValueError('Invalid segment ', segment)
117+
self.close()
118+
elif command is Command.C_POP:
119+
if segment in ['local', 'argument', 'this', 'that', 'pointer', 'temp', 'static']:
120+
if segment == 'local':
121+
self._asm_file.write('@LCL\n')
122+
self._asm_file.write('D=D+M\n')
123+
elif segment == 'argument':
124+
self._asm_file.write('@ARG\n')
125+
self._asm_file.write('D=D+M\n')
126+
elif segment == 'this':
127+
self._asm_file.write('@THIS\n')
128+
self._asm_file.write('D=D+M\n')
129+
elif segment == 'that':
130+
self._asm_file.write('@THAT\n')
131+
self._asm_file.write('D=D+M\n')
132+
elif segment == 'pointer':
133+
self._asm_file.write('@THIS\n')
134+
self._asm_file.write('D=D+A\n')
135+
elif segment == 'temp':
136+
self._asm_file.write('@5\n')
137+
self._asm_file.write('D=D+A\n')
138+
elif segment == 'static':
139+
self._asm_file.write('@16\n')
140+
self._asm_file.write('D=D+A\n')
141+
self._asm_file.write('@R13\n')
142+
self._asm_file.write('M=D\n')
143+
self._asm_file.write('@SP\n')
144+
self._asm_file.write('AM=M-1\n')
145+
self._asm_file.write('D=M\n')
146+
self._asm_file.write('@R13\n')
147+
self._asm_file.write('A=M\n')
148+
self._asm_file.write('M=D\n')
149+
else:
150+
raise ValueError('Invalid segment ', segment)
151+
self.close()
152+
else:
153+
raise ValueError('Invalid command ', command)
154+
self.close()
155+
156+
157+
def close(self):
158+
"""Closes the output Hack ASM file.
159+
"""
160+
self._asm_file.close()

07/vmt_modules/command.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env python3
2+
3+
4+
"""Command Enum
5+
Provides an enum for the command types associated with
6+
the VM language.
7+
"""
8+
9+
10+
import enum
11+
12+
13+
__author__ = "Merrick Ryman"
14+
__version__ = "1.0"
15+
16+
17+
class Command(enum.Enum):
18+
C_ARITHMETIC = 1
19+
C_PUSH = 2
20+
C_POP = 3
21+
C_LABEL = 4
22+
C_GOTO = 5
23+
C_IF = 6
24+
C_FUNCTION = 7
25+
C_RETURN = 8
26+
C_CALL = 9

07/vmt_modules/parser.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env python3
2+
3+
4+
"""The Parser Module
5+
Handles the parsing of a single .vm file, and encapsulates
6+
access to the input code. Overall: reads VM commands, parses them,
7+
and provides convenient access to their components. In addition,
8+
remove all white space and comments.
9+
"""
10+
11+
12+
from .command import Command
13+
14+
15+
class Parser:
16+
def __init__(self, in_path):
17+
self.in_path = in_path
18+
self._vm_commands = None
19+
self._vm_command_current = None
20+
self._vm_commands_iter = None
21+
self._parse_vm_file()
22+
self._reset_iter()
23+
24+
25+
def _parse_vm_file(self):
26+
"""Parse the Hack ASM file into memory so it
27+
is easier to work with. Remove comments, spaces,
28+
empty lines, etc, so all that remains are commands
29+
(and pseudo-commands). Also, create an iterable
30+
to get the next command.
31+
"""
32+
with open(self.in_path, 'r') as vm_file:
33+
lines = vm_file.readlines()
34+
lines = list(map(lambda x: x[:x.find('//')], lines))
35+
lines = list(filter(None, lines))
36+
self._vm_commands = self._format_vm_commands(lines)
37+
38+
39+
def _format_vm_commands(self, commands):
40+
"""Format a list of VM commands; in other words,
41+
break up the input arguments. Returns the
42+
commands in the form of a list of dictionaries.
43+
Each dictionary pertains to its particular
44+
command.
45+
"""
46+
formatted_list = []
47+
for command in commands:
48+
args_dict = {'name': None, 'arg1': None, 'arg2': None}
49+
args = command.split()
50+
args_dict['name'] = args[0]
51+
if len(args) == 2 or len(args) == 3:
52+
args_dict['arg1'] = args[1]
53+
if len(args) == 3:
54+
args_dict['arg2'] = args[2]
55+
formatted_list.append(args_dict)
56+
return formatted_list
57+
58+
59+
def _reset_iter(self):
60+
"""Resets (or sets) the iterator object that
61+
iterates over the vm commands. Useful for
62+
re-parsing the file as necessary.
63+
"""
64+
self._vm_commands_iter = iter(self._vm_commands)
65+
66+
67+
def has_more_commands(self):
68+
"""Are there any more commands in the file?
69+
Returns True if command iterable successfully
70+
gets the next command. Otherwise, return False.
71+
"""
72+
try:
73+
self._vm_command_current = next(self._vm_commands_iter)
74+
return True
75+
except StopIteration:
76+
return False
77+
78+
79+
def command_type(self):
80+
"""Returns the type of the current VM command.
81+
C_ARITHMETIC is returned for all the arithmetic
82+
commands.
83+
"""
84+
return {
85+
'push': Command.C_PUSH,
86+
'pop': Command.C_POP,
87+
'label': Command.C_LABEL,
88+
'goto': Command.C_GOTO,
89+
'if-goto': Command.C_IF,
90+
'function': Command.C_FUNCTION,
91+
'call': Command.C_CALL,
92+
'return': Command.C_RETURN
93+
}.get(self._vm_command_current['name'], Command.C_ARITHMETIC)
94+
95+
96+
def arg1(self):
97+
"""Returns the first argument of the current
98+
command. In the case of C_ARITHMETIC, the
99+
command itself (add, sub, etc.) is returned.
100+
Should not be called if the current command is
101+
C_RETURN.
102+
"""
103+
if self.command_type() is Command.C_ARITHMETIC:
104+
return self._vm_command_current['name']
105+
else:
106+
return self._vm_command_current['arg1']
107+
108+
109+
def arg2(self):
110+
"""Returns the second argument of the current
111+
command. Should only be called if the current
112+
command is C_PUSH, C_POP, C_FUNCTION, or
113+
C_CALL.
114+
"""
115+
return int(self._vm_command_current['arg2'])

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ HDL for Hack machine.
2121

2222
### Chapter 06 - Assembler
2323
Python3 for Hack Assembler and Hack Assembler(No Symbols).\
24-
Python3 for Assembler Modules Code, Command, Parser, and SymbolTable.
24+
Python3 for Assembler modules Code, Command, Parser, and SymbolTable.
2525
* **UPDATE 3/18/19:** Assembler module Parser v2.0 is now out! This adds support for macro-commands.
2626
* *For an example of this*, take a look at Max_no_macros.asm, Max_macros.asm, and their respective Hack files in the 06 project directory.
2727
* *Note that the Hack machine code is equivalent*, while the asm file with macro-commands has essentially halved.
@@ -30,7 +30,8 @@ Also take note that all A-Instructions have essentially 'piggy backed' onto C-In
3030
Of course, this also works for symbols: 'M=D[foo]' is equivalent to '@foo' followed by 'M=D'.
3131

3232
### Chapter 07 - VM I: Stack Arithmetic
33-
#### In Progress (soon...)
33+
Python3 for Virtual Machine Translator.\
34+
Python3 for VMT modules CodeWriter, Parser, and Command.
3435

3536
### Chapter 08 - VM II: Program Control
3637
#### In Progress.

0 commit comments

Comments
 (0)