Commits

Jonathan Eunice  committed c6545e9

about to register

  • Participants
  • Parent commits 716a303

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
 ============

File intensional.py

-
 
 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',

File test/test.py

-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()