-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconditional_interperter.py
More file actions
227 lines (202 loc) · 7.79 KB
/
conditional_interperter.py
File metadata and controls
227 lines (202 loc) · 7.79 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
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
"""
This is the solution for the second part of the software homework exercise for MLabs.
Author: Jacobo Fernandez Vargas
"""
CONTROL = ['GREATER', 'LOWER', 'EQUAL']
def load_instructions(file: str):
"""
Auxiliary function to read an instruction text file.
:param file: Path to the file with the instructions
:return: A list with the instructions.
"""
with open(file, 'r') as f:
lines = f.readlines()
instructions = []
for l in lines:
instructions += l.trim().split()
return instructions
def translate(lowLevel: list[str]):
"""
Function that translate the instruction set into python code to be executed by the eval function.
:param lowLevel: List of instructions in low level.
:return: Instruction set in python plus decorators needed for the control instructions.
"""
unary = ['LOAD_VARIABLE', 'LOAD_CONSTANT', 'ASSIGN_VARIABLE']
binary = []
python = []
idx = 0
while idx != len(lowLevel):
if lowLevel[idx] in unary:
if lowLevel[idx] == 'LOAD_VARIABLE':
python.append(f'inst.{lowLevel[idx].lower()}("{lowLevel[idx + 1]}")')
else:
python.append(f'inst.{lowLevel[idx].lower()}({lowLevel[idx + 1]})')
idx += 2
elif lowLevel[idx] in binary:
python.append(f'inst.{lowLevel[idx].lower()}({lowLevel[idx + 1]}, {lowLevel[idx + 2]})')
idx += 3
elif lowLevel[idx] in CONTROL:
python.append(f'inst.{lowLevel[idx].lower()}()')
python.append(lowLevel[idx])
python.append(lowLevel[idx + 1])
idx += 2
elif lowLevel[idx] == "RETURN":
python.append('inst.end()')
idx += 1
else:
python.append(f'inst.{lowLevel[idx].lower()}()')
idx += 1
return python
class Function():
"""
Class that manages a memory space. It is defined by the input arguments and the instruction set.
:cvar variables: Dictionary with the variables defined in the function. Acts as the memory of the system.
"""
variables = {}
def __init__(self, arguments: list[str], instructions: list[str]):
"""
:param arguments: List of the name of the arguments that this function needs when it is called.
:param instructions: The instruction list that define the function.
"""
self.functions_args = arguments
self.function_inst = translate(instructions)
def assign_variable(self, name: str, value: float):
"""
Updates the value of a variable. If it doesn't exist it will create it. This variable can only be accessed
inside this function.
:param name: Name of the variable
:param value: Value of the variable.
"""
self.variables[name] = value
def parse_inputs(self, values: list[float]):
"""
Creates the variables corresponding to the input of the function.
:param values: Values of the arguments of the function.
"""
for n,v in zip(self.functions_args, values):
self.assign_variable(n, v)
def __call__(self, args: list[float]):
"""
This function takes the role of the interpreter. It will execute all the instructions in the function in
addition to load the arguments. This version allows for some control instructions as well as multiple return
points.
:param args: Arguments to be inputed to the function.
:return: Value returned by the function
"""
inst = Instruction(self)
instructions = [f'self.parse_inputs({args})'] + self.function_inst
idx = 0
while idx != len(instructions):
instruction = instructions[idx]
if instruction in CONTROL:
loopLen = instructions[idx + 1]
idx += 2
if not ret:
idx += loopLen
instruction = instructions[idx]
ret = eval(instruction)
if instruction == 'inst.end()':
return ret
idx += 1
class Instruction():
"""
Class that implements all the arithmetic and logical capabilities of the language.
:cvar operands: Queue of the operands that are used by the different operations.
"""
operands = []
def __init__(self, function: Function):
"""
:param function: Function instance that holds the variables.
"""
self.function = function
def multiply(self):
"""
Multiplies the first two operands in the queue.
"""
o1 = self.operands.pop(0)
o2 = self.operands.pop(0)
self.operands.append(o1 * o2)
def add(self):
"""
Adds the first two operands in the queue.
"""
o1 = self.operands.pop(0)
o2 = self.operands.pop(0)
self.operands.append(o1 + o2)
def load_constant(self, value: float):
"""
Inserts into the queue a constant
:param value: Value to be inserted
"""
self.operands.append(value)
def load_variable(self, name: str):
"""
Inserts into the queue the value of a variable
:param name: Name of the variable
"""
self.operands.append(self.function.variables[name])
def assign_variable(self, name: str):
"""
Pops the first value of the queue and updates / creates a variable with that value.
:param name: Name of the variable
"""
value = self.collect()
self.function.assign_variable(name, value)
def end(self):
"""
Pops the first value of the queue and returns it.
:return: Value to be returned
"""
return self.operands.pop(0)
def collect(self):
"""
Pops the first value of the queue and returns it. It is used for the control commands, it cannot be used by the
user.
:return: Value to be returned
"""
return self.operands.pop(0)
def compare(self):
"""
Subtracts the first two operands in the queue.
"""
o1 = self.operands.pop(0)
o2 = self.operands.pop(0)
self.operands.append(o1 - o2)
def greater(self) -> bool:
"""
Checks whether the first value in the queue is greater than 0. It is used after the compare instruction.
:return: True if the first operand was greater than the second when compare was called. False otherwise.
"""
o = self.operands.pop(0)
return o > 0
def lower(self) -> bool:
"""
Checks whether the first value in the queue is lower than 0. It is used after the compare instruction.
:return: True if the first operand was lower than the second when compare was called. False otherwise.
"""
o = self.operands.pop(0)
return o < 0
def equal(self) -> bool:
"""
Checks whether the first value in the queue is 0. It is used after the compare instruction.
:return: True if the first operand equal to the second when compare was called. False otherwise.
"""
o = self.operands.pop(0)
return o == 0
instructions = ['LOAD_VARIABLE', 'x',
'LOAD_CONSTANT', 0,
'COMPARE',
'LOWER', 2,
'LOAD_CONSTANT', 0,
'RETURN',
'LOAD_VARIABLE', 'x',
'LOAD_CONSTANT', 2,
'MULTIPLY',
'LOAD_VARIABLE', 'y',
'ADD',
'RETURN'
]
arguments = ['x', 'y']
f = Function(arguments, instructions)
print(f([-1, 3]))
print(f([2, 3]))