PageRenderTime 36ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/prolog.py

http://hagoth.googlecode.com/
Python | 383 lines | 276 code | 49 blank | 58 comment | 46 complexity | 285f42bd46e72f13224da2a7b7f8f6ee MD5 | raw file
  1. """prolog.py
  2. A simple prolog implementation to give me ideas about how to make the real
  3. engine work.
  4. """
  5. from itertools import count, izip
  6. from copy import copy
  7. # This is used to do things like standardizing apart variable names. Every
  8. # variable must have a unique name, but it also has a "preferred" name assigned
  9. # by the user. This generates the unique name.
  10. def name_generator(prefix):
  11. """Generates unique names (using numbers as suffixes)"""
  12. for i in count():
  13. yield "%s%d" % (prefix, i)
  14. global_varname_factory = name_generator('v')
  15. class Predicate(object):
  16. def __init__( self, name, args=() ):
  17. self.name = name
  18. self.args = [x.copy() for x in args]
  19. def __str__( self ):
  20. if len(self.args) > 0:
  21. argstr = ", ".join(str(x) for x in self.args)
  22. return "%s(%s)" % (self.name, argstr)
  23. else:
  24. return "%s" % (self.name,)
  25. __repr__ = __str__
  26. def copy( self ):
  27. return self.__class__( self.name, self.args )
  28. def standardize_vars( self, factory, mapping ):
  29. for i, a in enumerate(self.args):
  30. if mapping.is_var(a):
  31. if a not in mapping:
  32. mapping.add(a, Var(factory.next()))
  33. self.args[i] = mapping[a]
  34. else:
  35. a.standardize_vars(factory, mapping)
  36. def var_within( self, var, mapping ):
  37. """Returns True if the variable is somewhere in this predicate"""
  38. var = mapping[var]
  39. for a in self.args:
  40. if mapping.is_var(a):
  41. a = mapping[a]
  42. if a.name == var.name:
  43. return True
  44. else:
  45. if a.var_within(var, mapping):
  46. return True
  47. return False
  48. def unify( self, other, mapping ):
  49. """Attempts to do unification with the given predicate and given
  50. variable mapping. Returns a boolean success value.
  51. The mapping will be changed when it comes back, so it should be copied
  52. before being passed in if rewindability is needed.
  53. """
  54. if mapping.is_var(other):
  55. other = mapping[other]
  56. # If it's still a variable after asking the map for it, then we defer
  57. # to that variable's unification routine.
  58. if mapping.is_var(other):
  59. return other.unify(self, mapping)
  60. # Other must be a predicate, so do the predicate matching logic: match
  61. # names and unify args.
  62. if self.name != other.name or len(self.args) != len(other.args):
  63. return False
  64. # Now attempt to do unification on each of the arguments. This is
  65. # recursively defined. Variables know how to do unification, for
  66. # example.
  67. for thisarg, otherarg in izip(self.args, other.args):
  68. if not thisarg.unify(otherarg, mapping):
  69. return False
  70. return True
  71. def substitute( self, mapping ):
  72. """Returns a copy of this predicate with all variables resolved"""
  73. pred = self.copy()
  74. for i, a in enumerate(pred.args):
  75. if mapping.is_var(a):
  76. a = mapping[a]
  77. # Vars just get inserted, predicates are recursively substituted
  78. if mapping.is_var(a):
  79. pred.args[i] = a
  80. else:
  81. pred.args[i] = a.substitute(mapping)
  82. return pred
  83. class Var(object):
  84. def __init__( self, name ):
  85. self.name = name
  86. def __str__( self ):
  87. return "_%s" % self.name
  88. __repr__ = __str__
  89. def copy( self ):
  90. return self.__class__( self.name )
  91. def unify( self, other, mapping ):
  92. # Are we a variable when the map is queried?
  93. me = mapping[self]
  94. # If not a variable anymore, defer to the predicate routine
  95. if not mapping.is_var(me):
  96. return me.unify(other, mapping)
  97. # If we are trying to unify with a variable, attempt to get its value
  98. # out of the mapping first.
  99. if mapping.is_var(other):
  100. other = mapping[other]
  101. # If it's still a variable, then we just add it to the mapping and move
  102. # on with life; nothing to see here.
  103. if mapping.is_var(other):
  104. mapping.add(me, other)
  105. return True
  106. # Not a variable? Must be a predicate! We can typically just assign
  107. # these as well, but we first have to ensure that the variable does not
  108. # appear (recursively) in any of the predicate's arguments.
  109. if other.var_within(me, mapping):
  110. return False
  111. else:
  112. mapping.add(me, other)
  113. return True
  114. class VarMap(object):
  115. """Contains variable assignment pairs"""
  116. def __init__( self ):
  117. self.vardict = {}
  118. def copy( self ):
  119. newmap = self.__class__()
  120. for k, (var, val) in self.vardict.iteritems():
  121. newmap.vardict[k] = (var.copy(), val.copy())
  122. return newmap
  123. def reversed( self ):
  124. map = self.__class__()
  125. for name, (var, val) in self.vardict.iteritems():
  126. map.add(val, var)
  127. return map
  128. def __str__( self ):
  129. items = []
  130. for k, (var, val) in self.vardict.iteritems():
  131. items.append("%s->%s" % (var, val))
  132. return "{" + ", ".join(items) + "}"
  133. __repr__ = __str__
  134. def is_var( self, value ):
  135. return isinstance(value, Var)
  136. def add( self, var, value ):
  137. """Adds a new variable mapping to the dictionary.
  138. Raises ValueError if a mapping already exists or the key is not a var.
  139. """
  140. if not self.is_var(var):
  141. raise ValueError("Tried to insert non-var %s into map" % (var,))
  142. if var.name in self.vardict:
  143. raise ValueError("%s is already in the mapping" % (var,))
  144. # No flattening is done at the moment
  145. self.vardict[var.name] = (var, value)
  146. def __contains__( self, var ):
  147. return var.name in self.vardict
  148. def __getitem__( self, var ):
  149. return self.deep_get(var)
  150. def shallow_get(self, var):
  151. if not self.is_var(var):
  152. raise ValueError("Got something other than a variable")
  153. var, val = self.vardict[var.name]
  154. return val
  155. def deep_get( self, var ):
  156. if not self.is_var(var):
  157. raise ValueError("Got something other than a variable")
  158. # You would think that the best thing to do in this case would be to
  159. # fail (the variable isn't in the mapping, right?), but it isn't. If
  160. # we ask for a variable that isn't here, then we just return that
  161. # variable. The purpose of the mapping is to tell us what stuff is
  162. # assigned to. This variable is unassigned and therefore is just
  163. # itself.
  164. if var not in self:
  165. return var
  166. # If it's in there, then obviously get it and follow the thread.
  167. var, val = self.vardict[var.name]
  168. while self.is_var(val) and val in self:
  169. var, val = self.vardict[val.name]
  170. return val
  171. class Rule(object):
  172. def __init__( self,
  173. consequent,
  174. antecedents = (),
  175. varfactory=global_varname_factory ):
  176. self.varmap = VarMap()
  177. self.consequent = consequent.copy()
  178. self.consequent.standardize_vars(varfactory, self.varmap)
  179. self.antecedents = [a.copy() for a in antecedents]
  180. for a in self.antecedents:
  181. a.standardize_vars(varfactory, self.varmap)
  182. def __str__( self ):
  183. if len(self.antecedents) > 0:
  184. antstr = ", ".join(str(x) for x in self.antecedents)
  185. return "%s<=%s::%s" % (self.consequent, antstr, self.varmap)
  186. else:
  187. return "%s::%s" % (self.consequent, self.varmap)
  188. __repr__ = __str__
  189. def copy( self ):
  190. return self.__class__(self.consequent, self.antecedents)
  191. def try_to_satisfy( self ):
  192. if not self.pre_test():
  193. self.commands()
  194. if not self.post_test():
  195. return False
  196. return True
  197. def pre_test( self ):
  198. """Runs after all antecedents have been shown to be true for a rule.
  199. If the test returns True, then no further action is
  200. taken and the rule is deemed True. If the test returns False, then the
  201. associated commands for the rule are run.
  202. """
  203. return True
  204. def post_test( self ):
  205. """Runs after this rule's commands have been executed.
  206. Its purpose is to verify that the commands succeeded. Typically it
  207. just runs the pre_test again to make sure that the commands worked.
  208. """
  209. return self.pre_test()
  210. def commands( self ):
  211. """Runs if the pre_test comes back False.
  212. The intent is to *make* the test True if at all possible (e.g., if a
  213. file is not up to date, the commands are run to build it).
  214. """
  215. pass
  216. class Prolog(object):
  217. def __init__( self ):
  218. # Contains all of the rules
  219. self.rules = []
  220. # Keyed on the name of the consequent predicate, to make searching
  221. # faster.
  222. self.rules_dict = {}
  223. def add_rule( self, rule ):
  224. self.rules.append(rule)
  225. if rule.consequent.name not in self.rules_dict:
  226. self.rules_dict[rule.consequent.name] = []
  227. self.rules_dict[rule.consequent.name].append(rule)
  228. def answer_iter( self, queries, varmap=None ):
  229. """Finds matches for an entire list of queries by finding answers for
  230. the first one and for each answer, passing the *rest* of the list into
  231. this function. When the list is empty, simply return the varmap
  232. because an empty list is vacuously true.
  233. """
  234. if varmap is None:
  235. varmap = VarMap()
  236. if not queries:
  237. yield varmap, []
  238. return
  239. query = queries[0]
  240. rest = queries[1:]
  241. # First, we determine whether the query (the first in the list) matches
  242. # any rules. For each matching rule, there are a number of ways in
  243. # which the entire antecedent list for that rule can be made true, and
  244. # we have to try them all. But that's okay, because we can just call
  245. # ourselve to get an iterator of all valid mappings for the entire list
  246. # (recursion is fun, right?)
  247. if query.name not in self.rules_dict:
  248. return
  249. for rule in self.rules_dict[query.name]:
  250. rulemap = varmap.copy()
  251. if query.unify(rule.consequent, rulemap):
  252. # We found a matching rule. Now try all of the ways that the
  253. # antecedents can be made true. For each of them, we call this
  254. # function again with the *rest* of the query list and yield
  255. # all resulting maps. Neat!
  256. for antmap, antrules in self.answer_iter(
  257. rule.antecedents, rulemap):
  258. for finalmap, finalrules in self.answer_iter(rest, antmap):
  259. # It is not quite enough in this system to have true
  260. # antecedents and therefore assume a true consequent.
  261. # If the following test succeeds, though, we can
  262. # proceed.
  263. if rule.try_to_satisfy():
  264. yield finalmap, [rule] + finalrules + antrules
  265. if __name__ == '__main__':
  266. prolog = Prolog()
  267. # Create some facts:
  268. prolog.add_rule(Rule(Predicate('exists', [
  269. Predicate('file',
  270. [Predicate('myfile'), Predicate('.cc')])
  271. ])))
  272. prolog.add_rule(Rule(Predicate('exists', [
  273. Predicate('file',[Predicate('myfile'), Predicate('.y')])
  274. ])))
  275. prolog.add_rule(Rule(Predicate('buildable', [
  276. Predicate('file',[Var('base'), Predicate('.o')])
  277. ]),
  278. [Predicate('buildable', [
  279. Predicate('file',[Var('base'), Predicate('.c')])
  280. ])
  281. ]))
  282. prolog.add_rule(Rule(Predicate('buildable', [
  283. Predicate('file',[Var('base'), Predicate('.c')])
  284. ]),
  285. [Predicate('exists', [
  286. Predicate('file',[Var('base'), Predicate('.y')])
  287. ])
  288. ]))
  289. prolog.add_rule(Rule(Predicate('buildable', [
  290. Predicate('file',[Var('base'), Predicate('.o')])
  291. ]),
  292. [Predicate('exists', [
  293. Predicate('file',[Var('base'), Predicate('.cc')])
  294. ])
  295. ]))
  296. q = Predicate('buildable',
  297. [Predicate('file',[Predicate('myfile'), Predicate('.o')])])
  298. print "RULES"
  299. for rule in prolog.rules:
  300. print rule
  301. print
  302. print "QUERY"
  303. print q
  304. print
  305. print "ANSWERS"
  306. for answer in prolog.answer_iter( [q] ):
  307. print answer
  308. # vim: et sts=4 sw=4