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