# coding=utf-8 import operator import threading import prolog.engine import db from . import problems __all__ = ['PrologSession'] def format_prolog_output(reply, output): messages = [text for text in map(operator.itemgetter(1), output)] # When an engine is destroyed, a nested data object has the actual query result. event = reply['event'] if event == 'destroy': reply = reply['data'] event = reply['event'] if event == 'success': messages.append(prolog.engine.pretty_vars(reply['data'][0])) return messages, 'ok', True and reply['more'] if event == 'failure': messages.append('false') return messages, 'ok', False if event == 'error': # Remove potential module name (engine ID) from the error message. messages.append('error: ' + reply['data'].replace("'{}':".format(reply['id']), '')) return messages, 'error', False return messages, 'ok', False # TODO: is it possible to reach this return statement? class PrologSession(object): """Abstracts a Prolog session. Only public methods are available to the outside world due to the use of multiprocessing managers. Therefore prefix any private methods with an underscore (_). No properties are accessible; use getters and setters instead. Values are passed by value instead of by reference (deep copy!). """ def __init__(self, user_session): self._user_session = user_session # the owning session self._access_lock = threading.Lock() self._engine_id = None self._problem_id = -1 def run(self, code): with self._access_lock: if self._engine_id is not None: prolog.engine.stop(self._engine_id) self._engine_id = None reply, output = prolog.engine.create(code=code) if reply.get('event') != 'create': raise Exception('System error: could not create a prolog engine') self._engine_id = reply['id'] messages = [text for text in map(operator.itemgetter(1), output)] status = 'error' if 'error' in map(operator.itemgetter(0), output) else 'ok' return messages, status, False def query(self, query): with self._access_lock: if self._engine_id is None: return ['Prolog is not running'], 'error', False try: return format_prolog_output(*prolog.engine.ask(self._engine_id, query)) except Exception as e: return [str(e)], 'error', False def step(self): with self._access_lock: if self._engine_id is None: return ['Prolog is not running'], 'error', False try: return format_prolog_output(*prolog.engine.next(self._engine_id)) except Exception as e: return [str(e)], 'error', False def end(self): """Stops the Prolog engine.""" with self._access_lock: if self._engine_id is not None: prolog.engine.stop(self._engine_id) self._engine_id = None self._problem_id = -1 return [], 'ok', False def __del__(self): # no locking needed if GC is removing us, as there cannot be any concurrent access by definition if hasattr(self, '_engine_id') and (self._engine_id is not None): prolog.engine.stop(self._engine_id) self._engine_id = None def run_for_user(self, user_id, problem_id, program, query): """A "shorthand" method to start a Prolog session, load correct solutions of all user's solved problems and the given program, and ask a query. """ conn = db.get_connection() try: cur = conn.cursor() try: cur.execute('select l.id, l.identifier, g.identifier, p.identifier from problem p inner join problem_group g on g.id = p.problem_group_id inner join language l on l.id = p.language_id where p.id = %s', (problem_id,)) row = cur.fetchone() language_id = row[0] language = row[1] problem_group = row[2] problem = row[3] cur.execute('select g.identifier, p.identifier from solution s inner join problem p on p.id = s.problem_id inner join problem_group g on g.id = p.problem_group_id where s.codeq_user_id = %s and s.done = True and s.problem_id != %s and p.language_id = %s', (user_id, problem_id, language_id)) solved_problems = cur.fetchall() finally: cur.close() finally: conn.commit() db.return_connection(conn) other_solutions = problems.solutions_for_problems(language, solved_problems) problem_module = problems.load_problem(language, problem_group, problem, 'common') problem_facts = problems.get_facts(language, problem_module) or '' code = other_solutions + problem_facts + program messages, status, have_more = self.run(code) if status == 'ok': more_messages, status, have_more = self.query(query) messages.extend(more_messages) self._problem_id = problem_id # self._user_session.update_solution(problem_id, [], program) # TODO return messages, status, have_more