Commits

Jonathan Eunice committed c6545e9

about to register

Comments (0)

Files changed (4)

 
 Intensional sets are part and parcel of all programming, even if they're not
 explicitly represented or called by that name.``intensional`` helps Python
-programs represent intensional sets directly. This provides several interesting
-improvements:
+programs represent intensional sets directly.
 
-  * A nicer way to use regular expressions
-  * A ``switch`` statement (actually, function) for Python
-
-
-Usage
-=====
-
-Instead of::
-
-    import re
-    
-    match = re.search(pattern, some_string)
-    if match:
-        print match.group(1)
-
-You can do an *en passant* test::
-
-    from intensional import Re
-    
-    if some_string in Re(pattern):
-        print _[1]
-        
-There are two things to note here: First,
-this turns the sense of the matching around, asking "is a given string *in*
-the set of items this pattern describes?" The ``Re`` pattern is an intensionally
-defined set (namely "all strings matching the pattern"). This order often makes
-excellent sense whey you have a clear intent for the test. For example, "is the
-given string within the set of *all legitimate commands*?"
-
-Second, the ``in`` test had the side effect of setting the underscore
-name ``_`` to the result. Python doesn't support *en passant* assignment, so
-you can't both test and collect results in the same motion, even though that's
-sometimes exactly appropriate. ``Re`` use introspection to get around this
-difficulty and provide neat results.
-
-If you prefer the more traditional ``re`` calls, you can still use them with the
-convenient *en passant* style.::
-
-    if Re(pattern).search(some_string):
-        print _[1]
-        
-``Re`` works even better with named pattern components, such as::
-
-    person = 'John Smith 48'
-    if person in Re(r'(?P<name>[\w\s]*)\s+(?P<age>\d+)'):
-        print _.name, "is", _.age, "years old"
-    else:
-        print "don't understand '{}'".format(person)
-        
-### need go back meahcnism
-        
-It's possible also to loop over the results, as in::
-
-    for found in Re(r'(pattern\w*)').findall('pattern is as pattern does'):
-        print found
-        
-``Re`` objects are `memoized <http://en.wikipedia.org/wiki/Memoization>`_ for efficiency, so they
-are only compiled once, regardless of how many times they're mentioned in the program.
-
-
-    
-Notes
-=====
-
-It's entirely possible to implement an intensional set like idea with
-other Python mechanisms, such as ``lambda`` expressions, test functions,
-and list comprehensions. Many (most?) Python programs already *de facto*
-use this implicit intensional set approach. Making intensional sets
-explicit, however, has the benefit of making what's being done more
-clear. It provides a mechanism for cleaning up several sort of ucky
-parts of Python:
-
- *  Messy, guts-exposed regular expression tests
- *  Overly verbose lambda expressions
- *  Long, non-DRY ``if``/``elif``/``else`` constructions (with ``switch``)
- *  A mess of mechanisms for collection filtering
+**NB** Active construciton zone. Very alpha!
 
 Installation
 ============
-
 
 from mementos import MementoMetaclass   # to memoize IntensionalSet
-from stuf import orderedstuf            # for ReMatch
 import re                               # for Re
 import fnmatch                          # for Glob
-import inspect                          # for en passant operation in Re
+import sys                              # for en passant operation in Re
+# import six
+import copy
 
 class IntensionalSet(object):
     """
-    An intensional set (actually, an intensionally defined set) is a set that
-    is defined by a definition, rather than an explicit listing of members
+    An intensional set (actually, an intensionally defined set) is a set
+    defined by a rule rather than an explicit listing of members
     (which would be an extensional set).
     
     Like other Python sets, IntensionalSet objects can include explicit
     items--but it also admits the possibility of rule-based membership.
+
     """
 
     __metaclass__ = MementoMetaclass
     
+    def union(self, other):
+        return Any(self, other)
     
-# Any = Union, Every = Intersection
+    def intersection(self, other):
+        return Every(self, other)
+    
+    def difference(self, other):
+        return ButNot(self, other)
+    
+    def symmetric_difference(self, other):
+        return EitherOr(self, other)
+    
+    def __or__(self, other):
+        # |, equivalent to union
+        return Any(self, other)
+    
+    def __and__(self, other):
+        # &, equivalent to intersection
+        return Every(self, other)
+    
+    def __sub__(self, other):
+        # -, equivalent to difference
+        return ButNot(self, other)
+    
+    def __xor__(self, other):
+        # ^, equivalent to symmetric_difference
+        return EitherOr(self, other)
+    
+    def __contains__(self, item):
+        raise TypeError('Should have been implemented by subclass')
+    
+    def __len__(self):
+        raise TypeError('Impossible to compute the cardinality of an intensional set.')
+    
+    def issubset(self, other):
+        raise TypeError('Impossible to test subset relation between intensional sets.')
+    
+    def __le__(self, other):
+        # <=, equivalent to issubset
+        raise TypeError('Impossible to test subset relation between intensional sets.')
+
+    def issuperset(self, other):
+        raise TypeError('Impossible to test superset relation between intensional sets.')
+    
+    def __le__(self, other):
+        # >=, equivalent to issuperset
+        raise TypeError('Impossible to test superset relation between intensional sets.')
+    
+    def copy(self):
+        return copy.copy(self)
+
+    def pop(self):
+        raise TypeError('Impractical to pop() an item from an intensional set.')
+        # and impossible in the general case
+    
+    ### remaining to be implemented
+    
+    # Many of these are self-modifying methods, which are harder to implement in the
+    # scheme we currenly have. If we had a top-level Set object with inclusions and
+    # exclusions components, could probably jigger these up. 
+    
+    def update(self, others):
+        raise NotImplementedError()
+    
+    union_update = update # backwards compatibility
+    
+    def __ior__(self, other):
+        # |=, similar to update but takes only one item, not an iterable
+        raise NotImplementedError()
+        
+    def intersection_update(self, others):
+        raise NotImplementedError()
+
+    def __iand__(self, other):
+        # &=, similar to intersection_update but takes only one item, not an iterable
+        raise NotImplementedError()
+    
+    def difference_update(self, others):
+        raise NotImplementedError()
+
+    def __isub__(self, other):
+        # -=, similar to difference_update but takes only one item, not an iterable
+        raise NotImplementedError()
+    
+    def symmetric_difference_update(self, others):
+        raise NotImplementedError()
+
+    def __ixor__(self, other):
+        # ^=, similar to symmetric_difference_update but takes only one item, not an iterable
+        raise NotImplementedError()
+    
+    def add(self, x):
+        raise NotImplementedError()
+    
+    def remove(self, x):
+        raise NotImplementedError()
+    
+    def discard(self, x):
+        raise NotImplementedError()
+    
+    def clear(self):
+        raise NotImplementedError()
+
+    
+    
+# Any = Union, Every = Intersection, ButNot = Difference, EitherOr = Xor / Symmetric Difference
 # might want to complete more of the set functions like symmetric difference, discard, etc
 
 # should this be reorganized into multiple files?
 
-class ReMatch(orderedstuf):
+class ReMatch(object):
     """
-    An attributes-exposed regular expression match object. Ideally this would be
-    a subclass of the re module's match object, but they are of type ``_sre.SRE_Match``
-    and `appear to be unsubclassable
+    An easier-to-use proxy for regular expression match objects. Ideally this would be
+    a subclass of the re module's match object, but their type ``_sre.SRE_Match``
+    `appears to be unsubclassable
     <http://stackoverflow.com/questions/4835352/subclassing-matchobject-in-python>`_.
-    Thus, ReMatch is a subclass of an attributes-exposed dict type, ``stuf``, into
-    which the relevant data has been copied.
+    Thus, ReMatch is a proxy exposes the match object's numeric (positional) and
+    named groups through indices and attributes. If a named group has the same
+    name as a match object method or property, it takes precedence. Either
+    change the name of the match group or access the underlying property thus:
+    ``x._match.property``
     """
-    pass
+     
+    def __init__(self, match):
+        self._match = match
+        self._groupdict = match.groupdict()
+        
+    def __getattr__(self, name):
+        if name in self.__dict__:
+            return self.__dict__[name]
+        if name in self._groupdict:
+            return self._groupdict[name]
+        try:
+            return getattr(self._match, name)
+        except AttributeError:
+            return AttributeError("no such attribute '{}'".format(name))
+        
+    def __getitem__(self, index):
+        return self._match.group(index)
 
-    # in future, might want to do a pass-through for methods of _sre.SRE_Match
-    # though they are currently available through x._match.method
-        
-def show_frames():
-    f = inspect.currentframe().f_back # the caller
-    while f:
-        print f, f.f_lineno
-        print "   f_locals:", ' '.join(f.f_locals.keys())
-        f = f.f_back
-    
+#def show_frames():
+#    f = inspect.currentframe().f_back # the caller
+#    while f:
+#        six.print_(f, f.f_lineno)
+#        six.print_("   f_locals:", ' '.join(f.f_locals.keys()))
+#        f = f.f_back
+
+EN_PASSANT_NAME = '_'  # any reason to make this settable by the user?
+
 class Re(IntensionalSet):
     
-    ### need go back meahcnism
-
+    # convenience copy of re flag constants
+    
+    DEBUG      = re.DEBUG
+    I          = re.I
+    IGNORECASE = re.IGNORECASE
+    L          = re.L
+    LOCALE     = re.LOCALE
+    M          = re.M
+    MULTILINE  = re.MULTILINE
+    S          = re.S
+    DOTALL     = re.DOTALL
+    U          = re.U
+    UNICODE    = re.UNICODE
+    X          = re.X
+    VERBOSE    = re.VERBOSE
+    
+    _ = None
+    
     def __init__(self, pattern, flags=0):
         self.pattern = pattern
         self.flags   = flags
         Given an _sre.SRE_Match object, create and return a corresponding
         ReMatch object. Also, set the en passant variable to it.
         """
-        if not m:
-            return m
-        result = ReMatch()
-        result['_match'] = m   # access to original match object
-        result.update(m.groupdict())  # update named groups
-        # update numerical / positional groups
-        result[0] = m.group(0)
-        for i, g in enumerate(m.groups(), start=1):
-            result[i] = g
-            
-        # this method -> calling method -> MementosMetaclass.__call__ -> Re.__init__ -> caller
-        inspect.currentframe().f_back.f_back.f_back.f_back.f_locals['_'] = result
 
-        # do we want to make the variable for '_' settable?
-        # do we want to wait for an attribute to be called to get it?
+        result = ReMatch(m) if m else m
+        Re._ = result
+        # _regroup() -> calling method -> MementosMetaclass.__call__ ->
+        # Re.__init__ -> caller (4 levels back)
+        # sys._getframe(4).f_locals[EN_PASSANT_NAME] = result
+        
+        # Not clear we can get the en passant idea to work reliably
+        # until we can, backing that idea out
+
         return result
-        
+
     def __contains__(self, item):
         # if not isinstance(item, basestring):
         #     item = str(item)
         return self._regroup(self.re.search(item))
     
-    # methods that return ReMatch objects
+    ### methods that return ReMatch objects
     
     def search(self, *args, **kwargs):
         return self._regroup(self.re.search(*args, **kwargs))
     def match(self, *args, **kwargs):
         return self._regroup(self.re.match(*args, **kwargs))
     
-    # methods that don't need ReMatch objects
-    
     def finditer(self, *args, **kwargs):
         return self.re.finditer(*args, **kwargs)
+ 
+    ### methods that don't need ReMatch objects
       
     def findall(self, *args, **kwargs):
         return self.re.findall(*args, **kwargs)
         return self.re.escape(*args, **kwargs)
 
 class Glob(IntensionalSet):
+    """
+    An item matches a Glob via Unix filesystem glob semantics.
+    
+    E.g. 'alpha' matches 'a*' and 'a????' but not 'b*'
+    """
         
-    def __init__(self, expr):
-        self.expr = expr
+    def __init__(self, pattern):
+        self.pattern = pattern
         
     def __contains__(self, item):
-        return fnmatch.fnmatch(item, self.expr)
+        return fnmatch.fnmatch(item, self.pattern)
     
     
 class Any(IntensionalSet):
         self.items = items
         self.exclusion = exclusion
 
-    def __contains__(self, item):
+    def __contains__(self, item):        # why the == self.items? 
         if item == self.items or item in self.items:
             if item != self.exclusion and item not in self.exclusion:
                 return True
         return False
+    
+class EitherOr(IntensionalSet):
+    """
+    An item is in an EitherOr if it's in subseta or subset b, but not both.
+    """
+    def __init__(self, a, b):
+        self.a = a
+        self.b = b
 
+    def __contains__(self, item):
+        if item in self.a:
+            return not item in self.b
+        elif item in self.b:
+            return not item in self.a
+        else:
+            return False
+        
 class Test(IntensionalSet):
     
     def __init__(self, expr, *args, **kwargs):
 
 setup(
     name='intensional',
-    version=verno("0.013"),
+    version=verno("0.101"),
     author='Jonathan Eunice',
     author_email='jonathan.eunice@gmail.com',
     description='Intensional sets in Python',
-from testharness import import_from_parent, test_run
-
-import_from_parent()
+ 
 from intensional import *
 
 def test_Re():
     testpat1 = Re(r'\b(s\w*)\b')
     assert testpat is testpat1   # test memoization
     if tests in testpat:
-        print _[1]
-        assert _[1] == 'some'
-        assert _._match.end(1) == 4
+        assert Re._[1] == 'some'
+        assert Re._.end(1) == 4
+        assert Re._._match.group(1) == Re._[1]
+    else:
+        raise ValueError('yes it is!!!')
     
     found = testpat.findall(tests)
     assert found == ['some', 'string']
     
+    person = 'John Smith 48'
+    if person in Re(r'(?P<name>[\w\s]*)\s+(?P<age>\d+)'):
+        assert Re._.name == 'John Smith'
+        assert int(Re._.age) == 48
+        assert Re._.name == Re._._match.group('name')
+        assert Re._.age  == Re._._match.group('age')
+    else:
+        raise ValueError('yes it is!!!')
+    
 def test_Any():
     ext = Any(1,2,3)
     assert 2 in ext
     assert 33 not in ext
-    print ext
     a2 = Any(12, Test('x.startswith("k")'))
     assert 12 in a2
     assert "kaboom" in a2
     assert 12 in a2
     assert 12 not in bn
     
+def test_union():
+    a = Any(44, Test('x < 12'))
+    b = Test('x > 100')
+    u = a.union(b)
+    assert 44 in u
+    assert 45 not in u
+    assert 101 in u
+    assert 100 not in u
+    assert -44 in u
+    
+    uu = a | b
+    assert 44 in uu
+    assert 45 not in uu
+    assert 101 in uu
+    assert 100 not in uu
+    assert -44 in uu
+    
+def test_intersection():
+    a = Any(44, Test('x < 12'))
+    b = Test('x >= 6')
+    i = a.intersection(b)
+    assert 6 in i
+    assert 11 in i
+    assert 12 not in i
+    assert 101 not in i
+    assert 100 not in i
+    assert -44 not in i
+    
+    ii = a & b
+    assert 6 in ii
+    assert 11 in ii
+    assert 12 not in ii
+    assert 101 not in ii
+    assert 100 not in ii
+    assert -44 not in ii
+    
+    
+def test_difference():
+    a = Test('x < 12')
+    b = Test('x >= 6')
+    d = a.difference(b)
+    assert 6 not in d
+    assert 5 in d
+    assert -100 in d
+    assert 11 not in d
+    assert 100 not in d
+    
+    dd = a - b
+    assert 6 not in dd
+    assert 5 in dd
+    assert -100 in dd
+    assert 11 not in dd
+    assert 100 not in dd
+    
+def test_symmetric_difference():
+    a = Test('x <= 10')
+    b = Test('x >= 5')
+    s = a.symmetric_difference(b)
+    assert 1 in s
+    assert 2 in s
+    assert 4 in s
+    assert 20 in s
+    assert 100 in s
+    assert 5 not in s
+    assert 10 not in s
+    assert 11 in s
+    
+    ss = a ^ b
+    assert 1 in ss
+    assert 2 in ss
+    assert 4 in ss
+    assert 20 in ss
+    assert 100 in ss
+    assert 5 not in ss
+    assert 10 not in ss
+    assert 11 in ss
+    
 def test_Glob():
     assert "alpha" in Glob("a*")
     assert "beta" not in Glob("a*")
     
 def test_Test():
     mytest = Test('x.startswith("a")')
-    print mytest
     assert 'a' in mytest
     assert 'alpha' in mytest
     assert 'sljd' not in mytest
-    
-    
-if __name__ == '__main__':
-    test_run()