loopinterpreter.py 12.9 KB
Newer Older
1
2
3
4
import lexer
import sys
import operator
import re
5
from IPython.display import clear_output
6
7
8
9
10
11
12
13
14
15


class ErrorHandler:
    def __init__(self, program, interpreter):
        sys.tracebacklimit = 0
        self.program = program
        self.line_number = 0
        self.interpreter = interpreter

    def handle_error(self, message):
16
17
        msg = ['Fehler in Zeile ' + str(self.line_number + 1),
               self.program.split('\n')[self.line_number],
18
               message]
19
        raise SyntaxError('\n'.join(msg)) from None
20

21
22
    def increase_line(self, value):
        self.line_number += value
23
24

    def handle_break(self):
25
        clear_output(wait=True)
26
27
        print('BREAK in Zeile ' + str(self.line_number))
        print('Aktueller Zustand:')
28
        for k, v in self.interpreter.values.items():
29
30
            print('Variable ' + k + ': ' + str(v))
        user_input = input('Drücke ENTER zum Fotfahren oder schreibe EXIT zum Beenden:')
31
32
33
34
35
36
37
38
39
        if user_input.lower() == 'exit':
            raise KeyboardInterrupt


class LOOPInterpreter:
    def __init__(self):
        self.regex_to_token = [(re.compile(r'\d+'), 'NUMBER'),
                               (re.compile(r'x\d+'), 'IDENTIFIER'),
                               (re.compile(r'\+'), 'PLUS'),
40
                               (re.compile(r'[−-]'), 'MINUS'),
41
                               (re.compile(r':=|≔'), 'ALLOCATION'),
42
43
44
45
46
                               (re.compile(r'LOOP'), 'LOOP'),
                               (re.compile(r'DO'), 'DO'),
                               (re.compile(r'END'), 'END'),
                               (re.compile(r';'), 'SEMICOLON'),
                               (re.compile(r'BREAK'), 'BREAK'),
47
                               (re.compile(r'\s+', re.MULTILINE), 'WHITESPACE'),
48
49
50
51
52
53
54
55
                               (re.compile(r'[^\n]*'), 'UNKNOWN')]
        self.values = {}
        self.lex = None
        self.error_handler = None

    def process_assignment(self, forbidden_identifiers, identifier_token_1):
        identifier_1 = identifier_token_1.v
        if identifier_1 in forbidden_identifiers:
56
57
58
59
60
            self.error_handler.handle_error('Identifier ' + identifier_1 +
                                            ' ist bereits in Loop vorhanden und darf nicht verwendet werden.')
        if not self.next_nonempty_token('Zuweisung', ':=').k == 'ALLOCATION':
            self.error_handler.handle_error(':= in Zuweisung erwartet.')
        identifier_token_2 = self.next_nonempty_token('Zuweisung', 'IDENTIFIER (x0, x1, ...) oder NUMBER')
61
62
63
64
65
        if identifier_token_2.k == 'NUMBER':
            value_1 = int(identifier_token_2.v)
            self.values.update({identifier_token_1.v: value_1})
            return self.next_token()
        if not identifier_token_2.k == 'IDENTIFIER':
66
            self.error_handler.handle_error('IDENTIFIER in Zuweisung erwartet.')
67
68
        identifier_2 = identifier_token_2.v
        if identifier_2 in forbidden_identifiers:
69
70
            self.error_handler.handle_error('Identifier ' + identifier_2 +
                                            ' ist bereits in Loop vorhanden und darf nicht verwendet werden.')
71
72
73
74
        if identifier_2 in self.values:
            value_2 = self.values.get(identifier_2)
        else:
            value_2 = 0
75
        operator_token = self.next_nonempty_token('Zuweisung', '+ oder -')
76
77
78
79
80
81
        op = None
        if operator_token.k == 'PLUS':
            op = operator.__add__
        elif operator_token.k == 'MINUS':
            op = operator.__sub__
        else:
82
83
            self.error_handler.handle_error('+ oder - in Zuweisung erwartet.')
        number_token = self.next_nonempty_token('Zuweisung', 'NUMBER')
84
        if not number_token.k == 'NUMBER':
85
            self.error_handler.handle_error('NUMBER in Zuweisung erwartet.')
86
87
88
89
90
91
92
        value_1 = max(0, op(value_2, int(number_token.v)))
        self.values.update({identifier_1: value_1})
        return self.next_token()

    def verify_assignment(self, forbidden_identifiers, identifier_token_1):
        identifier_1 = identifier_token_1.v
        if identifier_1 in forbidden_identifiers:
93
94
95
96
            self.error_handler.handle_error('Identifier ' + identifier_1 +
                                            ' ist bereits in Loop vorhanden und darf nicht verwendet werden.')
        if not self.next_nonempty_token('Zuweisung', ':=').k == 'ALLOCATION':
            self.error_handler.handle_error(':= in Zuweisung erwartet.')
97

98
        identifier_token_2 = self.next_nonempty_token('Zuweisung', 'IDENTIFIER (x0, x1, ...) oder NUMBER')
99
100
101
102
        if identifier_token_2.k == 'NUMBER':
            return self.next_token()

        if not identifier_token_2.k == 'IDENTIFIER':
103
            self.error_handler.handle_error('IDENTIFIER in Zuweisung erwartet.')
104
105
        identifier_2 = identifier_token_2.v
        if identifier_2 in forbidden_identifiers:
106
107
            self.error_handler.handle_error('Identifier ' + identifier_2 +
                                            ' ist bereits in Loop vorhanden und darf nicht verwendet werden.')
108

109
110
111
112
        if self.next_nonempty_token('Zuweisung', '+ oder -').k not in ['PLUS', 'MINUS']:
            self.error_handler.handle_error('+ oder - in Zuweisung erwartet.')
        if not self.next_nonempty_token('Zuweisung', 'NUMBER').k == 'NUMBER':
            self.error_handler.handle_error('NUMBER in Zuweisung erwartet.')
113
114
115
116
117
118
119
120
121
122

        return self.next_token()

    def process_loop(self, forbidden_identifiers, loop_token):
        identifier_token = self.next_nonempty_token('LOOP', 'IDENTIFIER (x0, x1, ...)')
        if not identifier_token.k == 'IDENTIFIER':
            self.error_handler.handle_error('IDENTIFIER in LOOP erwartet.')
        if identifier_token.v in forbidden_identifiers:
            self.error_handler.handle_error('Identifier ' + identifier_token.v +
                                            ' ist bereits in Loop vorhanden und darf nicht verwendet werden.')
123
        if not self.next_nonempty_token('LOOP', 'DO').k == 'DO':
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
            self.error_handler.handle_error('DO in LOOP erwartet.')

        if identifier_token.v in self.values:
            number_of_loops = int(self.values.get(identifier_token.v))
        else:
            number_of_loops = 0

        saved_position = self.lex.current_position
        saved_line = self.error_handler.line_number
        forbidden_identifiers.append(identifier_token.v)

        if number_of_loops == 0:
            end_found = False
            while not end_found:
                token = self.verify_program(forbidden_identifiers, self.next_token())
                if token is None or token.k not in ['SEMICOLON', 'END']:
140
                    self.error_handler.handle_error('SEMICOLON oder END in LOOP erwartet.')
141
142
143
144
145
146
147
148
149
150
151
152
                elif token.k == 'SEMICOLON':
                    continue
                elif token.k == 'END':
                    end_found = True

        for index in range(number_of_loops):
            self.lex.current_position = saved_position
            self.error_handler.line_number = saved_line
            end_found = False
            while not end_found:
                token = self.process_program(forbidden_identifiers, self.next_token())
                if token is None or token.k not in ['SEMICOLON', 'END']:
153
                    self.error_handler.handle_error('SEMICOLON oder END in LOOP erwartet.')
154
155
156
157
158
159
160
161
162
                elif token.k == 'SEMICOLON':
                    continue
                elif token.k == 'END':
                    end_found = True

        forbidden_identifiers.remove(identifier_token.v)
        return self.next_token()

    def verify_loop(self, forbidden_identifiers, loop_token):
163
        identifier_token = self.next_nonempty_token('LOOP', 'IDENTIFIER')
164
165
166
        if not identifier_token.k == 'IDENTIFIER':
            self.error_handler.handle_error('IDENTIFIER in LOOP erwartet.')
        if identifier_token.v in forbidden_identifiers:
167
168
169
            self.error_handler.handle_error('Identifier ' + identifier_token.v +
                                            ' ist bereits in Loop vorhanden und darf nicht verwendet werden.')
        if not self.next_nonempty_token('LOOP', 'DO').k == 'DO':
170
171
172
173
174
175
176
177
            self.error_handler.handle_error('DO in LOOP erwartet.')

        forbidden_identifiers.append(identifier_token.v)

        end_found = False
        while not end_found:
            token = self.verify_program(forbidden_identifiers, self.next_token())
            if token is None or token.k not in ['SEMICOLON', 'END']:
178
                self.error_handler.handle_error('SEMICOLON oder END in LOOP erwartet.')
179
180
181
182
183
184
185
186
187
188
            elif token.k == 'SEMICOLON':
                continue
            elif token.k == 'END':
                end_found = True

        forbidden_identifiers.remove(identifier_token.v)
        return self.next_token()

    def process_program(self, forbidden_identifiers, current_token):
        if current_token is None or current_token.k not in ['IDENTIFIER', 'LOOP']:
189
190
            self.error_handler.handle_error('Keine passende Anweisung gefunden\n' +
                                            'Erwartet: IDENTIFIER (x0, x1, ...) oder LOOP')
191
192
193
194
195
196
197
198
        elif current_token.k == 'IDENTIFIER':
            current_token = self.process_assignment(forbidden_identifiers, current_token)
        elif current_token.k == 'LOOP':
            current_token = self.process_loop(forbidden_identifiers, current_token)
        return current_token

    def verify_program(self, forbidden_identifiers, current_token):
        if current_token is None or current_token.k not in ['IDENTIFIER', 'LOOP']:
199
200
            self.error_handler.handle_error('Keine passende Anweisung gefunden\n' +
                                            'Erwartet: IDENTIFIER (x0, x1, ...) oder LOOP')
201
202
203
204
205
206
207
208
209
210
211
212
213
214
        elif current_token.k == 'IDENTIFIER':
            current_token = self.verify_assignment(forbidden_identifiers, current_token)
        elif current_token.k == 'LOOP':
            current_token = self.verify_loop(forbidden_identifiers, current_token)
        return current_token

    def next_token(self):
        new_token = self.lex.next()
        if new_token is None:
            return None
        elif new_token.k == 'BREAK':
            self.error_handler.handle_break()
            return self.next_token()
        elif new_token.k == 'WHITESPACE':
215
216
            if new_token.v.count('\n') > 0:
                self.error_handler.increase_line(new_token.v.count('\n'))
217
218
219
220
221
222
223
224
            return self.next_token()
        else:
            return new_token

    def next_nonempty_token(self, current_function, expected_token):
        token = self.next_token()
        if token is None:
            self.error_handler.handle_error(
225
                'Frühzeitiges Ende von ' + current_function + '\n' + 'Erwartet: ' + expected_token)
226
227
        return token

228
    def interpret(self, program, values=None):
229
230
231
        try:
            self.lex = lexer.Lexer(self.regex_to_token, program)
            self.error_handler = ErrorHandler(program, self)
232
233
234
            self.values = values
            if values is None:
                self.values = {}
235
236
237
238
239
240
            forbidden_identifiers = []
            current_token = self.next_token()
            while current_token is not None:
                current_token = self.process_program(forbidden_identifiers, current_token)
                if current_token is not None:
                    if not current_token.k == 'SEMICOLON':
241
                        self.error_handler.handle_error('Semicolon erwartet')
242
243
                    current_token = self.next_token()
                    if current_token is None:
244
245
246
247
                        self.error_handler.handle_error('Semikolons werden nur zur Trennung und nicht zum ' +
                                                        'Abschluss von Programmen verwendet')
            if 'x0' in self.values:
                return self.values.get('x0')
248
249
            return 0
        except KeyboardInterrupt:
250
251
            print('Die Ausführung des Programms wurde unterbrochen.\n' +
                  'Daher ist der Rückgabewert des Programms nicht definiert.')
252
253
254
            return -1


255
def interpret(program, value_list=None):
256
257
    """Funktion zum Ausführen eines LOOP-Programms.
        :param program: LOOP-Programm als String 'x1 := 4; LOOP x1 DO x0:=x0+1 END'
258
259
        :param value_list: Array von Integern ([v1, ..., vn]).
                           Das LOOP-Programm startet mit diesen Werten in x1, ..., xn.
260
261
        :returns integer: Gibt bei Abbruch -1 und sonst den Wert von x0 nach dem LOOP-Programm zurück.
        :usage interpret('x1:=10; x2:=8; x0:=x2+0; LOOP x1 DO x0:=x0+1 END')"""
262
    interpreter = LOOPInterpreter()
263
264
265
266
267
268
269
270
    values = None
    if value_list is not None:
        values = {}
        for index, value in enumerate(value_list):
            if not isinstance(value, int) or value < 0 or not int(value) == value:
                raise ValueError("Variablen können nur natürliche Zahlen zugewiesen bekommen.")
            values.update({'x' + str(index + 1): value})
    return interpreter.interpret(program, values)
271
272
273
274


if __name__ == '__main__':
    help(interpret)