Commit be48016d authored by Christopher Happe's avatar Christopher Happe
Browse files

LOOP-Interpreter zu Klasse gemacht

parent d5d1fc95
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
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)
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)
......
......@@ -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''')"