1 __doc__ = ''' Python interface to XSB Prolog, SWI Prolog, ECLiPSe Prolog and Flora2
2 by Markus Schatten <markus_dot_schatten_at_foi_dot_hr>
3 Faculty of Organization and Informatics,
4 Varazdin, Croatia, 2011
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA'''
19
20 __version__ = '1.0.1'
21
22 import pexpect as px
23 import re
24
25 xsbprompt = '[|][ ][?][-][ ]'
26 xsberror = '[+][+]Error.*'
27
28 var_re = re.compile( '[^a-zA-Z0-9_]([A-Z][a-zA-Z0-9_]*)' )
29 res_re = re.compile( "res[\(]'([A-Z][a-zA-Z0-9_]*)',[ ]?(.*)[\)]" )
30
32 '''Exception raised if XSB executable is not found on the specified path.'''
33 pass
34
36 '''Exception raised if loaded module has compile errors.'''
37 pass
38
40 '''Exception raised if query raises an error.'''
41 pass
42
44 '''Python interface to XSB Prolog (http://xsb.sf.net)'''
45 - def __init__( self, path='xsb', args='--nobanner --quietload' ):
46 '''Constructor method
47 Usage: xsb( path, args )
48 path - path to XSB executable (default: 'xsb')
49 args - command line arguments (default: '--nobanner --quietload')
50
51 self.engine becomes pexpect spawn instance of XSB Prolog shell
52
53 Raises: XSBExecutableNotFound'''
54 try:
55 self.engine = px.spawn( path + ' ' + args, timeout=5 )
56 self.engine.expect( xsbprompt )
57 except px.ExceptionPexpect:
58 raise XSBExecutableNotFound, 'XSB executable not found on the specified path. Try using xsb( "/path/to/XSB/bin/xsb" )'
59
60 - def load( self, module ):
61 '''Loads module into self.engine
62 Usage: instance.load( path )
63 path - path to module file
64
65 Raises: XSBCompileError'''
66 self.engine.sendline( "['" + module + "']." )
67 index = self.engine.expect( [ xsbprompt, xsberror ] )
68 if index == 1:
69 raise XSBCompileError, 'Error while compiling module "' + module + '". Error from XSB:\n' + self.engine.after
70
71 - def query( self, query ):
72 '''Queries current engine state
73 Usage: instance.query( query )
74 query - usual XSB Prolog query (example: 'likes( X, Y )')
75
76 Returns:
77 True - if yes/no query and answer is yes
78 False - if yes/no query and answer is no
79 List of dictionaries - if normal query. Dictionary keys are returned
80 variable names. Example:
81 >>> instance.query( 'likes( Person, Food )' )
82 [{'Person': 'john', 'Food': 'curry'}, {'Person': 'sandy', 'Food': 'mushrooms'}]
83
84 Raises: XSBQueryError'''
85 query = query.strip()
86 if query[ -1 ] != '.':
87 query += '.'
88 lvars = var_re.findall( query )
89 lvars = list( set( lvars ) )
90 if lvars == []:
91 self.engine.sendline( query )
92 index = self.engine.expect( [ xsbprompt, xsberror ] )
93 if index == 1:
94 raise XSBQueryError, 'Error while executing query "' + query + '". Error from XSB:\n' + self.engine.after
95 else:
96 if 'yes' in self.engine.before:
97 return True
98 else:
99 return False
100 else:
101 printer = self._printer( lvars, query )
102 self.engine.sendline( printer )
103 index = self.engine.expect( [ xsberror, xsbprompt ] )
104 if index == 0:
105 raise XSBQueryError, 'Error while executing query "' + query + '". Error from XSB:\n' + self.engine.after
106 else:
107 res = res_re.findall( self.engine.before.split( ',nl,fail.\n' )[ -1 ] )
108 results = []
109 counter = 0
110 temp = []
111 for i in res:
112 counter += 1
113 temp.append( i )
114 if counter % len( lvars ) == 0:
115 results.append( dict( temp ) )
116 temp = []
117 if results == []:
118 return False
119 return results
120
122 '''Private method for constructing a result printing query.
123 Usage: instance._printer( lvars, query )
124 lvars - list of logical variables to print
125 query - query containing the variables to be printed
126
127 Returns: string of the form 'query, writeln( res( 'VarName1', VarName1 ) ) ... writeln( res( 'VarNameN', VarNameN ) ),nl,fail.'
128 '''
129 query = query[ :-1 ]
130 elems = [ "writeln( res('''" + i + "'''," + i + ") )" for i in lvars ]
131 printer = query + ',' + ','.join( elems ) + ',nl,fail.'
132 return printer
133
134 swiprompt = '[?][-][ ]'
135 swierror = 'ERROR.*'
136
138 '''Exception raised if SWI-Prolog executable is not found on the specified path.'''
139 pass
140
142 '''Exception raised if loaded module has compile errors.'''
143 pass
144
146 '''Exception raised if query raises an error.'''
147 pass
148
150 '''Python interface to SWI Prolog (http://www.swi-prolog.org)'''
151 - def __init__( self, path='swipl', args='-q +tty' ):
152 '''Constructor method
153 Usage: swipl( path, args )
154 path - path to SWI executable (default: 'swipl')
155 args - command line arguments (default: '-q +tty')
156
157 self.engine becomes pexpect spawn instance of SWI Prolog shell
158
159 Raises: SWIExecutableNotFound'''
160 try:
161 self.engine = px.spawn( path + ' ' + args, timeout=5 )
162 self.engine.expect( swiprompt )
163 except px.ExceptionPexpect:
164 raise SWIExecutableNotFound, 'SWI-Prolog executable not found on the specified path. Try installing swi-prolog or using swipl( "/path/to/swipl" )'
165
166 - def load( self, module ):
167 '''Loads module into self.engine
168 Usage: instance.load( path )
169 path - path to module file
170
171 Raises: SWICompileError'''
172 self.engine.sendline( "['" + module + "']." )
173 index = self.engine.expect( [ swierror, swiprompt ] )
174 if index == 0:
175 raise SWICompileError, 'Error while compiling module "' + module + '". Error from SWI:\n' + self.engine.after
176
177 - def query( self, query ):
178 '''Queries current engine state
179 Usage: instance.query( query )
180 query - usual SWI Prolog query (example: 'likes( X, Y )')
181
182 Returns:
183 True - if yes/no query and answer is yes
184 False - if yes/no query and answer is no
185 List of dictionaries - if normal query. Dictionary keys are returned
186 variable names. Example:
187 >>> instance.query( 'likes( Person, Food )' )
188 [{'Person': 'john', 'Food': 'curry'}, {'Person': 'sandy', 'Food': 'mushrooms'}]
189
190 Raises: SWIQueryError'''
191 query = query.strip()
192 if query[ -1 ] != '.':
193 query += '.'
194 lvars = var_re.findall( query )
195 lvars = list( set( lvars ) )
196 if lvars == []:
197 self.engine.sendline( query )
198 index = self.engine.expect( [ swiprompt, swierror ] )
199 if index == 1:
200 raise SWIQueryError, 'Error while executing query "' + query + '". Error from SWI:\n' + self.engine.after
201 else:
202 if 'true' in self.engine.before:
203 return True
204 else:
205 return False
206 else:
207 printer = self._printer( lvars, query )
208 self.engine.sendline( printer )
209 index = self.engine.expect( [ swierror, swiprompt ] )
210 if index == 0:
211 raise SWIQueryError, 'Error while executing query "' + query + '". Error from SWI:\n' + self.engine.after
212 else:
213 res = res_re.findall( self.engine.before.split( ',nl,fail.\n' )[ -1 ] )
214 results = []
215 counter = 0
216 temp = []
217 for i in res:
218 counter += 1
219 temp.append( i )
220 if counter % len( lvars ) == 0:
221 results.append( dict( temp ) )
222 temp = []
223 if results == []:
224 return False
225 return results
226
228 '''Private method for constructing a result printing query.
229 Usage: instance._printer( lvars, query )
230 lvars - list of logical variables to print
231 query - query containing the variables to be printed
232
233 Returns: string of the form 'query, writeln( res( 'VarName1', VarName1 ) ) ... writeln( res( 'VarNameN', VarNameN ) ),nl,fail.'
234 '''
235 query = query[ :-1 ]
236 elems = [ "writeln( res('''" + i + "'''," + i + ") )" for i in lvars ]
237 printer = query + ',' + ','.join( elems ) + ',nl,fail.'
238 return printer
239
240 eclipseprompt = '[\[]eclipse [0-9]+[\]][:] '
241 eclipseerror = 'Abort.*'
242
244 '''Exception raised if ECLiPSe-Prolog executable is not found on the specified path.'''
245 pass
246
248 '''Exception raised if loaded module has compile errors.'''
249 pass
250
252 '''Exception raised if query raises an error.'''
253 pass
254
256 '''Python interface to ECLiPSe Prolog (http://eclipseclp.org)'''
257 - def __init__( self, path='eclipse', args='' ):
258 '''Constructor method
259 Usage: eclipse( path, args )
260 path - path to ECLiPSe executable (default: 'eclipse')
261 args - command line arguments (default: '')
262
263 self.engine becomes pexpect spawn instance of ECLiPSe Prolog shell
264
265 Raises: ECLiPSeExecutableNotFound'''
266 try:
267 self.engine = px.spawn( path + ' ' + args, timeout=5 )
268 except px.ExceptionPexpect:
269 raise ECLiPSeExecutableNotFound, 'ECLiPSe Prolog executable not found on the specified path.'
270 self.engine.expect( eclipseprompt )
271
272 - def load( self, module ):
273 '''Loads module into self.engine
274 Usage: instance.load( path )
275 path - path to module file
276
277 Raises: ECLiPSeCompileError'''
278 self.engine.sendline( "['" + module + "']." )
279 index = self.engine.expect( [ eclipseerror, eclipseprompt ] )
280 if index == 0:
281 raise ECLiPSeCompileError, 'Error while compiling module "' + module + '". Error from ECLiPSe:\n' + self.engine.after
282
283 - def query( self, query ):
284 '''Queries current engine state
285 Usage: instance.query( query )
286 query - usual ECLiPSe Prolog query (example: 'likes( X, Y )')
287
288 Returns:
289 True - if yes/no query and answer is yes
290 False - if yes/no query and answer is no
291 List of dictionaries - if normal query. Dictionary keys are returned
292 variable names. Example:
293 >>> instance.query( 'likes( Person, Food )' )
294 [{'Person': 'john', 'Food': 'curry'}, {'Person': 'sandy', 'Food': 'mushrooms'}]
295
296 Raises: ECLiPSeQueryError'''
297 query = query.strip()
298 if query[ -1 ] != '.':
299 query += '.'
300 lvars = var_re.findall( query )
301 lvars = list( set( lvars ) )
302 if lvars == []:
303 self.engine.sendline( query )
304 index = self.engine.expect( [ eclipseprompt, eclipseerror ] )
305 if index == 1:
306 raise ECLiPSeQueryError, 'Error while executing query "' + query + '". Error from ECLiPSe:\n' + self.engine.after
307 else:
308 if 'Yes' in self.engine.before:
309 return True
310 else:
311 return False
312 else:
313 printer = self._printer( lvars, query )
314 self.engine.sendline( printer )
315 index = self.engine.expect( [ eclipseerror, eclipseprompt ] )
316 if index == 0:
317 raise ECLiPSeQueryError, 'Error while executing query "' + query + '". Error from ECLiPSe:\n' + self.engine.after
318 else:
319 res = res_re.findall( self.engine.before.split( ',nl,fail.\n' )[ -1 ] )
320 results = []
321 counter = 0
322 temp = []
323 for i in res:
324 counter += 1
325 temp.append( i )
326 if counter % len( lvars ) == 0:
327 results.append( dict( temp ) )
328 temp = []
329 if results == []:
330 return False
331 return results
332
334 '''Private method for constructing a result printing query.
335 Usage: instance._printer( lvars, query )
336 lvars - list of logical variables to print
337 query - query containing the variables to be printed
338
339 Returns: string of the form 'query, writeln( res( 'VarName1', VarName1 ) ) ... writeln( res( 'VarNameN', VarNameN ) ),nl,fail.'
340 '''
341 query = query[ :-1 ]
342 elems = [ "writeln( res('\\'" + i + "\\''," + i + ") )" for i in lvars ]
343 printer = query + ',' + ','.join( elems ) + ',nl,fail.'
344 return printer
345
346 flora2prompt = 'flora2 [?][-][ ]'
347 flora2error = '[+][+]Error.*'
348
349 fvar_re = re.compile( '[?][a-zA-Z0-9][a-zA-Z0-9_]*' )
350 fres_re = re.compile( "[?]([a-zA-Z0-9_]*) [=] ([^\r]+)" )
351
352
354 '''Exception raised if Flora2 executable is not found on the specified path.'''
355 pass
356
358 '''Exception raised if loaded module has compile errors.'''
359 pass
360
362 '''Exception raised if query raises an error.'''
363 pass
364
366 '''Python interface to Flora2 (http://flora.sf.net)'''
367 - def __init__( self, path='runflora', args='--nobanner --quietload' ):
368 '''Constructor method
369 Usage: flora2( path, args )
370 path - path to Flora2 executable (default: 'runflora')
371 args - command line arguments (default: '--nobanner --quietload')
372
373 self.engine becomes pexpect spawn instance of Flora2 shell
374
375 Raises: SWIExecutableNotFound'''
376 try:
377 self.engine = px.spawn( path + ' ' + args, timeout=5 )
378 self.engine.expect( flora2prompt )
379 self.engine.expect( flora2prompt )
380 except px.ExceptionPexpect:
381 raise Flora2ExecutableNotFound, 'Flora-2 executable not found on the specified path. Try using flora2( "/path/to/flora2/runflora" )'
382
383 - def load( self, module ):
384 '''Loads module into self.engine
385 Usage: instance.load( path )
386 path - path to module file
387
388 Raises: Flora2CompileError'''
389 self.engine.sendline( "['" + module + "']." )
390 index = self.engine.expect( [ flora2prompt, flora2error ] )
391 if index == 1:
392 raise Flora2CompileError, 'Error while compiling module "' + module + '". Error from Flora2:\n' + self.engine.after
393
394 - def query( self, query ):
395 '''Queries current engine state
396 Usage: instance.query( query )
397 query - usual Flora2 query (example: '?x[ likes->?y ]')
398
399 Returns:
400 True - if yes/no query and answer is yes
401 False - if yes/no query and answer is no
402 List of dictionaries - if normal query. Dictionary keys are returned
403 variable names. Example:
404 >>> instance.query( '?person[ likes->?food ]' )
405 [{'person': 'john', 'food': 'curry'}, {'person': 'sandy', 'food': 'mushrooms'}]
406
407 Raises: Flora2QueryError'''
408 query = query.strip()
409 if query[ -1 ] != '.':
410 query += '.'
411 lvars = fvar_re.findall( query )
412 lvars = list( set( lvars ) )
413 if lvars == []:
414 self.engine.sendline( query )
415 index = self.engine.expect( [ flora2prompt, flora2error ] )
416 if index == 1:
417 raise Flora2QueryError, 'Error while executing query "' + query + '". Error from Flora2:\n' + self.engine.after
418 else:
419 if 'Yes' in self.engine.before:
420 return True
421 else:
422 return False
423 else:
424 self.engine.sendline( query )
425 index = self.engine.expect( [ flora2error, flora2prompt ] )
426 if index == 0:
427 raise Flora2QueryError, 'Error while executing query "' + query + '". Error from Flora2:\n' + self.engine.after
428 else:
429 res = fres_re.findall( self.engine.before )
430 results = []
431 counter = 0
432 temp = []
433 for i in res:
434 counter += 1
435 temp.append( i )
436 if counter % len( lvars ) == 0:
437 results.append( dict( temp ) )
438 temp = []
439 return results
440
441 if __name__ == '__main__':
442 x = xsb()
443 x.load( '../test/logic/test_xsb' )
444 print x.query( 'dislikes( john, mushrooms )' )
445 print x.query( 'likes( Person, Food )' )
446 del x
447
448 print "======="
449
450 s = swipl()
451 s.load( '../test/logic/test_swi' )
452 print s.query( 'dislikes( john, mushrooms )' )
453 print s.query( 'likes( Person, Food )' )
454 del s
455
456 e = eclipse()
457 e.load( '../test/logic/test_eclipse' )
458 print e.query( 'dislikes( john, mushrooms )' )
459 print e.query( 'likes( Person, Food )' )
460 del e
461
462 f = flora2()
463 f.load( '../test/logic/test_flora' )
464 print f.query( 'john[ dislikes->mushrooms ]' )
465 print f.query( '?person[ likes->?food ]' )
466 del f
467