-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinterpreter.py
236 lines (196 loc) · 10.1 KB
/
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
# SPDX-FileCopyrightText: © 2021 Jochem van Kranenburg <jochem.vankranenburg@outlook.com>
# PDX-License-Identifier: AGPL-3.0 License
from os import defpath
from typing import TypeVar, Union, Optional, List
from operator import is_not, add
from functools import partial, reduce
from itertools import chain
from parse.nodes import *
from interpret.context import Context
from interpret.number import Number
from interpret.function import Function
A = TypeVar('A')
B = TypeVar('B')
C = TypeVar('C')
# visit :: Node -> Context -> Number | [Number]
def visit(node : Node, context : Context) -> Union[Number, List[Number]]:
""" Visit node
This function visits a node and delegates the 'real' interpretation to their
corresponding inner-function. The entire Abstract Syntax Tree is interpreted
this way.
Parameters:
node (Node): Intially the root-node, but after first interpretation also other nodes.
context (Context): The context to use for interpretation.
"""
# visitOperatorNode :: OperatorNode -> Context -> Number
def visitOperatorNode(node : OperatorNode, context : Context) -> Number:
""" Interpret operator
Interpretation of the operator and/or the result thereof is delegated to the Number.
For that to be possible, the method to call is determined before it's executed.
Parameters:
node (OperatorNode): The operator node to interpret.
context (Context): The context to use for interpretation.
Returns:
Number: The result of execution of the operator.
"""
left = visit(node.left_node, context)
right = visit(node.right_node, context)
methodName = f'{type(node.operator).__name__}'.replace("Token", '') # AddToken becomes Add, MultiplyToken becomes Multiply, etc.
method = getattr(left, methodName)
return method(right)
# visitNumbernode :: NumberNode -> Context -> Number
def visitNumberNode(node : NumberNode, context : Context) -> Number:
""" Interpret number
Interpretation of numbers is as trivial as can be; entirely delegated to Number.
Parameters:
node (NumberNode): The number node to compile.
context (Context): The context to use for allocating registers and labels.
Returns:
Number: The resulting number object.
"""
return Number(node.token.stringToParse, node.token.lineNumber)
# visitVariableNode :: VariableNode -> Context -> Number
def visitVariableNode(node : VariableNode, context : Context) -> Number:
""" Interpret variable
Retrieves the value of a variable from the symboltable or stores the value of said
variable in the symboltable.
Parameters:
node (VariableNode): The variable node to interpret.
context (Context): The context to use for retrieving or storing variable value.
Returns:
Number: A number with the desired and/or resulting value.
"""
if node.value == None:
variableName = node.var_name.stringToParse
value = context.getSymbol(variableName)
else:
variableName = node.var_name
value = visit(node.value, context)
context.symbols[variableName] = value
return value
def visitIfNode(node : IfNode, context : Context) -> Optional[Number]:
""" Interpret if-statement
Execute expression of if statement when condition is met or exepression
of else statement when provided.
Parameters:
node (IfNode): The if node to interpret.
context (Context): The context to use for interpretation of condition and expression(s).
Returns:
Number: The optional result of the if and/or else expression.
"""
conditionIsMet: Number = visit(node.condition, context)
if conditionIsMet:
return visit(node.expression, context)
elif node.elseExpression:
return visit(node.elseExpression, context)
return None
# visitWhileNode :: WhileNode -> Context -> Nothing
def visitWhileNode(node : WhileNode, context : Context) -> None:
""" Interpret while-loop
Execute the while-body for as long as the condition is met.
Parameters:
node (WhileNode): The while node to interpret.
context (Context): The context to use for interpreting condition and body.
"""
conditionIsMet: Number = visit(node.condition, context)
if conditionIsMet:
visit(node.codeSequence, context) # Execute while-body once.
return visitWhileNode(node, context) # Then check for meeting condition.
return
# visitForNode :: ForNode -> Context -> Nothing
def visitForNode(node: ForNode, context: Context) -> None:
""" Interpret for-loop
Execute the for-body for as long as the iterator is within the desired
range.
Parameters:
node (ForNode): The for node to interpret.
context (Context): The context to use for interpreting condition and body.
"""
startValue = visit(node.startNode, context)
endValue = visit(node.endNode, context)
stepValue = visit(node.stepNode, context).value if node.stepNode else 1
condition = (lambda i: i < endValue.value) if stepValue >= 0 else (lambda i: i > endValue.value)
# iterate :: Integer -> Nothing
def iterate(i: int):
if condition(i):
context.symbols.update({node.varNameToken: Number(i, None)})
visit(node.bodyNode, context)
return iterate(i + stepValue)
return
iterate(startValue.value)
# visitFunctionNode :: FunctionNode -> Context -> Function
def visitFunctionNode(node : FunctionNode, context : Context) -> Function:
""" Interpret function
Create function from function definition and add the resulting definition
to the symboltable.
Parameters:
node (FunctionNode): The function node to interpret.
context (Context): The context to use for interpretation of function and to
add the definition to.
Returns:
Function: The function definition.
"""
functionValue = Function(node.name, node.codeSequence, node.arguments, context)
context.symbols[node.name] = functionValue
return functionValue
# visitCallNode :: CallNode -> Context -> Number | Nothing
def visitCallNode(node : CallNode, context : Context) -> Optional[Number]:
""" Interpret function call
Interpret function call after argument values have been determined.
Parameters:
node (CallNode): The call node to interpret.
context (Context): The context to use for determining argumentvalues and execution of the function.
Returns:
Number: Function result.
"""
arguments: List[Number] = []
valueToCall = visit(node.nodeToCall, context)
# The notation [*arguments, visit(node, context)] is a sneaky way of appending values to a list that returns the list.
# The append method works fine, but only appends to the object; it doesn't return the list with appended values. Used multiple times.
if node.argumentNodes != None:
arguments = list(chain(*map(lambda node: [*arguments, visit(node, context)], node.argumentNodes)))
return valueToCall.execute(arguments, context)
# visitListNode :: ListNode -> Context -> Number | [Number]
def visitListNode(node : ListNode, context : Context) -> Union[Number, List[Number]]:
""" Interpret list node
This method might require some more explanation. This method determines
the result of all nodes in the 'list'. These lists can occur since functions,
but also files, are allowed to return 'flush' multiple values. So, for example,
when a function returns once, there will only be one element in the returned list.
But when a function doesn't return at all, the results of all nodes will be in
the list. Same goes for functions or files returning multiple times; a list
will be returned.
Parameters:
node (ListNode): The list node to interpret.
context (Context): The context to use for interpreation of nodes in the list node.
Returns:
Number or List[Number]: The result(s) of the statements.
"""
returnNodes = map(lambda element: ReturnNode == type(element), node.elementNodes)
returnPresent: Number = reduce(add, returnNodes, 0) # Are there any nodes that really flush something?
def visitElement(elementNode):
if not returnPresent:
return visit(elementNode, context)
elif type(elementNode) == ReturnNode:
return visit(elementNode, context)
visit(elementNode, context)
elements: List[Number] = []
elements = list(chain(*map(lambda node: [*elements, visitElement(node)], node.elementNodes)))
elements = list(filter(partial(is_not, None), elements)) # Filter all None values. Note that None is equal to 0, so is_not is used.
if len(elements) == 1:
return elements[0]
return elements
# visitReturnNode :: ReturnNode -> Context -> Number | Nothing
def visitReturnNode(node : ReturnNode, context : Context) -> Optional[Number]:
""" Interpret return
Returns the result of interpretation of the node to return when there's
one present.
Parameters:
node (ReturnNode): The node of which the value should be returned.
context (Context): The context to use for determining return value.
"""
if node.nodeToReturn:
return visit(node.nodeToReturn, context)
functionName: str = f'visit{type(node).__name__}' # Determine name of function to call.
function = locals()[functionName] # Get function with corresponding name.
return function(node, context)