1def str_as_atr(s):
2    if s == '_':
3        return []
4    atr = []
5    i = 0
6    while i < len(s):
7        v = s[i]
8        if i + 1 < len(s) and s[i+1].isdigit():
9            n = 0
10            i = i + 1
11            while i < len(s) and s[i].isdigit():
12                n = n * 10 + int(s[i])
13                i += 1
14        else:
15            i += 1
16            n = 1
17        for j in range(n):
18            atr.append(v)
19    return atr
20
21
22def atr_as_str(atr):
23    if not atr:
24        return '_'
25    prefl = []
26    prestr = ''.join([str(x) for x in atr])
27    i = 0
28    while i < len(prestr):
29        c = prestr[i]
30        j = i+1
31        while j < len(prestr) and prestr[j] == c:
32            j += 1
33        if j - i > 2:
34            prefl.append(c)
35            prefl.append(str(j-i))
36        else:
37            while i < j:
38                prefl.append(c)
39                i += 1
40        i = j
41    return ''.join(prefl)
42
43
44def str_as_ixl(s):
45    return [ord(ch)-ord('a') for ch in str_as_atr(s)]
46
47
48def ixl_as_str(ixl):
49    return atr_as_str([chr(ix + ord('a')) for ix in ixl])
50
51
52class Paths:
53    def __init__(self, mod, rp, key, extended=True, andsets=(), variant=2):
54        self.mod = mod
55        self._hiding_tag_ = mod._hiding_tag_
56        self.key = key
57        self.rp = rp
58        self.extended = extended
59        self.srcrow = rp.get_row(self.key)
60        self.variant = variant
61        self.andsetbyname = {}
62        row = self.srcrow
63        while row is not None:
64            self.andsetbyname[row.ixlstr] = mod.Use.Anything
65            row = row.parent
66
67        if isinstance(andsets, dict):
68            self.andsetbyname.update(andsets)
69        elif isinstance(andsets, (tuple, list)):
70            row = self.srcrow
71            for i, s in enumerate(andsets):
72                if row is None:
73                    raise ValueError('andsets argument is too long')
74                if s is not None:
75                    self.andsetbyname[row.ixlstr] = s
76                row = row.parent
77        else:
78            raise TypeError('andsets argument must be dict, tuple, or list')
79
80        mod.OutputHandling.setup_printing(
81            self,
82            stop_only_when_told=variant >= 2)
83
84    def get_str_of_path_component_singleton(self, set):
85        return set.brief.lstrip('<1 ').rstrip('>')
86
87    def source_to_target_info(self):
88        src = 'Source'
89        tgt = 'Target'
90        via = 'Via'
91
92        row = self.srcrow
93        indent = 0
94        while row is not None:
95            if row.parent is None:
96                a = tgt
97            elif row is srcrow:
98                a = src
99            else:
100                a = via
101            a = ' '*indent + a
102            name = row.ixlstr
103            a = a + ' ' + ' '*(8+srcrow.depth*indinc -
104                               len(name)-len(a)) + name + ': '
105            yield a+row.getsummary(mod.line_length-len(a))
106            row = row.parent
107            indent += indinc
108
109    def _oh_get_label(self):
110        return 'Paths from source %r to target %r.' % (self.srcrow.ixlstr, '_')
111
112    def _oh_get_line_iter(self):
113        return getattr(self, 'get_line_iter_%s' % (self.variant,))()
114
115    def _oh_get_more_state_msg(self, startindex, lastindex):
116        return ''
117
118    def get_line_iter_1(self):
119        # Original variant indenting from left to right
120
121        mod = self.mod
122
123        srcrow = self.srcrow
124        srcset = srcrow.set
125
126        indinc = 2
127        if srcrow.depth >= 10:
128            indinc = 1
129
130        def genlines(row, ks, indent=0):
131            par = row.parent
132            for key, i, set in ks:
133                sidx = '%s[%d]' % (row.ixlstr, i)
134
135                if self.extended:
136                    strsing = self.get_str_of_path_component_singleton(set)
137                else:
138                    strsing = ''
139                vline = '%s %s %s %s' % (
140                    key,
141                    ' '*(40-len(key) - len(sidx)),
142                    sidx,
143                    strsing
144                )
145
146                yield vline
147
148                if par is None:
149                    continue
150
151                def get_nks(key, set):
152                    parset = set.referents & par.set
153                    for i, p in enumerate(parset.byid.parts):
154                        rels = mod.Path.relations(set.theone, p.theone)
155                        for rel in rels:
156                            if rel is mod.Path.identity:
157                                continue
158                            if rel is mod.Path.norelation:
159                                k = '??'
160                            else:
161                                k = str(rel) % ''
162                            k = ' '*(indent+indinc)+k
163                            yield k, i, p
164
165                for line in genlines(par, get_nks(key, set), indent+indinc):
166                    yield line
167
168        def get_ks():
169            for i, s in enumerate(srcset.byid.parts):
170                k = '[%d]  ' % i
171                k = k + (' -'*20)[:36-len(k)-srcrow.depth]
172                yield k, i, s
173
174        for line in genlines(srcrow, get_ks()):
175            yield line
176        return
177
178    def get_line_iter_2(self):
179        # Newer variant
180
181        mod = self.mod
182
183        srcrow = self.srcrow
184
185        indinc = 1
186        if srcrow.depth >= 10:
187            indinc = 1
188
189        lno = [0]
190
191        seen = {}
192
193        indir = 1
194
195        if indir == 1:
196            max_ixlstr_len = 0
197            max_str_len_set = 0
198            row = srcrow
199            while row:
200                if len(row.ixlstr) > max_ixlstr_len:
201                    max_ixlstr_len = len(row.ixlstr)
202                if len(str(len(row.set.nodes))) > max_str_len_set:
203                    max_str_len_set = len(str(len(row.set.nodes)))
204                row = row.parent
205
206        def genlines(row, part, idx):
207
208            seen[part.nodes, row.depth] = lno[0]
209            sidx = row.ixlstr
210            idxs = '[%d]' % idx
211            if indir < 0:
212                indent = (row.depth)*indinc
213                sidx = '%s%s%s' % (
214                    sidx, ' '*(6+indent-len(sidx)-len(idxs)), idxs)
215                if row.parent is None:
216                    sidx += ' == %s' % part.brief
217
218            else:
219                #idxs = ('[%.'+str(max_str_len_set)+'d]')%idx
220                sidx = '%s%s%s' % (sidx,
221                                   ' '*(3+max_str_len_set +
222                                        max_ixlstr_len-len(sidx)-len(idxs)),
223                                   idxs)
224                sidx += ' ' * (srcrow.depth + 1 - row.depth)
225                if row.parent is not None:
226                    sidx += '@'
227                else:
228                    sidx += '= %s' % part.brief
229
230            if row.parent is None:
231                #vline += ' == %s'%self.get_str_of_path_component_singleton(part)
232                vline = '%2s: %s' % (lno[0], sidx)
233                lno[0] += 1
234                yield ('STOP_AFTER', vline)
235                return
236
237            referents = part.referents & row.parent.set & self.andsetbyname[row.parent.ixlstr]
238            relations = mod.Path.relations
239            iso = mod.Use.iso
240            s = part.theone
241            t = [(relations(s, p.theone), p.by(referents.er), i)
242                 for (i, p) in enumerate(referents.byid.parts)]
243            for (rels, p, i) in t:
244                relstrings = []
245                for rel in rels:
246                    if rel is mod.Path.identity:
247                        continue
248                    if rel is mod.Path.norelation:
249                        k = '??'
250                    else:
251                        k = str(rel) % ''
252                    relstrings.append(k)
253
254                relsstr = ' / '.join(relstrings)
255                seenlno = seen.get((p.nodes, row.parent.depth))
256                vline = '%2s: %s' % (lno[0], sidx)
257                lno[0] += 1
258                if seenlno is not None:
259                    relsstr += ' -> #%d' % seenlno
260                    yield ('STOP_AFTER', vline + ' ' + relsstr)
261                else:
262                    yield vline + ' ' + relsstr
263                    for line in genlines(row.parent, p, i):
264                        yield line
265
266        for i, p in enumerate((srcrow.set & self.andsetbyname[srcrow.ixlstr]).byid.parts):
267            for line in genlines(srcrow, p, i):
268                yield line
269
270
271class RefPatIter:
272    def __init__(self, rp, start=0):
273        self.rp = rp
274        self._hiding_tag_ = rp._hiding_tag_
275        self.ix = start
276
277    def __iter__(self):
278        return self
279
280    def __next__(self):
281        try:
282            x = self.rp[self.ix]
283        except IndexError:
284            raise StopIteration
285        self.ix += 1
286        return x
287
288
289class RefPatRow:
290    def __init__(self, rp, kindset, seenline, ixl, parent):
291        self.rp = rp
292        self._hiding_tag_ = rp._hiding_tag_
293        self.kindset = kindset
294        self.kind, self.set = kindset
295        assert self.set <= self.kind
296        self.seenline = seenline
297        self.ixl = ixl[:]
298        self.parent = parent
299        if parent is not None:
300            self.depth = parent.depth + 1
301        else:
302            self.depth = 0
303
304        self.index = 0
305        self.maxdepth = rp.depth
306        self.max_str_len = rp.mod.line_length
307        self.ixlstr = ixl_as_str(ixl)
308        self.isready = 0
309        self.children = []
310
311    def __str__(self):
312        prestr = '%2d: %s ' % (self.index, self.ixlstr)
313
314        if self.index & 1:
315            fillpat = ' ' * 100
316        else:
317            fillpat = '-'*100
318
319        lps = len(prestr)
320        fill = fillpat[lps:9+self.depth]
321
322        if self.seenline:
323            ref = '[^ %s]' % self.seenline.index
324        elif self.isroot:
325            ref = '[R]'
326        elif self.depth > 0 and self.set <= self.rp.stopkind:
327            ref = '[S]'
328        elif self.depth < self.maxdepth:
329            ref = '[-]'
330        else:
331            ref = '[+]'
332
333        prefix = '%s%s %s ' % (prestr, fill, ref)
334        return '%s%s' % (prefix, self.getsummary(self.max_str_len-len(prefix)))
335
336    def getchild(self, ix):
337        while ix >= len(self.children) and not self.isready:
338            self.rp.generate(len(self.rp.lines))
339        return self.children[ix]
340
341    def getsummary(self, max_len):
342        kind, set = self.kind, self.set
343        summary = set.fam.get_str_refpat(set, kind, max_len)
344        return summary
345
346
347class ReferencePattern:
348    __doc__ = '<Help Text'
349    help = """\
350Methods
351
352
353
354
355"""
356
357    def __init__(self, mod, set, depth, er, relimg, bf, stopkind, nocyc):
358        self.mod = mod
359        self._hiding_tag_ = mod._hiding_tag_
360        self.View = mod.View
361        self.set = set
362        self.depth = depth
363        self.er = er
364        self.bf = bf
365        self.stopkind = stopkind
366        self.nocyc = nocyc
367        self.is_initialized = 0
368
369        self.totcount = set.count
370        self.totsize = set.indisize
371        self.kind = set.kind
372        self.kindset = (self.kind, self.set)
373        self.relimg = relimg
374        self.top = self
375
376        mod.OutputHandling.setup_printing(self)
377        self.View.referrers_add_target(self)
378        self.reset_nogc()
379        self.is_initialized = 1
380
381    def __getattr__(self, s):
382        if not self.is_initialized:
383            raise AttributeError(s)
384
385        try:
386            return getattr(self.__class__, s)
387        except AttributeError:
388            pass
389        try:
390            row = self.get_row_named(s)
391        except ValueError:
392            raise AttributeError(s)
393        return row.set
394
395    def __getitem__(self, ix):
396        return self.get_row_indexed(ix).set
397
398    def __iter__(self, start=0):
399        return RefPatIter(self, start)
400
401    def __len__(self):
402        self.generate()
403        return len(self.lines)
404
405    def _oh_get_label(self):
406        return 'Reference Pattern by <' + self.er.classifier.get_byname() + '>.'
407
408    def _oh_get_more_state_msg(self, startindex, lastindex):
409        if self.isfullygenerated:
410            msg = '%d more lines. ' % (len(self.lines) - 1-lastindex,)
411        else:
412            msg = ''
413        return msg
414
415    def _oh_get_line_iter(self):
416        it = self.iterlines(0)
417        for el in it:
418            yield str(el)
419
420    def generate(self, ix=None):
421        while ix is None or ix < 0 or ix >= len(self.lines):
422            try:
423                self.lines.append(next(self.lg))
424            except StopIteration:
425                self.isfullygenerated = 1
426                return
427            self.lines[-1].index = len(self.lines) - 1
428
429    def get_row(self, key):
430        try:
431            [][key]
432        except TypeError:
433            return self.get_row_named(key)
434        except IndexError:
435            return self.get_row_indexed(key)
436
437    def get_row_indexed(self, ix):
438        self.generate(ix)
439        return self.lines[ix]
440
441    def get_row_named(self, name):
442        row = self.get_row_indexed(0)
443        for ix in str_as_ixl(name):
444            try:
445                row = row.getchild(ix)
446            except IndexError:
447                raise ValueError(
448                    'Reference pattern has no row named %r' % name)
449        return row
450
451    def iterlines(self, start=None):
452        if start is None:
453            start = 0
454        while 1:
455            try:
456                yield self.get_row_indexed(start)
457            except IndexError:
458                return
459            start += 1
460
461    def linegenerator(self, xxx_todo_changeme, ixl, parent=None):
462        (kind, set) = xxx_todo_changeme
463        seenline = self.seensets.get(set.nodes)
464        ixl = list(ixl)
465        line = RefPatRow(self, (kind, set), seenline=seenline,
466                         ixl=ixl, parent=parent)
467        children = self.get_children(line)
468        line.isroot = not children
469        if seenline is None:
470            self.seensets[set.nodes] = line
471        if parent is not None:
472            parent.children.append(line)
473        yield line
474
475        depth = line.depth
476        if (not seenline and depth < self.depth and
477                (depth == 0 or not (set <= self.stopkind))):
478            for i, cs in enumerate(children):
479                ixl.append(i)
480                for rl in self.linegenerator(cs, ixl, line):
481                    yield rl
482                ixl.pop()
483        line.isready = 1
484
485    def get_children(self, line):
486        (kind, set) = line.kindset
487        chset = self.relimg(set)
488        if self.nocyc:
489            while line is not None:
490                chset -= line.set
491                line = line.parent
492        return [(row.kind, row.set)
493                for row in self.get_partition(chset, self.er).get_rows()]
494
495    def get_partition(self, set, er):
496        p = self.mod.Part.partition(set, er)
497        return p
498
499    def paths(self, key, **kwds):
500        return Paths(self.mod, self, key, **kwds)
501
502    def reset(self):
503        self.reset_nogc()
504        self.printer.reset()
505        self.mod._root.gc.collect()
506
507    def reset_nogc(self):
508        self.isfullygenerated = 0
509        self.seensets = {}
510        self.lines = []
511        self.lg = self.linegenerator(self.kindset, [])
512        self.lastindex = None
513
514
515class _GLUECLAMP_:
516    _preload_ = ('_hiding_tag_',)
517
518    depth = 7
519    line_length = 80
520
521    _uniset_exports = ('rp',)
522    _chgable_ = ('er',
523                 'depth',
524                 'line_length',
525                 )
526
527    # 'module imports'
528
529    _imports_ = (
530        '_parent:OutputHandling',
531        '_parent:Part',
532        '_parent:Path',
533        '_parent:UniSet',
534        '_parent:Use',
535        '_parent:View',
536        '_parent.View:_hiding_tag_',
537    )
538
539    #
540
541    def _get_er(self): return self.Use.Clodo
542
543    def _get_stopkind(self):
544        hp = self.Use
545        return (
546            hp.Type.Module |
547            hp.Type.Type |
548            hp.Type.Module.dictof |
549            hp.Type.Type.dictof |
550            hp.Type.Code |
551            hp.Type.Frame
552        )
553
554    def rp(self, X, depth=None, er=None, imdom=0, bf=0, src=None, stopkind=None,
555           nocyc=False, ref=None):
556        """rp(X, depth=None, er=None, imdom=0, bf=0, src=None, stopkind=None, nocyc=False, ref=None)
557Reference pattern forming.
558Arguments
559        X       Set of objects for which a reference pattern is sought.
560        depth   The depth to which the pattern will be generated. The
561                default is taken from depth of this module.
562        er      The equivalence relation to partition the referrers. The default
563                is Clodo.
564        imdom   If true, the immediate dominators will be used instead
565                of the referrers. This will take longer time to calculate,
566                but may be useful to reduce the complexity of the reference
567                pattern.
568        bf      If true, the pattern will be printed in breadth-first
569                order instead of depth-first. (Experimental.)
570        src     If specified, an alternative reference source instead
571                of the default root.
572        stopkind
573        nocyc
574        ref
575
576Description
577        Return a reference pattern object based on the objects in the set X.
578        The reference pattern object is of class ReferencePattern. It is
579        described in XXX.
580"""
581        if depth is None:
582            depth = self.depth
583        X = self.UniSet.idset_adapt(X)
584        if src is not None:
585            # Creates an entire new guppy tree
586            # Mostly for testing purposes -
587            # can likely cause type problems generally
588            src = self.UniSet.idset_adapt(src)
589            self = self._root.guppy.Root().guppy.heapy.RefPat
590            self.View.root = tuple(src.nodes)
591            X = self.Use.idset(X.nodes)
592
593        if er is None:
594            er = self.er  # NEEDS to be loaded after new Classifier created
595        if imdom:
596            def relimg(X): return X.imdom
597        elif ref is not None:
598            if ref == 'gc':
599                def relimg(X): return X.referrers_gc
600            elif ref == 'gcx':
601                relimg = (lambda x: x.referrers_gc
602                          - self._root.guppy.sets.ImmNodeSet
603                          - self._parent.heapyc.NodeGraph
604                          - self.View.ObservationList)
605            elif ref == 'imdom':
606                def relimg(X): return X.imdom
607            elif callable(ref):
608                relimg = ref
609            else:
610                raise ValueError(
611                    "ref should be 'gc', 'gcx', 'imdom', or a callable")
612        else:
613            def relimg(X): return X.referrers
614        if stopkind is None:
615            stopkind = self.stopkind
616        rp = ReferencePattern(self, X, depth, er, relimg, bf, stopkind, nocyc)
617        return rp
618