import lexer import sys import operator import re from IPython.display import clear_output 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): msg = ['Fehler in Zeile ' + str(self.line_number + 1), self.program.split('\n')[self.line_number], message] raise SyntaxError('\n'.join(msg)) from None def increase_line(self, value): self.line_number += value def handle_break(self): clear_output(wait=True) print('BREAK in Zeile ' + str(self.line_number)) print('Aktueller Zustand:') for k, v in self.interpreter.values.items(): print('Variable ' + k + ': ' + str(v)) user_input = input('Drücke ENTER zum Fotfahren oder schreibe EXIT zum Beenden:') if user_input.lower() == 'exit': raise KeyboardInterrupt def handle_endless_loop(self): self.handle_error("Mögliche Endlosschleife gefunden.") class LOOPInterpreter: def __init__(self): self.regex_to_token = [(re.compile(r'\d+'), 'NUMBER'), (re.compile(r'x\d+'), 'IDENTIFIER'), (re.compile(r'\+'), 'PLUS'), (re.compile(r'[−-]'), 'MINUS'), (re.compile(r':=|≔'), 'ALLOCATION'), (re.compile(r'LOOP'), 'LOOP'), (re.compile(r'DO'), 'DO'), (re.compile(r'END'), 'END'), (re.compile(r';'), 'SEMICOLON'), (re.compile(r'BREAK'), 'BREAK'), (re.compile(r'\s+', re.MULTILINE), 'WHITESPACE'), (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: 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') 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': self.error_handler.handle_error('IDENTIFIER in Zuweisung erwartet.') identifier_2 = identifier_token_2.v if identifier_2 in forbidden_identifiers: self.error_handler.handle_error('Identifier ' + identifier_2 + ' ist bereits in Loop vorhanden und darf nicht verwendet werden.') if identifier_2 in self.values: value_2 = self.values.get(identifier_2) else: value_2 = 0 operator_token = self.next_nonempty_token('Zuweisung', '+ oder -') op = None if operator_token.k == 'PLUS': op = operator.__add__ elif operator_token.k == 'MINUS': op = operator.__sub__ else: self.error_handler.handle_error('+ oder - in Zuweisung erwartet.') number_token = self.next_nonempty_token('Zuweisung', 'NUMBER') if not number_token.k == 'NUMBER': self.error_handler.handle_error('NUMBER in Zuweisung erwartet.') 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: 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') if identifier_token_2.k == 'NUMBER': return self.next_token() if not identifier_token_2.k == 'IDENTIFIER': self.error_handler.handle_error('IDENTIFIER in Zuweisung erwartet.') identifier_2 = identifier_token_2.v if identifier_2 in forbidden_identifiers: self.error_handler.handle_error('Identifier ' + identifier_2 + ' ist bereits in Loop vorhanden und darf nicht verwendet werden.') 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.') 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.') if not self.next_nonempty_token('LOOP', 'DO').k == 'DO': 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']: self.error_handler.handle_error('SEMICOLON oder END in LOOP erwartet.') 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']: self.error_handler.handle_error('SEMICOLON oder END in LOOP erwartet.') 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): identifier_token = self.next_nonempty_token('LOOP', 'IDENTIFIER') 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.') if not self.next_nonempty_token('LOOP', 'DO').k == 'DO': 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']: self.error_handler.handle_error('SEMICOLON oder END in LOOP erwartet.') 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']: self.error_handler.handle_error('Keine passende Anweisung gefunden\n' + 'Erwartet: IDENTIFIER (x0, x1, ...) oder LOOP') 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']: self.error_handler.handle_error('Keine passende Anweisung gefunden\n' + 'Erwartet: IDENTIFIER (x0, x1, ...) oder LOOP') 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': if new_token.v.count('\n') > 0: self.error_handler.increase_line(new_token.v.count('\n')) 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( 'Frühzeitiges Ende von ' + current_function + '\n' + 'Erwartet: ' + expected_token) return token def interpret(self, program, values=None): try: self.lex = lexer.Lexer(self.regex_to_token, program) self.error_handler = ErrorHandler(program, self) self.values = values if values is None: self.values = {} 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': self.error_handler.handle_error('Semicolon erwartet') current_token = self.next_token() if current_token is None: 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') return 0 except KeyboardInterrupt: print('Die Ausführung des Programms wurde unterbrochen.\n' + 'Daher ist der Rückgabewert des Programms nicht definiert.') return -1 def interpret(program, value_list=None): """Funktion zum Ausführen eines LOOP-Programms. :param program: LOOP-Programm als String 'x1 := 4; LOOP x1 DO x0:=x0+1 END' :param value_list: Array von Integern ([v1, ..., vn]). Das LOOP-Programm startet mit diesen Werten in x1, ..., xn. :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')""" interpreter = LOOPInterpreter() 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) if __name__ == '__main__': help(interpret)