-
Notifications
You must be signed in to change notification settings - Fork 2
/
CodeWriter.py
259 lines (208 loc) · 8.09 KB
/
CodeWriter.py
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# To do: improve docstrings
class CodeWriter(list):
"""
Generates symbolic Hack assembly code from parsed VM commands.
"""
SEGMENTS = {
"local": "@LCL",
"argument": "@ARG",
"this": "@THIS",
"that": "@THAT"
}
def __init__(self, file_name, include_comments):
self.current_file_name = file_name
self.include_comments = include_comments
self.comparison_count = 0
self.call_count = 0
def set_current_file_name(self, file_name):
self.current_file_name = file_name
def generate_command(self, parsed_line):
command, args = parsed_line
# Include comments (new function?)
if self.include_comments:
comment = "// " + command + " "
if args[0]:
comment += args[0] + " "
if args[1]:
comment += args[1]
self.append(comment)
# Memory Access
if command in ("push", "pop"):
self.generate_push_pop(command, args[0], int(args[1]))
# Arithmetic / Logic
elif command in ("add", "sub", "and", "or"):
self.generate_arithmetic(command)
elif command in ("neg", "not"):
self.generate_unary(command)
elif command in ("lt", "eq", "gt"):
self.generate_comparison(command)
# Branching
elif command == "label":
self.generate_label(args[0])
elif command == "goto":
self.generate_goto(args[0])
elif command == "if-goto":
self.generate_if_goto(args[0])
# Function
elif command == "function":
self.generate_function(args[0], args[1])
elif command == "call":
self.generate_call(args[0], args[1])
elif command == "return":
self.generate_return()
else:
raise Exception() # To Do - elaborate
self.append("\n")
def generate_push_pop(self, command, segment, i):
# To Do: improve comments
# Select target register (for push OR pop)
if segment == "constant":
if command == "pop":
raise Exception() # To Do - elaborate
self.append("@{}".format(i))
elif segment in ("local", "argument", "this", "that"):
self.extend([
self.SEGMENTS[segment], "D=M", "@{}".format(i), "A=D+A"
])
elif segment == "pointer":
if i not in (0, 1):
raise Exception() # To Do - elaborate
self.append("@R{}".format(3+i))
elif segment == "temp":
if i not in range(8):
raise Exception() # To Do - elaborate
self.append("@R{}".format(5+i))
elif segment == "static":
self.append("@{}.{}".format(self.current_file_name, i))
self.append("D=M")
else:
raise Exception() # To Do - elaborate
if command == "push":
if segment == "constant":
self.append("D=A")
else:
self.append("D=M")
self.extend([
# Set *SP to *addr
"@SP", "A=M", "M=D",
# Increment SP
"@SP", "M=M+1"
])
else: # command == "pop"
self.extend([
"D=A",
# Decrement SP
"@SP", "AM=M-1",
# Set *addr, *SP, and D to be equal
"D=M", "@R13", "A=M", "M=D"])
def generate_arithmetic(self, command):
# Select values to perform operation
self.extend(["@SP", "AM=M-1", "D=M", "A=A-1"])
# Perform arithmetic operation
if command == "add":
self.append("M=M+D")
elif command == "sub":
self.append("M=M-D")
elif command == "and":
self.append("M=M&D")
elif command == "or":
self.append("M=M|D")
def generate_unary(self, command):
# Select value to perform operation
self.extend(["@SP", "A=M-1"])
# Perform unary operation
if command == "neg":
self.append("M=-M")
elif command == "not":
self.append("M=!M")
def generate_comparison(self, command):
# Create labels
if_label = "c{}_if_{}".format(self.comparison_count, command)
else_label = "c{}_else".format(self.comparison_count)
# Select values for comparison, find difference
# (If difference is negative, then first < second, etc.)
self.extend(["@SP", "AM=M-1", "D=M", "A=A-1", "D=M-D"])
# Perform comparison operation
if command == "lt":
# if first < second, jump to C*_IF_LT
self.extend(["@"+if_label, "D;JLT"])
elif command == "eq":
# if first == second, jump to C*_IF_EQ
self.extend(["@"+if_label, "D;JEQ"])
elif command == "gt":
# if first > second, jump to C*_IF_GT
self.extend(["@"+if_label, "D;JGT"])
#
# Save boolean result to stack
self.extend([
# Set to false (0), jump to C*_ELSE
"D=0", "@"+else_label, "0;JMP",
# C*_IF_** jump destination, set to true (-1)
"({})".format(if_label), "D=-1",
# C*_ELSE jump destination
"({})".format(else_label),
# Save result to stack
"@SP", "A=M-1", "M=D"
])
#
self.comparison_count += 1 # For unique labels
def generate_label(self, label):
self.append("({})".format(label))
def generate_goto(self, label):
self.extend(["@"+label, "0;JMP"])
def generate_if_goto(self, label):
self.extend(["@SP", "M=M-1", "A=M", "D=M", "@"+label, "D;JNE"])
def generate_function(self, function_name, n_vars):
# Create label
self.append("({})".format(function_name))
# Push n_vars local variables onto the stack, initialized at 0
for i in range(int(n_vars)):
self.extend(["@SP", "A=M", "M=0", "@SP", "M=M+1"])
def generate_call(self, function_name, n_args):
# Create function label
label = "{}$ret.{}".format(function_name, self.call_count)
# Push return address (declared below) onto the stack
self.extend([
"@"+label, "D=A", "@SP", "A=M", "M=D", "@SP", "M=M+1"])
# Push LCL, ARG, THIS, and THAT pointers onto the stack
for pointer in ["@LCL", "@ARG", "@THIS", "@THAT"]:
self.extend([
pointer, "D=M", "@SP", "A=M", "M=D", "@SP", "M=M+1"])
self.extend([
# Set LCL pointer to SP
"@SP", "D=M", "@LCL", "M=D",
# Set ARG pointer to start of arguments in current frame
"@5", "D=D-A", "@"+str(n_args), "D=D-A", "@ARG", "M=D",
# Go to function
"@"+function_name, "0;JMP",
# Generate return address label
"({})".format(label)])
# Increment call count
self.call_count += 1
def generate_return(self):
self.extend([
# Copy LCL to R13
"@LCL", "D=M", "@R13", "M=D",
# Calculate return address, store it in R14
"@5", "A=D-A", "D=M", "@R14", "M=D",
# Move result to beginning of the ARG segment
"@SP", "M=M-1", "A=M", "D=M", "@ARG", "A=M", "M=D",
# Move SP to one after ARG
"@ARG", "D=M", "@SP", "M=D+1",
# Restore THAT
"@R13", "AM=M-1", "D=M", "@THAT", "M=D",
# Restore THIS
"@R13", "AM=M-1", "D=M", "@THIS", "M=D",
# Restore ARG
"@R13", "AM=M-1", "D=M", "@ARG", "M=D",
# Restore LCL
"@R13", "AM=M-1", "D=M", "@LCL", "M=D",
# (above, loop instead?)
# Jump to return address
"@R14", "A=M", "0;JMP"
])
def generate_init(self):
self.extend(["@256", "D=A", "@SP", "M=D"])
self.generate_call("Sys.init", 0)
def generate_end(self):
self.extend(["(EXIT)", "@EXIT", "0;JMP"])