yuce / pyswip

PySwip is a Python-Prolog interface that enables querying SWI-Prolog in your Python programs.
https://pyswip.org
MIT License
479 stars 98 forks source link

No Support for Dictionaries #50

Open zhanwenchen opened 5 years ago

zhanwenchen commented 5 years ago
from pyswip import Prolog
prolog = Prolog()
list(prolog.query('Customer = customer{age: 26, name: jodie}'))

The output is

[{'Customer': 'dict(customer, jodie, name, 26, age)'}]

This behavior does not match Prolog's. In the SWI-Prolog terminal, the query

Customer = customer{age: 26, name: jodie}.

yields the result

Customer = customer{age:26, name:jodie}.
allComputableThings commented 4 years ago

I have a monkey-patch for dicts deserialization. Replace these functions in easy.py

def getTerm(t, functor_to_constructor=None):
    """
    :param t:
    :param functor_to_constructor:
    :return:
    """
    if t is None:
        return None
    p = PL_term_type(t)
    if p < PL_TERM:
        res = _getterm_router[p](t)
    elif PL_is_list(t):
        res = getList(t, functor_to_constructor=functor_to_constructor,
                      visited=visited)
    elif p == PL_DICT:
        # assert PL_is_functor(t)
        res = getDict(t
                      , functor_to_constructor=functor_to_constructor)

    else:
        res = getFunctor(t, functor_to_constructor=functor_to_constructor)
    return res

def getDict(term, functor_to_constructor=None):
    """
    Return t as a list.
    """

    if isinstance(term, Term):
        term = term.handle
    elif not isinstance(term, (c_void_p, int)):
        raise ArgumentTypeError((str(Term), str(int)), str(type(term)))

    f = functor_t()
    if PL_get_functor(term, byref(f)):
        # get args
        args = []
        arity = PL_functor_arity(f.value)
        # let's have all args be consecutive
        a0 = PL_new_term_refs(arity)
        for i, a in enumerate(range(1, arity + 1)):
            if PL_get_arg(a, term, a0 + i):
                args.append(getTerm(a0 + i,
                                    functor_to_constructor=functor_to_constructor))
            else:
                raise Exception("Missing arg")

        def pairwise(t):
            it = iter(t)
            return zip(it,it)

        tag = args[0]  # Dict Tag. Looks something like a class name.

        d = {k.value:v
             for v, k in pairwise(args[1:])}
        if functor_to_constructor is not None \
                and tag.value in functor_to_constructor:
            # Treat the tag as a class name and the keys
            # as named constructor parameters.
            return functor_to_constructor[tag.value](**d)

        return d
    else:
        res = getFunctor(term, functor_to_constructor=functor_to_constructor)
        return res

... parsing the output is straightforward.

However, the bigger problems is that dict seem to misbehave with the .query call, which pyswip implements as:

pyrun(GoalString,BindingList) :- 
      (atom_chars(A,GoalString),
      atom_to_term(A,Goal,BindingList),
      call(Goal))).

Asserting this rountine in swipl, we can test it with dicts without data-marshalling concerns:

?- pyrun("fact(X)",BindingList).    
BindingList = ['X'=123].

For the dict query we get:

?- pyrun("A = point{x:1,y:2}.x.", BindingList).
BindingList = ['A'=point{x:1, y:2}.x].

or more verbosely:

?- atom_chars(A__,"X=point{x:1, y:2}.x"), atom_to_term(A__,Goal,BindingList), call(Goal).
A__ = 'X=point{x:1, y:2}.x',
Goal =  (point{x:1, y:2}.x=point{x:1, y:2}.x),
BindingList = ['X'=point{x:1, y:2}.x].

The vanilla swipl query without pyrun should be:

?- A = point{x:1,y:2}.x
A = 1.

so pyrun has changed the query somewhere and caused A not to be bound to 1 in the goal. :-(

Can anyone suggest a fix?