Skip to content

Commit 4441ad7

Browse files
authored
Refactor easy (1) (#189)
Refactored easy
1 parent 83e205e commit 4441ad7

File tree

8 files changed

+262
-38
lines changed

8 files changed

+262
-38
lines changed

docs/source/api/easy.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Easy
2+
----
3+
4+
.. automodule:: pyswip.easy
5+
:members:

docs/source/api/modules.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ API Documentation
55

66
examples
77
prolog
8+
easy
89

910

1011

src/pyswip/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@
2323

2424
from pyswip.prolog import Prolog as Prolog
2525
from pyswip.easy import *
26+
from pyswip.core import *

src/pyswip/core.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,11 @@ def PL_STRINGS_MARK():
11321132
PL_register_foreign = _lib.PL_register_foreign
11331133
PL_register_foreign = check_strings(0, None)(PL_register_foreign)
11341134

1135+
PL_register_foreign_in_module = _lib.PL_register_foreign_in_module
1136+
PL_register_foreign_in_module = check_strings([0, 1], None)(
1137+
PL_register_foreign_in_module
1138+
)
1139+
11351140
PL_new_atom = _lib.PL_new_atom
11361141
PL_new_atom.argtypes = [c_char_p]
11371142
PL_new_atom.restype = atom_t

src/pyswip/easy.py

Lines changed: 133 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,86 @@
1818
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1919
# SOFTWARE.
2020

21-
from pyswip.core import *
21+
import inspect
22+
from typing import Union, Callable, Optional
23+
24+
from pyswip.core import (
25+
PL_new_atom,
26+
PL_register_atom,
27+
PL_atom_wchars,
28+
PL_get_atom,
29+
PL_unregister_atom,
30+
PL_new_term_ref,
31+
PL_compare,
32+
PL_get_chars,
33+
PL_copy_term_ref,
34+
PL_unify_atom,
35+
PL_unify_string_chars,
36+
PL_unify_integer,
37+
PL_unify_bool,
38+
PL_unify_float,
39+
PL_unify_list,
40+
PL_unify_nil,
41+
PL_term_type,
42+
PL_put_term,
43+
PL_new_functor,
44+
PL_functor_name,
45+
PL_functor_arity,
46+
PL_get_functor,
47+
PL_new_term_refs,
48+
PL_get_arg,
49+
PL_cons_functor_v,
50+
PL_put_atom_chars,
51+
PL_put_integer,
52+
PL_put_functor,
53+
PL_put_nil,
54+
PL_cons_list,
55+
PL_get_long,
56+
PL_get_float,
57+
PL_is_list,
58+
PL_get_list,
59+
PL_register_foreign_in_module,
60+
PL_call,
61+
PL_new_module,
62+
PL_pred,
63+
PL_open_query,
64+
PL_next_solution,
65+
PL_cut_query,
66+
PL_close_query,
67+
PL_VARIABLE,
68+
PL_STRINGS_MARK,
69+
PL_TERM,
70+
PL_DICT,
71+
PL_ATOM,
72+
PL_STRING,
73+
PL_INTEGER,
74+
PL_FLOAT,
75+
PL_Q_NODEBUG,
76+
PL_Q_CATCH_EXCEPTION,
77+
PL_FA_NONDETERMINISTIC,
78+
CVT_VARIABLE,
79+
BUF_RING,
80+
REP_UTF8,
81+
CVT_ATOM,
82+
CVT_STRING,
83+
CFUNCTYPE,
84+
cleaned,
85+
cast,
86+
c_size_t,
87+
byref,
88+
c_void_p,
89+
atom_t,
90+
create_string_buffer,
91+
c_char_p,
92+
functor_t,
93+
c_int,
94+
c_long,
95+
c_double,
96+
foreign_t,
97+
term_t,
98+
control_t,
99+
module_t,
100+
)
22101

23102

24103
integer_types = (int,)
@@ -134,11 +213,7 @@ def __hash__(self):
134213
return self.handle
135214

136215

137-
def isstr(s):
138-
return isinstance(s, str)
139-
140-
141-
class Variable(object):
216+
class Variable:
142217
__slots__ = "handle", "chars"
143218

144219
def __init__(self, handle=None, name=None):
@@ -170,7 +245,7 @@ def _fun(self, value, t):
170245
if type(value) == Atom:
171246
fun = PL_unify_atom
172247
value = value.handle
173-
elif isstr(value):
248+
elif isinstance(value, str):
174249
fun = PL_unify_string_chars
175250
value = value.encode()
176251
elif type(value) == int:
@@ -284,7 +359,9 @@ def fromTerm(cls, term):
284359

285360
fromTerm = classmethod(fromTerm)
286361

287-
value = property(lambda s: s.__value)
362+
@property
363+
def value(self):
364+
return self.__value
288365

289366
def __call__(self, *args):
290367
assert self.arity == len(args) # FIXME: Put a decent error message
@@ -346,12 +423,10 @@ def putTerm(term, value):
346423
value.put(term)
347424
elif isinstance(value, list):
348425
putList(term, value)
349-
elif isinstance(value, Atom):
350-
print("ATOM")
351426
elif isinstance(value, Functor):
352427
PL_put_functor(term, value.handle)
353428
else:
354-
raise Exception("Not implemented")
429+
raise Exception(f"Not implemented for type: {type(value)}")
355430

356431

357432
def putList(l, ls): # noqa: E741
@@ -498,8 +573,6 @@ def getVariable(t):
498573

499574

500575
def _callbackWrapper(arity=1, nondeterministic=False):
501-
global arities
502-
503576
res = arities.get((arity, nondeterministic))
504577
if res is None:
505578
if nondeterministic:
@@ -514,8 +587,6 @@ def _callbackWrapper(arity=1, nondeterministic=False):
514587

515588

516589
def _foreignWrapper(fun, nondeterministic=False):
517-
global funwraps
518-
519590
res = funwraps.get(fun)
520591
if res is None:
521592

@@ -535,32 +606,42 @@ def wrapper(*args):
535606
cwraps = []
536607

537608

538-
def registerForeign(func, name=None, arity=None, flags=0):
539-
"""Register a Python predicate
540-
``func``: Function to be registered. The function should return a value in
541-
``foreign_t``, ``True`` or ``False``.
542-
``name`` : Name of the function. If this value is not used, ``func.func_name``
543-
should exist.
544-
``arity``: Arity (number of arguments) of the function. If this value is not
545-
used, ``func.arity`` should exist.
609+
def registerForeign(
610+
func: Callable, name: str = "", arity: Optional[int] = None, flags: int = 0
611+
):
546612
"""
547-
global cwraps
613+
Registers a Python callable as a Prolog predicate
548614
549-
if arity is None:
550-
arity = func.arity
615+
:param func: Callable to be registered. The callable should return a value in ``foreign_t``, ``True`` or ``False``.
616+
:param name: Name of the callable. If the name is not specified, it is derived from ``func.__name__``.
617+
:param arity: Number of parameters of the callable. If not specified, it is derived from the callable signature.
618+
:param flags: Only supported flag is ``PL_FA_NONDETERMINISTIC``.
551619
552-
if name is None:
553-
name = func.__name__
620+
See: `PL_register_foreign <https://www.swi-prolog.org/pldoc/man?CAPI=PL_register_foreign>`_.
554621
622+
.. Note::
623+
This function is deprecated.
624+
Use :py:meth:`Prolog.register_foreign` instead.
625+
"""
626+
if not callable(func):
627+
raise ValueError("func is not callable")
555628
nondeterministic = bool(flags & PL_FA_NONDETERMINISTIC)
629+
if arity is None:
630+
# backward compatibility
631+
if hasattr(func, "arity"):
632+
arity = func.arity
633+
else:
634+
arity = len(inspect.signature(func).parameters)
635+
if nondeterministic:
636+
arity -= 1
637+
if not name:
638+
name = func.__name__
556639

557640
cwrap = _callbackWrapper(arity, nondeterministic)
558641
fwrap = _foreignWrapper(func, nondeterministic)
559642
fwrap2 = cwrap(fwrap)
560643
cwraps.append(fwrap2)
561-
return PL_register_foreign(name, arity, fwrap2, flags)
562-
# return PL_register_foreign(name, arity,
563-
# _callbackWrapper(arity)(_foreignWrapper(func)), flags)
644+
return PL_register_foreign_in_module(None, name, arity, fwrap2, flags)
564645

565646

566647
newTermRef = PL_new_term_ref
@@ -588,13 +669,30 @@ def call(*terms, **kwargs):
588669
return PL_call(t.handle, module)
589670

590671

591-
def newModule(name):
592-
"""Create a new module.
593-
``name``: An Atom or a string
672+
def newModule(name: Union[str, Atom]) -> module_t:
673+
"""
674+
Returns a module with the given name.
675+
676+
The module is created if it does not exist.
677+
678+
.. NOTE::
679+
This function is deprecated. Use ``module`` instead.
680+
681+
:param name: Name of the module
682+
"""
683+
return module(name)
684+
685+
686+
def module(name: Union[str, Atom]) -> module_t:
687+
"""
688+
Returns a module with the given name.
689+
690+
The module is created if it does not exist.
691+
692+
:param name: Name of the module
594693
"""
595694
if isinstance(name, str):
596695
name = Atom(name)
597-
598696
return PL_new_module(name.handle)
599697

600698

src/pyswip/prolog.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
Provides the basic Prolog interface.
2323
"""
2424

25-
from typing import Union, Generator
25+
import functools
26+
import inspect
27+
from typing import Union, Generator, Callable, Optional
2628
from pathlib import Path
2729

2830
from pyswip.utils import resolve_path
@@ -33,6 +35,8 @@
3335
PL_Q_NODEBUG,
3436
PL_Q_CATCH_EXCEPTION,
3537
PL_Q_NORMAL,
38+
PL_FA_NONDETERMINISTIC,
39+
CFUNCTYPE,
3640
PL_initialise,
3741
PL_open_foreign_frame,
3842
PL_new_term_ref,
@@ -49,6 +53,10 @@
4953
PL_cut_query,
5054
PL_thread_self,
5155
PL_thread_attach_engine,
56+
PL_register_foreign_in_module,
57+
foreign_t,
58+
term_t,
59+
control_t,
5260
)
5361

5462

@@ -111,6 +119,7 @@ class Prolog:
111119

112120
# We keep track of open queries to avoid nested queries.
113121
_queryIsOpen = False
122+
_cwraps = []
114123

115124
class _QueryWrapper(object):
116125
def __init__(self):
@@ -359,6 +368,71 @@ def query(
359368
"""
360369
return cls._QueryWrapper()(query, maxresult, catcherrors, normalize)
361370

371+
@classmethod
372+
@functools.cache
373+
def _callback_wrapper(cls, arity, nondeterministic):
374+
ps = [foreign_t] + [term_t] * arity
375+
if nondeterministic:
376+
return CFUNCTYPE(*(ps + [control_t]))
377+
return CFUNCTYPE(*ps)
378+
379+
@classmethod
380+
@functools.cache
381+
def _foreign_wrapper(cls, fun, nondeterministic=False):
382+
def wrapper(*args):
383+
if nondeterministic:
384+
args = [getTerm(arg) for arg in args[:-1]] + [args[-1]]
385+
else:
386+
args = [getTerm(arg) for arg in args]
387+
r = fun(*args)
388+
return True if r is None else r
389+
390+
return wrapper
391+
392+
@classmethod
393+
def register_foreign(
394+
cls,
395+
func: Callable,
396+
/,
397+
name: str = "",
398+
arity: Optional[int] = None,
399+
*,
400+
module: str = "",
401+
nondeterministic: bool = False,
402+
):
403+
"""
404+
Registers a Python callable as a Prolog predicate
405+
406+
:param func:
407+
Callable to be registered. The callable should return a value in ``foreign_t``, ``True`` or ``False`` or ``None``.
408+
Returning ``None`` is equivalent to returning ``True``.
409+
:param name:
410+
Name of the callable. If the name is not specified, it is derived from ``func.__name__``.
411+
:param arity:
412+
Number of parameters of the callable. If not specified, it is derived from the callable signature.
413+
:param module:
414+
Name of the module to register the predicate. By default, the current module.
415+
:param nondeterministic:
416+
Set the foreign callable as nondeterministic
417+
"""
418+
if not callable(func):
419+
raise ValueError("func is not callable")
420+
module = module or None
421+
flags = PL_FA_NONDETERMINISTIC if nondeterministic else 0
422+
if arity is None:
423+
arity = len(inspect.signature(func).parameters)
424+
if nondeterministic:
425+
arity -= 1
426+
if not name:
427+
name = func.__name__
428+
429+
cwrap = cls._callback_wrapper(arity, nondeterministic)
430+
# TODO: check func
431+
fwrap = cls._foreign_wrapper(func, nondeterministic)
432+
fwrap = cwrap(fwrap)
433+
cls._cwraps.append(fwrap)
434+
return PL_register_foreign_in_module(module, name, arity, fwrap, flags)
435+
362436

363437
def normalize_values(values):
364438
from pyswip.easy import Atom, Functor

tests/test_examples.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@
2828

2929
import pytest
3030

31-
from pyswip import *
32-
3331
examples = [
3432
"create_term.py",
3533
"father.py",

0 commit comments

Comments
 (0)