diff --git a/pyswip/core.py b/pyswip/core.py index 3f867b7..b5b45c0 100644 --- a/pyswip/core.py +++ b/pyswip/core.py @@ -1367,5 +1367,9 @@ def cleanupProlog(): # TODO Prolog documentation says cleanup with code 0 may be interrupted # If the program has come to an end the prolog system should not # interfere with that. Therefore we may want to use 1 instead of 0. - PL_cleanup(int(_hook.exit_code or 0)) + try: + exit_code = int(_hook.exit_code or 0) + except ValueError: + exit_code = 0 + PL_cleanup(exit_code) _isCleaned = True diff --git a/pyswip/prolog.py b/pyswip/prolog.py index d19fc1e..5a3e677 100644 --- a/pyswip/prolog.py +++ b/pyswip/prolog.py @@ -80,16 +80,19 @@ class Prolog: # We keep track of open queries to avoid nested queries. _queryIsOpen = False + _queryWrapper = None class _QueryWrapper(object): def __init__(self): + self._swipl_qid = None + self._swipl_fid = None if Prolog._queryIsOpen: raise NestedQueryError("The last query was not closed") def __call__(self, query, maxresult, catcherrors, normalize): Prolog._init_prolog_thread() - swipl_fid = PL_open_foreign_frame() + self._swipl_fid = PL_open_foreign_frame() swipl_head = PL_new_term_ref() swipl_args = PL_new_term_refs(2) @@ -143,6 +146,11 @@ def _init_prolog_thread(cls): elif pengine_id == -2: print("{WARN} Single-threaded swipl build, beware!") + @classmethod + def abort(cls): + # The clean_up method is called to make sure that no query is still running + cls._queryWrapper.clean_up() + @classmethod def asserta(cls, assertion, catcherrors=False): next(cls.query(assertion.join(["asserta((", "))."]), catcherrors=catcherrors)) @@ -183,7 +191,8 @@ def query(cls, query, maxresult=-1, catcherrors=True, normalize=True): >>> print sorted(prolog.query("father(michael,X)")) [{'X': 'gina'}, {'X': 'john'}] """ - return cls._QueryWrapper()(query, maxresult, catcherrors, normalize) + cls._queryWrapper = cls._QueryWrapper() + return cls._queryWrapper(query, maxresult, catcherrors, normalize) def normalize_values(values): diff --git a/tests/test_prolog.py b/tests/test_prolog.py index 6b61cbb..31c506f 100644 --- a/tests/test_prolog.py +++ b/tests/test_prolog.py @@ -111,3 +111,38 @@ def test_prolog_read_file(self): prolog = pl.Prolog() prolog.consult("tests/test_read.pl") list(prolog.query('read_file("tests/test_read.pl", S)')) + + def test_abort_query(self): + """ + SWI-Prolog cannot have nested queries called by the foreign function + interface, that is, if we open a query and are getting results from it, + we cannot open another query before closing that one. + + However, the interface allows to interrupt a query via the abort method. + This test makes sure that this feature works correctly. + """ + p = pl.Prolog() + + # Add something to the base + p.assertz("father(john,mich)") + p.assertz("father(john,gina)") + p.assertz("mother(jane,mich)") + + somequery = "father(john, Y)" + otherquery = "mother(jane, X)" + + + # This should not throw an exception + for q in p.query(somequery): + p.abort() + for j in p.query(otherquery): + # This should not throw an error, because we aborted the previous query + pass + + # But this one should + with self.assertRaises(pl.NestedQueryError): + for q in p.query(somequery): + for j in p.query(otherquery): + # This should throw an error, because I opened the second + # query + pass