Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add abort functionality and handle exception in cleanup #150

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion pyswip/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1366,5 +1366,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
13 changes: 11 additions & 2 deletions pyswip/prolog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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):
Expand Down
35 changes: 35 additions & 0 deletions tests/test_prolog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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