diff --git a/info4/kapitel-8/Interpreter/loop_interpreter.py b/info4/kapitel-8/Interpreter/loop_interpreter.py deleted file mode 100644 index 1900fbc31027d09fc0c025ac96817bb550f920e6..0000000000000000000000000000000000000000 --- a/info4/kapitel-8/Interpreter/loop_interpreter.py +++ /dev/null @@ -1,254 +0,0 @@ -import lexer -import sys -import operator -import re - -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':=|≔'), 'EQUALS'), - (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'\n', re.MULTILINE), 'LINEBREAK'), - (re.compile(r'\s+'), 'WHITESPACE'), - (re.compile(r'[^\n]*'), 'UNKNOWN')] - -global error_handler, lex, values - - -class ErrorHandler: - def __init__(self, program): - sys.tracebacklimit = 0 - self.program = program - self.line_number = 0 - - 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): - self.line_number += 1 - - def handle_break(self): - print("BREAK in Zeile " + str(self.line_number)) - print("Aktueller Zustand:") - for k, v in 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 process_assignment(forbidden_identifiers, identifier_token_1): - global values - identifier_1 = identifier_token_1.v - if identifier_1 in forbidden_identifiers: - error_handler.handle_error("Identifier " + identifier_1 + - " ist bereits in Loop vorhanden und darf nicht verwendet werden.") - if not next_nonempty_token("Zuweisung", ":=").k == 'EQUALS': - error_handler.handle_error(":= in Zuweisung erwartet.") - identifier_token_2 = next_nonempty_token("Zuweisung", "IDENTIFIER (x0, x1, ...) oder NUMBER") - if identifier_token_2.k == 'NUMBER': - value_1 = int(identifier_token_2.v) - values.update({identifier_token_1.v: value_1}) - return next_token() - if not identifier_token_2.k == 'IDENTIFIER': - error_handler.handle_error("IDENTIFIER in Zuweisung erwartet.") - identifier_2 = identifier_token_2.v - if identifier_2 in forbidden_identifiers: - error_handler.handle_error("Identifier " + identifier_2 + - " ist bereits in Loop vorhanden und darf nicht verwendet werden.") - if identifier_2 in values: - value_2 = values.get(identifier_2) - else: - value_2 = 0 - operator_token = next_nonempty_token("Zuweisung", "+ oder -") - op = None - if operator_token.k == 'PLUS': - op = operator.__add__ - elif operator_token.k == 'MINUS': - op = operator.__sub__ - else: - error_handler.handle_error("+ oder - in Zuweisung erwartet.") - number_token = next_nonempty_token("Zuweisung", "NUMBER") - if not number_token.k == 'NUMBER': - error_handler.handle_error("NUMBER in Zuweisung erwartet.") - value_1 = max(0, op(value_2, int(number_token.v))) - values.update({identifier_1: value_1}) - return next_token() - - -def verify_assignment(forbidden_identifiers, identifier_token_1): - identifier_1 = identifier_token_1.v - if identifier_1 in forbidden_identifiers: - error_handler.handle_error("Identifier " + identifier_1 + - " ist bereits in Loop vorhanden und darf nicht verwendet werden.") - if not next_nonempty_token("Zuweisung", ":=").k == 'EQUALS': - error_handler.handle_error(":= in Zuweisung erwartet.") - - identifier_token_2 = next_nonempty_token("Zuweisung", "IDENTIFIER (x0, x1, ...) oder NUMBER") - if identifier_token_2.k == 'NUMBER': - return next_token() - - if not identifier_token_2.k == 'IDENTIFIER': - error_handler.handle_error("IDENTIFIER in Zuweisung erwartet.") - identifier_2 = identifier_token_2.v - if identifier_2 in forbidden_identifiers: - error_handler.handle_error("Identifier " + identifier_2 + - " ist bereits in Loop vorhanden und darf nicht verwendet werden.") - - if next_nonempty_token("Zuweisung", "+ oder -").k not in ['PLUS', 'MINUS']: - error_handler.handle_error("+ oder - in Zuweisung erwartet.") - if not next_nonempty_token("Zuweisung", "NUMBER").k == 'NUMBER': - error_handler.handle_error("NUMBER in Zuweisung erwartet.") - - return next_token() - - -def process_loop(forbidden_identifiers, loop_token): - global values - identifier_token = next_nonempty_token('LOOP', 'IDENTIFIER (x0, x1, ...)') - if not identifier_token.k == 'IDENTIFIER': - error_handler.handle_error('IDENTIFIER in LOOP erwartet.') - if identifier_token.v in forbidden_identifiers: - error_handler.handle_error('Identifier ' + identifier_token.v + - ' ist bereits in Loop vorhanden und darf nicht verwendet werden.') - if not next_nonempty_token("LOOP", "DO").k == 'DO': - error_handler.handle_error('DO in LOOP erwartet.') - - if identifier_token.v in values: - number_of_loops = int(values.get(identifier_token.v)) - else: - number_of_loops = 0 - - saved_position = lex.current_position - saved_line = error_handler.line_number - forbidden_identifiers.append(identifier_token.v) - - if number_of_loops == 0: - end_found = False - while not end_found: - token = verify_program(forbidden_identifiers, next_token()) - if token is None or token.k not in ['SEMICOLON', 'END']: - 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): - lex.current_position = saved_position - error_handler.line_number = saved_line - end_found = False - while not end_found: - token = process_program(forbidden_identifiers, next_token()) - if token is None or token.k not in ['SEMICOLON', 'END']: - 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 next_token() - - -def verify_loop(forbidden_identifiers, loop_token): - identifier_token = next_nonempty_token("LOOP", "IDENTIFIER") - if not identifier_token.k == 'IDENTIFIER': - error_handler.handle_error('IDENTIFIER in LOOP erwartet.') - if identifier_token.v in forbidden_identifiers: - error_handler.handle_error("Identifier " + identifier_token.v + - " ist bereits in Loop vorhanden und darf nicht verwendet werden.") - if not next_nonempty_token("LOOP", "DO").k == 'DO': - error_handler.handle_error('DO in LOOP erwartet.') - - forbidden_identifiers.append(identifier_token.v) - - end_found = False - while not end_found: - token = verify_program(forbidden_identifiers, next_token()) - if token is None or token.k not in ['SEMICOLON', 'END']: - 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 next_token() - - -def process_program(forbidden_identifiers, current_token): - if current_token is None or current_token.k not in ['IDENTIFIER', 'LOOP']: - error_handler.handle_error("Keine passende Anweisung gefunden\n" + - "Erwartet: IDENTIFIER (x0, x1, ...) oder LOOP") - elif current_token.k == 'IDENTIFIER': - current_token = process_assignment(forbidden_identifiers, current_token) - elif current_token.k == 'LOOP': - current_token = process_loop(forbidden_identifiers, current_token) - return current_token - - -def verify_program(forbidden_identifiers, current_token): - if current_token is None or current_token.k not in ['IDENTIFIER', 'LOOP']: - error_handler.handle_error("Keine passende Anweisung gefunden\n" + - "Erwartet: IDENTIFIER (x0, x1, ...) oder LOOP") - elif current_token.k == 'IDENTIFIER': - current_token = verify_assignment(forbidden_identifiers, current_token) - elif current_token.k == 'LOOP': - current_token = verify_loop(forbidden_identifiers, current_token) - return current_token - - -def next_token(): - new_token = lex.next() - if new_token is None: - return None - elif new_token.k == 'BREAK': - error_handler.handle_break() - return next_token() - elif new_token.k == 'LINEBREAK': - error_handler.increase_line() - return next_token() - elif new_token.k == 'WHITESPACE': - return next_token() - else: - return new_token - - -def next_nonempty_token(current_function, expected_token): - token = next_token() - if token is None: - error_handler.handle_error("Frühzeitiges Ende von " + current_function + "\n" + "Erwartet: " + expected_token) - return token - - -def interpret(program): - try: - global error_handler, lex, values - lex = lexer.Lexer(regex_to_token, program) - error_handler = ErrorHandler(program) - values = {} - forbidden_identifiers = [] - current_token = next_token() - while current_token is not None: - current_token = process_program(forbidden_identifiers, current_token) - if current_token is not None: - if not current_token.k == 'SEMICOLON': - error_handler.handle_error("Semicolon erwartet") - current_token = next_token() - if current_token is None: - error_handler.handle_error("Semikolons werden nur zur Trennung und nicht zum " + - "Abschluss von Programmen verwendet") - if "x0" in values: - return values.get("x0") - return 0 - except KeyboardInterrupt: - return -1 diff --git a/info4/kapitel-8/Interpreter/loopinterpreter.py b/info4/kapitel-8/Interpreter/loopinterpreter.py new file mode 100644 index 0000000000000000000000000000000000000000..a98dd29bcf13866232e6f46190abf95bda98eced --- /dev/null +++ b/info4/kapitel-8/Interpreter/loopinterpreter.py @@ -0,0 +1,253 @@ +import lexer +import sys +import operator +import re + + +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): + self.line_number += 1 + + def handle_break(self): + 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 + + +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':=|≔'), 'EQUALS'), + (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'\n', re.MULTILINE), 'LINEBREAK'), + (re.compile(r'\s+'), '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 == 'EQUALS': + 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 == 'EQUALS': + 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 == 'LINEBREAK': + self.error_handler.increase_line() + return self.next_token() + elif new_token.k == 'WHITESPACE': + 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): + try: + self.lex = lexer.Lexer(self.regex_to_token, program) + self.error_handler = ErrorHandler(program, self) + 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: + return -1 + + +def interpret(program): + interpreter = LOOPInterpreter() + return interpreter.interpret(program) diff --git a/info4/kapitel-8/Interpreter/test_loop_interpreter.py b/info4/kapitel-8/Interpreter/test_loop_interpreter.py index 0f93732ae8b1fef4427111a83c6fc6e85e960f06..7f47bced451b943979473eac7ab027674e8365d1 100644 --- a/info4/kapitel-8/Interpreter/test_loop_interpreter.py +++ b/info4/kapitel-8/Interpreter/test_loop_interpreter.py @@ -1,19 +1,23 @@ -from loop_interpreter import interpret +from loopinterpreter import interpret import unittest from unittest import mock + def input_exit(prompt): return "EXIT" + def input_continue(prompt): return "" + def init_without_tokens(self, regex_to_token, program): self.regex_to_token = {} self.program = program self.current_position = 0 -class InterpreterTest(unittest.TestCase): + +class LOOPInterpreterTest(unittest.TestCase): def test_assignment_default_zero(self): self.assertEqual(interpret('x0:=x0 + 0'), 0) self.assertEqual(interpret('x0:=x1 + 0'), 0) @@ -219,12 +223,12 @@ class InterpreterTest(unittest.TestCase): x0:=x2+2'''), 5) self.assertEqual(interpret('x1:=x1-2;\n x0:=x1+2'), 2) - @mock.patch('interpreter.input', side_effect=input_exit) + @mock.patch('loopinterpreter.input', side_effect=input_exit) def test_break_exit(self, input): self.assertEqual(interpret('x1:=2; BREAK x0:=2'), -1) self.assertEqual(interpret('LOOP x1 DO BREAK x2:= 2 END'), -1) - @mock.patch('interpreter.input', side_effect=input_continue) + @mock.patch('loopinterpreter.input', side_effect=input_continue) def test_break_continue(self, input): self.assertEqual(interpret('x1:=2; LOOP x1 DO x0:=x0+2 BREAK END'), 4) diff --git a/info4/kapitel-8/LOOP-Programme.ipynb b/info4/kapitel-8/LOOP-Programme.ipynb index b91361a0b5cfac0456f01e388952bdb9b9c2f991..b86b9340195f1c46988d58a36d887e3feceb37bd 100644 --- a/info4/kapitel-8/LOOP-Programme.ipynb +++ b/info4/kapitel-8/LOOP-Programme.ipynb @@ -42,12 +42,12 @@ }, "outputs": [], "source": [ - "%run Interpreter/interpreter.py" + "%run Interpreter/loop_interpreter.py" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -56,7 +56,7 @@ "9" ] }, - "execution_count": 5, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -66,8 +66,9 @@ "x1:=3;\n", "x2:=6;\n", "LOOP x1 DO\n", - " x0≔ x0 + 1\n", - "END''')" + " x2≔ x2 + 1\n", + "END;\n", + "x0≔ x2 + 0''')" ] }, { @@ -82,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -93,7 +94,7 @@ "Aktueller Zustand:\n", "Variable x1: 5\n", "Variable x0: 2\n", - "Drücke ENTER zum Fotfahren oder schreibe EXIT zum Beenden:1\n" + "Drücke ENTER zum Fotfahren oder schreibe EXIT zum Beenden:\n" ] }, { @@ -102,7 +103,7 @@ "2" ] }, - "execution_count": 8, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" }