GoPLS Viewer

Home|gopls/cmd/digraph/digraph.go
1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5/*
6The digraph command performs queries over unlabelled directed graphs
7represented in text form.  It is intended to integrate nicely with
8typical UNIX command pipelines.
9
10Usage:
11
12    your-application | digraph [command]
13
14The support commands are:
15
16    nodes
17        the set of all nodes
18    degree
19        the in-degree and out-degree of each node
20    transpose
21        the reverse of the input edges
22    preds <node> ...
23        the set of immediate predecessors of the specified nodes
24    succs <node> ...
25        the set of immediate successors of the specified nodes
26    forward <node> ...
27        the set of nodes transitively reachable from the specified nodes
28    reverse <node> ...
29        the set of nodes that transitively reach the specified nodes
30    somepath <node> <node>
31        the list of nodes on some arbitrary path from the first node to the second
32    allpaths <node> <node>
33        the set of nodes on all paths from the first node to the second
34    sccs
35        all strongly connected components (one per line)
36    scc <node>
37        the set of nodes strongly connected to the specified one
38    focus <node>
39        the subgraph containing all directed paths that pass through the specified node
40
41Input format:
42
43Each line contains zero or more words. Words are separated by unquoted
44whitespace; words may contain Go-style double-quoted portions, allowing spaces
45and other characters to be expressed.
46
47Each word declares a node, and if there are more than one, an edge from the
48first to each subsequent one. The graph is provided on the standard input.
49
50For instance, the following (acyclic) graph specifies a partial order among the
51subtasks of getting dressed:
52
53    $ cat clothes.txt
54    socks shoes
55    "boxer shorts" pants
56    pants belt shoes
57    shirt tie sweater
58    sweater jacket
59    hat
60
61The line "shirt tie sweater" indicates the two edges shirt -> tie and
62shirt -> sweater, not shirt -> tie -> sweater.
63
64Example usage:
65
66Using digraph with existing Go tools:
67
68    $ go mod graph | digraph nodes # Operate on the Go module graph.
69    $ go list -m all | digraph nodes # Operate on the Go package graph.
70
71Show the transitive closure of imports of the digraph tool itself:
72
73    $ go list -f '{{.ImportPath}} {{join .Imports " "}}' ... | digraph forward golang.org/x/tools/cmd/digraph
74
75Show which clothes (see above) must be donned before a jacket:
76
77    $ digraph reverse jacket
78*/
79package main // import "golang.org/x/tools/cmd/digraph"
80
81// TODO(adonovan):
82// - support input files other than stdin
83// - support alternative formats (AT&T GraphViz, CSV, etc),
84//   a comment syntax, etc.
85// - allow queries to nest, like Blaze query language.
86
87import (
88    "bufio"
89    "bytes"
90    "errors"
91    "flag"
92    "fmt"
93    "io"
94    "os"
95    "sort"
96    "strconv"
97    "strings"
98    "unicode"
99    "unicode/utf8"
100)
101
102func usage() {
103    fmt.Fprintf(os.Stderr`Usage: your-application | digraph [command]
104
105The support commands are:
106    nodes
107        the set of all nodes
108    degree
109        the in-degree and out-degree of each node
110    transpose
111        the reverse of the input edges
112    preds <node> ...
113        the set of immediate predecessors of the specified nodes
114    succs <node> ...
115        the set of immediate successors of the specified nodes
116    forward <node> ...
117        the set of nodes transitively reachable from the specified nodes
118    reverse <node> ...
119        the set of nodes that transitively reach the specified nodes
120    somepath <node> <node>
121        the list of nodes on some arbitrary path from the first node to the second
122    allpaths <node> <node>
123        the set of nodes on all paths from the first node to the second
124    sccs
125        all non-trivial strongly connected components, one per line
126        (single-node components are only printed for nodes with self-loops)
127    scc <node>
128        the set of nodes nodes strongly connected to the specified one
129    focus <node>
130        the subgraph containing all directed paths that pass through the specified node
131`)
132    os.Exit(2)
133}
134
135func main() {
136    flag.Usage = usage
137    flag.Parse()
138
139    args := flag.Args()
140    if len(args) == 0 {
141        usage()
142    }
143
144    if err := digraph(args[0], args[1:]); err != nil {
145        fmt.Fprintf(os.Stderr"digraph: %s\n"err)
146        os.Exit(1)
147    }
148}
149
150type nodelist []string
151
152func (l nodelistprintln(sep string) {
153    for inode := range l {
154        if i > 0 {
155            fmt.Fprint(stdoutsep)
156        }
157        fmt.Fprint(stdoutnode)
158    }
159    fmt.Fprintln(stdout)
160}
161
162type nodeset map[string]bool
163
164func (s nodesetsort() nodelist {
165    nodes := make(nodelistlen(s))
166    var i int
167    for node := range s {
168        nodes[i] = node
169        i++
170    }
171    sort.Strings(nodes)
172    return nodes
173}
174
175func (s nodesetaddAll(x nodeset) {
176    for node := range x {
177        s[node] = true
178    }
179}
180
181// A graph maps nodes to the non-nil set of their immediate successors.
182type graph map[string]nodeset
183
184func (g graphaddNode(node stringnodeset {
185    edges := g[node]
186    if edges == nil {
187        edges = make(nodeset)
188        g[node] = edges
189    }
190    return edges
191}
192
193func (g graphaddEdges(from stringto ...string) {
194    edges := g.addNode(from)
195    for _to := range to {
196        g.addNode(to)
197        edges[to] = true
198    }
199}
200
201func (g graphreachableFrom(roots nodesetnodeset {
202    seen := make(nodeset)
203    var visit func(node string)
204    visit = func(node string) {
205        if !seen[node] {
206            seen[node] = true
207            for e := range g[node] {
208                visit(e)
209            }
210        }
211    }
212    for root := range roots {
213        visit(root)
214    }
215    return seen
216}
217
218func (g graphtranspose() graph {
219    rev := make(graph)
220    for nodeedges := range g {
221        rev.addNode(node)
222        for succ := range edges {
223            rev.addEdges(succnode)
224        }
225    }
226    return rev
227}
228
229func (g graphsccs() []nodeset {
230    // Kosaraju's algorithm---Tarjan is overkill here.
231
232    // Forward pass.
233    S := make(nodelist0len(g)) // postorder stack
234    seen := make(nodeset)
235    var visit func(node string)
236    visit = func(node string) {
237        if !seen[node] {
238            seen[node] = true
239            for e := range g[node] {
240                visit(e)
241            }
242            S = append(Snode)
243        }
244    }
245    for node := range g {
246        visit(node)
247    }
248
249    // Reverse pass.
250    rev := g.transpose()
251    var scc nodeset
252    seen = make(nodeset)
253    var rvisit func(node string)
254    rvisit = func(node string) {
255        if !seen[node] {
256            seen[node] = true
257            scc[node] = true
258            for e := range rev[node] {
259                rvisit(e)
260            }
261        }
262    }
263    var sccs []nodeset
264    for len(S) > 0 {
265        top := S[len(S)-1]
266        S = S[:len(S)-1// pop
267        if !seen[top] {
268            scc = make(nodeset)
269            rvisit(top)
270            if len(scc) == 1 && !g[top][top] {
271                continue
272            }
273            sccs = append(sccsscc)
274        }
275    }
276    return sccs
277}
278
279func (g graphallpaths(fromto stringerror {
280    // Mark all nodes to "to".
281    seen := make(nodeset// value of seen[x] indicates whether x is on some path to "to"
282    var visit func(node stringbool
283    visit = func(node stringbool {
284        reachesTook := seen[node]
285        if !ok {
286            reachesTo = node == to
287            seen[node] = reachesTo
288            for e := range g[node] {
289                if visit(e) {
290                    reachesTo = true
291                }
292            }
293            if reachesTo && node != to {
294                seen[node] = true
295            }
296        }
297        return reachesTo
298    }
299    visit(from)
300
301    // For each marked node, collect its marked successors.
302    var edges []string
303    for n := range seen {
304        for succ := range g[n] {
305            if seen[succ] {
306                edges = append(edgesn+" "+succ)
307            }
308        }
309    }
310
311    // Sort (so that this method is deterministic) and print edges.
312    sort.Strings(edges)
313    for _e := range edges {
314        fmt.Fprintln(stdoute)
315    }
316
317    return nil
318}
319
320func (g graphsomepath(fromto stringerror {
321    type edge struct{ fromto string }
322    seen := make(nodeset)
323    var dfs func(path []edgefrom stringbool
324    dfs = func(path []edgefrom stringbool {
325        if !seen[from] {
326            seen[from] = true
327            if from == to {
328                // fmt.Println(path, len(path), cap(path))
329                // Print and unwind.
330                for _e := range path {
331                    fmt.Fprintln(stdoute.from+" "+e.to)
332                }
333                return true
334            }
335            for e := range g[from] {
336                if dfs(append(pathedge{fromfromtoe}), e) {
337                    return true
338                }
339            }
340        }
341        return false
342    }
343    maxEdgesInGraph := len(g) * (len(g) - 1)
344    if !dfs(make([]edge0maxEdgesInGraph), from) {
345        return fmt.Errorf("no path from %q to %q"fromto)
346    }
347    return nil
348}
349
350func parse(rd io.Reader) (grapherror) {
351    g := make(graph)
352
353    var linenum int
354    in := bufio.NewScanner(rd)
355    for in.Scan() {
356        linenum++
357        // Split into words, honoring double-quotes per Go spec.
358        wordserr := split(in.Text())
359        if err != nil {
360            return nilfmt.Errorf("at line %d: %v"linenumerr)
361        }
362        if len(words) > 0 {
363            g.addEdges(words[0], words[1:]...)
364        }
365    }
366    if err := in.Err(); err != nil {
367        return nilerr
368    }
369    return gnil
370}
371
372// Overridable for redirection.
373var stdin io.Reader = os.Stdin
374var stdout io.Writer = os.Stdout
375
376func digraph(cmd stringargs []stringerror {
377    // Parse the input graph.
378    gerr := parse(stdin)
379    if err != nil {
380        return err
381    }
382
383    // Parse the command line.
384    switch cmd {
385    case "nodes":
386        if len(args) != 0 {
387            return fmt.Errorf("usage: digraph nodes")
388        }
389        nodes := make(nodeset)
390        for node := range g {
391            nodes[node] = true
392        }
393        nodes.sort().println("\n")
394
395    case "degree":
396        if len(args) != 0 {
397            return fmt.Errorf("usage: digraph degree")
398        }
399        nodes := make(nodeset)
400        for node := range g {
401            nodes[node] = true
402        }
403        rev := g.transpose()
404        for _node := range nodes.sort() {
405            fmt.Fprintf(stdout"%d\t%d\t%s\n"len(rev[node]), len(g[node]), node)
406        }
407
408    case "transpose":
409        if len(args) != 0 {
410            return fmt.Errorf("usage: digraph transpose")
411        }
412        var revEdges []string
413        for nodesuccs := range g.transpose() {
414            for succ := range succs {
415                revEdges = append(revEdgesfmt.Sprintf("%s %s"nodesucc))
416            }
417        }
418        sort.Strings(revEdges// make output deterministic
419        for _e := range revEdges {
420            fmt.Fprintln(stdoute)
421        }
422
423    case "succs""preds":
424        if len(args) == 0 {
425            return fmt.Errorf("usage: digraph %s <node> ... "cmd)
426        }
427        g := g
428        if cmd == "preds" {
429            g = g.transpose()
430        }
431        result := make(nodeset)
432        for _root := range args {
433            edges := g[root]
434            if edges == nil {
435                return fmt.Errorf("no such node %q"root)
436            }
437            result.addAll(edges)
438        }
439        result.sort().println("\n")
440
441    case "forward""reverse":
442        if len(args) == 0 {
443            return fmt.Errorf("usage: digraph %s <node> ... "cmd)
444        }
445        roots := make(nodeset)
446        for _root := range args {
447            if g[root] == nil {
448                return fmt.Errorf("no such node %q"root)
449            }
450            roots[root] = true
451        }
452        g := g
453        if cmd == "reverse" {
454            g = g.transpose()
455        }
456        g.reachableFrom(roots).sort().println("\n")
457
458    case "somepath":
459        if len(args) != 2 {
460            return fmt.Errorf("usage: digraph somepath <from> <to>")
461        }
462        fromto := args[0], args[1]
463        if g[from] == nil {
464            return fmt.Errorf("no such 'from' node %q"from)
465        }
466        if g[to] == nil {
467            return fmt.Errorf("no such 'to' node %q"to)
468        }
469        if err := g.somepath(fromto); err != nil {
470            return err
471        }
472
473    case "allpaths":
474        if len(args) != 2 {
475            return fmt.Errorf("usage: digraph allpaths <from> <to>")
476        }
477        fromto := args[0], args[1]
478        if g[from] == nil {
479            return fmt.Errorf("no such 'from' node %q"from)
480        }
481        if g[to] == nil {
482            return fmt.Errorf("no such 'to' node %q"to)
483        }
484        if err := g.allpaths(fromto); err != nil {
485            return err
486        }
487
488    case "sccs":
489        if len(args) != 0 {
490            return fmt.Errorf("usage: digraph sccs")
491        }
492        buf := new(bytes.Buffer)
493        oldStdout := stdout
494        stdout = buf
495        for _scc := range g.sccs() {
496            scc.sort().println(" ")
497        }
498        lines := strings.SplitAfter(buf.String(), "\n")
499        sort.Strings(lines)
500        stdout = oldStdout
501        io.WriteString(stdoutstrings.Join(lines""))
502
503    case "scc":
504        if len(args) != 1 {
505            return fmt.Errorf("usage: digraph scc <node>")
506        }
507        node := args[0]
508        if g[node] == nil {
509            return fmt.Errorf("no such node %q"node)
510        }
511        for _scc := range g.sccs() {
512            if scc[node] {
513                scc.sort().println("\n")
514                break
515            }
516        }
517
518    case "focus":
519        if len(args) != 1 {
520            return fmt.Errorf("usage: digraph focus <node>")
521        }
522        node := args[0]
523        if g[node] == nil {
524            return fmt.Errorf("no such node %q"node)
525        }
526
527        edges := make(map[string]struct{})
528        for from := range g.reachableFrom(nodeset{nodetrue}) {
529            for to := range g[from] {
530                edges[fmt.Sprintf("%s %s"fromto)] = struct{}{}
531            }
532        }
533
534        gtrans := g.transpose()
535        for from := range gtrans.reachableFrom(nodeset{nodetrue}) {
536            for to := range gtrans[from] {
537                edges[fmt.Sprintf("%s %s"tofrom)] = struct{}{}
538            }
539        }
540
541        edgesSorted := make([]string0len(edges))
542        for e := range edges {
543            edgesSorted = append(edgesSortede)
544        }
545        sort.Strings(edgesSorted)
546        fmt.Fprintln(stdoutstrings.Join(edgesSorted"\n"))
547
548    default:
549        return fmt.Errorf("no such command %q"cmd)
550    }
551
552    return nil
553}
554
555// -- Utilities --------------------------------------------------------
556
557// split splits a line into words, which are generally separated by
558// spaces, but Go-style double-quoted string literals are also supported.
559// (This approximates the behaviour of the Bourne shell.)
560//
561//    `one "two three"` -> ["one" "two three"]
562//    `a"\n"b` -> ["a\nb"]
563func split(line string) ([]stringerror) {
564    var (
565        words   []string
566        inWord  bool
567        current bytes.Buffer
568    )
569
570    for len(line) > 0 {
571        rsize := utf8.DecodeRuneInString(line)
572        if unicode.IsSpace(r) {
573            if inWord {
574                words = append(wordscurrent.String())
575                current.Reset()
576                inWord = false
577            }
578        } else if r == '"' {
579            var ok bool
580            sizeok = quotedLength(line)
581            if !ok {
582                return nilerrors.New("invalid quotation")
583            }
584            serr := strconv.Unquote(line[:size])
585            if err != nil {
586                return nilerr
587            }
588            current.WriteString(s)
589            inWord = true
590        } else {
591            current.WriteRune(r)
592            inWord = true
593        }
594        line = line[size:]
595    }
596    if inWord {
597        words = append(wordscurrent.String())
598    }
599    return wordsnil
600}
601
602// quotedLength returns the length in bytes of the prefix of input that
603// contain a possibly-valid double-quoted Go string literal.
604//
605// On success, n is at least two (""); input[:n] may be passed to
606// strconv.Unquote to interpret its value, and input[n:] contains the
607// rest of the input.
608//
609// On failure, quotedLength returns false, and the entire input can be
610// passed to strconv.Unquote if an informative error message is desired.
611//
612// quotedLength does not and need not detect all errors, such as
613// invalid hex or octal escape sequences, since it assumes
614// strconv.Unquote will be applied to the prefix.  It guarantees only
615// that if there is a prefix of input containing a valid string literal,
616// its length is returned.
617//
618// TODO(adonovan): move this into a strconv-like utility package.
619func quotedLength(input string) (n intok bool) {
620    var offset int
621
622    // next returns the rune at offset, or -1 on EOF.
623    // offset advances to just after that rune.
624    next := func() rune {
625        if offset < len(input) {
626            rsize := utf8.DecodeRuneInString(input[offset:])
627            offset += size
628            return r
629        }
630        return -1
631    }
632
633    if next() != '"' {
634        return // error: not a quotation
635    }
636
637    for {
638        r := next()
639        if r == '\n' || r < 0 {
640            return // error: string literal not terminated
641        }
642        if r == '"' {
643            return offsettrue // success
644        }
645        if r == '\\' {
646            var skip int
647            switch next() {
648            case 'a''b''f''n''r''t''v''\\''"':
649                skip = 0
650            case '0''1''2''3''4''5''6''7':
651                skip = 2
652            case 'x':
653                skip = 2
654            case 'u':
655                skip = 4
656            case 'U':
657                skip = 8
658            default:
659                return // error: invalid escape
660            }
661
662            for i := 0i < skipi++ {
663                next()
664            }
665        }
666    }
667}
668
MembersX
digraph.BlockStmt.RangeStmt_9030.node
errors
graph.sccs.rev
graph.allpaths.BlockStmt.BlockStmt.RangeStmt_6674.e
digraph
graph.allpaths.to
parse.BlockStmt.err
digraph.BlockStmt.gtrans
unicode
graph.sccs.g
graph.allpaths.g
graph.allpaths.from
graph.somepath.edge.to
digraph.BlockStmt.RangeStmt_9721.root
split.BlockStmt.BlockStmt.err
quotedLength.offset
nodeset.addAll.RangeStmt_4540.node
graph.reachableFrom.seen
graph.sccs.BlockStmt.BlockStmt.RangeStmt_6045.e
graph.allpaths.edges
strings
stdin
digraph.BlockStmt.RangeStmt_11839.from
split.current
nodelist.println
nodelist.println.sep
graph.reachableFrom.g
quotedLength.BlockStmt.BlockStmt.i
graph.reachableFrom
graph.reachableFrom.BlockStmt.BlockStmt.RangeStmt_5156.e
graph.somepath.from
digraph.BlockStmt.RangeStmt_12228.e
graph.addEdges.RangeStmt_4913.to
graph.transpose
graph.sccs.sccs
graph.somepath.BlockStmt.BlockStmt.RangeStmt_7636.e
quotedLength.BlockStmt.BlockStmt.skip
bytes
parse
digraph.BlockStmt.oldStdout
quotedLength.BlockStmt.r
nodeset.sort.RangeStmt_4415.node
digraph.BlockStmt.RangeStmt_9266.succs
graph.somepath.edge
graph.somepath.BlockStmt.BlockStmt.BlockStmt.RangeStmt_7538.e
graph.somepath.edge.from
parse.err
stdout
split
nodeset.sort.i
graph.transpose.g
graph.allpaths
graph.somepath
graph.sccs.scc
graph.somepath.seen
split.inWord
digraph.BlockStmt.RangeStmt_12018.BlockStmt.RangeStmt_12083.to
split.BlockStmt.r
graph.reachableFrom.roots
graph.somepath.g
digraph.BlockStmt.result
digraph.BlockStmt.RangeStmt_12018.from
graph.addEdges
graph.sccs.S
utf8
main.args
nodeset.addAll
nodeset.addAll.x
digraph.BlockStmt.RangeStmt_8957.node
digraph.BlockStmt.buf
digraph.BlockStmt.RangeStmt_11518.scc
graph
graph.sccs.RangeStmt_5803.node
parse.in
digraph.g
digraph.BlockStmt.edges
quotedLength
graph.transpose.rev
graph.transpose.RangeStmt_5322.edges
graph.sccs.BlockStmt.BlockStmt.RangeStmt_5729.e
digraph.BlockStmt.roots
graph.allpaths.RangeStmt_7133.e
parse.g
nodeset
nodeset.addAll.s
graph.addEdges.g
graph.sccs
flag
fmt
sort
strconv
digraph.BlockStmt.RangeStmt_11839.BlockStmt.RangeStmt_11899.to
graph.allpaths.seen
graph.allpaths.RangeStmt_6927.n
digraph.BlockStmt.RangeStmt_9467.e
digraph.BlockStmt.lines
main.err
digraph.BlockStmt.RangeStmt_9266.BlockStmt.RangeStmt_9310.succ
digraph.BlockStmt.g
split.BlockStmt.BlockStmt.s
graph.addNode.g
bufio
nodelist
nodelist.println.RangeStmt_4181.i
nodeset.sort.s
split.line
quotedLength.input
io
digraph.BlockStmt.edgesSorted
digraph.cmd
digraph.BlockStmt.RangeStmt_9266.node
nodelist.println.l
nodeset.sort.nodes
graph.addEdges.to
graph.somepath.to
parse.linenum
digraph.err
digraph.BlockStmt.nodes
quotedLength.ok
digraph.BlockStmt.RangeStmt_10041.root
digraph.BlockStmt.RangeStmt_11131.scc
split.BlockStmt.size
graph.transpose.RangeStmt_5322.BlockStmt.RangeStmt_5373.succ
graph.sccs.seen
graph.allpaths.RangeStmt_6927.BlockStmt.RangeStmt_6951.succ
parse.BlockStmt.words
digraph.BlockStmt.RangeStmt_8764.node
digraph.BlockStmt.err
split.words
split.BlockStmt.BlockStmt.ok
main
graph.addEdges.edges
digraph.BlockStmt.rev
parse.rd
usage
nodelist.println.RangeStmt_4181.node
graph.addNode
graph.transpose.RangeStmt_5322.node
os
nodeset.sort
digraph.BlockStmt.revEdges
quotedLength.BlockStmt.BlockStmt.size
quotedLength.n
graph.addNode.node
graph.addEdges.from
graph.reachableFrom.RangeStmt_5207.root
digraph.args
quotedLength.BlockStmt.BlockStmt.r
Members
X