Commit 5b7589c0 authored by Christopher Happe's avatar Christopher Happe
Browse files

Timeout für Endlosschleifen in WHILE-Programmen closes #2

parent 52d7c7f6
from whileinterpreter import interpret
from test_loop_interpreter import LOOPInterpreterTest
from unittest import mock
def str_yes(a):
return "J"
class WHILEInterpreterTest(LOOPInterpreterTest):
......@@ -109,3 +114,7 @@ class WHILEInterpreterTest(LOOPInterpreterTest):
interpret('x1:=2;; x0:=3')
with self.assertRaises(SyntaxError):
interpret('WHILE x1 != 0 DO x0:=2;;x2:=1 END')
@mock.patch('whileinterpreter.input', side_effect=str_yes)
def test_infinite_loop(self, input):
self.assertEqual(interpret('x1:=100000; WHILE x1 != 0 DO x0:=1; x1:=x1-1 END', 1), -1)
from loopinterpreter import LOOPInterpreter
import lexer
from loopinterpreter import LOOPInterpreter, ErrorHandler
import re
import signal
class Timeout:
def __init__(self, time):
self.time = time
def __enter__(self):
if self.time > 0:
signal.signal(signal.SIGALRM, self.interrupt)
signal.alarm(self.time)
def __exit__(self, exc_type, exc_value, exc_traceback):
signal.alarm(0)
def interrupt(self, sig_num, stack_frame):
try:
abort = input('''Die Funktion rechnet relativ lange.
Vielleicht liegt eine Endlosschleife vor.
Möchten sie abbrechen? [J,n]:''')
if abort.upper() in ['J', 'JA', 'Y', 'YES', '']:
raise KeyboardInterrupt
signal.alarm(self.time)
except EOFError:
pass
class WHILEInterpreter(LOOPInterpreter):
def __init__(self):
def __init__(self, timeout=60):
super().__init__()
self.regex_to_token = [(re.compile(r'\d+'), 'NUMBER'),
(re.compile(r'x\d+'), 'IDENTIFIER'),
......@@ -19,11 +45,12 @@ class WHILEInterpreter(LOOPInterpreter):
(re.compile(r'BREAK'), 'BREAK'),
(re.compile(r'\s+', re.MULTILINE), 'WHITESPACE'),
(re.compile(r'[^\n]*'), 'UNKNOWN')]
self.timeout = timeout
def process_program(self, forbidden_identifiers, current_token):
if current_token is None or current_token.k not in ['IDENTIFIER', 'LOOP', 'WHILE']:
self.error_handler.handle_error("Keine passende Anweisung gefunden\n" +
"Erwartet: IDENTIFIER (x0, x1, ...), LOOP oder WHILE")
self.error_handler.handle_error('Keine passende Anweisung gefunden\n' +
'Erwartet: IDENTIFIER (x0, x1, ...), LOOP oder WHILE')
elif current_token.k == 'IDENTIFIER':
current_token = self.process_assignment(forbidden_identifiers, current_token)
elif current_token.k == 'LOOP':
......@@ -34,8 +61,8 @@ class WHILEInterpreter(LOOPInterpreter):
def verify_program(self, forbidden_identifiers, current_token):
if current_token is None or current_token.k not in ['IDENTIFIER', 'LOOP', 'WHILE']:
self.error_handler.handle_error("Keine passende Anweisung gefunden\n" +
"Erwartet: IDENTIFIER (x0, x1, ...), LOOP oder WHILE")
self.error_handler.handle_error('Keine passende Anweisung gefunden\n' +
'Erwartet: IDENTIFIER (x0, x1, ...), LOOP oder WHILE')
elif current_token.k == 'IDENTIFIER':
current_token = self.verify_assignment(forbidden_identifiers, current_token)
elif current_token.k == 'LOOP':
......@@ -51,16 +78,16 @@ class WHILEInterpreter(LOOPInterpreter):
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("WHILE", "UNGLEICH").k == 'NOTEQUALS':
if not self.next_nonempty_token('WHILE', 'UNGLEICH').k == 'NOTEQUALS':
self.error_handler.handle_error('UNGLEICH in WHILE erwartet.')
zero_token = self.next_nonempty_token("WHILE", "0")
zero_token = self.next_nonempty_token('WHILE', '0')
if not zero_token.k == 'NUMBER':
self.error_handler.handle_error('0 in WHILE erwartet.')
if not int(zero_token.v) == 0:
self.error_handler.handle_error('0 in WHILE erwartet.')
if not self.next_nonempty_token("WHILE", "DO").k == 'DO':
if not self.next_nonempty_token('WHILE', 'DO').k == 'DO':
self.error_handler.handle_error('DO in WHILE erwartet.')
if identifier_token.v in self.values:
......@@ -76,7 +103,7 @@ class WHILEInterpreter(LOOPInterpreter):
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 WHILE erwartet.")
self.error_handler.handle_error('SEMICOLON oder END in WHILE erwartet.')
elif token.k == 'SEMICOLON':
continue
elif token.k == 'END':
......@@ -89,7 +116,7 @@ class WHILEInterpreter(LOOPInterpreter):
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 WHILE erwartet.")
self.error_handler.handle_error('SEMICOLON oder END in WHILE erwartet.')
elif token.k == 'SEMICOLON':
continue
elif token.k == 'END':
......@@ -102,29 +129,29 @@ class WHILEInterpreter(LOOPInterpreter):
return self.next_token()
def verify_while(self, forbidden_identifiers, current_token):
identifier_token = self.next_nonempty_token("WHILE", "IDENTIFIER")
identifier_token = self.next_nonempty_token('WHILE', 'IDENTIFIER')
if not identifier_token.k == 'IDENTIFIER':
self.error_handler.handle_error('IDENTIFIER in WHILE 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("WHILE", "UNGLEICH").k == 'NOTEQUALS':
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('WHILE', 'UNGLEICH').k == 'NOTEQUALS':
self.error_handler.handle_error('UNGLEICH in WHILE erwartet.')
zero_token = self.next_nonempty_token("WHILE", "0")
zero_token = self.next_nonempty_token('WHILE', '0')
if not zero_token.k == 'NUMBER':
self.error_handler.handle_error('0 in WHILE erwartet.')
if not int(zero_token.v) == 0:
self.error_handler.handle_error('0 in WHILE erwartet.')
if not self.next_nonempty_token("WHILE", "DO").k == 'DO':
if not self.next_nonempty_token('WHILE', 'DO').k == 'DO':
self.error_handler.handle_error('DO in WHILE erwartet.')
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 WHILE erwartet.")
self.error_handler.handle_error('SEMICOLON oder END in WHILE erwartet.')
elif token.k == 'SEMICOLON':
continue
elif token.k == 'END':
......@@ -132,7 +159,30 @@ class WHILEInterpreter(LOOPInterpreter):
return self.next_token()
def interpret(program):
interpreter = WHILEInterpreter()
def interpret(self, program):
try:
with Timeout(self.timeout):
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, timeout=60):
interpreter = WHILEInterpreter(timeout)
return interpreter.interpret(program)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment