Welcome, guest | Sign In | My Account | Store | Cart

this is an extension to Shai Berger's Pythologic to include support for PyLog. See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/303057 You'll also need http://christophe.delord.free.fr/en/pylog/

This program also adds the "&" syntatic sugar. See the example class Test.

Python, 299 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
"""
Here's some changes to the code to add support for pylog. You'll need pylog to run the test. Also, changed the code to use & instead of requiring []. Also added beginings of support for forward chaining. Enjoy! -Huu 

"""
#
# Pythologic.py
#
# Add logic programming (Prolog) syntax into Python.
#
# (c) 2004 Shai Berger
#
import string
from pylog import *


class Struct:
    def __init__(self, database, head, subs):
        """
        The head and subs are essential - what makes this struct.
        The database should only be used while structs are constructed,
        and later removed.
        """
        self.database = database
        self.head = head
        self.subs = subs
        self.prev = None
        self.next = None        
        self.forwardchain_body = None
        self.conditional_body = None
        self.forwardchain_head = None
        self.conditional_head = None
        
    def __pos__(self):
        """
        unary + means insert into database as fact
        """
        self.database.add_fact(self)
        return self

    def __neg__(self):
        """
        unary - means retract from database as fact
        """
        return self

    def __lshift__(self, body):
        """
        The ideal is
        consequent(args) << cond1(args1),...
        for now we must do with
        consequent(args) << (cond1(args1),...)
        """
        self.conditional_body =  body
        body.conditional_head = self
        self.database.add_conditional(self)
        return body


    def __rshift__(self, head):
        """
        create a forward chaining rule.

        """
        head.forwardchain_body = self
        self.forwardchain_head = head
        self.database.add_forwardchain(head)
        return self

    def __rand__(self, other):
        """
        concatenates predicates
        a & b -> c

        """
        if self.forwardchain_head:
            self.prev = other
            other.next = self
            return other
        elif other.conditional_head:
            self.prev = other
            other.next = self
            return self
        else:
            self.prev = other
            other.next = self
            return self

    def __ror__(self, next):
        """
        concatenates predicates
        a & b -> c
        TODO
        """
        print "ror", self, next
        return self

    def __str__(self):
        def tostr(s):
            if isinstance(s, list):
                return "["+string.joinfields(map(str,s), ", ")+"]"
            else:
                return str(s)

        def str_prev(s):
            ret0 = ""
            while s!= None:
                if ret0 == "":
                    ret0 = str(s)
                else:
                    ret0 = str(s) + ", " + ret0                    
                s = s.prev
            return ret0

        def str_next(s):
            ret0 = ""
            while s!= None:
                if ret0 == "":
                    ret0 = str(s)
                else:
                    ret0 = ret0 + ", " + str(s)                    
                s = s.next
            return ret0
        
        subs = map (tostr, self.subs)
        ret = str(self.head) + "(" + string.join(subs,',') + ")"
        if self.forwardchain_body:
            ret = str_prev(self.forwardchain_body) + " -> " + ret
        if self.conditional_body:
            ret = ret + " :- " + str_next(self.conditional_body)
        return ret
        

class Symbol:
    def __init__ (self, name, database):
        self.name = name
        self.database = database
    def __call__ (self, *args):
        return Struct(self.database, self, args)
    def __str__(self):
        return self.name

class Constant(Symbol):
    """
    A constant is a name. Its value is its name too.
    """
    def value(self): return self.name

class Variable(Symbol):
    def __str__(self):
#        return "?"+self.name
        return self.name


def symbol(name, database):
    if (name[0] in string.uppercase):
        return Variable(name,database)
    else:
        return Constant(name, database)

class Database:
    def __init__(self):
        self.facts = []
        self.conditionals = []
        self.forwardchain = []        
    def add_fact(self, fact):
        self.facts.append(fact)
    def add_conditional(self,head):
        self.conditionals.append(head)
    def add_forwardchain(self,head):
        self.forwardchain.append(head)

    def prt(self):
        """
        Print the database in somewhat readable (prolog) form
        """
        for f in self.facts: print f, "."
        for f in self.forwardchain: print f, "."
        for f in self.conditionals: print f, "."


    def compile(self):
        # do pylog's compile
        exec compile(self.__str__()) in globals()


    def __str__(self):
        ret = ""
        for f in self.facts: ret += str(f) +  ".\n"
        for f in self.conditionals: ret += str(f) +  ".\n"
#       forward chaining not yet supported
#        for f in self.forwardchain: ret += str(f) +  ".\n"                
        return ret
        
    def consult(self, func):
        """
        Include definitions from func into database
        """
        try:
            code = func.func_code
        except:
            raise TypeError, "function or method argument expected"
        names = code.co_names
        locally_defined = code.co_varnames
        globally_defined = func.func_globals.keys()
        defined = locally_defined+tuple(globally_defined)
        # Python < 2.0
        # undefined = filter (lambda n,d=defined: n not in d, names)
        # Modern Python
        undefined = [name for name in names if name not in defined]
        # Generate the new global environment for the function;
        # to the old environment, add definitions for all undefined
        # symbols, which relate to this database (self). When the
        # symbols are operated on in the function, they will add
        # facts and conditionals to the database.
        newglobals = func.func_globals.copy()
        for name in undefined:
            newglobals[name] = symbol(name, self)
        exec code in newglobals
        

    def consult_and_transform(self, func):
        """
        A helper for decorator implementation
        """
        self.consult(func)
        return LogicalFunction(self, func)

class LogicalFunction:
    """
    This class replaces a logical function once it has
    been consulted, to avoid erroneous use
    """
    def __init__(self, database, func):
        self.database=database
        self.logical_function=func
    def __call__(self):
        raise TypeError, "Logical functions are not really callable"

def logical(database):
    """
    A decorator for logical functions
    """
    return database.consult_and_transform

class Test:
    db = Database()

    @logical(db)
    def _rules():
        likes('sam',Food) << indian(Food) & mild(Food)
        likes('sam',Food) << chinese(Food)
        likes('sam',Food) << italian(Food)
        likes('sam','chips')

        +indian('curry')
        +indian('dahl')
        +indian('tandoori')
        +indian('kurma')

        +mild('dahl')
        +mild('tandoori')
        +mild('kurma')

        +chinese('chow_mein')
        +chinese('chop_suey')
        +chinese('sweet_and_sour')

        +italian('pizza')
        +italian('spaghetti')


    def __init__(self):
        self.db.prt()
        self.db.compile()
    
    def test(self):
        WHO, WHAT = Var('WHO'), Var('WHAT')
        queries =	[
            likes('sam','dahl'),
            likes('sam','chop_suey'),
            likes('sam','pizza'),
            likes('sam','chips'),
            likes('sam','curry'),
            likes(WHO,WHAT),
                                ]

        for query in queries:
                print "?", query
                n=0
                for _ in query():
                        print "\tyes:", query
                        n += 1
                if n==0:
                        print "\tno"
        
if __name__ == "__main__":

    test = Test()
    test.test()