-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathinterpreterv1.py
142 lines (124 loc) · 5.53 KB
/
interpreterv1.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
# Add to spec:
# - printing out a nil value is undefined
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
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"))
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_NODE:
self.__call_func(statement)
elif statement.elem_type == "=":
self.__assign(statement)
elif statement.elem_type == InterpreterBase.VAR_DEF_NODE:
self.__var_def(statement)
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)
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"))
if not self.env.set(var_name, value_obj):
super().error(
ErrorType.NAME_ERROR, f"Undefined variable {var_name} in assignment"
)
def __var_def(self, var_ast):
var_name = var_ast.get("name")
if not self.env.create(var_name, Value(Type.INT, 0)):
super().error(
ErrorType.NAME_ERROR, f"Duplicate definition for variable {var_name}"
)
def __eval_expr(self, expr_ast):
if expr_ast.elem_type == InterpreterBase.INT_NODE:
return Value(Type.INT, expr_ast.get("val"))
if expr_ast.elem_type == InterpreterBase.STRING_NODE:
return Value(Type.STRING, expr_ast.get("val"))
if expr_ast.elem_type == InterpreterBase.VAR_NODE:
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_NODE:
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.elem_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