1from functools import reduce
2
3from guppy.etc.Descriptor import property_exp
4
5
6class UniSet(object):
7    __slots__ = '_hiding_tag_', 'fam',  '_origin_'
8    _help_url_ = 'heapy_UniSet.html#heapykinds.UniSet'
9    _instahelp_ = ''
10
11    _doc_nodes = """nodes: ImmNodeSet
12
13The actual objects contained in x. These are called nodes because
14they are treated with equality based on address, and not on the
15generalized equality that is used by ordinary builtin sets or dicts."""
16
17    def __and__(self, other):
18        """
19Return the intersection of self and other.
20"""
21        return self.fam.c_binop('and', self, other)
22
23    __rand__ = __and__
24
25    def __call__(self, *args, **kwds): return self.fam.c_call(self, args, kwds)
26
27    def __contains__(self, other):
28        """
29Return True if other is a member of self, False otherwise.
30"""
31        return self.fam.c_contains(self, other)
32
33    def __eq__(self, other):
34        """
35Return True if self contains the same elements as other,
36False otherwise."""
37        return self <= other and self >= other
38
39    def __hash__(self):
40        """
41Return an hash based on the kind of the set of self and
42the addresses of its elements, if any.
43        """
44        return self.fam.c_hash(self)
45
46    def __invert__(self):
47        """
48Return the complement of self.
49"""
50        return self.fam.c_unop('invert', self)
51
52    def __ge__(self, other):
53        """
54Return True if self is a superset of (and may be equal to) other,
55False otherwise.
56"""
57        if self is other:
58            return True
59        if not isinstance(other, UniSet):
60            other = self.fam.c_uniset(other)
61        return self.fam.c_ge(self, other)
62
63    def __gt__(self, other):
64        """
65Return True if self is a strict (may not be equal to) superset of other.
66False otherwise.
67"""
68        return self >= other and not self <= other
69
70    def __getattr__(self, other):
71        """
72Get family-specific attribute.
73"""
74        return self.fam.mod.View.enter(lambda: self.fam.c_getattr(self, other))
75
76    def __le__(self, other):
77        """
78Return True if self is a subset of (and may be equal to) other,
79False otherwise.
80"""
81        if self is other:
82            return True
83        if not isinstance(other, UniSet):
84            other = self.fam.c_uniset(other)
85        return self.fam.c_le(self, other)
86
87    def __lshift__(return_spec, argument_spec):
88        """
89<<This is about to change, does not work as one may expected.
90Nov 19 2005. >>>
91
92Return a 'mapping' set, which may be used for specification and test
93purposes. It implements the syntax:
94
95        return_spec << argument_spec
96
97The elements of the set returned are the callable objects that return
98values in return_spec, when called with arguments according to
99argument_spec. The return_spec may be any kind of sets that can test
100for element containment. The argument_spec may be a set or a tuple. If
101it is a set, it should be able to generate some examples, to allow the
102mapping to be tested.  When argument_spec is a set, the mapping will
103have a single argument. Any number of arguments may be specified using
104an argument_spec which is a tuple.  The arguments are then specified
105with sets, that should be able to generate examples. Special features
106of the mapping such as optional arguments may be specified in the same
107way as when using the 'mapping' function in the Spec.py module.
108
109
110"""
111        return return_spec.fam.c_lshift(return_spec, argument_spec)
112
113    def __lt__(self, other):
114        """
115Return True if self is a strict (may not be equal to) subset of other,
116False otherwise.
117"""
118        return self <= other and not self >= other
119
120    def __mul__(self, other):
121        """
122Return the cartesian product of self and other, which is the set of
123pairs where the first element is a member of self and the second
124element is a member of other.
125
126NOTE: Unlike what one might expect from the way the cartesian product
127may be defined mathematically, the operation as implemented here is
128nonassociative, i.e.
129
130     a*b*c == (a*b)*c != a*(b*c)
131
132
133In the mathematical case, a*b*c would be a set of triples, but here it
134becomes a set of pairs with the first element in (a*b) and the second
135element in c.
136
137To create sets of triples etc. the cprod() factory function in Spec.py
138could be used directly.
139"""
140        if not isinstance(other, UniSet):
141            other = self.fam.c_uniset(other)
142        return self.fam.c_mul(self, other)
143
144    def __ne__(self, other):
145        """
146Return True if self does not equal other,
147False otherwise. See also: __eq__.
148"""
149        return not self == other
150
151    def __bool__(self):
152        """
153Return True if self contains some element,
154False otherwise.
155"""
156        return self.fam.c_nonzero(self)
157
158    def __or__(self, other):
159        """
160Return the union of self and other.
161"""
162        return self.fam.c_binop('or', self, other)
163
164    __ror__ = __or__
165
166    def __repr__(self):
167        """
168Return a string representing self.  This is usually the same string
169as from __str__.
170
171"""
172        return self.fam.c_repr(self)
173
174    def __str__(self):
175        """
176Return a string representing self. The string is usually the same as the .brief
177attribute, but a major exception is the IdentitySet class.
178
179"""
180        return self.fam.c_str(self)
181
182    def __sub__(self, other):
183        """
184Return the assymetrical set difference.  That is, the set of elements
185in self, except those that are in others.
186"""
187        if not isinstance(other, UniSet):
188            other = self.fam.c_uniset(other)
189        return self.fam.c_sub(self, other)
190
191    def __rsub__(self, other):
192        """
193Return the assymetrical set difference.  That is, the
194set of elements in other, except those that are in self.
195
196This is like __sub__ except it handles the case when the left
197argument is not a UniSet (but convertible to a UniSet).
198"""
199        if not isinstance(other, UniSet):
200            other = self.fam.c_uniset(other)
201        return other.fam.c_sub(other, self)
202
203    def __xor__(self, other):
204        """
205Return the symmetrical set difference.  That is, the set of elements
206that are in one of self or other, but not in both.
207"""
208        if not isinstance(other, UniSet):
209            other = self.fam.c_uniset(other)
210        return self.fam.c_xor(self, other)
211    __rxor__ = __xor__
212
213    brief = property_exp(lambda self: self.fam.c_get_brief(self),
214                     doc="""\
215A string representation of self, which is brief relative to the
216representation returned by __str__ and __repr__. (In many cases it is
217the same - both are then brief - but for IdentitySet objects the brief
218representation is typically much shorter than the non-brief one.)"""
219                     )
220
221    def _get_help(self):
222        return self.fam.mod._root.guppy.doc.help_instance(self)
223
224    doc = property_exp(lambda self: self.fam.mod._root.guppy.etc.Help.dir(self))
225
226    def get_ckc(self):
227        # Get low-level classification information, where available.
228        # Returns a tuple (classifier, kind, comparator)
229        return self.fam.c_get_ckc(self)
230
231    def _derive_origin_(self, doc):
232        """
233Return information about the 'origin' of the set. This was intended to be
234used for specification purposes - is experimental, noncomplete, temporary.
235"""
236        return self.fam.c_derive_origin(self, doc)
237
238    def disjoint(self, other):
239        """
240Return True if self and other are disjoint sets, False otherwise. This
241is equivalent to calculating
242
243        (self & other) == Nothing
244
245but may be implemented more efficiently in some cases.
246"""
247        return self.fam.c_disjoint(self, other)
248
249    def get_examples(self, env):
250        """
251Return an iterable object or an iterator, which provides someexamples
252of the elements of self.  (A minimum of 2 examples should normally be
253provided, but it may depend on some test configuration options.)
254
255This is used for automatic test generation from specifications.  The
256env argument is according to specification of TestEnv in Spec.py,
257"""
258        return self.fam.c_get_examples(self, env)
259
260    def get_render(self):
261        """
262Return a function that may be used to render the representation of the
263elements of self. This is mainly intended for internal representation
264support.
265
266The function returned depends on the kind of elements self
267contains. The rendering function is choosen so that it will be
268appropriate, and can be used safely, for all objects of that kind.
269For the most general kind of objects, the rendering function will only
270return an address representation. For more specialized kinds, the
271function may provide more information, and can be equivalent to the
272builtin repr() when the kind is narrow enough that it would work for
273all elements of that kind without exception.
274
275"""
276        return self.fam.c_get_render(self)
277
278    def test_contains(self, element, env):
279        """
280Test if self contains the element object.  This is used mainly for
281internal use for automatic (experimental) testing of specifications.
282
283The env argument is according to specification of TestEnv in Spec.py.
284It provides support for things that depends on the specific test
285situation, such as a test reporting protocol. If test_contains did
286find the element to be contained in self, the method will return
287(usually True). But if the element was not contained in self, the
288method should call env.failed(message), and return whatever may
289be returned; though typically env.failed() would raise an exception.
290         """
291        return self.fam.c_test_contains(self, element, env)
292
293    biper = property_exp(lambda self: self.fam.c_get_biper(self),
294                     doc="""\
295A bipartitioning equivalence relation based on x. This may be used to
296partition or classify sets into two equivalence classes:
297
298x.biper(0) == x
299    The set of elements that are in x.
300x.biper(1) == ~x
301    The set of elements that are not in x.
302        """)
303
304    dictof = property_exp(lambda self: self.fam.c_get_dictof(self),
305                      doc="""dictof: UniSet
306
307If x represents a kind of objects with a builtin __dict__ attribute,
308x.dictof is the kind representing the set of all those dict
309objects. In effect, x.dictof maps lambda e:getattr(e, '__dict__') for
310all objects e in x. But it is symbolically evaluated to generate a new
311symbolic set (a Kind).""")
312
313
314class Kind(UniSet):
315    __slots__ = 'arg',
316
317    def __init__(self, fam, arg):
318        self.fam = fam
319        self._hiding_tag_ = fam._hiding_tag_
320        self.arg = arg
321        self._origin_ = None
322
323    def alt(self, cmp):
324        return self.fam.c_alt(self, cmp)
325
326
327class IdentitySet(UniSet):
328    __slots__ = '_er', '_partition', '_more'
329    _help_url_ = 'heapy_UniSet.html#heapykinds.IdentitySet'
330
331    def __init__(self, fam):
332        self.fam = fam
333        self._hiding_tag_ = fam._hiding_tag_
334        self._origin_ = None
335
336    def __getitem__(self, idx): return self.fam.c_getitem(self, idx)
337    def __len__(self): return self.fam.c_len(self)
338    def __iter__(self): return self.fam.c_iter(self)
339
340    def __str__(self):
341        """
342Return a string representating self. This differs from the .brief
343attribute in that it is a tabular representation.
344
345...
346
347"""
348
349        return self.fam.c_str(self)
350
351    def get_rp(self, depth=None, er=None, imdom=0, bf=0, src=None,
352               stopkind=None, nocyc=False, ref=None):
353        """
354x.get_rp(depth=None, er=None, imdom=0, bf=0, src=None, stopkind=None,
355        nocyc=False, ref=None)
356
357Return an object representing the pattern of references to the objects in X.
358The returned object is of kind ReferencePattern.
359
360Arguments
361        depth   The depth to which the pattern will be generated. The
362                default is taken from depth of this module.
363        er      The equivalence relation to partition the referrers.
364                The default is Clodo.
365
366        imdom   If true, the immediate dominators will be used instead
367                of the referrers. This will take longer time to
368                calculate, but may be useful to reduce the complexity
369                of the reference pattern.
370
371        bf      If true, the pattern will be printed in breadth-first
372                order instead of depth-first. (Experimental.)
373        src     If specified, an alternative reference source instead
374                of the default root.
375        stopkind
376                The referrers of objects of kind stopkind will not be
377                followed.
378        nocyc   When True, certain cycles will not be followed.
379        ref
380
381See also
382        rp (a shorthand for common cases)
383
384"""
385        return self.fam.RefPat.rp(self, depth, er, imdom, bf, src, stopkind,
386                                  nocyc, ref)
387
388    def get_shpaths(self, src=None, avoid_nodes=None, avoid_edges=()):
389        """x.get_shpaths(draw:[src, avoid_nodes, avoid_edges]) -> Paths
390
391Return an object containing the shortest paths to objects in x.
392The optional arguments are:
393
394    src:IdentitySet             An alternative source set of objects
395    avoid_nodes:IdentitySet     Nodes to avoid
396    avoid_edges:NodeGraph       Edges to avoid
397
398"""
399        return self.fam.Path.shpaths(self, src, avoid_nodes, avoid_edges)
400
401    # 'Normal' methods
402
403    def by(self, er):
404        """ x.by(er) -> A copy of x, but using er for equiv. relation. """
405        return self.fam.get_by(self, er)
406
407    def diff(self, other):
408        return self.stat - other.by(self.er).stat
409
410    def dump(self, *args, **kwds):
411        """ Dump statistical data to a file
412          Shorthand for .stat.dump """
413        self.stat.dump(*args, **kwds)
414
415    byclodo = property_exp(lambda self: self.by('Clodo'), doc="""\
416A copy of self, but with 'Clodo' as the equivalence relation.""")
417
418    byidset = property_exp(lambda self: self.by('Idset'), doc="""\
419A copy of self, but with 'Idset' as the equivalence relation.
420
421Note
422    This is mainly for special purpose internal use. The Id
423equivalence relation is more efficient when partitioning large
424sets.""")
425
426    byid = property_exp(lambda self: self.by('Id'), doc="""\
427A copy of self, but with 'Id' as the equivalence relation.""")
428
429    bymodule = property_exp(lambda self: self.by('Module'), doc="""\
430A copy of self, but with 'Module' as the equivalence relation.""")
431
432    byprod = property_exp(lambda self: self.by('Prod'), doc="""\
433A copy of self, but with 'Prod' as the equivalence relation.""")
434
435    byrcs = property_exp(lambda self: self.by('Rcs'), doc="""\
436A copy of self, but with 'Rcs' as the equivalence relation.""")
437
438    bysize = property_exp(lambda self: self.by('Size'), doc="""\
439A copy of self, but with 'Size' as the equivalence relation.""")
440
441    bytype = property_exp(lambda self: self.by('Type'), doc="""\
442A copy of self, but with 'Type' as the equivalence relation.""")
443
444    byunity = property_exp(lambda self: self.by('Unity'), doc="""\
445A copy of self, but with 'Unity' as the equivalence relation.""")
446
447    byvia = property_exp(lambda self: self.by('Via'), doc="""
448A copy of self, but with 'Via' as the equivalence relation.""")
449
450    er = property_exp(lambda self: self.fam.get_er(self), doc="""\
451The equivalence relation used for partitioning when representing /
452printing this set.""")
453
454    count = property_exp(lambda self: len(self.nodes), doc="""\
455The number of individual objects in the set.""")
456
457    dominos = property_exp(lambda self: self.fam.View.dominos(self), doc="""\
458The set 'dominated' by a set of objects. This is the objects that will
459become deallocated, directly or indirectly, when the objects in the
460set are deallocated.
461
462See also: domisize.""")
463
464    domisize = property_exp(lambda self: self.fam.View.domisize(self), doc="""\
465The dominated size of a set of objects. This is the total size of
466memory that will become deallocated, directly or indirectly, when the
467objects in the set are deallocated.
468
469See also: dominos, size.
470""")
471
472    imdom = property_exp(lambda self: self.fam.View.imdom(self), doc="""\
473The immediate dominators of a set of objects. The immediate dominators
474is a subset of the referrers. It includes only those referrers that
475are reachable directly, avoiding any other referrer.""")
476
477    indisize = size = property_exp(lambda self: self.fam.View.indisize(self), doc="""\
478The total 'individual' size of the set of objects.  The individual
479size of an object is the size of memory that is allocated directly in
480the object, not including any externally visible subobjects. See also:
481domisize.""")
482
483    kind = property_exp(lambda self: self.er[self], doc="""\
484The kind of objects in the set. The kind is the union of the
485element-wise classifications as determined by the equivalence relation
486in use by the set.""")
487
488    maprox = property_exp(lambda self: MappingProxy(self), doc="""\
489An object that can be used to map operations to the objects in self,
490forming a new set of the result. The returned object is an instance of
491MappingProxy.
492
493This works currently as follows:
494
495o Getting an attribute of the MappingProxy object will get the
496  attribute from each of the objects in the set and form a set of the
497  results. If there was an exception when getting some attribute, it
498  would be ignored.
499
500o Indexing the MappingProxy object will index into each of the objects
501  in the set and return a set of the results. Exceptions will be
502  ignored.
503
504Example:
505
506>>> hp.iso({'a':'b'}, {'a':1}).maprox['a'].byid
507Set of 2  objects. Total size = 40 bytes.
508 Index     Size   %   Cumulative  %   Kind: Name/Value/Address
509     0       28  70.0        28  70.0 str: 'b'
510     1       12  30.0        40 100.0 int: 1
511>>>
512
513<This is an experimental feature, so the name is intentionally made
514mystically-sounding, and is a shorthand for 'mapping proxy'.>""")
515
516    more = property_exp(lambda self: self.fam.get_more(self), doc="""\
517An object that can be used to show more lines of the string
518representation of self. The object returned, a MorePrinter instance,
519has a string representation that continues after the end of the
520representation of self.""")
521
522    all = property_exp(lambda self: self.fam.get_all(self), doc="""\
523An object that can be used to show all lines of the string
524representation of self.""")
525
526    owners = property_exp(lambda self: self.fam.get_owners(self), doc="""\
527The set of objects that 'own' objects in self. The owner is defined
528for an object of type dict, as the object (if any) that refers to the
529object via its special __dict__ attribute.""")
530
531    partition = property_exp(lambda self: self.fam.get_partition(self), doc="""\
532A partition of the set of objects in self. The set is partitioned into
533subsets by equal kind, as given by a equivalence relation.  Unless
534otherwise specified, the equivalence relation used is 'byclodo', which
535means it classifies 'by type or dict owner'. Different
536equivalence relations are specified for sets created by the 'by_...'
537attributes of any IdentitySet object.
538
539The value is an instance of guppy.heapy.Part.Partition.""")
540
541    parts = property_exp(lambda self: self.fam.get_parts(self), doc="""\
542An iterable object, that can be used to iterate over the 'parts' of
543self. The iteration order is determined by the sorting order the set
544has, in the table printed when partitioned.""")
545
546    pathsin = property_exp(lambda self: self.get_shpaths(self.referrers), doc="""\
547The paths from the direct referrers of the objects in self.""")
548
549    pathsout = property_exp(lambda self: self.referents.get_shpaths(self), doc="""\
550The paths to the referents of the objects in self.""")
551
552    referents = property_exp(lambda self: self.fam.View.referents(self), doc="""\
553The set of objects that are directly referred to by any of the objects
554in self.""")
555
556    referrers = property_exp(lambda self: self.fam.View.referrers(self), doc="""\
557The set of objects that directly refer to any of the objects in self.""")
558
559    rp = property_exp(get_rp, doc="""\
560rp: ReferencePattern
561
562An object representing the pattern of references to the objects in X.
563
564See also
565    get_rp""")
566
567    shpaths = property_exp(get_shpaths, doc="""x.shpaths: Paths
568
569An object containing the shortest paths to objects in x.
570
571Synonym
572    sp
573See also
574    get_shpaths""")
575
576    shpaths = property_exp(get_shpaths, doc="""x.sp: Paths
577
578An object containing the shortest paths to objects in x.
579
580Synonym
581    sp
582See also
583    get_shpaths""")
584
585    sp = property_exp(get_shpaths, doc="""x.sp: Paths
586
587An object containing the shortest paths to objects in x.
588
589Synonym
590    shpaths
591See also
592    get_shpaths""")
593
594    stat = property_exp(lambda self: self.partition.get_stat(), doc="""\
595x.stat: Stat
596
597An object summarizing the statistics of the partitioning of x. This is
598useful when only the statistics is required, not the objects
599themselves. The statistics can be dumped to a file, unlike the set of
600objects itself.""")
601
602    theone = property_exp(lambda self: self.fam.get_theone(self), doc="""\
603theone: Anything
604
605The one object in a singleton set. In case the set does not contain
606exactly one object, the exception ValueError will be raised.
607""")
608
609    prod = property_exp(lambda self: self.fam.get_prod(self), doc="""\
610theone: MorePrinter
611
612The traceback for the producer for the one object in a singleton set.
613""")
614
615
616class IdentitySetMulti(IdentitySet):
617    __slots__ = 'nodes',
618
619    def __init__(self, fam, nodes):
620        super().__init__(fam)
621        self.nodes = nodes
622
623
624class IdentitySetSingleton(IdentitySet):
625    __slots__ = '_node',
626    _help_url_ = 'heapy_UniSet.html#heapykinds.IdentitySetSingleton'
627
628    def __init__(self, fam, node):
629        super().__init__(fam)
630        self._node = node
631
632    # RefPat (eg) depends on this being usable as a hashable key.
633    nodes = property_exp(lambda self: self.fam.immnodeset((self._node,)), doc="""\
634x.nodes: ImmNodeSet
635
636The actual objects contained in x. These are called nodes because they
637are treated with equality based on address, and not on the generalized
638equality that is used by ordinary builtin sets or dicts.""")
639
640    def _get_theone(self):
641        return self._node
642
643    theone = property_exp(_get_theone)
644
645
646class EquivalenceRelation(UniSet):
647    """\
648An equivalence relation is a binary relation between two elements of a
649set which groups them together as being "equivalent" in some way.
650
651An equivalence relation is reflexive, symmetric, and transitive. In
652other words, the following must hold for "~" to be an equivalence
653relation on X:
654
655    * Reflexivity: a ~ a
656    * Symmetry: if a ~ b then b ~ a
657    * Transitivity: if a ~ b and b ~ c then a ~ c.
658
659An equivalence relation partitions a set into several disjoint
660subsets, called equivalence classes. All the elements in a given
661equivalence class are equivalent among themselves, and no element is
662equivalent with any element from a different class.
663"""
664
665    __slots__ = 'classifier', 'erargs'
666    _help_url_ = 'heapy_UniSet.html#heapykinds.EquivalenceRelation'
667
668    def __init__(self, fam, classifier, erargs=()):
669        self.fam = fam
670        self._hiding_tag_ = fam._hiding_tag_
671        self.classifier = classifier
672        self.erargs = erargs
673        self._origin_ = None
674
675    def __getitem__(self, idx):
676        return self.fam.c_getitem(self, idx)
677
678    def _get_dictof(self):
679        return self.fam.Classifiers.mker_dictof(self)
680    dictof = property_exp(_get_dictof)
681
682    def _get_refdby(self):
683        return self.fam.Classifiers.mker_refdby(self)
684    refdby = property_exp(_get_refdby)
685
686    def sokind(self, *args, **kwds):
687        return self.classifier.get_sokind(self, *args, **kwds)
688
689
690class MappingProxy(object):
691    __slots__ = '_set_',
692
693    def __init__(self, set):
694        self._set_ = set
695
696    def __getattribute__(self, name):
697        if name == '_set_':
698            return object.__getattribute__(self, name)
699        return self._set_.fam.maprox_getattr(self._set_, name)
700
701    def __getitem__(self, name):
702        return self._set_.fam.maprox_getitem(self._set_, name)
703
704
705class Family:
706    supercl = None
707
708    def __init__(self, mod):
709        self.mod = mod
710        self.Doc = mod._parent.Doc
711        self._hiding_tag_ = mod._hiding_tag_
712        self.types = mod.types
713        self.disjoints = mod.immnodeset()
714        self.export_dict = self.mod.export_dict
715        self.supers = mod.immnodeset([self])
716        self.Set = Kind
717
718    def __call__(self, arg):
719        return self.Set(self, arg)
720
721    def _derive_origin_(self, origin):
722        return self.Doc.add_origin(self, origin)
723
724    def specotup(self, tup):
725        r = self.Set(self, tup)
726        r = self.Doc.add_origin(r, self.Doc.callfunc(self, *tup))
727        return r
728
729    def specoarg(self, arg):
730        r = self.Set(self, arg)
731        r = self.Doc.add_origin(r, self.Doc.callfunc(self, arg))
732        return r
733
734    def specoargtup(self, arg, tup):
735        r = self.Set(self, arg)
736        r = self.Doc.add_origin(r, self.Doc.callfunc(self, *tup))
737        return r
738
739    def add_export(self, name, value):
740        if self.export_dict is self.mod.export_dict:
741            self.export_dict = self.mod.export_dict.copy()
742        if name in self.export_dict and self.export_dict[name] is not value:
743            raise ValueError('Duplicate: %s' % name)
744        self.export_dict[name] = value
745
746    def c_alt(self, a, cmp):
747        raise ValueError('No alternative set for family %s.' % self)
748
749    def c_binop(self, op, a, b):
750        if not isinstance(b, UniSet):
751            b = self.c_uniset(b)
752        r = getattr(self, 'c_'+op)(a, b)
753        # r = self.Doc.add_origin(r, self.Doc.binop(op, a.doc, b.doc))
754        return r
755
756    def c_unop(self, op, a):
757        r = getattr(self, 'c_'+op)(a)
758        # r = self.Doc.add_origin(r, self.Doc.unop(op, a.doc))
759        return r
760
761    def c_derive_origin(self, a, b):
762        return self.Doc.add_origin(a, b)
763
764    def c_call(self, a, args, kwds):
765        raise ValueError('Not callable set')
766
767    def c_contains(self, a, b):
768        mod = self.mod
769        return (a & mod.iso(b)) is not mod.Nothing
770
771    def c_get_biper(self, a):
772        return self.mod.Classifiers.biper(a)
773
774    def c_get_dictof(self, a):
775        return self.mod.Classifiers.dictof(a)
776
777    def c_disjoint(self, a, b):
778        # Determine if a, b are disjoint
779        return (a & b) is self.mod.Nothing
780
781    def c_factordisjoint(self, a, b):
782        # Given a and b factors, and not a <= b and not b <= a,
783        # determine if they are disjoint
784
785        return getattr(self, '_factordisjoint_%s' % (b.fam.opname,))(a, b)
786
787    def c_get_brief_alt(self, a, alt):
788        return '[%s %s]' % (alt, self.c_get_brief(a))
789
790    def c_uniset(self, X):
791        return self.mod.uniset_from_setcastable(X)
792
793    def c_get_examples(self, a, env):
794        return []
795
796    def c_getattr(self, a, b, args=(), kwds={}):
797        d = self.export_dict
798        if b in d:
799            return d[b](a, *args, **kwds)
800        return self.c_getattr2(a, b)
801
802    def c_getattr2(self, a, b):
803        raise AttributeError(b)
804
805    def c_get_render(self, a):
806        return self.mod.summary_str.str_address
807
808    def c_get_str_for(self, a, b):
809        # A modification of str, for some cases,
810        # when the set a is used as a determination of an idset b
811        # Normally the same as brief, but.. 'dict of' will be different for eg module
812        return a.brief
813
814    def c_get_idpart_header(self, a):
815        render = a.get_render()
816        h = getattr(render, 'im_func', render)
817        h = getattr(h, '_idpart_header', None)
818        if not h:
819            h = 'Value'
820        return h
821
822    def c_get_idpart_label(self, a):
823        return '<%s>' % a
824
825    def c_get_idpart_render(self, a):
826        return self.c_get_render(a)
827
828    def c_get_idpart_sortrender(self, a):
829        render = self.c_get_idpart_render(a)
830        if render is repr:
831            return 'IDENTITY'
832        h = getattr(render, 'im_func', render)
833        render = getattr(h, '_idpart_sortrender', render)
834        return render
835
836    def c_hash(self, a):
837        return hash(a.arg)
838
839    def c_iter(self, a):
840        raise TypeError('iteration over non-sequence')
841
842    def c_len(self, a):
843        raise TypeError('len() of unsized object')
844
845    def c_nonzero(self, a):
846        return True
847
848    def c_mul(self, a, b):
849        return self.mod._parent.Spec.cprod(a, b)
850
851    def c_lshift(self, a, b):
852        return self.Doc.add_origin(self.c_map(a, b), self.Doc.binop('lshift', a, b))
853
854    def c_map(self, a, b):
855        if isinstance(b, list):
856            b = tuple(b)
857        if not isinstance(b, tuple):
858            b = b,
859        t = b + ('->', a)
860        return self.mod._parent.Spec.mapping(*t)
861
862    def c_repr(self, a):
863        return self.c_str(a)
864
865    def c_str(self, a):
866        return self.c_get_brief(a)
867
868    def c_sub(self, a, b):
869        return a & ~b
870
871    def c_test_contains(self, a, b, env):
872        if not self.c_contains(a, b):
873            return env.failed('%s: %s does not contain %s' % (self.__class__, env.name(a), env.name(b)))
874        return True
875
876    def c_xor(self, a, b):
877        return (a - b) | (b - a)
878
879    def _or_OR(self, a, b):
880        return b.fam._or_TERM(b, a)
881
882    def _rand_ATOM(self, a, b):
883        return self._and_ATOM(a, b)
884
885
886class AtomFamily(Family):
887    isatom = True
888    isfactor = True
889    opname = 'ATOM'
890
891    def __init__(self, mod):
892        Family.__init__(self, mod)
893        self.disjoints |= [self]
894
895    def c_and(self, a, b):
896        return b.fam._and_ATOM(b, a)
897
898    def _and_ATOM(self, a, b):
899        return self.mod.fam_And(a, b)
900
901    def _and_AND(self, a, b):
902        return b.fam._and_ATOM(b, a)
903
904    def _and_FACTOR(self, a, b):
905        return self.mod.fam_And(a, b)
906
907    def _and_INVERT(self, a, b):
908        return b.fam._and_ATOM(b, a)
909
910    def _factordisjoint_ATOM(self, a, b):
911        return (a.fam.disjoints & b.fam.supers or
912                b.fam.disjoints & a.fam.supers)
913
914    def _factordisjoint_INVERT(self, a, b):
915        return b.fam._factordisjoint_ATOM(b, a)
916
917    def c_le(self, a, b):
918        return b.fam._ge_ATOM(b, a)
919
920    _le_AND = _le_INVERT = _le_AND = c_le
921
922    def _le_ATOM(self, a, b):
923        # b is known to not be Nothing since its c_ge doesn't call back
924        return self.supercl is not None and self.supercl <= b
925
926    def c_ge(self, a, b):
927        return b.fam._le_ATOM(b, a)
928
929    _ge_INVERT = _ge_AND = c_ge
930
931    def _ge_ATOM(self, a, b):
932        # b is known to not be Nothing since its c_le doesn't call back
933        return b.fam.supercl is not None and b.fam.supercl <= a
934
935    def c_or(self, a, b):
936        return b.fam._or_ATOM(b, a)
937
938    def _or_ATOM(self, a, b):
939        return self.mod.fam_Or(a, b)
940
941    _or_AND = _or_INVERT = c_or
942
943    def c_invert(self, a):
944        return self.mod.fam_Invert(a)
945
946    def defrefining(self, arg):
947        self.supercl = arg
948        self.supers |= arg.fam.supers
949
950    def defdisjoint(self, *args):
951        # Define disjointness of sets under the condition that
952        # neither of them is a subset of the other (determined in some other way.)
953        # I.E., define that there is no partial overlap.
954        # Declare that all sets of my (self) family are disjoint under this condition
955        # from all sets of each family in args.
956
957        self.disjoints |= args
958        sc = self.supercl
959        if sc is not None:
960            self.disjoints |= sc.fam.disjoints
961
962    def defrefidis(self, arg):
963        self.defrefining(arg)
964        self.defdisjoint(arg.fam)
965
966    def fam_union(self):
967        return self.supercl
968
969
970class ArgAtomFamily(AtomFamily):
971    def _and_ID(self, a, b):
972        cla, k, cmp = self.c_get_ckc(a)
973        return cla.select_ids(b, k, cmp)
974
975    def _ge_ATOM(self, a, b):
976        # b is known to not be Nothing since its c_le doesn't call back
977        if self is b.fam:
978            return a.arg == b.arg
979        return b.fam.supercl is not None and b.fam.supercl <= a
980
981    def _le_ATOM(self, a, b):
982        # b is known to not be Nothing since its c_ge doesn't call back
983        if self is b.fam:
984            return a.arg == b.arg
985        return self.supercl is not None and self.supercl <= b
986
987    def c_get_ckc(self, a):
988        return self.classifier, a.arg, '=='
989
990
991class AndFamily(Family):
992    opname = 'AND'
993    isatom = False
994    isfactor = False
995
996    def __call__(self, a, b):
997        if a <= b:
998            return a
999        if b <= a:
1000            return b
1001        if a.fam.c_factordisjoint(a, b):
1002            return self.mod.Nothing
1003        return self._cons((a, b))
1004
1005    def _cons(self, arg):
1006        # We allow explicit non-normalized constructions, as an optimization
1007        # for a in arg:
1008        #    assert a.fam.isatom or isinstance(a.fam, InvertFamily)
1009        if len(arg) > 1:
1010            return self.Set(self, tuple(arg))
1011        elif len(arg) == 1:
1012            return arg[0]
1013        else:
1014            return self.mod.Nothing
1015
1016    def c_get_examples(self, a, env):
1017        ex = []
1018        for ai in a.arg:
1019            try:
1020                e = env.get_examples(ai)
1021            except CoverageError:
1022                pass
1023            else:
1024                for ei in list(e):
1025                    for aj in a.arg:
1026                        if aj is not ai:
1027                            if not env.contains(aj, ei):
1028                                break
1029                    else:
1030                        ex.append(ei)
1031        return ex
1032
1033    def c_and(self, a, b):
1034        return b.fam._and_AND(b, a)
1035
1036    def _and_AND(self, a, b):
1037        for b in b.arg:
1038            a &= b
1039        return a
1040
1041    def _and_FACTOR(self, a, b):
1042        # a0 & a1 & ... & b
1043        xs = []
1044        for ai in a.arg:
1045            if ai <= b:
1046                return a
1047            elif b <= ai:
1048                pass
1049            elif ai.fam.c_factordisjoint(ai, b):
1050                return self.mod.Nothing
1051            else:
1052                xs.append(ai)
1053        xs.append(b)
1054        return self._cons(xs)
1055
1056    _and_ATOM = _and_INVERT = _and_FACTOR
1057
1058    def _and_ID(self, a, b):
1059        b = a.arg[0] & b
1060        for a in a.arg[1:]:
1061            if b is self.mod.Nothing:
1062                break
1063            b = a & b
1064        return b
1065
1066    def c_le(self, a, b):
1067        return b.fam._ge_AND(b, a)
1068
1069    def _le_TERM(self, a, b):
1070        b = a & b
1071        if b.fam is not self or len(b.arg) != len(a.arg):
1072            return False
1073        for x in a.arg:
1074            for y in b.arg:
1075                if x <= y:
1076                    break
1077            else:
1078                return False
1079        return True
1080
1081    _le_ATOM = _le_INVERT = _le_AND = _le_TERM
1082
1083    def c_ge(self, a, b):
1084        return b.fam._le_AND(b, a)
1085
1086    def _ge_TERM(self, a, b):
1087        for a in a.arg:
1088            if not a >= b:
1089                return False
1090        return True
1091
1092    _ge_ATOM = _ge_INVERT = _ge_AND = _ge_TERM
1093
1094    def c_or(self, a, b):
1095        return b.fam._or_AND(b, a)
1096
1097    def _or_AND(self, a, b):
1098        # a0 & a1 ... | b0 & b1 ...
1099        # =
1100        Omega = ~self.mod.Nothing
1101        for i, ai in enumerate(a.arg):
1102            for j, bj in enumerate(b.arg):
1103                if ai | bj == Omega:
1104                    aa = self._cons(a.arg[:i] + a.arg[i+1:])
1105                    bb = self._cons(b.arg[:j] + b.arg[j+1:])
1106                    if aa == bb:
1107                        return aa
1108        return self.mod.fam_Or(a, b)
1109
1110    def _or_TERM(self, a, b):
1111        # a0 & a1 ... | b
1112        if a <= b:
1113            return b
1114        if b <= a:
1115            return a
1116
1117        xs = []
1118        for ai in a.arg:
1119            aib = ai | b
1120            if aib.fam.isfactor:
1121                xs.append(aib)
1122            else:
1123                break
1124        else:
1125            r = ~self.mod.Nothing
1126            for x in xs:
1127                r &= x
1128            return r
1129        return self.mod.fam_Or(a, b)
1130
1131    _or_ATOM = _or_INVERT = _or_TERM
1132
1133    def c_invert(self, a):
1134        # ~(a0 & a1 ...) = ~a0 | ~a1 ...
1135        r = self.mod.Nothing
1136        for ai in a.arg:
1137            r |= ~ai
1138        return r
1139
1140    def c_contains(self, a, b):
1141        for x in a.arg:
1142            if b not in x:
1143                return False
1144        return True
1145
1146    def c_test_contains(self, a, b, env):
1147        for x in a.arg:
1148            if not env.test_contains(x, b, 'and'):
1149                return env.failed('Failed')
1150        return True
1151
1152    def c_disjoint3(self, a, b):
1153        return (a & b) is self.mod.Nothing
1154
1155    def c_get_render(self, c):
1156        for kind in c.arg:
1157            r = kind.get_render()
1158            if r:
1159                return r
1160
1161        def r(o):
1162            return hex(id(o))
1163        return r
1164
1165    def c_get_brief(self, c):
1166        names = [kind.brief for kind in c.arg]
1167        # names.sort() ?? I think now I want them in given order.
1168        return '(%s)' % ' & '.join(names) + ')'
1169
1170    def c_get_ckc(self, a):
1171        return (
1172            self.mod.Classifiers.mker_and([x.biper for x in a.arg]).classifier,
1173            (0,)*len(a.arg),
1174            '=='
1175        )
1176
1177    def c_repr(self, a):
1178        reprs = [repr(k) for k in a.arg]
1179        return '(%s)' % ' & '.join(reprs)
1180
1181
1182class OrFamily(Family):
1183    opname = 'OR'
1184    isatom = False
1185    isfactor = False
1186
1187    def __call__(self, a, b):
1188        if b <= a:
1189            return a
1190        if a <= b:
1191            return b
1192        return self._cons((a, b))
1193
1194    def _cons(self, arg):
1195        # Must only be called with maximalized args
1196        for a in arg:
1197            assert a.fam.isfactor or isinstance(a.fam, AndFamily)
1198        if len(arg) > 1:
1199            return Family.__call__(self, tuple(arg))
1200        elif len(arg) == 1:
1201            return arg[0]
1202        else:
1203            return self.mod.Nothing
1204
1205    def c_contains(self, a, b):
1206        for x in a.arg:
1207            if b in x:
1208                return True
1209        return False
1210
1211    def c_get_ckc(self, a):
1212        return self.mod.Use.findex(*a.arg).classifier, len(a.arg), '<'
1213
1214    def c_get_examples(self, a, env):
1215        exa = [iter(env.get_examples(x)) for x in a.arg]
1216        while 1:
1217            n = 0
1218            for i, e in enumerate(exa):
1219                if e is not None:
1220                    try:
1221                        yield next(e)
1222                    except StopIteration:
1223                        exa[i] = None
1224                    else:
1225                        n += 1
1226            if not n:
1227                break
1228
1229    def c_test_contains(self, a, b, env):
1230        return env.forsome(a.arg, lambda x: env.test_contains(x, b, 'Some x'), 'or')
1231
1232    def c_and(self, a, b):
1233        if self is b.fam:
1234            return self._and_OR(a, b)
1235        else:
1236            return self._and_TERM(a, b)
1237
1238    def _and_TERM(self, a, b):
1239        # (a0 | a1 ..) & b = a0 & b | a1 & b | ...
1240        r = self.mod.Nothing
1241        for a in a.arg:
1242            r |= a & b
1243        return r
1244
1245    _and_ATOM = _and_INVERT = _and_AND = _and_TERM
1246
1247    def _and_OR(self, a, b):
1248        # (a0 | a1 ..) & (b0 | b1 ..) = a0 & b0 | a0 & b1 ... a1 & b0 | a1 & b1 ...
1249        r = self.mod.Nothing
1250        for a in a.arg:
1251            for bi in b.arg:
1252                r |= a & bi
1253        return r
1254
1255    def _and_ID(self, a, b):
1256        ai = a.arg[0]
1257        r = ai.fam._and_ID(ai, b)
1258        for ai in a.arg[1:]:
1259            r |= ai.fam._and_ID(ai, b)
1260        return r
1261
1262    def _ge_TERM(self, a, b):
1263        a = a & b
1264        if a.fam is self:
1265            if b.fam is not a.fam or len(b.arg) != len(a.arg):
1266                return False
1267            assert 0
1268        else:
1269            return b <= a
1270
1271    _ge_ATOM = _ge_INVERT = _ge_AND = _ge_TERM
1272
1273    def c_ge(self, a, b):
1274        if b.fam is self:
1275            return self.c_le(b, a)
1276        else:
1277            return self._ge_TERM(a, b)
1278
1279    def c_le(self, a, b):
1280        for x in a.arg:
1281            if not x <= b:
1282                return False
1283        return True
1284
1285    _le_ATOM = _le_INVERT = _le_AND = c_le
1286
1287    def c_or(self, a, b):
1288        return b.fam._or_OR(b, a)
1289
1290    def _or_TERM(self, a, b):
1291        # a0 | a1 ... | b
1292        xs = []
1293        lt = False
1294        for a in a.arg:
1295            if not b >= a:
1296                xs.append(a)
1297                if b <= a:
1298                    lt = True
1299        if not lt:
1300            xs.append(b)
1301        return self._cons(xs)
1302
1303    _or_ATOM = _or_INVERT = _or_AND = _or_TERM
1304
1305    def _or_OR(self, a, b):
1306        # (a0 | a1 ...) | (b0 | b1 ...)
1307        xs = maximals(a.arg + b.arg)
1308        return self._cons(xs)
1309
1310    def c_invert(self, a):
1311        # ~(a0 | a1 ...) = ~a0 & ~a1 ...
1312        r = ~a.arg[0]
1313        for ai in a.arg[1:]:
1314            r &= ~ai
1315        return r
1316
1317    def c_get_render(self, c):
1318        renders = self.mod.mutnodeset([kind.get_render() for kind in c.arg])
1319        if len(renders) == 1:
1320            return list(renders)[0]
1321        else:
1322            def r(o):
1323                return hex(id(o))
1324            r._idpart_header = 'Address'
1325            r._idpart_sortrender = lambda x: id(x)
1326            return r
1327
1328    def c_get_brief(self, c):
1329        names = [kind.brief for kind in c.arg]
1330        names.sort()
1331        return '(' + ' | '.join(names) + ')'
1332
1333    def c_get_idpart_header(self, a):
1334        return 'Brief'
1335
1336    def c_get_idpart_label(self, a):
1337        return '<mixed>'
1338
1339    def c_get_idpart_render(self, a):
1340        er = self.mod.Use.Clodo
1341        cla = er.classifier
1342        cli = cla.cli
1343
1344        brmemo = {}
1345
1346        def render(x):
1347            k = cli.classify(x)
1348
1349            br = brmemo.get(k)
1350            if br is None:
1351                kind = cla.get_kind(k)
1352                b = cla.get_kind(k).brief
1353                r = kind.get_render()
1354                br = (b, r)
1355                brmemo[k] = br
1356            b, r = br
1357
1358            return '%s: %s' % (b, r(x))
1359
1360        return render
1361
1362    def c_get_idpart_sortrender(self, a):
1363        er = self.mod.Use.Clodo
1364        cla = er.classifier
1365        cli = cla.cli
1366
1367        brmemo = {}
1368
1369        def render(x):
1370            k = cli.classify(x)
1371            br = brmemo.get(k)
1372            if br is None:
1373                kind = cla.get_kind(k)
1374                b = cla.get_kind(k).brief
1375                r = kind.fam.c_get_idpart_sortrender(kind)
1376                br = (b, r)
1377                brmemo[k] = br
1378            else:
1379                b, r = br
1380            if r != 'IDENTITY':
1381                x = r(x)
1382            return (b, x)
1383        return render
1384
1385    def c_repr(self, a):
1386        reprs = [repr(k) for k in a.arg]
1387        reprs.sort()
1388        return '(%s)' % ' | '.join(reprs)
1389
1390
1391class InvertFamily(Family):
1392    opname = 'INVERT'
1393    isatom = False
1394    isfactor = True
1395
1396    def __call__(self, a):
1397        assert a.fam.isatom
1398        if a is self.mod.Nothing:
1399            return self.mod.NotNothing
1400        else:
1401            return Family.__call__(self, a)
1402
1403    def c_test_contains(self, a, b, env):
1404        return env.test_contains_not(a.arg, b, 'InvertFamily')
1405
1406    def c_contains(self, a, b):
1407        return not b in a.arg
1408
1409    def c_and(self, a, b):
1410        return b.fam._and_INVERT(b, a)
1411
1412    _and_AND = c_and
1413
1414    def _and_FACTOR(self, a, b):
1415        # ~a.arg & ~b.arg
1416        # ~a.arg & b
1417        # Is normal form?
1418        x = a.arg & b
1419        if x.fam.isatom:
1420            a = self(x)
1421        return self.mod.fam_And(a, b)
1422
1423    _and_ATOM = _and_INVERT = _and_FACTOR
1424
1425    def _and_ID(self, a, b):
1426        return b - (b & a.arg)
1427
1428    def _factordisjoint_ATOM(self, a, b):
1429        # ~ a.arg <disjoint> b
1430        return b <= a.arg
1431
1432    def _factordisjoint_INVERT(self, a, b):
1433        # ~ a.arg <disjoint> ~b.arg
1434        return False
1435
1436    def c_le(self, a, b):
1437        return b.fam._ge_INVERT(b, a)
1438
1439    _le_AND = c_le
1440
1441    def _le_ATOM(self, a, b):
1442        # ~a.arg <= b
1443        return False
1444
1445    def _le_INVERT(self, a, b):
1446        # ~a.arg <= ~b.arg
1447        return b.arg <= a.arg
1448
1449    def c_ge(self, a, b):
1450        # ~a.arg >= b
1451        return a.arg.disjoint(b)
1452
1453    _ge_ATOM = _ge_INVERT = _ge_AND = c_ge
1454
1455    def c_or(self, a, b):
1456        return b.fam._or_INVERT(b, a)
1457
1458    _or_AND = c_or
1459
1460    def _or_FACTOR(self, a, b):
1461        # ~a.arg | b
1462        if a.arg <= b:
1463            return ~self.mod.Nothing
1464        x = a.arg & b
1465        if x is self.mod.Nothing:
1466            return a
1467        return self.mod.fam_Or(a, b)
1468
1469    _or_ATOM = _or_INVERT = _or_FACTOR
1470
1471    def c_invert(self, a):
1472        # ~(~a.arg) = a.arg
1473        return a.arg
1474
1475    def c_get_render(self, a):
1476        return a.arg.get_render()
1477
1478    def c_get_brief(self, a):
1479        n = a.arg.brief
1480        if (not (n.startswith('(') or n.startswith('<')) and
1481                ' ' in n):
1482            n = '(%s)' % n
1483        return '~%s' % n
1484
1485    def c_get_ckc(self, a):
1486        # This uses only existing machinery for C-level classification.
1487        # The alternatives are discussed in Notes 21 Sep 2005.
1488
1489        return (
1490            a.arg.biper.classifier,
1491            0,
1492            '!='
1493        )
1494
1495    def c_repr(self, a):
1496        return '~%s' % repr(a.arg)
1497
1498
1499class FamilyFamily(AtomFamily):
1500    def __init__(self, mod):
1501        AtomFamily.__init__(self, mod)
1502        self.add_export('union', lambda x: x.arg.fam_union())
1503
1504    def c_contains(self, a, b):
1505        return isinstance(b, UniSet) and b.fam is a.arg
1506
1507    def c_get_brief(self, c):
1508        return '<Family: %s>' % c.arg.__class__
1509
1510
1511class IdentitySetFamily(AtomFamily):
1512    def __init__(self, mod):
1513        AtomFamily.__init__(self, mod)
1514        self.defrefining(mod.Anything)
1515
1516        self.immnodeset = mod.immnodeset
1517        self.Part = mod.Part
1518        self.Path = mod.Path
1519        self.RefPat = mod.RefPat
1520        self.View = mod.View
1521        self.Use = mod.Use
1522
1523    def __call__(self, *args, **kwds):
1524        return self._cons(args, **kwds)
1525
1526    def _cons(self, arg, er=None):
1527        # arg is a sequence of nodes
1528        arg = self.immnodeset(arg)
1529        if not arg:
1530            return self.mod.Nothing
1531
1532        elif len(arg) == 1:
1533            r = IdentitySetSingleton(self, tuple(arg)[0])
1534        else:
1535            r = IdentitySetMulti(self, arg)
1536        if er is not None:
1537            r._er = er
1538        return r
1539
1540    def c_and(self, a, b):
1541        if b.fam is self:
1542            return self._cons(a.nodes & b.nodes)
1543        elif b.fam is self.mod.fam_Invert:
1544            return self._and_INVERT(a, b)
1545        else:
1546            return b.fam._and_ID(b, a)
1547
1548    def _and_ATOM(self, a, b):
1549        if b.fam is self:
1550            return self._cons(a.nodes & b.nodes)
1551        else:
1552            return b.fam._and_ID(b, a)
1553
1554    def _and_AND(self, a, b):
1555        return b.fam._and_ID(b, a)
1556
1557    def _and_ID(self, a, b):
1558        return self._cons(a.nodes & b.nodes)
1559
1560    def _and_INVERT(self, a, b):
1561        if b.arg.fam is self:
1562            return self._cons(a.nodes - b.arg.nodes)
1563        elif b is self.mod.NotNothing:
1564            return a
1565        else:
1566            return b.fam._and_ID(b, a)
1567
1568    def c_get_ckc(self, a):
1569        return self.mod.Classifiers.Idset.classifier, a.nodes, '<='
1570
1571    def c_hash(self, a):
1572        return hash(a.nodes)
1573
1574    def c_iter(self, a):
1575        # It's not well-defined to iterate and is considered error-prone
1576        # and may be SO much slower than expected
1577        # they need to be explicit to iterate over elements or partition subset
1578        raise TypeError('iteration over non-sequence')
1579
1580    def c_len(self, a):
1581        # The length corresponds to
1582        # o the number of rows in how it is printed
1583        # o the max getitem-wise index + 1
1584        # (Notes May 13 2005)
1585        return a.partition.numrows
1586
1587    def c_contains(self, a, b):
1588        return b in a.nodes
1589
1590    def c_le(self, a, b):
1591        if not b.fam is self:
1592            b = b.fam._and_ID(b, a)
1593        return a.nodes <= b.nodes
1594
1595    _le_ATOM = _le_INVERT = _le_AND = c_le
1596
1597    def c_or(self, a, b):
1598        if b.fam is self:
1599            return self._cons(a.nodes | b.nodes)
1600        else:
1601            a = a - b.fam._and_ID(b, a)
1602            return b.fam._or_ATOM(b, a)
1603
1604    _or_ATOM = _or_INVERT = _or_AND = _or_OR = c_or
1605
1606    def c_get_brief(self, c):
1607        return self.get_str_summary(c)
1608
1609    def c_get_render(self, a):
1610        return a.kind.get_render()
1611
1612    def c_getitem(self, a, idx):
1613        return a.partition.get_set(idx)
1614
1615    def c_str(self, a):
1616        return a.more._oh_printer.get_str_of_top()
1617
1618    def maprox_getattr(self, set, name):
1619        ns = self.mod.mutnodeset()
1620        for x in set.nodes:
1621            try:
1622                v = getattr(x, name)
1623            except AttributeError:
1624                pass
1625            else:
1626                ns.add(v)
1627        return self._cons(self.mod.immnodeset(ns))
1628
1629    def maprox_getitem(self, set, idx):
1630        ns = self.mod.mutnodeset()
1631        for x in set.nodes:
1632            try:
1633                v = x[idx]
1634            except (KeyError, IndexError):
1635                pass
1636            else:
1637                ns.add(v)
1638        return self._cons(self.mod.immnodeset(ns))
1639
1640    def c_get_idpart_header(self, a):
1641        return 'Kind: Name/Value/Address'
1642
1643    def c_get_idpart_label(self, a):
1644        return ''
1645
1646    def c_get_idpart_render(self, a):
1647        def render(x):
1648            x = self.mod.iso(x)
1649            r = x.brief.lstrip('<1 ').rstrip('>')
1650            return r
1651        return render
1652
1653    def get_by(self, a, er):
1654        ers = []
1655        if isinstance(er, EquivalenceRelation):
1656            ers.append(er)
1657        else:
1658            try:
1659                ss = er.split('&')
1660            except Exception:
1661                raise TypeError(
1662                    'by(): Equivalence relation or string expected.')
1663            if ss == ['']:
1664                ss = []
1665            for s in ss:
1666                try:
1667                    if not s.istitle() or s.startswith('er_'):
1668                        s = 'er_'+s
1669                    er = getattr(self.Use, s)
1670                except AttributeError:
1671                    raise ValueError(
1672                        'by(): No such equivalence relation defined in heapy.Use: %r' % s)
1673                ers.append(er)
1674
1675        if not ers:
1676            er = self.Use.Unity
1677        else:
1678            er = ers[0]
1679            for i in range(1, len(ers)):
1680                er &= ers[i]
1681        if a.er is not er:
1682            a = self._cons(a.nodes, er=er)
1683        return a
1684
1685    def get_er(self, a):
1686        try:
1687            er = a._er
1688        except AttributeError:
1689            er = self.mod.Use.Clodo
1690            a._er = er
1691        return er
1692
1693    def get_more(self, a):
1694        try:
1695            m = a._more
1696        except AttributeError:
1697            m = self.mod.OutputHandling.more_printer(a, a.partition)
1698            a._more = m
1699        return m
1700
1701    def get_all(self, a):
1702        return a.more.all
1703
1704    def get_owners(self, a):
1705        return self.mod.Use.Clodo.classifier.owners(a)
1706
1707    def get_partition(self, a):
1708        try:
1709            p = a._partition
1710        except AttributeError:
1711            a.fam.View.clear_check()
1712            p = a.fam.Part.partition(a, a.er)
1713            a._partition = p
1714        return p
1715
1716    def get_str_idpart(self, set, cla):
1717        # Get the string that is used for the 'identity partition'
1718        # when the objects share a common classification (cla)
1719        s = cla.fam.c_get_str_for(cla, set)
1720        return s
1721
1722    def get_str_refpat(self, set, cla, max_length):
1723        # Get the string that is used at the end of a reference pattern line
1724        strs = []
1725        strs.append('%d ' % set.count)
1726        strs.append(cla.fam.c_get_str_for(cla, set))
1727        strs.append(': ')
1728        strs.append(self.get_str_rendered(
1729            set, cla, max_length-len(''.join(strs))))
1730        s = ''.join(strs)
1731        if len(s) > max_length:
1732            s = s[:max_length - 3]+'...'
1733        return s
1734
1735    def get_str_rendered(self, set, cla, max_length=None):
1736        if max_length is None:
1737            max_length = 50
1738        strs = []
1739        lens = 0
1740        render = cla.get_render()
1741        for p in set.nodes:
1742            rs = render(p)
1743            if lens and lens + len(rs) + 2 >= max_length:
1744                strs[-1] += '...'  # but what can be done in limited time
1745                break
1746            lens += len(rs) + 2
1747            strs.append(rs)
1748        strs.sort()
1749        return ', '.join(strs)
1750
1751    def get_str_summary(self, c, max_length=None, er=None):
1752        if max_length is None:
1753            max_length = self.mod.max_summary_length
1754        if er is None:
1755            er = c.er
1756        set = c.nodes
1757        items = er.classifier.partition(set)
1758        keys = [k for k, v in items]
1759        cla = reduce(lambda x, y: x | y, keys)
1760        s = '<%d %s' % (len(set), cla.fam.c_get_str_for(cla, c))
1761        s += ': '
1762        bslen = len(s)
1763
1764        bstrs = []
1765        for cla, set in items:
1766            css = self.get_str_rendered(set, cla, max_length-bslen)
1767            if len(items) > 1:
1768                css = '<%d %s: %s>' % (set.count, cla, css)
1769            bstrs.append(css)
1770            bslen += len(css) + 3
1771            if bslen > max_length:
1772                break
1773
1774        # Don't use the initial count when comparing
1775        if len(bstrs) > 1:
1776            bstrs.sort(key=lambda x: x[x.index(' '):])
1777        s += ' | '.join(bstrs) + '>'
1778        if len(s) > max_length:
1779            s = s[:max_length-4]+'...>'
1780        return s
1781
1782    def get_parts(self, X):
1783        return [x for x in X.partition.get_sets()]
1784
1785    def get_theone(self, set):
1786        if len(set.nodes) == 1:
1787            return list(set.nodes)[0]
1788        raise ValueError('theone requires a singleton set')
1789
1790    def get_prod(self, set):
1791        obj = self.get_theone(set)
1792        self.mod.Use._check_tracemalloc()
1793        tb = self.mod.tracemalloc.get_object_traceback(obj)
1794        if tb is None:
1795            return
1796
1797        try:
1798            frames = tb.format(most_recent_first=True)
1799        except TypeError:
1800            # Py < 3.7
1801            frames = tb.format()
1802
1803        # TODO: move to a delicated file
1804        class Printer:
1805            def _oh_get_line_iter(self):
1806                yield 'Traceback (most recent call first):'
1807                yield from frames
1808
1809        printer = Printer()
1810        printer.mod = self.mod
1811        self.mod.OutputHandling.setup_printing(printer)
1812        return printer
1813
1814
1815class EmptyFamily(IdentitySetFamily):
1816    # Inherits from IdentitySetFamily because the special exported methods
1817    # tend to be required by applications.
1818    # There is only one object of EmptyFamily: UniSet.Nothing
1819    # The new method implementations added here are mostly for optimization.
1820    # (Other families may assume the EmptyFamily have these methods.)
1821    # The .nodes is an empty immnodeset so IdentitySetFamily methods should work.
1822    # The main change from IdentitySetFamily is the representations.
1823    def __init__(self, mod):
1824        IdentitySetFamily.__init__(self, mod)
1825
1826    def c_and(self, a, b):
1827        return a
1828
1829    _and_ATOM = _and_INVERT = _and_AND = _and_OR = _and_ID = c_and
1830
1831    def c_contains(self, a, b):
1832        return False
1833
1834    def c_ge(self, a, b):
1835        if b is a:
1836            return True
1837        return False
1838
1839    _ge_ATOM = _ge_INVERT = _ge_AND = c_ge
1840
1841    def c_get_brief(self, a):
1842        return '<Nothing>'
1843
1844    def c_repr(self, a):
1845        return '%s%s' % (self.mod.Use.reprefix, 'Nothing')
1846
1847    def c_iter(self, a):
1848        return iter(())
1849
1850    def c_le(self, a, b):
1851        return True
1852
1853    _le_ATOM = _le_INVERT = _le_AND = c_le
1854
1855    def c_len(self, a):
1856        return 0
1857
1858    def c_nonzero(self, a):
1859        return False
1860
1861    def c_or(self, a, b):
1862        return b
1863
1864    _or_ATOM = _or_INVERT = _or_AND = _or_OR = c_or
1865
1866    def c_str(self, a):
1867        return self.c_get_brief(a)
1868
1869    def c_sub(self, a, b):
1870        return a
1871
1872    def c_xor(self, a, b):
1873        return b
1874
1875
1876class EquivalenceRelationFamily(AtomFamily):
1877    def __init__(self, mod):
1878        AtomFamily.__init__(self, mod)
1879        self.Set = EquivalenceRelation
1880        self.Use = mod.Use
1881        self.Classifiers = mod.Classifiers
1882
1883    def __call__(self, constructor, *args, **kwds):
1884        # Passing classifier constructor rather than constructed classifier,
1885        # to make sure there is a 1-1 relation between equivalence relations and classifers.
1886        cl = constructor(*args, **kwds)
1887        er = self.Set(self, cl)
1888        cl.er = er
1889        return er
1890
1891    def c_contains(self, a, b):
1892        # XXX should have a smoother protocol
1893        try:
1894            return len(b.by(a)) == 1
1895        except AttributeError:
1896            try:
1897                ckc = b.get_ckc()
1898            except Exception:
1899                return False
1900            else:
1901                return ckc[0].er <= a and ckc[2] == '=='
1902
1903    def c_getattr(self, a, name):
1904        classifier = a.classifier
1905        try:
1906            g = getattr(classifier, 'get_attr_for_er')
1907        except AttributeError:
1908            raise AttributeError(name)
1909        return g(name)
1910
1911    def c_and(self, a, b):
1912        if b.fam is not self:
1913            return AtomFamily.c_and(self, a, b)
1914        ers = []
1915        for x in (a, b):
1916            if x.erargs:
1917                ers.extend(x.erargs)
1918            else:
1919                ers.append(x)
1920        ers = minimals(ers)
1921        if len(ers) == 1:
1922            return ers[0]
1923        er = self.Classifiers.mker_and(ers)
1924        er.erargs = tuple(ers)
1925        return er
1926
1927    def _ge_ATOM(self, a, b):
1928        if b.fam is self:
1929            return a.classifier in b.classifier.super_classifiers
1930        return False
1931
1932    def _le_ATOM(self, a, b):
1933        if b.fam is self:
1934            return b.classifier in a.classifier.super_classifiers
1935        return False
1936
1937    def c_call(self, a, args, kwds):
1938        return a.classifier.get_userkind(*args, **kwds)
1939
1940    def c_get_brief(self, a):
1941        return 'Equiv. relation %s' % a.classifier
1942
1943    def c_getitem(self, a, idx):
1944        return a.classifier.relimg(self.mod.nodeset_adapt(idx))
1945
1946    def c_repr(self, a):
1947        return a.classifier.get_reprname()
1948
1949
1950class Summary_str:
1951    def __init__(self, mod):
1952        self.mod = mod
1953        types = mod.types._module
1954        self.invtypes = {}
1955        for k, v in sorted(types.__dict__.items()):
1956            if isinstance(v, type):
1957                self.invtypes[v] = 'types.%s' % k
1958        for k, v in sorted(types.__builtins__.items()):
1959            if isinstance(v, type):
1960                self.invtypes[v] = k
1961
1962        # This is to make common printouts prettier / shorter (: and clearer ? :)
1963        # but may be disabled for clearer repr()
1964
1965        self.shorter_invtypes = {}
1966        for name in ('module', 'function'):
1967            t = getattr(types, name.capitalize()+'Type')
1968            self.shorter_invtypes[t] = name
1969
1970        self.table = {
1971            mod.NodeSet: self.str_address_len,
1972            bool: self.str_repr,
1973            types.BuiltinFunctionType: self.str_builtin_function,
1974            types.CodeType: self.str_code,
1975            complex: self.str_repr,
1976            dict: self.str_address_len,
1977            float: self.str_repr,
1978            types.FrameType: self.str_frame,
1979            types.FunctionType: self.str_function,
1980            int: self.str_repr,
1981            list: self.str_address_len,
1982            type(None): self.str_repr,
1983            types.MethodType: self.str_method,
1984            types.ModuleType: self.str_module,
1985            types.TracebackType: self.str_traceback,
1986            bytes: self.str_limrepr,
1987            str: self.str_limrepr,
1988            tuple: self.str_address_len,
1989            type: self.str_type,
1990        }
1991
1992    def __call__(self, key, longer=False):
1993        x = self.table.get(key)
1994        if x is None:
1995            if issubclass(key, type):
1996                x = self.str_type
1997            else:
1998                x = self.str_address
1999        if longer and 'longer' in x.__func__.__code__.co_varnames:
2000            return lambda k: x(k, longer=longer)
2001        else:
2002            return x
2003
2004    def set_function(self, type, func):
2005        self.table[type] = func
2006
2007    def str_address(self, x):
2008        return hex(id(x))
2009    str_address._idpart_header = 'Address'
2010    str_address._idpart_sortrender = id
2011
2012    def str_address_len(self, x):
2013        return self.str_address(x)+self.str_len(x)
2014    str_address_len._idpart_header = 'Address*Length'
2015    str_address_len._idpart_sortrender = id
2016
2017    def str_builtin_function(self, x):
2018        n = x.__name__
2019        m = x.__module__
2020        if m != 'builtins':
2021            n = '%s.%s' % (m, n)
2022        return n
2023    str_builtin_function._idpart_header = 'Name'
2024
2025    def str_code(self, x):
2026        return '%s:%d:%s' % (self.mod._root.os.path.basename(x.co_filename),
2027                             x.co_firstlineno,
2028                             x.co_name)
2029    str_code._idpart_header = 'File:Line:Name'
2030
2031    def str_frame(self, x):
2032        return '<%s at %s>' % (x.f_code.co_name, self.str_address(x))
2033    str_frame._idpart_header = 'Name at Address'
2034
2035    def str_function(self, x):
2036        return '%s.%s' % (x.__module__, x.__name__)
2037    str_function._idpart_header = 'Name'
2038
2039    def str_len(self, x):
2040        return '*%d' % len(x)
2041    str_len._idpart_header = 'Length'
2042
2043    def str_method(self, x):
2044        cn = self.str_type(x.__self__.__class__)
2045        if x.__self__ is not None:
2046            cn = '<%s at %s>' % (cn, self.str_address(x.__self__))
2047        func = x.__func__
2048        try:
2049            func_name = func.__func__
2050        except AttributeError:
2051            func_name = func.__name__
2052        return '%s.%s' % (cn, func_name)
2053    str_method._idpart_header = 'Type/<Type at address> . method'
2054
2055    def str_module(self, x):
2056        return x.__name__
2057    str_module._idpart_header = 'Name'
2058
2059    def str_limrepr(self, x):
2060        return self.mod._root.reprlib.repr(x)
2061    str_limrepr._idpart_header = 'Representation (limited)'
2062    str_limrepr._idpart_sortrender = 'IDENTITY'
2063    str_repr = repr
2064
2065    def str_traceback(self, x):
2066        return '<in frame %s at %s>' % (self.str_frame(x.tb_frame), self.str_address(x))
2067    str_traceback._idpart_header = 'Frame at Address'
2068
2069    def str_type(self, x, longer=False):
2070        if x in self.shorter_invtypes and not longer:
2071            return self.shorter_invtypes[x]
2072        if x in self.invtypes:
2073            return self.invtypes[x]
2074        return '%s.%s' % (x.__module__, x.__name__)
2075    str_type._idpart_header = 'Name'
2076
2077    def str_type_longer(self, x):
2078        if x in self.invtypes:
2079            return self.invtypes[x]
2080        return '%s.%s' % (x.__module__, x.__name__)
2081    str_type._longer_method = lambda x: str_type
2082
2083
2084def maximals(A, le=lambda x, y: x <= y):
2085    " Find the maximal element(s) of a partially ordered sequence"
2086    r = []
2087    for x in A:
2088        for a in A:
2089            if le(x, a) and not le(a, x):
2090                break
2091        else:
2092            for a in r:
2093                if le(x, a):
2094                    break
2095            else:
2096                r.append(x)
2097    return r
2098
2099
2100def minimals(A, le=lambda x, y: x <= y):
2101    " Find the minimal element(s) of a sequence of partially ordered elements"
2102    r = []
2103    for x in A:
2104        for a in A:
2105            if le(a, x) and not le(x, a):
2106                break
2107        else:
2108            for a in r:
2109                if le(a, x):
2110                    break
2111            else:
2112                r.append(x)
2113    return r
2114
2115
2116class _GLUECLAMP_:
2117    max_summary_length = 80
2118    auto_convert_type = True
2119    auto_convert_iter = False   # Can give problems if enabled; notes 22/11-04
2120    out_reach_module_names = ('UniSet', 'View', 'Path', 'RefPat')
2121
2122    _chgable_ = ('max_summary_length', 'out_reach_module_names',
2123                 'auto_convert_type', 'auto_convert_iter', 'output')
2124
2125    # _preload_ = ('_hiding_tag_',)
2126
2127    # Module 'imports'
2128
2129    _imports_ = (
2130        '_parent:Classifiers',
2131        '_parent:ImpSet',
2132        '_parent.ImpSet:emptynodeset',
2133        '_parent.ImpSet:immnodeset',
2134        '_parent.ImpSet:mutnodeset',
2135        '_parent.ImpSet:NodeSet',
2136        '_parent:Part',
2137        '_parent:Path',
2138        '_parent:RefPat',
2139        '_parent:OutputHandling',
2140        '_parent:View',
2141        '_parent.View:_hiding_tag_',
2142        '_parent.View:hv',
2143        '_parent:Use',
2144        '_root:tracemalloc',
2145        '_root:types',
2146    )
2147
2148    #
2149
2150    def _get_Anything(self): return self.Use.Unity.classifier.get_kind(None)
2151    def _get_Nothing(self): return IdentitySetMulti(
2152        EmptyFamily(self), self.emptynodeset)
2153    def _get_NotNothing(self): return Family.__call__(
2154        self.fam_Invert, self.Nothing)
2155
2156    def _get_export_dict(self):
2157        d = {}
2158        for k, v in list(self.out_reach_dict.items()):
2159            sc = getattr(v, '_uniset_exports', ())
2160            for sc in sc:
2161                x = getattr(v, sc)
2162                if sc in d and d[sc] is not x:
2163                    raise RuntimeError(
2164                        'Duplicate export: %r defined in: %r' % (sc, k))
2165                d[sc] = x
2166        return d
2167
2168    def _get_out_reach_dict(self):
2169        d = {}
2170        for name in self.out_reach_module_names:
2171            d[name] = getattr(self._parent, name)
2172        return d
2173
2174    def _get_summary_str(self): return self.Summary_str(self)
2175
2176    def _get_fam_And(self): return self.AndFamily(self)
2177    def _get_fam_EquivalenceRelation(
2178        self): return EquivalenceRelationFamily(self)
2179
2180    def _get_fam_Or(self): return self.OrFamily(self)
2181    def _get_fam_IdentitySet(self): return self.IdentitySetFamily(self)
2182    def _get_fam_Invert(self): return self.InvertFamily(self)
2183    def _get_fam_Family(self): return self.FamilyFamily(self)
2184
2185    def _get_fam_mixin_argatom(self):
2186        memo = {}
2187
2188        def f(Mixin, *args, **kwds):
2189            C = memo.get(Mixin)
2190            if C is None:
2191                class C(Mixin, self.ArgAtomFamily):
2192                    def __init__(self, mod, *args, **kwds):
2193                        mod.ArgAtomFamily.__init__(self, mod)
2194                        Mixin.__init__(self, mod, *args, **kwds)
2195
2196                C.__qualname__ = C.__name__ = Mixin.__name__
2197                memo[Mixin] = C
2198            return C(self, *args, **kwds)
2199        return f
2200
2201    def idset_adapt(self, X):
2202        if isinstance(X, self.IdentitySet):
2203            ids = X
2204        elif isinstance(X, self.NodeSet):
2205            ids = self.idset(X)
2206        else:
2207            raise TypeError(
2208                'IdentitySet or NodeSet expected, got %r.' % type(X))
2209        if X._hiding_tag_ is not self._hiding_tag_:
2210            raise ValueError(
2211                "The argument has wrong _hiding_tag_, you may convert it by Use.idset or Use.iso.")
2212        return ids
2213
2214    def idset(self, iterable, er=None):
2215        return self.fam_IdentitySet._cons(self.immnodeset(iterable), er=er)
2216
2217    def _get_iso(self):
2218        return self.fam_IdentitySet
2219
2220    def isuniset(self, obj):
2221        return isinstance(obj, self.UniSet)
2222        # Or has some particular attributes?
2223
2224    def nodeset_adapt(self, X):
2225        if isinstance(X, self.NodeSet):
2226            ns = X
2227        elif isinstance(X, self.IdentitySet):
2228            ns = X.nodes
2229        else:
2230            raise TypeError(
2231                'IdentitySet or NodeSet expected, got %r.' % type(X))
2232        if X._hiding_tag_ is not self._hiding_tag_:
2233            raise ValueError(
2234                "The argument has wrong _hiding_tag_, you may convert it by Use.idset or Use.iso.")
2235        return ns
2236
2237    def retset(self, X):
2238        if not isinstance(X, self.IdentitySet):
2239            X = self.idset(X)
2240        return X
2241
2242    def union(self, args, maximized=False):
2243        if not args:
2244            return self.Nothing
2245        a = args[0]
2246        for b in args[1:]:
2247            a |= b
2248        return a
2249
2250        # This optimization didn't work for idsets!!
2251        # XXX to fix back
2252
2253        if not maximized:
2254            args = maximals(args)
2255        return self.fam_Or._cons(args)
2256
2257    def uniset_from_setcastable(self, X):
2258        if isinstance(X, UniSet) and X._hiding_tag_ is self._hiding_tag_:
2259            return X
2260
2261        types = self.types
2262        if isinstance(X, type) and self.auto_convert_type:
2263            return self.Use.Type(X)
2264        elif isinstance(X, self.NodeSet) and X._hiding_tag_ is self._hiding_tag_:
2265            return self.idset(X)
2266        elif self.auto_convert_iter:
2267            try:
2268                it = iter(X)
2269            except TypeError:
2270                pass  # Will raise a 'more informative' exception below
2271            else:
2272                return self.idset(it)
2273        raise TypeError(
2274            "Argument is not automatically convertible to a UniSet with correct _hiding_tag_.")
2275