-
Notifications
You must be signed in to change notification settings - Fork 0
/
interpreterv2.py
174 lines (150 loc) · 5.97 KB
/
interpreterv2.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
from env_v1 import EnvironmentManager
from type_valuev1 import Type, Value, create_value, get_printable
from intbase import InterpreterBase, ErrorType
from brewparse import parse_program
# Main interpreter class
class Interpreter(InterpreterBase):
# constants
NIL_VALUE = create_value(InterpreterBase.NIL_DEF)
BIN_OPS = {"+", "-"}
# methods
def __init__(self, console_output=True, inp=None, trace_output=False):
super().__init__(console_output, inp)
self.trace_output = trace_output
self.__setup_ops()
# run a program that's provided in a string
# usese the provided Parser found in brewparse.py to parse the program
# into an abstract syntax tree (ast)
def run(self, program):
ast = parse_program(program)
self.__set_up_function_table(ast)
main_func = self.__get_func_by_name("main")
self.env = EnvironmentManager()
self.__run_statements(main_func.get("statements"))
self.env.print()
def __set_up_function_table(self, ast):
self.func_name_to_ast = {}
for func_def in ast.get("functions"):
self.func_name_to_ast[func_def.get("name")] = func_def
def __get_func_by_name(self, name):
if name not in self.func_name_to_ast:
super().error(ErrorType.NAME_ERROR, f"Function {name} not found")
return self.func_name_to_ast[name]
def __run_statements(self, statements):
# all statements of a function are held in arg3 of the function AST node
for statement in statements:
if self.trace_output:
print(statement)
if statement.elem_type == InterpreterBase.FCALL_DEF:
self.__call_func(statement)
elif statement.elem_type == "=":
self.__assign(statement)
return Interpreter.NIL_VALUE
def __call_func(self, call_node):
func_name = call_node.get("name")
if func_name == "print":
return self.__call_print(call_node)
if func_name == "inputi":
return self.__call_input(call_node)
# add code here later to call other functions
super().error(ErrorType.NAME_ERROR, f"Function {func_name} not found")
def __call_print(self, call_ast):
output = ""
for arg in call_ast.get("args"):
result = self.__eval_expr(arg) # result is a Value object
output = output + get_printable(result)
super().output(output)
return Interpreter.NIL_VALUE
def __call_input(self, call_ast):
args = call_ast.get("args")
if args is not None and len(args) == 1:
result = self.__eval_expr(args[0])
super().output(get_printable(result))
elif args is not None and len(args) > 1:
super().error(
ErrorType.NAME_ERROR, "No inputi() function that takes > 1 parameter"
)
inp = super().get_input()
if call_ast.get("name") == "inputi":
return Value(Type.INT, int(inp))
# we can support inputs here later
def __assign(self, assign_ast):
var_name = assign_ast.get("name")
value_obj = self.__eval_expr(assign_ast.get("expression"))
self.env.set(var_name, value_obj)
def __eval_expr(self, expr_ast):
if expr_ast.elem_type == InterpreterBase.INT_DEF:
return Value(Type.INT, expr_ast.get("val"))
if expr_ast.elem_type == InterpreterBase.STRING_DEF:
return Value(Type.STRING, expr_ast.get("val"))
if expr_ast.elem_type == InterpreterBase.VAR_DEF:
var_name = expr_ast.get("name")
val = self.env.get(var_name)
if val is None:
super().error(ErrorType.NAME_ERROR, f"Variable {var_name} not found")
return val
if expr_ast.elem_type == InterpreterBase.FCALL_DEF:
return self.__call_func(expr_ast)
if expr_ast.elem_type in Interpreter.BIN_OPS:
return self.__eval_op(expr_ast)
def __eval_op(self, arith_ast):
left_value_obj = self.__eval_expr(arith_ast.get("op1"))
right_value_obj = self.__eval_expr(arith_ast.get("op2"))
if left_value_obj.type() != right_value_obj.type():
super().error(
ErrorType.TYPE_ERROR,
f"Incompatible types for {arith_ast.elem_type} operation",
)
if arith_ast.elem_type not in self.op_to_lambda[left_value_obj.type()]:
super().error(
ErrorType.TYPE_ERROR,
f"Incompatible operator {arith_ast.get_type} for type {left_value_obj.type()}",
)
f = self.op_to_lambda[left_value_obj.type()][arith_ast.elem_type]
return f(left_value_obj, right_value_obj)
def __setup_ops(self):
self.op_to_lambda = {}
# set up operations on integers
self.op_to_lambda[Type.INT] = {}
self.op_to_lambda[Type.INT]["+"] = lambda x, y: Value(
x.type(), x.value() + y.value()
)
self.op_to_lambda[Type.INT]["-"] = lambda x, y: Value(
x.type(), x.value() - y.value()
)
# add other operators here later for int, string, bool, etc
def main():
#all programs will be provided to your interpreter as a python string,
# just as shown here.
program_source = """func main() {
x = 5 + 6;
print("The sum is: ", x, " not ", 5 + 5);
}
"""
program_source2 = """func main() {
x = 5;
y = 6;
z = "tester";
x = z;
print(x,y,z);
print(x, " ", y, " ", z);
}
"""
program_source3 = """func main() {
y = 10;
x = 1 + (10 + (9 - y));
print(x);
print(y);
}
"""
program_source4 = """func main() {
foo = 5;
print("The answer is: ", (10 + foo) - 6, "!");
inputi("Enter a value just cuz!");
}
"""
# this is how you use our parser to parse a valid Brewin program into
# an AST:
i.run(program_source2)
i = Interpreter()
main()