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

Improve list representations, unicode support and multiple threading usage #97

Merged
merged 6 commits into from
Jul 7, 2020
Merged
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
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ Galileo Sartor
Stuart Reynolds
Prologrules
Dylan Lukes
Guglielmo Gemignani

64 changes: 56 additions & 8 deletions pyswip/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1042,9 +1042,41 @@ def check_and_call(*args):

PL_unify_integer = _lib.PL_unify_integer
PL_unify_atom_chars = _lib.PL_unify_atom_chars

PL_unify_float = _lib.PL_unify_float
PL_unify_float.argtypes = [term_t, c_double]
PL_unify_float.restype = c_int

PL_unify_bool = _lib.PL_unify_bool
PL_unify_bool.argtypes = [term_t, c_int]
PL_unify_bool.restype = c_int

PL_unify_list = _lib.PL_unify_list
PL_unify_list.argtypes = [term_t, term_t, term_t]
PL_unify_list.restype = c_int

PL_unify_atom_chars = _lib.PL_unify_atom_chars
PL_unify_atom_chars.argtypes = [term_t, c_char_p]
PL_unify_atom_chars.restype = c_int

PL_foreign_control = _lib.PL_foreign_control
PL_foreign_control.argtypes = [control_t]
PL_foreign_control.restypes = c_int

PL_foreign_context_address = _lib.PL_foreign_context_address
PL_foreign_context_address.argtypes = [control_t]
PL_foreign_context_address.restypes = c_void_p

PL_retry_address = _lib._PL_retry_address
PL_retry_address.argtypes = [c_void_p]
PL_retry_address.restypes = foreign_t

PL_unify = _lib.PL_unify
PL_unify.restype = c_int

PL_succeed = 1
PL_PRUNED = 1

#PL_EXPORT(int) PL_unify_arg(int index, term_t t, term_t a) WUNUSED;
PL_unify_arg = _lib.PL_unify_arg
PL_unify_arg.argtypes = [c_int, term_t, term_t]
Expand Down Expand Up @@ -1229,25 +1261,41 @@ def check_and_call(*args):

PL_is_initialised = _lib.PL_is_initialised

intptr_t = c_long
ssize_t = intptr_t
wint_t = c_uint


#typedef struct
#{
# typedef struct
# {
# int __count;
# union
# {
# wint_t __wch;
# char __wchb[4];
# } __value; /* Value so far. */
#} __mbstate_t;
# } __mbstate_t;

# /*******************************
# * THREADING *
# *******************************/

PL_thread_self = _lib.PL_thread_self
PL_thread_self.restype = c_int

PL_thread_attach_engine = _lib.PL_thread_attach_engine
PL_thread_attach_engine.argtypes = [c_void_p]
PL_thread_attach_engine.restype = c_int


class _mbstate_t_value(Union):
_fields_ = [("__wch",wint_t),
("__wchb",c_char*4)]
_fields_ = [("__wch", wint_t),
("__wchb", c_char * 4)]


class mbstate_t(Structure):
_fields_ = [("__count",c_int),
("__value",_mbstate_t_value)]
_fields_ = [("__count", c_int),
("__value", _mbstate_t_value)]


# stream related funcs
Sread_function = CFUNCTYPE(ssize_t, c_void_p, c_char_p, c_size_t)
Expand Down
31 changes: 28 additions & 3 deletions pyswip/easy.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ def __hash__(self):
return self.handle


# support unicode also in python 2
try:
isinstance("", basestring)
def isstr(s):
return isinstance(s, basestring)
except NameError:
def isstr(s):
return isinstance(s, str)


class Variable(object):
__slots__ = "handle", "chars"

Expand All @@ -154,7 +164,7 @@ def __init__(self, handle=None, name=None):
self.chars = self.chars.decode()

def unify(self, value):
if type(value) == str:
if isstr(value):
fun = PL_unify_atom_chars
value = value.encode()
elif type(value) == int:
Expand All @@ -166,13 +176,28 @@ def unify(self, value):
elif type(value) == list:
fun = PL_unify_list
else:
raise
raise TypeError('Cannot unify {} with value {} due to the value unknown type {}'.
format(self, value, type(value)))

if self.handle is None:
t = PL_new_term_ref(self.handle)
else:
t = PL_copy_term_ref(self.handle)
fun(t, value)

if type(value) == list:
a = PL_new_term_ref(self.handle)
if type(value[0]) == int:
element_fun = PL_unify_integer
elif type(value[0]) == float:
element_fun = PL_unify_float
else:
raise
if value:
for element in value:
fun(t, a, t)
element_fun(a, element)
else:
fun(t, value)
self.handle = t

def get_value(self):
Expand Down
29 changes: 29 additions & 0 deletions pyswip/prolog.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def __init__(self):
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()

swipl_head = PL_new_term_ref()
Expand Down Expand Up @@ -115,6 +116,7 @@ def __call__(self, query, maxresult, catcherrors, normalize):
except AttributeError:
v = {}
for r in [x.value for x in t]:
r = normalize_values(r)
v.update(r)
yield v
else:
Expand All @@ -131,6 +133,16 @@ def __call__(self, query, maxresult, catcherrors, normalize):
PL_discard_foreign_frame(swipl_fid)
Prolog._queryIsOpen = False

@classmethod
def _init_prolog_thread(cls):
pengine_id = PL_thread_self()
if pengine_id == -1:
pengine_id = PL_thread_attach_engine(None)
if pengine_id == -1:
raise PrologError("Unable to attach new Prolog engine to the thread")
elif pengine_id == -2:
print("{WARN} Single-threaded swipl build, beware!")

@classmethod
def asserta(cls, assertion, catcherrors=False):
next(cls.query(assertion.join(["asserta((", "))."]), catcherrors=catcherrors))
Expand Down Expand Up @@ -173,3 +185,20 @@ def query(cls, query, maxresult=-1, catcherrors=True, normalize=True):
"""
return cls._QueryWrapper()(query, maxresult, catcherrors, normalize)


def normalize_values(values):
from pyswip.easy import Atom, Functor
if isinstance(values, Atom):
return values.value
if isinstance(values, Functor):
normalized = values.name.value
if values.arity:
normalized_args = ([str(normalize_values(arg)) for arg in values.args])
normalized = normalized + '(' + ', '.join(normalized_args) + ')'
return normalized
elif isinstance(values, dict):
return {key: normalize_values(v) for key, v in values.items()}
elif isinstance(values, (list, tuple)):
return [normalize_values(v) for v in values]
return values

22 changes: 18 additions & 4 deletions tests/test_prolog.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@
Tests the Prolog class.
"""


import unittest
import doctest

import pyswip.prolog as pl # This implicitly tests library loading code
import pyswip.prolog as pl # This implicitly tests library loading code


class TestProlog(unittest.TestCase):
Expand All @@ -48,7 +47,7 @@ def test_nested_queries(self):
Since this is a user error, we just ensure that a appropriate error
message is thrown.
"""

p = pl.Prolog()

# Add something to the base
Expand All @@ -64,14 +63,29 @@ def test_nested_queries(self):
pass
for _ in p.query(otherquery):
pass

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

def test_prolog_functor_in_list(self):
p = pl.Prolog()
p.assertz('f([g(a,b),h(a,b,c)])')
self.assertEqual([{"L": [u"g(a, b)", u"h(a, b, c)"]}], list(p.query("f(L)")))
p.retract("f([g(a,b),h(a,b,c)])")

def test_prolog_functor_in_functor(self):
p = pl.Prolog()
p.assertz("f([g([h(a,1), h(b,1)])])")
self.assertEqual([{'G': [u"g([u'h(a, 1)', u'h(b, 1)'])"]}], list(p.query('f(G)')))
p.assertz("a([b(c(x), d([y, z, w]))])")
self.assertEqual([{'B': [u"b(c(x), d(['y', 'z', 'w']))"]}], list(p.query('a(B)')))
p.retract("f([g([h(a,1), h(b,1)])])")
p.retract("a([b(c(x), d([y, z, w]))])")

def test_prolog_strings(self):
"""
See: https://github.com/yuce/pyswip/issues/9
Expand Down