-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbytecode.py
More file actions
175 lines (133 loc) · 5.71 KB
/
bytecode.py
File metadata and controls
175 lines (133 loc) · 5.71 KB
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
from tokens import *
from visitor import NodeVisitor
class BytecodeVisitor(NodeVisitor):
"""Turns the AST returned from the parser into stack-based bytecode that the VM can execute"""
def __init__(self):
self.bytecode = ""
self.label_count = 0
def runtime_error(self, message, node):
raise BytecodeError(message, node.line, node.column)
def emit(self, line):
self.bytecode += f"{line}\n"
def visit_Program(self, node):
self.emit("JMP __main\n")
# expand self.visit(node.block) to avoid encoding main jump into every block
for declaration in node.block.declarations:
self.visit(declaration)
self.emit("LABEL __main")
self.visit(node.block.compound_statement)
self.emit("HALT")
def visit_Assign(self, node):
self.visit(node.right)
self.emit(f"STORE {node.left.name}")
def visit_ProcedureCall(self, node):
for arg in node.params:
self.visit(arg)
self.emit(f"CALL {node.name}")
def visit_FunctionCall(self, node):
for arg in node.params:
self.visit(arg)
self.emit(f"CALL {node.name}")
def visit_If(self, node):
self.visit(node.condition) # push the boolean value of the condition
else_label = f"else_{self.label_count}" # create the labels with the label number so they are distinct
end_label = f"end_{self.label_count}"
self.label_count += 1
self.emit(f"JMP_IF_FALSE {else_label}")
self.visit(node.true_statement)
if node.false_statement is not None:
self.emit(f"JMP {end_label}")
self.emit(f"LABEL {else_label}")
self.visit(node.false_statement)
self.emit(f"LABEL {end_label}")
return
self.emit(f"LABEL {else_label}")
def visit_While(self, node):
start_label = f"start_{self.label_count}" # create the labels with the label number so they are distinct
end_label = f"end_{self.label_count}"
self.label_count += 1
self.emit(f"LABEL {start_label}")
self.visit(node.condition)
self.emit(f"JMP_IF_FALSE {end_label}")
self.visit(node.statement)
self.emit(f"JMP {start_label}")
self.emit(f"LABEL {end_label}")
def visit_For(self, node):
start_label = f"start_{self.label_count}" # create the labels with the label number so they are distinct
end_label = f"end_{self.label_count}"
self.label_count += 1
self.visit(node.start_expr)
self.emit(f"STORE {node.var.name}") # load the start expression into the iterator
self.emit(f"LABEL {start_label}")
self.emit(f"LOAD {node.var.name}")
self.visit(node.end_expr)
self.emit("LTE" if node.direction == TO else "GTE")
self.emit(f"JMP_IF_FALSE {end_label}") # ends the loop
self.visit(node.statement)
self.emit(f"LOAD {node.var.name}") # load the iterator
self.emit("PUSH_INT 1")
self.emit("ADD" if node.direction == TO else "SUB") # TO increases iterator, DOWNTO decreases it
self.emit(f"STORE {node.var.name}") # stores the iterator again
self.emit(f"JMP {start_label}")
self.emit(f"LABEL {end_label}")
def visit_WriteLn(self, node):
self.visit(node.expression)
self.emit("WRITELN")
def visit_Write(self, node):
self.visit(node.expression)
self.emit("WRITE")
def visit_ProcDecl(self, node):
self.emit(f"LABEL {node.name}")
for param in reversed(node.params): # Params are reversed because they are pushed to the stack in the correct order so they need to popped in reverse
self.emit(f"STORE {param.var_node.name}")
self.visit(node.block) # emits the body of the procedure
self.emit("RET\n") # returns nothing
def visit_FuncDecl(self, node):
self.emit(f"LABEL {node.name}")
for param in reversed(node.params): # Params are reversed because they are pushed to the stack in the correct order so they need to popped in reverse
self.emit(f"STORE {param.var_node.name}")
self.visit(node.block) # emits the body of the function
self.emit(f"LOAD {node.name}")
self.emit("RET\n")
BINARY_OPCODE_MAP = {
PLUS: "ADD",
MINUS: "SUB",
MUL: "MUL",
FLOAT_DIV: "DIV",
INTEGER_DIV: "IDIV",
EQUAL: "EQ",
NOT_EQUAL: "NEQ",
LESS_THAN: "LT",
LESS_EQUAL: "LTE",
GREATER_THAN: "GT",
GREATER_EQUAL: "GTE",
}
def visit_BinaryOperation(self, node):
self.visit(node.left)
self.visit(node.right)
opcode = self.BINARY_OPCODE_MAP.get(node.value)
if opcode is not None:
self.emit(opcode)
return
self.runtime_error(f"Unsupported binary operator: {node.value}", node)
def visit_UnaryOperation(self, node):
if node.child is not None:
self.visit(node.child)
if node.value == PLUS:
return
elif node.value == MINUS:
self.emit("NEG")
# return -childvalue
return
self.runtime_error(f"Unsupported unary operator: {node.value}", node)
def visit_Var(self, node):
self.emit(f"LOAD {node.name}")
def visit_IntegerNode(self, node):
self.emit(f"PUSH_INT {node.value}")
def visit_RealNode(self, node):
self.emit(f"PUSH_REAL {node.value}")
def visit_BooleanNode(self, node):
self.emit(f"PUSH_BOOL {'TRUE' if node.value else 'FALSE'}")
def visit_StringNode(self, node):
# uses repr() instead of str() to return the value of node.value
self.emit(f"PUSH_STR {node.value!r}")