OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package utils | |
6 | |
7 import ( | |
8 "bytes" | |
9 "fmt" | |
10 ) | |
11 | |
12 // This file contains an implementation of an algorithm to check the well-founde dness | |
13 // of two-sorted directed graphs. See the paper | |
14 // "Well-Founded Two-Sorted Directed Graphs" (https://goo.gl/ipxFKu) for backgro und | |
15 // and detailed definitions. Here we give only a high-level overview. | |
16 // | |
17 // A two-sorted graph is a directed graph that contains two sorts of nodes calle d circle nodes and | |
18 // square nodes. A node in a two-sorted graph is *well-founded* iff it satisfies the | |
19 // following recursive definition: | |
20 // (i) Leaf nodes are well-founded | |
21 // (ii) A circle node is well-founded iff all of its children are well-founded | |
22 // (iii) A non-leaf square node is well-founded iff at least one of its children is well-founded. | |
23 // (See the paper for a more logically correct definition.) | |
24 // | |
25 // See the comments on the main function |CheckWellFounded| below for a descript ion | |
26 // of the algorithm. | |
27 | |
28 /////////////////////////////////////////////////// | |
29 // Node type | |
30 // | |
31 // A |Node| is a node in a directed graph. | |
32 /////////////////////////////////////////////////// | |
33 | |
34 type Node interface { | |
35 // Returns the list of children of this node. | |
36 OutEdges() []Node | |
37 | |
38 // Returns whether or not this node is a square node. | |
39 IsSquare() bool | |
40 | |
41 // SetKnownWellFounded is invoked by the algorithm in order to set the | |
42 // fact that the algorithm has determined that this node is well-founded . | |
43 // An implementation of |Node| should cache this--even between different | |
44 // invocations of the algorithm on different starting nodes--and return | |
45 // |true| from the method |KnownWellFounded| just in case this method ha s | |
46 // ever been invoked. In this way any nodes verified to be well-founded during | |
47 // one run of the algorithm do not need to be checked during a later run of the | |
48 // algorithm on a different starting node. | |
49 SetKnownWellFounded() | |
50 | |
51 // Returns whether or not the function |SetKnownWellFounded| has ever | |
52 // been invoked on this node, during the lifetime of a program using | |
53 // this library. | |
54 KnownWellFounded() bool | |
55 | |
56 // Returns the name of this node, appropriate for debugging. | |
57 DebugName() string | |
58 } | |
59 | |
60 /////////////////////////////////////////////////// | |
61 // CheckWellFounded function | |
62 // | |
63 // This is the main public function. | |
64 /////////////////////////////////////////////////// | |
65 | |
66 // CheckWellFounded checks whether the sub-graph rooted at |root| contains any i ll-founded nodes. | |
67 // If any ill-founded nodes are detected then a non-nil |CycleDescription| is re turned containing | |
68 // a cycle of ill-founded nodes. If every node is well-founded then nil is retur ned. | |
69 // | |
70 // If the graph contains only circle nodes then well-foundedness is equivalent t o | |
71 // acyclicality and so the returned cycle is a proof that the nodes contained in it | |
72 // are ill-founded. But in general (if the graph contains square nodes) the retu rned | |
73 // |CycleDescription| is only meant to serve as an example of some of the | |
74 // ill-foundedness contained in the subgraph. The cycle is guaranteed to contain only | |
75 // ill-founded nodes and the cycle may be considered part of a proof that thes n odes | |
76 // are in fact ill-founded. But the cycle does not necessarily contain every | |
77 // ill-founded node and it does not | |
78 // necessarily constitute a complete proof of the ill-foundedness | |
79 // because a graph that contains square nodes is allowed to contain some cycles and still be | |
80 // well-founded. The intended application of the returned CycleDescription is to be used to | |
81 // describe to a user the location of the ill-foundedness in order to allow the user to modify | |
82 // the graph in order to make it well-founded. | |
83 // | |
84 // This function may be invoked multiple times on different starting nodes durin g the life | |
85 // of a program. If the function is invoked once on node |x| and no ill-foundedn ess is | |
86 // found and then the function is invoked later on node |y| and if node |z| is r eachable | |
87 // from both |x| and |y| then node |z| will be marked |KnownWellFounded| during the | |
88 // first run of the function and so the graph below |z| will not have to be insp ected | |
89 // during the second run of the function. | |
90 // | |
91 // Our algorithm proceeds in three phases: | |
92 // (1) Phase 1 consists of a depth-first traversal of the graph whose purpose is two-fold: | |
93 // (a) To prove directly that as many nodes as possible are well-founded and mark them | |
94 // so by invoking SetKnownWellFounded(). | |
95 // (b) To prepare for phase 2 by constructing two sets of nodes called the | foundationSet| and | |
96 // the |pendingSet| | |
97 // See the comments at |checkWellFoundedPhase1| for more details. | |
98 // | |
99 // In many cases phase 1 will be able to prove that every node is well-founded a nd so the algorithm | |
100 // will terminate without entering phase 2. This is the case for example if the graph has no | |
101 // cycles at all. | |
102 // | |
103 // (2) The purpose of phase 2 is to propogate the |KnownWellFounded| property to the remaining well-founded | |
104 // nodes (the ones that could not be verified as well-founded during phase 1 .) Phase 2 proceeds in | |
105 // multiple rounds. During each round the |KnownWellFounded| property is pro pogated from the | |
106 // |foundationSet| to the |pendingSet|. See the comments at |checkWellFounde dPhase2| for more details. | |
107 // | |
108 // If there are no ill-founded nodes then the algorithm terminates after phase 2. | |
109 // | |
110 // (3) Phase 3 of the algorithm consists of building a |CycleDescription| in the case that there are ill-founded | |
111 // nodes. See the method |findKnownCycle|. | |
112 | |
113 func CheckWellFounded(root Node) *CycleDescription { | |
114 return checkWellFounded(root, nil) | |
115 } | |
116 | |
117 // checkWellFounded is a package-private version of CheckWellFounded intendend t o be invoked by tests. | |
118 // It offers a second parameter |debugDataRequest|. If this is non-nil then its fields will be filled | |
119 // in with debugging data about the output of phase 1. | |
120 func checkWellFounded(root Node, debugDataRequest *debugData) *CycleDescription { | |
121 if root == nil { | |
122 return nil | |
123 } | |
124 finder := makeCycleFinder() | |
125 finder.debugDataRequest = debugDataRequest | |
126 holder := finder.holderForNode(root) | |
127 return finder.checkWellFounded(holder) | |
128 } | |
129 | |
130 /////////////////////////////////////////////////// | |
131 /// cycleFinder type | |
132 /// | |
133 /// A cycleFinder is an object that holds state during the execution of the algo rithm. | |
134 /////////////////////////////////////////////////// | |
135 type cycleFinder struct { | |
136 | |
137 // Maps each node seen by the computation to its holder. | |
138 nodeToHolder map[Node]*nodeHolder | |
139 | |
140 foundationSet, pendingSet NodeSet | |
141 | |
142 visitationIndex int | |
143 currentPath nodeStack | |
144 | |
145 debugDataRequest *debugData | |
146 } | |
147 | |
148 type debugData struct { | |
149 initialPendingSet []Node | |
150 initialFoundationSet []Node | |
151 } | |
152 | |
153 func makeCycleFinder() cycleFinder { | |
154 finder := cycleFinder{} | |
155 finder.nodeToHolder = make(map[Node]*nodeHolder) | |
156 finder.foundationSet = MakeNodeSet() | |
157 finder.pendingSet = MakeNodeSet() | |
158 finder.currentPath = nodeStack{} | |
159 return finder | |
160 } | |
161 | |
162 // checkWellFounded contains the top-level implementation of the algorithm. | |
163 func (finder *cycleFinder) checkWellFounded(nodeHolder *nodeHolder) *CycleDescri ption { | |
164 if nodeHolder.node.KnownWellFounded() { | |
165 // This node is already known to be well-founded because of an e arlier | |
166 // execution of the algorithm. | |
167 return nil | |
168 } | |
169 | |
170 // Perform phase 1. | |
171 finder.checkWellFoundedPhase1(nodeHolder) | |
172 | |
173 // In tests we pass back some debugging information here. | |
174 if finder.debugDataRequest != nil { | |
175 finder.debugDataRequest.initialPendingSet = finder.pendingSet.To NodeSlice() | |
176 finder.debugDataRequest.initialFoundationSet = finder.foundation Set.ToNodeSlice() | |
177 } | |
178 | |
179 // All nodes have been verified as well-founded. | |
180 if finder.pendingSet.Empty() { | |
181 return nil | |
182 } | |
183 | |
184 // Perform phase 2. | |
185 finder.checkWellFoundedPhase2(nodeHolder) | |
186 | |
187 // All nodes have been verified as well-founded. | |
188 if finder.pendingSet.Empty() { | |
189 return nil | |
190 } | |
191 | |
192 // If we are here then there is at least one ill-founded node. | |
193 | |
194 // In order to build a canonical cycle description, find the illfounded | |
195 // node with the least visitation order. | |
196 var minVisitationOrder int | |
197 var minIllfoundedNode Node = nil | |
198 for n, _ := range finder.pendingSet.elements { | |
199 if minIllfoundedNode == nil || n.visitationOrder < minVisitation Order { | |
200 minVisitationOrder = n.visitationOrder | |
201 minIllfoundedNode = n.node | |
202 } | |
203 } | |
204 | |
205 // Starting from the ill-founded node with the least visitation order, | |
206 // build a canonical cycle. | |
207 freshCycleFinder := makeCycleFinder() | |
208 holder := freshCycleFinder.holderForNode(minIllfoundedNode) | |
209 return freshCycleFinder.findKnownCycle(holder) | |
210 } | |
211 | |
212 // checkWellFoundedPhase1 is a recursive helper function that does a depth-first traversal | |
213 // of the graph rooted at |nodeHolder|. The goal of phase 1 is to mark as many | |
214 // of the nodes as possible as |KnownWellFounded| and to set up the |pendingSet| , | |
215 // the |foundationSet|, and the |parentsToBeNotified| sets so that the remaining | |
216 // well-founded nodes will be marked as |KnownWellFounded| in phase 2. | |
217 // | |
218 // In more detail the following steps are performed for each node x: | |
219 // (a1) If it can be verified during the traversal that x is well-founded then | |
220 // x will be marked as |KnownWellFounded|. This occurs if x is a leaf, or x | |
221 // is a circle and each child node of x is known well-founded before being | |
222 // visited as a child of x, or x is a square and at-least one child node of x | |
223 // is known well-founded before being visited as a child of x. | |
224 // (a2) Otherwise if it cannot be determined during traveral that x is well-foun ded then | |
225 // (i) x is added to the |pendingSet|. | |
226 // (ii) x is added to the |parentsToBeNotified| set of all of its children. | |
227 // (b) In step (a1) if at the time x is found to be well-founded x already has | |
228 // some parent node x' in its |parentsToBeNotified| set (meaning that step a2 oc curred | |
229 // earlier for x' and so x' is in the |pendingSet|) then x is added to the |foun dationSet|. | |
230 // In phase 2, the fact that x is in the foundation set and x' is in the pending set will be | |
231 // used to propogate known-wellfoundedness to x'. | |
232 func (finder *cycleFinder) checkWellFoundedPhase1(nodeHolder *nodeHolder) { | |
233 if nodeHolder.node.KnownWellFounded() { | |
234 // This node is known to be well-founded before being visited. | |
235 // This occurs when the node was marked |KnownWellFounded| durin g a | |
236 // previous run of the algorithm. It follows that all nodes reac hable | |
237 // from this node have also been so marked. We therefore don't n eed | |
238 // to traverse the part of the graph below this node during this run | |
239 // of the algorithm and so we treat this node as a leaf node. | |
240 nodeHolder.state = vsVisitEnded | |
241 return | |
242 } | |
243 | |
244 // Mark the visit as started. | |
245 nodeHolder.state = vsVisitStarted | |
246 | |
247 // Next we examine each of the children and recurse into the unvisited o nes. | |
248 sawUnverifiedChild := false | |
249 for _, child := range nodeHolder.node.OutEdges() { | |
250 childHolder := finder.holderForNode(child) | |
251 if childHolder.state == vsUnvisited { | |
252 // Recursively visit this child. | |
253 finder.checkWellFoundedPhase1(childHolder) | |
254 } | |
255 | |
256 // After having visited a child we use the results to update the status of this node. | |
257 // We could express the logic here more concisely, but the logi c is easier | |
258 // to understand if we treat circles and squares seperately, | |
259 if nodeHolder.node.IsSquare() { | |
260 if nodeHolder.node.KnownWellFounded() { | |
261 // This square node has already been marked |Kno wnWellFounded| becuase | |
262 // of an earlier iteration through this loop. Th ere is nothing else to do. | |
263 continue | |
264 } | |
265 if childHolder.node.KnownWellFounded() { | |
266 // We mark a square node as |KnownWellFounded| a s soon as we can so | |
267 // that if any of its descendants are also paren ts, the well-foundedness | |
268 // has a chance to propogate to the descendant i n a recursive call. | |
269 nodeHolder.node.SetKnownWellFounded() | |
270 } else { | |
271 // This square node is not yet known to be well- founded and the child node | |
272 // is not yet known to be well-founded. Set up a back link from the child. | |
273 childHolder.parentsToBeNotified.Add(nodeHolder) | |
274 sawUnverifiedChild = true | |
275 } | |
276 continue // Done handling the square case. | |
277 } | |
278 | |
279 // Else the node is a circle. If the child is not yet known to b e well-founded | |
280 // set up a back link from the child to this node. | |
281 if !childHolder.node.KnownWellFounded() { | |
282 childHolder.parentsToBeNotified.Add(nodeHolder) | |
283 sawUnverifiedChild = true | |
284 } | |
285 } | |
286 | |
287 // If a circle node has only well-founded children, or a square node has no children at all, | |
288 // then the node is well-founded. | |
289 if !sawUnverifiedChild && !nodeHolder.node.KnownWellFounded() { | |
290 nodeHolder.node.SetKnownWellFounded() | |
291 } | |
292 | |
293 // Possibly add this node to the |foundationSet| or the |pendingSet|. | |
294 if nodeHolder.node.KnownWellFounded() { | |
295 if !nodeHolder.parentsToBeNotified.Empty() { | |
296 finder.foundationSet.Add(nodeHolder) | |
297 } | |
298 } else { | |
299 finder.pendingSet.Add(nodeHolder) | |
300 } | |
301 | |
302 // Mark the visit as ended. | |
303 nodeHolder.state = vsVisitEnded | |
304 return | |
305 } | |
306 | |
307 // checkWellFoundedPhase2 performs phase 2 of the algorithm. The goal is to | |
308 // propogate known well-foundedness along the back-links that were established | |
309 // during phase 1. We have two sets of nodes: the |foundationSet| and the | |
310 // |pendingSet|. The |pendingSet| consists of all nodes that are not currently | |
311 // known to be well-founded. If the |pendingSet| is not empty when this method | |
312 // returns, then the nodes in the |pendingSet| are ill-founded. The algorithm | |
313 // proceeds in rounds. The |foundationSet| consists of the current frontier of | |
314 // the propogation. That is, the |foundationSet| consists of the nodes discovere d | |
315 // to be well-founded in the previous round. (It starts with the nodes discovere d | |
316 // to be well-founded during phase 1, pruned to nodes that have parents that | |
317 // are in the |pendingSet|.) During each round we propogate known well-foundedne ss | |
318 // from the nodes in the |foundationSet| to their parents in the |pendingSet| | |
319 // that can now be verified as known well-foudned. | |
320 func (finder *cycleFinder) checkWellFoundedPhase2(nodeHolder *nodeHolder) { | |
321 for !finder.foundationSet.Empty() { | |
322 nextFoundationSet := MakeNodeSet() | |
azani
2016/04/22 18:37:14
Alternatively, you could add a Pop() method on Nod
rudominer
2016/04/23 01:22:30
Done.
| |
323 for n, _ := range finder.foundationSet.elements { | |
324 for p, _ := range n.parentsToBeNotified.elements { | |
325 if finder.pendingSet.Contains(p) { | |
326 knownWellFounded := true | |
327 if !p.node.IsSquare() { | |
328 for _, child := range p.node.Out Edges() { | |
329 if child != p.node && !c hild.KnownWellFounded() { | |
330 knownWellFounded = false | |
331 break | |
332 } | |
333 } | |
334 } | |
335 if knownWellFounded { | |
336 p.node.SetKnownWellFounded() | |
337 nextFoundationSet.Add(p) | |
338 finder.pendingSet.Remove(p) | |
339 } | |
340 } | |
341 } | |
342 } | |
343 finder.foundationSet = nextFoundationSet | |
344 } | |
345 } | |
346 | |
347 // findKnownCycle finds and returns a |CycleDescription| starting from a node th at is known | |
348 // to be ill-founded. This proceeds by following edges from an ill-founded node to | |
349 // an ill-founded child node until a cycle is formed. We return a *canonical* cy cle, | |
350 // meaning we start from the least possible node and follow edges to the least p ossible | |
azani
2016/04/22 18:37:14
"least possible node" is ambiguous.
Also, I wonde
rudominer
2016/04/23 01:22:30
Done
| |
351 // child node. This is done in order to make testing of the algorithm easier. | |
352 // We are not concerned with optimizing the performance of phase 3 because in th e intended application | |
353 // phase 3 can occur at most once in the lifetime of a program: Once an ill-foun ded node is detected the | |
354 // program exits with a cycle descritpion allowing the user to fix the ill-found edness. | |
azani
2016/04/22 18:37:14
s/descritpion/description
rudominer
2016/04/23 01:22:30
Done.
| |
355 func (finder *cycleFinder) findKnownCycle(nodeHolder *nodeHolder) *CycleDescript ion { | |
azani
2016/04/22 18:37:14
Maybe rename findKnownIllFoundedCycle.
rudominer
2016/04/23 01:22:30
Done.
| |
356 // Mark the current node as started | |
357 nodeHolder.state = vsVisitStarted | |
358 finder.currentPath.Push(nodeHolder) | |
359 for _, child := range nodeHolder.node.OutEdges() { | |
360 childHolder := finder.holderForNode(child) | |
361 if childHolder.state == vsVisitStarted { | |
362 // If the child has been started but not finished then w e have found a cycle | |
363 // from the child to the current node back to the child. | |
364 return newCycleDescription(finder.currentPath.elements, childHolder, nodeHolder) | |
365 } else if !childHolder.node.KnownWellFounded() { | |
366 return finder.findKnownCycle(childHolder) | |
367 } | |
368 } | |
369 panic("Should not get here.") | |
azani
2016/04/22 18:37:14
Maybe:
"Could not find a known ill-founded cycle.
rudominer
2016/04/23 01:22:30
Done.
| |
370 } | |
371 | |
372 // Returns the nodeHolder for the given node. | |
373 func (finder *cycleFinder) holderForNode(node Node) *nodeHolder { | |
374 if holder, found := finder.nodeToHolder[node]; found { | |
375 return holder | |
376 } | |
377 | |
378 // This is the first time we have seen this node. Assign it a new | |
379 // visitor order. | |
380 holder := newNodeHolder(node, finder.visitationIndex) | |
381 finder.visitationIndex++ | |
382 finder.nodeToHolder[node] = holder | |
383 return holder | |
384 } | |
385 | |
386 //////////////////////////////////////////////////// | |
387 // nodeHolder type | |
388 //////////////////////////////////////////////////// | |
389 | |
390 type visitationState int | |
391 | |
392 const ( | |
393 vsUnvisited visitationState = iota | |
394 vsVisitStarted | |
395 vsVisitEnded | |
396 ) | |
397 | |
398 // A nodeHolder is an internal data structure used by the algorithm. | |
399 // It holds one node plus data about that node used by the algorithm. | |
400 type nodeHolder struct { | |
401 // The node | |
402 node Node | |
403 | |
404 parentsToBeNotified NodeSet | |
405 | |
406 visitationOrder int | |
407 | |
408 state visitationState | |
409 } | |
410 | |
411 func newNodeHolder(node Node, visitationOrder int) *nodeHolder { | |
412 nodeHolder := new(nodeHolder) | |
413 nodeHolder.node = node | |
414 nodeHolder.parentsToBeNotified = MakeNodeSet() | |
415 nodeHolder.state = vsUnvisited | |
416 nodeHolder.visitationOrder = visitationOrder | |
417 return nodeHolder | |
418 } | |
419 | |
420 ////////////////////////////////////////////// | |
421 /// nodeStack type | |
422 ////////////////////////////////////////////// | |
423 | |
424 // A nodeStack is a stack of *nodeHolders | |
425 type nodeStack struct { | |
426 elements []*nodeHolder | |
427 } | |
428 | |
429 func (stack *nodeStack) Push(n *nodeHolder) { | |
430 stack.elements = append(stack.elements, n) | |
431 } | |
432 | |
433 func (stack *nodeStack) Size() int { | |
434 return len(stack.elements) | |
435 } | |
436 | |
437 func (stack *nodeStack) Pop() (n *nodeHolder) { | |
438 lastIndex := stack.Size() - 1 | |
439 n = stack.elements[lastIndex] | |
440 stack.elements = stack.elements[:lastIndex] | |
441 return | |
442 } | |
443 | |
444 func (stack *nodeStack) Peek() (n *nodeHolder) { | |
445 return stack.elements[stack.Size()-1] | |
446 } | |
447 | |
448 func (stack *nodeStack) String() string { | |
449 var buffer bytes.Buffer | |
450 fmt.Fprintf(&buffer, "[") | |
451 first := true | |
452 for _, e := range stack.elements { | |
453 if !first { | |
454 fmt.Fprintf(&buffer, ", ") | |
455 } | |
456 fmt.Fprintf(&buffer, "%s", e.node.DebugName()) | |
457 first = false | |
458 } | |
459 fmt.Fprintln(&buffer, "]") | |
460 return buffer.String() | |
461 } | |
462 | |
463 /////////////////////////////////////////////////// | |
464 /// NodeSet type | |
465 /////////////////////////////////////////////////// | |
466 | |
467 // A NodeSet is a set of nodeHolders. | |
468 type NodeSet struct { | |
469 elements map[*nodeHolder]bool | |
470 } | |
471 | |
472 // MakeNodeSet makes a new empty NodeSet. | |
473 func MakeNodeSet() NodeSet { | |
474 nodeSet := NodeSet{} | |
475 nodeSet.elements = make(map[*nodeHolder]bool) | |
476 return nodeSet | |
477 } | |
478 | |
479 // Add adds a Node to a NodeSet. | |
480 func (set *NodeSet) Add(node *nodeHolder) { | |
481 set.elements[node] = true | |
482 } | |
483 | |
484 // AddAll adds all the nodes from |otherSet| to |set|. | |
485 func (set *NodeSet) AddAll(otherSet NodeSet) { | |
486 for e, _ := range otherSet.elements { | |
487 set.elements[e] = true | |
488 } | |
489 } | |
490 | |
491 // Contains returns whether or not |node| is an element of |set|. | |
492 func (set *NodeSet) Contains(node *nodeHolder) bool { | |
493 _, ok := set.elements[node] | |
494 return ok | |
495 } | |
496 | |
497 func (set *NodeSet) Remove(node *nodeHolder) { | |
498 delete(set.elements, node) | |
499 } | |
500 | |
501 func (set *NodeSet) Empty() bool { | |
502 return len(set.elements) == 0 | |
503 } | |
504 | |
505 func (set *NodeSet) ToNodeSlice() []Node { | |
506 slice := make([]Node, 0, len(set.elements)) | |
507 for n, _ := range set.elements { | |
508 slice = append(slice, n.node) | |
509 } | |
510 return slice | |
511 } | |
512 | |
513 func (set *NodeSet) Size() int { | |
514 return len(set.elements) | |
515 } | |
516 | |
517 // compareNodeSets is a package-private method used in our tests. It returns | |
518 // a non-nil error in case expected is not equal to actual. | |
519 func compareNodeSets(expected, actual *NodeSet) error { | |
520 for n, _ := range expected.elements { | |
521 if !actual.Contains(n) { | |
522 return fmt.Errorf("%s is in expected but not actual", n. node.DebugName()) | |
523 } | |
524 } | |
525 for n, _ := range actual.elements { | |
526 if !expected.Contains(n) { | |
527 return fmt.Errorf("%s is in actual but not expected", n. node.DebugName()) | |
528 } | |
529 } | |
530 return nil | |
531 } | |
532 | |
533 // String returns a human readable string representation of |set|. | |
534 func (set *NodeSet) String() string { | |
535 var buffer bytes.Buffer | |
536 fmt.Fprintf(&buffer, "{") | |
537 first := true | |
538 for e, _ := range set.elements { | |
539 if !first { | |
540 fmt.Fprintf(&buffer, ", ") | |
541 } | |
542 fmt.Fprintf(&buffer, "%s", e.node.DebugName()) | |
543 first = false | |
544 } | |
545 fmt.Fprintln(&buffer, "}") | |
546 return buffer.String() | |
547 } | |
548 | |
549 /////////////////////////////////////////////////// | |
550 // CycleDescription type | |
551 // | |
552 // A |CycleDescription| describes a cycle in a directed graph. | |
553 /////////////////////////////////////////////////// | |
554 | |
555 type CycleDescription struct { | |
556 first, last Node | |
557 path []Node | |
558 } | |
559 | |
560 func (c *CycleDescription) String() string { | |
561 var buffer bytes.Buffer | |
562 fmt.Fprintf(&buffer, "first:%s", c.first.DebugName()) | |
563 fmt.Fprintf(&buffer, ", last:%s", c.last.DebugName()) | |
564 fmt.Fprintf(&buffer, ", path:{") | |
565 first := true | |
566 for _, n := range c.path { | |
567 if !first { | |
568 fmt.Fprintf(&buffer, ", ") | |
569 } | |
570 fmt.Fprintf(&buffer, "%s", n.DebugName()) | |
571 first = false | |
572 } | |
573 fmt.Fprintln(&buffer, "}") | |
574 return buffer.String() | |
575 } | |
576 | |
577 func newCycleDescription(path []*nodeHolder, first, last *nodeHolder) *CycleDesc ription { | |
578 description := CycleDescription{} | |
579 description.first = first.node | |
580 description.last = last.node | |
581 description.path = make([]Node, 0, len(path)) | |
582 for _, n := range path { | |
583 if len(description.path) > 0 || n.node == first.node { | |
584 description.path = append(description.path, n.node) | |
585 } | |
586 } | |
587 if description.path[len(description.path)-1] != last.node { | |
588 panic(fmt.Sprintf("%s != %s", description.path[len(description.p ath)-1], last.node)) | |
589 } | |
590 return &description | |
591 } | |
OLD | NEW |