Skip to content

Commit ef2fe12

Browse files
committed
Chapter 8 - Virtual Machine II: Program Control
Python3 for Virtual Machine Translator.\nPython3 for VMT modules CodeWriter, Parser, and Command.\nImplements Stage I - Stage IV of the VM.
1 parent a50f7bc commit ef2fe12

File tree

7 files changed

+655
-1
lines changed

7 files changed

+655
-1
lines changed

08/vmt.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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+
__author__ = "Merrick Ryman"
20+
__version__ = "2.0"
21+
22+
23+
def main():
24+
vm_path = Path(sys.argv[-1])
25+
vm_glob = []
26+
asm_path = (str(vm_path.parent)
27+
+ '/'
28+
+ (vm_path.name[:vm_path.name.find('.')]
29+
+ '.asm'))
30+
31+
if vm_path.name == 'vmt.py':
32+
raise IOError('Please supply a .vm source file/directory.')
33+
if vm_path.is_dir():
34+
vm_glob = list(vm_path.glob('*.vm'))
35+
if not vm_glob:
36+
raise RuntimeError('No *.vm files found in supplied directory.')
37+
asm_path = (str(vm_path.parent)
38+
+ '/' + vm_path.name
39+
+ '/' + vm_path.name
40+
+ '.asm')
41+
elif vm_path.is_file():
42+
if vm_path.suffix != '.vm':
43+
raise IOError('Valid VM source file not supplied.')
44+
vm_glob.append(vm_path)
45+
46+
code_writer = CodeWriter(asm_path)
47+
code_writer.write_init()
48+
for vm_file in vm_glob:
49+
code_writer.set_file_name(vm_file.name)
50+
func_name = '$'
51+
vm_parser = Parser(vm_file)
52+
while vm_parser.has_more_commands():
53+
if vm_parser.command_type() is Command.C_ARITHMETIC:
54+
code_writer.write_arithmetic(vm_parser.arg1())
55+
elif vm_parser.command_type() is Command.C_PUSH:
56+
code_writer.write_push_pop(Command.C_PUSH,
57+
vm_parser.arg1(),
58+
vm_parser.arg2())
59+
elif vm_parser.command_type() is Command.C_POP:
60+
code_writer.write_push_pop(Command.C_POP,
61+
vm_parser.arg1(),
62+
vm_parser.arg2())
63+
elif vm_parser.command_type() is Command.C_LABEL:
64+
code_writer.write_label(func_name+'$'+vm_parser.arg1())
65+
elif vm_parser.command_type() is Command.C_GOTO:
66+
code_writer.write_goto(func_name+'$'+vm_parser.arg1())
67+
elif vm_parser.command_type() is Command.C_IF:
68+
code_writer.write_if(func_name+'$'+vm_parser.arg1())
69+
elif vm_parser.command_type() is Command.C_FUNCTION:
70+
code_writer.write_function(vm_parser.arg1(), vm_parser.arg2())
71+
func_name = vm_parser.arg1()
72+
elif vm_parser.command_type() is Command.C_CALL:
73+
code_writer.write_call(vm_parser.arg1(), vm_parser.arg2())
74+
elif vm_parser.command_type() is Command.C_RETURN:
75+
code_writer.write_return()
76+
else:
77+
raise ValueError('Command not recognized: ',
78+
vm_parser.command_type())
79+
code_writer.close()
80+
81+
print('[=>] IN .vm file(s):')
82+
print(*vm_glob, sep='\n')
83+
print('[<=] OUT .asm file:\n' + asm_path)
84+
85+
86+
if __name__ == "__main__":
87+
main()

08/vmt_modules/__init__.py

Whitespace-only changes.

08/vmt_modules/code_writer-bu.py

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#!/usr/bin/env python3
2+
3+
4+
"""The CodeWriter Module
5+
Translates given VM commands into Hack ASM code.
6+
Implements: + Stage I (Arithmetic and Logic Commands)
7+
+ Stage II: (Memory Access Commands)
8+
+ Stage III: (Program Flow Commands)
9+
+ Stage IV: (Function Calling Commands)
10+
"""
11+
12+
13+
from .command import Command
14+
15+
16+
__author__ = "Merrick Ryman"
17+
__version__ = "2.0"
18+
19+
20+
class CodeWriter:
21+
def __init__(self, out_path):
22+
self.out_path = out_path
23+
self._vm_file = None
24+
self._asm_file = open(out_path, 'w+')
25+
self._bool_jmp_logic_symbol = ''
26+
self._dynamic_labels = {'lt': 0, 'eq': 0, 'gt': 0, 'ret': 0}
27+
28+
def _write_asm_commands(self, asm_commands):
29+
self._asm_file.writelines('{}\n'.format(x) for x in asm_commands)
30+
31+
def set_file_name(self, file_name):
32+
"""Informs the code writer that the
33+
translation of a new VM file has started.
34+
"""
35+
self._vm_file = file_name[:file_name.find('.')]
36+
37+
def write_arithmetic(self, command):
38+
"""Writes the assembly code that is the
39+
translation of the given arithmetic
40+
command.
41+
"""
42+
out = []
43+
if command in ['neg', 'not']:
44+
out.append('A=M-1[SP]')
45+
if command == 'neg':
46+
out.append('M=-M')
47+
elif command == 'not':
48+
out.append('M=!M')
49+
else:
50+
out.extend(['AM=M-1[SP]', 'D=M', 'A=A-1'])
51+
if command == 'add':
52+
out.append('M=M+D')
53+
elif command == 'sub':
54+
out.append('M=M-D')
55+
elif command in ['lt', 'eq', 'gt']:
56+
out.extend(['D=M-D', 'M=0'])
57+
if command == 'lt':
58+
self._bool_jmp_logic_symbol = 'LTJGE${}'.format(self._dynamic_labels['lt'])
59+
self._dynamic_labels['lt'] += 1
60+
out.append('D;JGE[{}]'.format(self._bool_jmp_logic_symbol))
61+
elif command == 'eq':
62+
self._bool_jmp_logic_symbol = 'EQJNE${}'.format(self._dynamic_labels['eq'])
63+
self._dynamic_labels['eq'] += 1
64+
out.append('D;JNE[{}]'.format(self._bool_jmp_logic_symbol))
65+
elif command == 'gt':
66+
self._bool_jmp_logic_symbol = 'GTJLE${}'.format(self._dynamic_labels['gt'])
67+
self._dynamic_labels['gt'] += 1
68+
out.append('D;JLE[{}]'.format(self._bool_jmp_logic_symbol))
69+
out.extend(['A=M-1[SP]', 'M=-1', '({})'.format(self._bool_jmp_logic_symbol)])
70+
elif command == 'and':
71+
out.append('M=M&D')
72+
elif command == 'or':
73+
out.append('M=M|D')
74+
self._write_asm_commands(out)
75+
76+
def write_push_pop(self, command, segment, index):
77+
"""Writes the assembly code that is the
78+
translation of the given command, where
79+
command is either C_PUSH or C_POP.
80+
"""
81+
out = []
82+
out.append('D=A[{}]'.format(index))
83+
if command is Command.C_PUSH:
84+
if segment == 'constant':
85+
out.extend(['AM=M+1[SP]', 'A=A-1', 'M=D'])
86+
elif segment in ['local', 'argument', 'this', 'that',
87+
'pointer', 'temp', 'static']:
88+
if segment == 'local':
89+
out.append('A=D+M[LCL]')
90+
elif segment == 'argument':
91+
out.append('A=D+M[ARG]')
92+
elif segment == 'this':
93+
out.append('A=D+M[THIS]')
94+
elif segment == 'that':
95+
out.append('A=D+M[THAT]')
96+
elif segment == 'pointer':
97+
out.append('A=D+A[THIS]')
98+
elif segment == 'temp':
99+
out.append('A=D+A[5]')
100+
elif segment == 'static':
101+
out.append('@{}.{}'.format(self._vm_file, index))
102+
out.extend(['D=M', 'AM=M+1[SP]', 'A=A-1', 'M=D'])
103+
else:
104+
raise ValueError('Invalid segment ', segment)
105+
self.close()
106+
elif command is Command.C_POP:
107+
if segment in ['local', 'argument', 'this', 'that',
108+
'pointer', 'temp', 'static']:
109+
if segment == 'local':
110+
out.append('D=D+M[LCL]')
111+
elif segment == 'argument':
112+
out.append('D=D+M[ARG]')
113+
elif segment == 'this':
114+
out.append('D=D+M[THIS]')
115+
elif segment == 'that':
116+
out.append('D=D+M[THAT]')
117+
elif segment == 'pointer':
118+
out.append('D=D+A[THIS]')
119+
elif segment == 'temp':
120+
out.append('D=D+A[5]')
121+
elif segment == 'static':
122+
out.append('D=A[{}.{}]'.format(self._vm_file, index))
123+
out.extend(['M=D[R13]', 'AM=M-1[SP]', 'D=M', 'A=M[R13]', 'M=D'])
124+
else:
125+
raise ValueError('Invalid segment ', segment)
126+
self.close()
127+
else:
128+
raise ValueError('Invalid command ', command)
129+
self.close()
130+
self._write_asm_commands(out)
131+
132+
def write_init(self):
133+
"""Writes Hack ASM that effects the VM
134+
initialization, also called bootstrap
135+
code. This code must be placed at the
136+
beginning of the output file.
137+
"""
138+
self._write_asm_commands(['D=A[256]', 'M=D[SP]'])
139+
self.write_call('Sys.init', 0)
140+
141+
def write_label(self, label):
142+
"""Writes Hack ASM that effects the
143+
label command.
144+
"""
145+
self._write_asm_commands(['({})'.format(label)])
146+
147+
def write_goto(self, label):
148+
"""Writes Hack ASM that effects the
149+
goto command.
150+
"""
151+
self._write_asm_commands(['0;JMP[{}]'.format(label)])
152+
153+
def write_if(self, label):
154+
"""Writes Hack ASM that effects the
155+
if-goto command.
156+
"""
157+
self._write_asm_commands(['AM=M-1[SP]', 'D=M',
158+
'D;JNE[{}]'.format(label)])
159+
160+
def _push_pointer_value(self, pointer_name):
161+
"""Push the value of a special pointer,
162+
given the name (LCL, ARG, THIS, THAT).
163+
"""
164+
self._write_asm_commands(['D=M[{}]'.format(pointer_name), 'AM=M+1[SP]',
165+
'A=A-1', 'M=D'])
166+
167+
def write_call(self, function_name, num_args):
168+
"""Writes Hack ASM that effects the
169+
call command.
170+
"""
171+
return_label = '{}$ret.{}'.format(function_name, self._dynamic_labels['ret'])
172+
self._dynamic_labels['ret'] += 1
173+
self._write_asm_commands(['D=A[{}]'.format(return_label), 'AM=M+1[SP]',
174+
'A=A-1', 'M=D'])
175+
self._push_pointer_value('LCL')
176+
self._push_pointer_value('ARG')
177+
self._push_pointer_value('THIS')
178+
self._push_pointer_value('THAT')
179+
self._write_asm_commands(['D=M[SP]', 'D=D-A[{}]'.format(num_args),
180+
'D=D-A[5]', 'M=D[ARG]'])
181+
self._write_asm_commands(['D=M[SP]', 'M=D[LCL]'])
182+
self._write_asm_commands(['0;JMP[{}]'.format(function_name)])
183+
self.write_label(return_label)
184+
185+
def write_return(self):
186+
"""Writes Hack ASM that effects the
187+
return command. Oh boy, this was a fun
188+
one to implement...
189+
"""
190+
self._write_asm_commands(['D=M[LCL]', 'M=D[R13]'])
191+
self._write_asm_commands(['D=D-A[5]', 'A=D', 'D=M', 'M=D[R14]'])
192+
self._write_asm_commands(['AM=M-1[SP]', 'D=M', 'A=M[ARG]', 'M=D'])
193+
self._write_asm_commands(['D=M+1[ARG]', 'M=D[SP]'])
194+
self._write_asm_commands(['D=M-1[R13]', 'A=D', 'D=M', 'M=D[THAT]'])
195+
self._write_asm_commands(['D=A[2]', 'D=M-D[R13]', 'A=D', 'D=M', 'M=D[THIS]'])
196+
self._write_asm_commands(['D=A[3]', 'D=M-D[R13]', 'A=D', 'D=M', 'M=D[ARG]'])
197+
self._write_asm_commands(['D=A[4]', 'D=M-D[R13]', 'A=D', 'D=M', 'M=D[LCL]'])
198+
self._write_asm_commands(['A=M[R14]', '0;JMP'])
199+
200+
def write_function(self, function_name, num_locals):
201+
"""Writes Hack ASM that effects the
202+
function command.
203+
"""
204+
self.write_label(function_name)
205+
for x in range(0, num_locals):
206+
self.write_push_pop(Command.C_PUSH, 'constant', 0)
207+
208+
def close(self):
209+
"""Closes the output Hack ASM file.
210+
"""
211+
self._asm_file.close()

0 commit comments

Comments
 (0)