text: # MIT 6.034 Lab 1: Rule-Based Systems
  
  from collections import MutableMapping as DictMixin
  import re
  
  class ClobberedDictKey(Exception):
      "A flag that a variable has been assigned two incompatible values."
      pass
  
  class NoClobberDict(DictMixin):
      """
      A dictionary-like object that prevents its values from being
      overwritten by different values. If that happens, it indicates a
      failure to match.
      """
      def __init__(self, initial_dict = None):
          if initial_dict == None:
              self._dict = {}
          else:
              self._dict = dict(initial_dict)
  
      def __getitem__(self, key):
          return self._dict[key]
  
      def __setitem__(self, key, value):
          if key in self._dict and self._dict[key] != value:
              raise ClobberedDictKey(key, value)
  
          self._dict[key] = value
  
      def __delitem__(self, key):
          del self._dict[key]
  
      def __len__(self):
          return len(self.__dict__)
  
      def __contains__(self, key):
          return self._dict.__contains__(key)
  
      def __iter__(self):
          return self._dict.__iter__()
  
      def iteritems(self):
          return iter(self._dict.items())
  
      def keys(self):
          return list(self._dict.keys())
  
  # A regular expression for finding variables.
  AIRegex = re.compile(r'\(\?(\S+)\)')
  
  def AIStringToRegex(AIStr):
      return AIRegex.sub( r'(?P<\1>\S+)', AIStr )+'$'
  
  def AIStringToPyTemplate(AIStr):
      return AIRegex.sub( r'%(\1)s', AIStr )
  
  def AIStringVars(AIStr):
      # This is not the fastest way of doing things, but
      # it is probably the most explicit and robust
      return set([ AIRegex.sub(r'\1', x) for x in AIRegex.findall(AIStr) ])
  
  