-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfim_interpreter.py
391 lines (329 loc) · 13.2 KB
/
fim_interpreter.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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
import special_words
import fim_ast
import utility
from fim_lexer import Keywords, Literals, Token
from fim_callable import FimClass, FimCallable
from fim_exception import FimRuntimeException
from environment import Environment
import fim_callable
from node_visitor import NodeVisitor
import operator
class Interpreter(NodeVisitor):
def __init__(self, parser):
self.parser = parser
self.globals = Environment()
self.environment = self.globals
self.locals = {}
def reset(self):
self.globals = Environment()
self.environment = self.globals
self.locals = {}
def interpret(self, tree):
for variable in self.globals.values():
if isinstance(variable, fim_ast.AST):
self.visit(variable)
return self.visit(tree)
def resolve(self, node, depth):
self.locals[node] = depth
def set_builtin_globals(self):
self.globals.define(
special_words.base_class_name,
FimClass(special_words.base_class_name, None, {}, {}))
self.define_builtin_types()
def visit_BinOp(self, node):
left = self.visit(node.left)
right = self.visit(node.right)
try:
if node.op.type == Keywords.ADDITION:
return left + right
elif node.op.type == Keywords.SUBTRACTION:
return left - right
elif node.op.type == Keywords.MULTIPLICATION:
return left * right
elif node.op.type == Keywords.DIVISION:
return left / right
elif node.op.type == Keywords.GREATER_THAN:
return left > right
elif node.op.type == Keywords.LESS_THAN:
return left < right
elif node.op.type == Keywords.GREATER_THAN_OR_EQUAL:
return left >= right
elif node.op.type == Keywords.LESS_THAN_OR_EQUAL:
return left <= right
elif node.op.type == Keywords.EQUAL:
return left == right
elif node.op.type == Keywords.NOT_EQUAL:
return left != right
elif node.op.type == Keywords.AND:
if type(left) == float and type(right) == float:
return left + right
return left and right
elif node.op.type == Keywords.OR:
return left or right
elif node.op.type == Keywords.XOR:
return left ^ right
elif node.op.type == Keywords.CONCAT:
return stringify(left) + stringify(right)
elif node.op.type == Keywords.MODULO:
return left % right
else:
raise FimRuntimeException(node.op,
f"Unknown operator: {node.op}")
except TypeError:
raise FimRuntimeException(
node.op,
f'Cannot perform operation {node.op.value}'
f' with {stringify(left)} and {stringify(right)}')
def visit_UnaryOp(self, node):
op = node.op.type
if op == Keywords.NOT:
return not (self.visit(node.expr))
def visit_Number(self, node):
return float(node.value)
def visit_String(self, node):
return node.value
def visit_Char(self, node):
return node.value
def visit_Bool(self, node):
return node.value
def visit_Null(self, node):
return node.value
def visit_Compound(self, node):
self.execute_compound(node.children, Environment(self.environment))
def visit_Root(self, node):
for declaration in node.children:
if isinstance(declaration, fim_ast.Class):
continue
self.visit(declaration)
def execute_compound(self, statements, environment):
previous_env = self.environment
try:
self.environment = environment
for statement in statements:
self.visit(statement)
finally:
self.environment = previous_env
def visit_If(self, node):
if self.visit(node.condition):
self.visit(node.then_branch)
elif node.else_branch is not None:
self.visit(node.else_branch)
def visit_While(self, node):
while self.visit(node.condition):
self.visit(node.body)
def visit_DoWhile(self, node):
while True:
self.visit(node.body)
if not self.visit(node.condition):
break
def visit_For(self, node):
self.visit(node.init)
body = node.body
self.visit(fim_ast.While(node.condition, body))
def visit_ForIter(self, node):
self.visit(node.init)
for i in iter(self.visit(node.iterable)):
self.environment.assign(node.init.left, i)
self.visit(node.body)
def visit_StatementList(self, node):
for child in node.children:
self.visit(child)
def visit_NoOp(self, node):
pass
def visit_Assign(self, node):
value = self.visit(node.right)
distance = self.locals.get(node.left)
if distance is not None:
self.environment.assign_at(distance, node.left.token, value)
else:
try:
instance = self.environment.get(special_words.this)
instance.set(node.left, value)
except FimRuntimeException:
self.globals.assign(node.left, value)
return value
def visit_VariableDeclaration(self, node):
var_name = node.left.value
self.environment.define(var_name, self.visit(node.right))
def visit_Var(self, node):
val = self.lookup_variable(node.token, node)
if hasattr(node, 'index'):
if utility.is_float_and_int(node.index):
index = int(node.index)
else:
raise FimRuntimeException(
node.token,
f"index {node.index} must be an integer")
return val.elements[index]
if isinstance(val, fim_callable.FimCallable) and val.arity() == 0:
return val.call(self, [])
return val
def lookup_variable(self, token, node):
distance = self.locals.get(node)
if distance is not None:
return self.environment.get_at(distance, token.value)
else:
try:
instance = self.environment.get(special_words.this)
res = instance.get(token)
return res
except FimRuntimeException:
return self.globals.get(token.value)
def visit_FunctionCall(self, node):
function = self.visit(node.name)
arguments = []
for argument in node.arguments:
arguments.append(self.visit(argument))
if len(arguments) != function.arity():
raise FimRuntimeException(
node.name,
f"Function '{node}' expected {function.arity()}"
f" arguments, got {len(arguments)}")
if isinstance(function, fim_callable.FimCallable):
return function.call(self, arguments)
else:
raise FimRuntimeException(node.name, f"{node} is not a function")
def visit_Return(self, node):
value = None
if node.value is not None:
value = self.visit(node.value)
raise fim_callable.FimReturn(value)
def visit_Increment(self, node):
distance = self.locals.get(node.variable)
if isinstance(node.value, int):
value = node.value
else:
value = self.visit(node.value)
if distance is None:
self.environment.modify(node.variable.token, operator.add, value)
else:
self.environment.modify_at(
distance, node.variable.token, operator.add, value)
def visit_Decrement(self, node):
distance = self.locals.get(node.variable)
if distance is None:
self.environment.modify(node.variable.token, operator.sub, 1)
else:
self.environment.modify_at(
distance, node.variable.token, operator.sub, 1)
def visit_Print(self, node):
res = self.visit(node.expr)
print(stringify(res))
def visit_Read(self, node):
line = input()
self.visit_Assign(fim_ast.Assign(
node.variable,
fim_ast.String(
Token(line, Literals.STRING, None, None, None, None))))
def visit_Function(self, node):
self.environment.declare(node.name)
fim_function = fim_callable.FimFunction(node, self.environment)
self.environment.assign(node.name, fim_function)
def visit_Class(self, node):
superclass = self.lookup_variable(
node.superclass.token, node.superclass)
if isinstance(superclass, fim_ast.AST):
superclass = self.visit(superclass)
if not isinstance(superclass, FimClass):
raise FimRuntimeException(node.superclass.token,
f"{superclass} is not a class")
# I use assign because it was defined as fim_ast.Class in resolver
self.environment.assign(node.name, None)
methods = {}
main_method_token = None
for method in node.methods.values():
function = fim_callable.FimFunction(method, self.environment)
methods[method.token.value] = function
if method.is_main:
main_method_token = function.declaration.token
fields = {}
for field in node.fields.values():
fields[field.left.value] = self.visit(field.right)
fim_class = FimClass(node.name.value, superclass, methods, fields)
self.environment.assign(node.name, fim_class)
if main_method_token is not None:
instance = fim_class.call(self, [])
instance.get(main_method_token).call(self, [])
def visit_Interface(self, node):
pass
def visit_Get(self, node):
obj = self.visit(node.object)
if isinstance(obj, fim_callable.FimArray):
array = self.visit(node.object)
index = self.visit(node.name)
if utility.is_float_and_int(index):
index = int(index)
else:
raise FimRuntimeException(node.name,
f"Index {index} must be an integer")
return array.elements[index]
if isinstance(obj, fim_callable.FimInstance):
field = obj.get(node.name)
if isinstance(field, FimCallable) and not node.has_parameters:
return field.call(self, [])
return field
raise FimRuntimeException(
node.object.token,
f"{node.object.token} only instances or arrays have properties")
def visit_Set(self, node):
obj = self.visit(node.object)
if isinstance(obj, fim_callable.FimInstance):
value = self.visit(node.value)
obj.set(node.name, value)
return value
raise FimRuntimeException(
node.object.token,
f"{node.object.token} only instances have properties")
def visit_Switch(self, node):
cases = {}
for case, body in node.cases.items():
cases[self.visit(case)] = body
variable_value = self.visit(node.variable)
if variable_value in cases:
self.visit(cases[variable_value])
else:
self.visit(node.default)
def visit_Import(self, node):
pass
def visit_Array(self, node):
array_name = node.name.value
elements = []
if isinstance(node.elements, fim_ast.BinOp):
bin_op = node.elements
while isinstance(bin_op, fim_ast.BinOp):
elements.append(self.visit(bin_op.right))
bin_op = bin_op.left
self.environment.define(array_name, fim_callable.FimArray(elements))
def visit_ArrayElementAssignment(self, node):
array = self.lookup_variable(node.left.token, node.left)
if not isinstance(node.index, int):
index = self.visit(node.index)
if utility.is_float_and_int(index):
index = int(index)
node.index = index
if node.index >= len(array.elements):
array.elements.extend(
[None] * (node.index - len(array.elements) + 1))
array.elements[node.index] = self.visit(node.right)
def define_builtin_types(self):
builtin_types = [
'number', 'a number', 'the number',
'letter', 'a letter', 'the letter',
'character', 'a character', 'the character',
'sentence', 'phrase', 'quote', 'word', 'name',
'a sentence', 'a phrase', 'a quote', 'a word', 'a name',
'the sentence', 'the phrase', 'the quote', 'the word', 'the name',
'logic', 'the logic', 'argument', 'an argument', 'the argument']
for name in builtin_types:
self.environment.define(name, None)
def stringify(obj):
# if res is float and can be int, convert it to int
if utility.is_float_and_int(obj):
return str(int(obj))
if obj is None:
return "nothing"
if obj is True:
return "true"
if obj is False:
return "false"
return str(obj)