Skip to content

Commit

Permalink
Refactor easy (1) (#189)
Browse files Browse the repository at this point in the history
Refactored easy
  • Loading branch information
yuce authored Oct 23, 2024
1 parent 83e205e commit 4441ad7
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 38 deletions.
5 changes: 5 additions & 0 deletions docs/source/api/easy.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Easy
----

.. automodule:: pyswip.easy
:members:
1 change: 1 addition & 0 deletions docs/source/api/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ API Documentation

examples
prolog
easy



1 change: 1 addition & 0 deletions src/pyswip/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@

from pyswip.prolog import Prolog as Prolog
from pyswip.easy import *
from pyswip.core import *
5 changes: 5 additions & 0 deletions src/pyswip/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,11 @@ def PL_STRINGS_MARK():
PL_register_foreign = _lib.PL_register_foreign
PL_register_foreign = check_strings(0, None)(PL_register_foreign)

PL_register_foreign_in_module = _lib.PL_register_foreign_in_module
PL_register_foreign_in_module = check_strings([0, 1], None)(
PL_register_foreign_in_module
)

PL_new_atom = _lib.PL_new_atom
PL_new_atom.argtypes = [c_char_p]
PL_new_atom.restype = atom_t
Expand Down
168 changes: 133 additions & 35 deletions src/pyswip/easy.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,86 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from pyswip.core import *
import inspect
from typing import Union, Callable, Optional

from pyswip.core import (
PL_new_atom,
PL_register_atom,
PL_atom_wchars,
PL_get_atom,
PL_unregister_atom,
PL_new_term_ref,
PL_compare,
PL_get_chars,
PL_copy_term_ref,
PL_unify_atom,
PL_unify_string_chars,
PL_unify_integer,
PL_unify_bool,
PL_unify_float,
PL_unify_list,
PL_unify_nil,
PL_term_type,
PL_put_term,
PL_new_functor,
PL_functor_name,
PL_functor_arity,
PL_get_functor,
PL_new_term_refs,
PL_get_arg,
PL_cons_functor_v,
PL_put_atom_chars,
PL_put_integer,
PL_put_functor,
PL_put_nil,
PL_cons_list,
PL_get_long,
PL_get_float,
PL_is_list,
PL_get_list,
PL_register_foreign_in_module,
PL_call,
PL_new_module,
PL_pred,
PL_open_query,
PL_next_solution,
PL_cut_query,
PL_close_query,
PL_VARIABLE,
PL_STRINGS_MARK,
PL_TERM,
PL_DICT,
PL_ATOM,
PL_STRING,
PL_INTEGER,
PL_FLOAT,
PL_Q_NODEBUG,
PL_Q_CATCH_EXCEPTION,
PL_FA_NONDETERMINISTIC,
CVT_VARIABLE,
BUF_RING,
REP_UTF8,
CVT_ATOM,
CVT_STRING,
CFUNCTYPE,
cleaned,
cast,
c_size_t,
byref,
c_void_p,
atom_t,
create_string_buffer,
c_char_p,
functor_t,
c_int,
c_long,
c_double,
foreign_t,
term_t,
control_t,
module_t,
)


integer_types = (int,)
Expand Down Expand Up @@ -134,11 +213,7 @@ def __hash__(self):
return self.handle


def isstr(s):
return isinstance(s, str)


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

def __init__(self, handle=None, name=None):
Expand Down Expand Up @@ -170,7 +245,7 @@ def _fun(self, value, t):
if type(value) == Atom:
fun = PL_unify_atom
value = value.handle
elif isstr(value):
elif isinstance(value, str):
fun = PL_unify_string_chars
value = value.encode()
elif type(value) == int:
Expand Down Expand Up @@ -284,7 +359,9 @@ def fromTerm(cls, term):

fromTerm = classmethod(fromTerm)

value = property(lambda s: s.__value)
@property
def value(self):
return self.__value

def __call__(self, *args):
assert self.arity == len(args) # FIXME: Put a decent error message
Expand Down Expand Up @@ -346,12 +423,10 @@ def putTerm(term, value):
value.put(term)
elif isinstance(value, list):
putList(term, value)
elif isinstance(value, Atom):
print("ATOM")
elif isinstance(value, Functor):
PL_put_functor(term, value.handle)
else:
raise Exception("Not implemented")
raise Exception(f"Not implemented for type: {type(value)}")


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


def _callbackWrapper(arity=1, nondeterministic=False):
global arities

res = arities.get((arity, nondeterministic))
if res is None:
if nondeterministic:
Expand All @@ -514,8 +587,6 @@ def _callbackWrapper(arity=1, nondeterministic=False):


def _foreignWrapper(fun, nondeterministic=False):
global funwraps

res = funwraps.get(fun)
if res is None:

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


def registerForeign(func, name=None, arity=None, flags=0):
"""Register a Python predicate
``func``: Function to be registered. The function should return a value in
``foreign_t``, ``True`` or ``False``.
``name`` : Name of the function. If this value is not used, ``func.func_name``
should exist.
``arity``: Arity (number of arguments) of the function. If this value is not
used, ``func.arity`` should exist.
def registerForeign(
func: Callable, name: str = "", arity: Optional[int] = None, flags: int = 0
):
"""
global cwraps
Registers a Python callable as a Prolog predicate
if arity is None:
arity = func.arity
:param func: Callable to be registered. The callable should return a value in ``foreign_t``, ``True`` or ``False``.
:param name: Name of the callable. If the name is not specified, it is derived from ``func.__name__``.
:param arity: Number of parameters of the callable. If not specified, it is derived from the callable signature.
:param flags: Only supported flag is ``PL_FA_NONDETERMINISTIC``.
if name is None:
name = func.__name__
See: `PL_register_foreign <https://www.swi-prolog.org/pldoc/man?CAPI=PL_register_foreign>`_.
.. Note::
This function is deprecated.
Use :py:meth:`Prolog.register_foreign` instead.
"""
if not callable(func):
raise ValueError("func is not callable")
nondeterministic = bool(flags & PL_FA_NONDETERMINISTIC)
if arity is None:
# backward compatibility
if hasattr(func, "arity"):
arity = func.arity
else:
arity = len(inspect.signature(func).parameters)
if nondeterministic:
arity -= 1
if not name:
name = func.__name__

cwrap = _callbackWrapper(arity, nondeterministic)
fwrap = _foreignWrapper(func, nondeterministic)
fwrap2 = cwrap(fwrap)
cwraps.append(fwrap2)
return PL_register_foreign(name, arity, fwrap2, flags)
# return PL_register_foreign(name, arity,
# _callbackWrapper(arity)(_foreignWrapper(func)), flags)
return PL_register_foreign_in_module(None, name, arity, fwrap2, flags)


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


def newModule(name):
"""Create a new module.
``name``: An Atom or a string
def newModule(name: Union[str, Atom]) -> module_t:
"""
Returns a module with the given name.
The module is created if it does not exist.
.. NOTE::
This function is deprecated. Use ``module`` instead.
:param name: Name of the module
"""
return module(name)


def module(name: Union[str, Atom]) -> module_t:
"""
Returns a module with the given name.
The module is created if it does not exist.
:param name: Name of the module
"""
if isinstance(name, str):
name = Atom(name)

return PL_new_module(name.handle)


Expand Down
76 changes: 75 additions & 1 deletion src/pyswip/prolog.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
Provides the basic Prolog interface.
"""

from typing import Union, Generator
import functools
import inspect
from typing import Union, Generator, Callable, Optional
from pathlib import Path

from pyswip.utils import resolve_path
Expand All @@ -33,6 +35,8 @@
PL_Q_NODEBUG,
PL_Q_CATCH_EXCEPTION,
PL_Q_NORMAL,
PL_FA_NONDETERMINISTIC,
CFUNCTYPE,
PL_initialise,
PL_open_foreign_frame,
PL_new_term_ref,
Expand All @@ -49,6 +53,10 @@
PL_cut_query,
PL_thread_self,
PL_thread_attach_engine,
PL_register_foreign_in_module,
foreign_t,
term_t,
control_t,
)


Expand Down Expand Up @@ -111,6 +119,7 @@ class Prolog:

# We keep track of open queries to avoid nested queries.
_queryIsOpen = False
_cwraps = []

class _QueryWrapper(object):
def __init__(self):
Expand Down Expand Up @@ -359,6 +368,71 @@ def query(
"""
return cls._QueryWrapper()(query, maxresult, catcherrors, normalize)

@classmethod
@functools.cache
def _callback_wrapper(cls, arity, nondeterministic):
ps = [foreign_t] + [term_t] * arity
if nondeterministic:
return CFUNCTYPE(*(ps + [control_t]))
return CFUNCTYPE(*ps)

@classmethod
@functools.cache
def _foreign_wrapper(cls, fun, nondeterministic=False):
def wrapper(*args):
if nondeterministic:
args = [getTerm(arg) for arg in args[:-1]] + [args[-1]]
else:
args = [getTerm(arg) for arg in args]
r = fun(*args)
return True if r is None else r

return wrapper

@classmethod
def register_foreign(
cls,
func: Callable,
/,
name: str = "",
arity: Optional[int] = None,
*,
module: str = "",
nondeterministic: bool = False,
):
"""
Registers a Python callable as a Prolog predicate
:param func:
Callable to be registered. The callable should return a value in ``foreign_t``, ``True`` or ``False`` or ``None``.
Returning ``None`` is equivalent to returning ``True``.
:param name:
Name of the callable. If the name is not specified, it is derived from ``func.__name__``.
:param arity:
Number of parameters of the callable. If not specified, it is derived from the callable signature.
:param module:
Name of the module to register the predicate. By default, the current module.
:param nondeterministic:
Set the foreign callable as nondeterministic
"""
if not callable(func):
raise ValueError("func is not callable")
module = module or None
flags = PL_FA_NONDETERMINISTIC if nondeterministic else 0
if arity is None:
arity = len(inspect.signature(func).parameters)
if nondeterministic:
arity -= 1
if not name:
name = func.__name__

cwrap = cls._callback_wrapper(arity, nondeterministic)
# TODO: check func
fwrap = cls._foreign_wrapper(func, nondeterministic)
fwrap = cwrap(fwrap)
cls._cwraps.append(fwrap)
return PL_register_foreign_in_module(module, name, arity, fwrap, flags)


def normalize_values(values):
from pyswip.easy import Atom, Functor
Expand Down
2 changes: 0 additions & 2 deletions tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@

import pytest

from pyswip import *

examples = [
"create_term.py",
"father.py",
Expand Down
Loading

0 comments on commit 4441ad7

Please sign in to comment.