OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package utils | 5 package utils |
6 | 6 |
7 import ( | 7 import ( |
8 "bytes" | 8 "bytes" |
9 "fmt" | 9 "fmt" |
10 ) | 10 ) |
(...skipping 10 matching lines...) Loading... |
21 // (ii) A circle node is well-founded iff all of its children 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. | 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.) | 23 // (See the paper for a more logically correct definition.) |
24 // | 24 // |
25 // See the comments on the main function |CheckWellFounded| below for a descript
ion | 25 // See the comments on the main function |CheckWellFounded| below for a descript
ion |
26 // of the algorithm. | 26 // of the algorithm. |
27 | 27 |
28 /////////////////////////////////////////////////// | 28 /////////////////////////////////////////////////// |
29 // Node type | 29 // Node type |
30 // | 30 // |
31 // A |Node| is a node in a directed graph. | 31 // A |Node| is a node in a directed graph. A user of this library is responsible |
| 32 // for implementing this interface. This library will use the Go equality operat
or |
| 33 // to compare instances of |Node| and it is the responsibility of the user |
| 34 // to ensure that x == y iff x and y represent the same logical node. |
32 /////////////////////////////////////////////////// | 35 /////////////////////////////////////////////////// |
33 | 36 |
34 type Node interface { | 37 type Node interface { |
35 // Returns the list of children of this node. | 38 // Returns the list of children of this node. |
36 » OutEdges() []Node | 39 » OutEdges() []OutEdge |
37 | 40 |
38 // Returns whether or not this node is a square node. | 41 // Returns whether or not this node is a square node. |
39 IsSquare() bool | 42 IsSquare() bool |
40 | 43 |
41 // SetKnownWellFounded is invoked by the algorithm in order to set the | 44 // SetKnownWellFounded is invoked by the algorithm in order to set the |
42 // fact that the algorithm has determined that this node is well-founded
. | 45 // fact that the algorithm has determined that this node is well-founded
. |
43 // An implementation of |Node| should cache this--even between different | 46 // An implementation of |Node| should cache this--even between different |
44 // invocations of the algorithm on different starting nodes--and return | 47 // invocations of the algorithm on different starting nodes--and return |
45 // |true| from the method |KnownWellFounded| just in case this method ha
s | 48 // |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 | 49 // 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 | 50 // one run of the algorithm do not need to be checked during a later run
of the |
48 // algorithm on a different starting node. | 51 // algorithm on a different starting node. |
49 SetKnownWellFounded() | 52 SetKnownWellFounded() |
50 | 53 |
51 // Returns whether or not the function |SetKnownWellFounded| has ever | 54 // Returns whether or not the function |SetKnownWellFounded| has ever |
52 // been invoked on this node, during the lifetime of a program using | 55 // been invoked on this node, during the lifetime of a program using |
53 // this library. | 56 // this library. |
54 KnownWellFounded() bool | 57 KnownWellFounded() bool |
55 | 58 |
56 » // Returns the name of this node, appropriate for debugging. | 59 » // Returns a human-readable name for this node. Not used by the algorith
m |
57 » DebugName() string | 60 » // but used for generating debugging and error messages. |
| 61 » Name() string |
| 62 } |
| 63 |
| 64 type OutEdge struct { |
| 65 » // The Label on an edge is opaque data that may be used by the client ho
wever it wishes. |
| 66 » // The algorithm never inspects this data. |
| 67 » Label interface{} |
| 68 |
| 69 » // The target node of the out edge. |
| 70 » Target Node |
58 } | 71 } |
59 | 72 |
60 /////////////////////////////////////////////////// | 73 /////////////////////////////////////////////////// |
61 // CheckWellFounded function | 74 // CheckWellFounded function |
62 // | 75 // |
63 // This is the main public function. | 76 // This is the main public function. |
64 /////////////////////////////////////////////////// | 77 /////////////////////////////////////////////////// |
65 | 78 |
66 // CheckWellFounded checks whether the sub-graph rooted at |root| contains any i
ll-founded nodes. | 79 // 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 | 80 // If any ill-founded nodes are detected then a non-nil |CycleDescription| is re
turned containing |
(...skipping 59 matching lines...) Loading... |
127 return finder.checkWellFounded(holder) | 140 return finder.checkWellFounded(holder) |
128 } | 141 } |
129 | 142 |
130 /////////////////////////////////////////////////// | 143 /////////////////////////////////////////////////// |
131 /// cycleFinder type | 144 /// cycleFinder type |
132 /// | 145 /// |
133 /// A cycleFinder is an object that holds state during the execution of the algo
rithm. | 146 /// A cycleFinder is an object that holds state during the execution of the algo
rithm. |
134 /////////////////////////////////////////////////// | 147 /////////////////////////////////////////////////// |
135 type cycleFinder struct { | 148 type cycleFinder struct { |
136 | 149 |
137 » // Maps each node seen by the computation to its holder. | 150 » // Maps each node seen by the computation to its holder. Note that this
assumes |
| 151 » // that the implementation of Node uses values that are comparable and |
| 152 » // obey identity semantics. |
138 nodeToHolder map[Node]*nodeHolder | 153 nodeToHolder map[Node]*nodeHolder |
139 | 154 |
140 foundationSet, pendingSet NodeSet | 155 foundationSet, pendingSet NodeSet |
141 | 156 |
142 visitationIndex int | 157 visitationIndex int |
143 currentPath nodeStack | |
144 | 158 |
145 debugDataRequest *debugData | 159 debugDataRequest *debugData |
146 } | 160 } |
147 | 161 |
148 type debugData struct { | 162 type debugData struct { |
149 initialPendingSet []Node | 163 initialPendingSet []Node |
150 initialFoundationSet []Node | 164 initialFoundationSet []Node |
151 } | 165 } |
152 | 166 |
153 func makeCycleFinder() cycleFinder { | 167 func makeCycleFinder() cycleFinder { |
154 finder := cycleFinder{} | 168 finder := cycleFinder{} |
155 finder.nodeToHolder = make(map[Node]*nodeHolder) | 169 finder.nodeToHolder = make(map[Node]*nodeHolder) |
156 finder.foundationSet = MakeNodeSet() | 170 finder.foundationSet = MakeNodeSet() |
157 finder.pendingSet = MakeNodeSet() | 171 finder.pendingSet = MakeNodeSet() |
158 finder.currentPath = nodeStack{} | |
159 return finder | 172 return finder |
160 } | 173 } |
161 | 174 |
162 // checkWellFounded contains the top-level implementation of the algorithm. | 175 // checkWellFounded contains the top-level implementation of the algorithm. |
163 func (finder *cycleFinder) checkWellFounded(nodeHolder *nodeHolder) *CycleDescri
ption { | 176 func (finder *cycleFinder) checkWellFounded(nodeHolder *nodeHolder) *CycleDescri
ption { |
164 if nodeHolder.node.KnownWellFounded() { | 177 if nodeHolder.node.KnownWellFounded() { |
165 // This node is already known to be well-founded because of an e
arlier | 178 // This node is already known to be well-founded because of an e
arlier |
166 // execution of the algorithm. | 179 // execution of the algorithm. |
167 return nil | 180 return nil |
168 } | 181 } |
(...skipping 30 matching lines...) Loading... |
199 if minIllfoundedNode == nil || n.visitationOrder < minVisitation
Order { | 212 if minIllfoundedNode == nil || n.visitationOrder < minVisitation
Order { |
200 minVisitationOrder = n.visitationOrder | 213 minVisitationOrder = n.visitationOrder |
201 minIllfoundedNode = n.node | 214 minIllfoundedNode = n.node |
202 } | 215 } |
203 } | 216 } |
204 | 217 |
205 // Starting from the ill-founded node with the least visitation order, | 218 // Starting from the ill-founded node with the least visitation order, |
206 // build a canonical cycle. | 219 // build a canonical cycle. |
207 freshCycleFinder := makeCycleFinder() | 220 freshCycleFinder := makeCycleFinder() |
208 holder := freshCycleFinder.holderForNode(minIllfoundedNode) | 221 holder := freshCycleFinder.holderForNode(minIllfoundedNode) |
209 » return freshCycleFinder.findKnownIllFoundedCycle(holder) | 222 » return freshCycleFinder.findKnownIllFoundedCycle(holder, nil) |
210 } | 223 } |
211 | 224 |
212 // checkWellFoundedPhase1 is a recursive helper function that does a depth-first
traversal | 225 // 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 | 226 // 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|
, | 227 // of the nodes as possible as |KnownWellFounded| and to set up the |pendingSet|
, |
215 // the |foundationSet|, and the |parentsToBeNotified| sets so that the remaining | 228 // the |foundationSet|, and the |parentsToBeNotified| sets so that the remaining |
216 // well-founded nodes will be marked as |KnownWellFounded| in phase 2. | 229 // well-founded nodes will be marked as |KnownWellFounded| in phase 2. |
217 // | 230 // |
218 // In more detail the following steps are performed for each node x: | 231 // 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 | 232 // (a1) If it can be verified during the traversal that x is well-founded then |
(...skipping 19 matching lines...) Loading... |
239 // of the algorithm and so we treat this node as a leaf node. | 252 // of the algorithm and so we treat this node as a leaf node. |
240 nodeHolder.state = vsVisitEnded | 253 nodeHolder.state = vsVisitEnded |
241 return | 254 return |
242 } | 255 } |
243 | 256 |
244 // Mark the visit as started. | 257 // Mark the visit as started. |
245 nodeHolder.state = vsVisitStarted | 258 nodeHolder.state = vsVisitStarted |
246 | 259 |
247 // Next we examine each of the children and recurse into the unvisited o
nes. | 260 // Next we examine each of the children and recurse into the unvisited o
nes. |
248 sawUnverifiedChild := false | 261 sawUnverifiedChild := false |
249 » for _, child := range nodeHolder.node.OutEdges() { | 262 » for _, edge := range nodeHolder.node.OutEdges() { |
250 » » childHolder := finder.holderForNode(child) | 263 » » childHolder := finder.holderForNode(edge.Target) |
251 if childHolder.state == vsUnvisited { | 264 if childHolder.state == vsUnvisited { |
252 // Recursively visit this child. | 265 // Recursively visit this child. |
253 finder.checkWellFoundedPhase1(childHolder) | 266 finder.checkWellFoundedPhase1(childHolder) |
254 } | 267 } |
255 | 268 |
256 // After having visited a child we use the results to update the
status of this node. | 269 // 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 | 270 // We could express the logic here more concisely, but the logi
c is easier |
258 // to understand if we treat circles and squares seperately, | 271 // to understand if we treat circles and squares seperately, |
259 if nodeHolder.node.IsSquare() { | 272 if nodeHolder.node.IsSquare() { |
260 if nodeHolder.node.KnownWellFounded() { | 273 if nodeHolder.node.KnownWellFounded() { |
(...skipping 55 matching lines...) Loading... |
316 // to be well-founded during phase 1, pruned to nodes that have parents that | 329 // to be well-founded during phase 1, pruned to nodes that have parents that |
317 // are in the |pendingSet|.) We iteratively remove a node n from the foundation
set and | 330 // are in the |pendingSet|.) We iteratively remove a node n from the foundation
set and |
318 // for each of its parents p for which p is in the pending set and for which we
can now verify | 331 // for each of its parents p for which p is in the pending set and for which we
can now verify |
319 // well-foundedness, we remove p from the pending set and add it to the foundati
on set. | 332 // well-foundedness, we remove p from the pending set and add it to the foundati
on set. |
320 func (finder *cycleFinder) checkWellFoundedPhase2() { | 333 func (finder *cycleFinder) checkWellFoundedPhase2() { |
321 for n := finder.foundationSet.removeRandomElement(); n != nil; n = finde
r.foundationSet.removeRandomElement() { | 334 for n := finder.foundationSet.removeRandomElement(); n != nil; n = finde
r.foundationSet.removeRandomElement() { |
322 for p, _ := range n.parentsToBeNotified.elements { | 335 for p, _ := range n.parentsToBeNotified.elements { |
323 if finder.pendingSet.Contains(p) { | 336 if finder.pendingSet.Contains(p) { |
324 knownWellFounded := true | 337 knownWellFounded := true |
325 if !p.node.IsSquare() { | 338 if !p.node.IsSquare() { |
326 » » » » » for _, child := range p.node.OutEdges()
{ | 339 » » » » » for _, edge := range p.node.OutEdges() { |
| 340 » » » » » » child := edge.Target |
327 if child != p.node && !child.Kno
wnWellFounded() { | 341 if child != p.node && !child.Kno
wnWellFounded() { |
328 knownWellFounded = false | 342 knownWellFounded = false |
329 break | 343 break |
330 } | 344 } |
331 } | 345 } |
332 } | 346 } |
333 if knownWellFounded { | 347 if knownWellFounded { |
334 p.node.SetKnownWellFounded() | 348 p.node.SetKnownWellFounded() |
335 finder.foundationSet.Add(p) | 349 finder.foundationSet.Add(p) |
336 finder.pendingSet.Remove(p) | 350 finder.pendingSet.Remove(p) |
337 } | 351 } |
338 } | 352 } |
339 } | 353 } |
340 } | 354 } |
341 } | 355 } |
342 | 356 |
| 357 // A pathElement is a node and one of its out-edges. |
| 358 type pathElement struct { |
| 359 node Node |
| 360 edge OutEdge |
| 361 } |
| 362 |
| 363 type nodePath []pathElement |
| 364 |
343 // findKnownIllFoundedCycle finds and returns a |CycleDescription| starting from
a node that is known | 365 // findKnownIllFoundedCycle finds and returns a |CycleDescription| starting from
a node that is known |
344 // to be ill-founded. This proceeds by following edges from an ill-founded node
to | 366 // to be ill-founded. This proceeds by following edges from an ill-founded node
to |
345 // an ill-founded child node until a cycle is formed. We return a *canonical* cy
cle, | 367 // an ill-founded child node until a cycle is formed. We return a *canonical* cy
cle, |
346 // meaning we start from the node with the least possible visit index and follow
edges to | 368 // meaning we start from the node with the least possible visit index and follow
edges to |
347 // the child node with the least possible visit index. This is done in order to
make testing of the algorithm easier. | 369 // the child node with the least possible visit index. This is done in order to
make testing of the algorithm easier. |
348 // We are not concerned with optimizing the performance of phase 3 because in th
e intended application | 370 // We are not concerned with optimizing the performance of phase 3 because in th
e intended application |
349 // phase 3 can occur at most once in the lifetime of a program: Once an ill-foun
ded node is detected the | 371 // phase 3 can occur at most once in the lifetime of a program: Once an ill-foun
ded node is detected the |
350 // program exits with a cycle description allowing the user to fix the ill-found
edness. | 372 // program exits with a cycle description allowing the user to fix the ill-found
edness. |
351 func (finder *cycleFinder) findKnownIllFoundedCycle(nodeHolder *nodeHolder) *Cyc
leDescription { | 373 // |
| 374 // This method is recursive. To initiate the recursion |nodeHolder| should be se
t to the starting node and |
| 375 // |path| should be nil. In subsequent recursive calls |nodeHolder| is the curre
nt node of the walk and |
| 376 // |path| is the path from the starting node to the current node. |
| 377 func (finder *cycleFinder) findKnownIllFoundedCycle(nodeHolder *nodeHolder, path
nodePath) *CycleDescription { |
| 378 » // If this is the start of the recursion initialize the path. |
| 379 » if path == nil { |
| 380 » » path = make(nodePath, 0) |
| 381 » } |
| 382 |
352 // Mark the current node as started | 383 // Mark the current node as started |
353 nodeHolder.state = vsVisitStarted | 384 nodeHolder.state = vsVisitStarted |
354 » finder.currentPath.Push(nodeHolder) | 385 » for _, edge := range nodeHolder.node.OutEdges() { |
355 » for _, child := range nodeHolder.node.OutEdges() { | 386 » » childHolder := finder.holderForNode(edge.Target) |
356 » » childHolder := finder.holderForNode(child) | |
357 if childHolder.state == vsVisitStarted { | 387 if childHolder.state == vsVisitStarted { |
358 // If the child has been started but not finished then w
e have found a cycle | 388 // If the child has been started but not finished then w
e have found a cycle |
359 // from the child to the current node back to the child. | 389 // from the child to the current node back to the child. |
360 » » » return newCycleDescription(finder.currentPath.elements,
childHolder, nodeHolder) | 390 » » » path = append(path, pathElement{nodeHolder.node, edge}) |
| 391 » » » return newCycleDescription(path, childHolder.node, nodeH
older.node) |
361 } else if !childHolder.node.KnownWellFounded() { | 392 } else if !childHolder.node.KnownWellFounded() { |
362 » » » return finder.findKnownIllFoundedCycle(childHolder) | 393 » » » path = append(path, pathElement{nodeHolder.node, edge}) |
| 394 » » » return finder.findKnownIllFoundedCycle(childHolder, path
) |
363 } | 395 } |
364 } | 396 } |
365 panic("Program logic error: Could not find a known ill-founded cycle.") | 397 panic("Program logic error: Could not find a known ill-founded cycle.") |
366 } | 398 } |
367 | 399 |
368 // Returns the nodeHolder for the given node. | 400 // Returns the nodeHolder for the given node. |
369 func (finder *cycleFinder) holderForNode(node Node) *nodeHolder { | 401 func (finder *cycleFinder) holderForNode(node Node) *nodeHolder { |
370 if holder, found := finder.nodeToHolder[node]; found { | 402 if holder, found := finder.nodeToHolder[node]; found { |
371 return holder | 403 return holder |
372 } | 404 } |
(...skipping 33 matching lines...) Loading... |
406 | 438 |
407 func newNodeHolder(node Node, visitationOrder int) *nodeHolder { | 439 func newNodeHolder(node Node, visitationOrder int) *nodeHolder { |
408 nodeHolder := new(nodeHolder) | 440 nodeHolder := new(nodeHolder) |
409 nodeHolder.node = node | 441 nodeHolder.node = node |
410 nodeHolder.parentsToBeNotified = MakeNodeSet() | 442 nodeHolder.parentsToBeNotified = MakeNodeSet() |
411 nodeHolder.state = vsUnvisited | 443 nodeHolder.state = vsUnvisited |
412 nodeHolder.visitationOrder = visitationOrder | 444 nodeHolder.visitationOrder = visitationOrder |
413 return nodeHolder | 445 return nodeHolder |
414 } | 446 } |
415 | 447 |
416 ////////////////////////////////////////////// | |
417 /// nodeStack type | |
418 ////////////////////////////////////////////// | |
419 | |
420 // A nodeStack is a stack of *nodeHolders | |
421 type nodeStack struct { | |
422 elements []*nodeHolder | |
423 } | |
424 | |
425 func (stack *nodeStack) Push(n *nodeHolder) { | |
426 stack.elements = append(stack.elements, n) | |
427 } | |
428 | |
429 func (stack *nodeStack) Size() int { | |
430 return len(stack.elements) | |
431 } | |
432 | |
433 func (stack *nodeStack) Pop() (n *nodeHolder) { | |
434 lastIndex := stack.Size() - 1 | |
435 n = stack.elements[lastIndex] | |
436 stack.elements = stack.elements[:lastIndex] | |
437 return | |
438 } | |
439 | |
440 func (stack *nodeStack) Peek() (n *nodeHolder) { | |
441 return stack.elements[stack.Size()-1] | |
442 } | |
443 | |
444 func (stack *nodeStack) String() string { | |
445 var buffer bytes.Buffer | |
446 fmt.Fprintf(&buffer, "[") | |
447 first := true | |
448 for _, e := range stack.elements { | |
449 if !first { | |
450 fmt.Fprintf(&buffer, ", ") | |
451 } | |
452 fmt.Fprintf(&buffer, "%s", e.node.DebugName()) | |
453 first = false | |
454 } | |
455 fmt.Fprintln(&buffer, "]") | |
456 return buffer.String() | |
457 } | |
458 | |
459 /////////////////////////////////////////////////// | 448 /////////////////////////////////////////////////// |
460 /// NodeSet type | 449 /// NodeSet type |
461 /////////////////////////////////////////////////// | 450 /////////////////////////////////////////////////// |
462 | 451 |
463 // A NodeSet is a set of nodeHolders. | 452 // A NodeSet is a set of nodeHolders. |
464 type NodeSet struct { | 453 type NodeSet struct { |
465 elements map[*nodeHolder]bool | 454 elements map[*nodeHolder]bool |
466 } | 455 } |
467 | 456 |
468 // MakeNodeSet makes a new empty NodeSet. | 457 // MakeNodeSet makes a new empty NodeSet. |
(...skipping 66 matching lines...) Loading... |
535 | 524 |
536 func (set *NodeSet) Size() int { | 525 func (set *NodeSet) Size() int { |
537 return len(set.elements) | 526 return len(set.elements) |
538 } | 527 } |
539 | 528 |
540 // compareNodeSets is a package-private method used in our tests. It returns | 529 // compareNodeSets is a package-private method used in our tests. It returns |
541 // a non-nil error in case expected is not equal to actual. | 530 // a non-nil error in case expected is not equal to actual. |
542 func compareNodeSets(expected, actual *NodeSet) error { | 531 func compareNodeSets(expected, actual *NodeSet) error { |
543 for n, _ := range expected.elements { | 532 for n, _ := range expected.elements { |
544 if !actual.Contains(n) { | 533 if !actual.Contains(n) { |
545 » » » return fmt.Errorf("%s is in expected but not actual", n.
node.DebugName()) | 534 » » » return fmt.Errorf("%s is in expected but not actual", n.
node.Name()) |
546 } | 535 } |
547 } | 536 } |
548 for n, _ := range actual.elements { | 537 for n, _ := range actual.elements { |
549 if !expected.Contains(n) { | 538 if !expected.Contains(n) { |
550 » » » return fmt.Errorf("%s is in actual but not expected", n.
node.DebugName()) | 539 » » » return fmt.Errorf("%s is in actual but not expected", n.
node.Name()) |
551 } | 540 } |
552 } | 541 } |
553 return nil | 542 return nil |
554 } | 543 } |
555 | 544 |
556 // String returns a human readable string representation of |set|. | 545 // String returns a human readable string representation of |set|. |
557 func (set *NodeSet) String() string { | 546 func (set *NodeSet) String() string { |
558 var buffer bytes.Buffer | 547 var buffer bytes.Buffer |
559 fmt.Fprintf(&buffer, "{") | 548 fmt.Fprintf(&buffer, "{") |
560 first := true | 549 first := true |
561 for e, _ := range set.elements { | 550 for e, _ := range set.elements { |
562 if !first { | 551 if !first { |
563 fmt.Fprintf(&buffer, ", ") | 552 fmt.Fprintf(&buffer, ", ") |
564 } | 553 } |
565 » » fmt.Fprintf(&buffer, "%s", e.node.DebugName()) | 554 » » fmt.Fprintf(&buffer, "%s", e.node.Name()) |
566 first = false | 555 first = false |
567 } | 556 } |
568 fmt.Fprintln(&buffer, "}") | 557 fmt.Fprintln(&buffer, "}") |
569 return buffer.String() | 558 return buffer.String() |
570 } | 559 } |
571 | 560 |
572 /////////////////////////////////////////////////// | 561 /////////////////////////////////////////////////// |
573 // CycleDescription type | 562 // CycleDescription type |
574 // | 563 // |
575 // A |CycleDescription| describes a cycle in a directed graph. | 564 // A |CycleDescription| describes a cycle in a directed graph. |
576 /////////////////////////////////////////////////// | 565 /////////////////////////////////////////////////// |
577 | 566 |
578 type CycleDescription struct { | 567 type CycleDescription struct { |
579 » first, last Node | 568 » First, Last Node |
580 » path []Node | 569 » Path []OutEdge |
581 } | 570 } |
582 | 571 |
583 func (c *CycleDescription) String() string { | 572 func (c *CycleDescription) String() string { |
584 var buffer bytes.Buffer | 573 var buffer bytes.Buffer |
585 » fmt.Fprintf(&buffer, "first:%s", c.first.DebugName()) | 574 » fmt.Fprintf(&buffer, "first:%s", c.First.Name()) |
586 » fmt.Fprintf(&buffer, ", last:%s", c.last.DebugName()) | 575 » fmt.Fprintf(&buffer, ", last:%s", c.Last.Name()) |
587 fmt.Fprintf(&buffer, ", path:{") | 576 fmt.Fprintf(&buffer, ", path:{") |
588 » first := true | 577 » fmt.Fprintf(&buffer, "%s", c.First.Name()) |
589 » for _, n := range c.path { | 578 » for _, edge := range c.Path { |
590 » » if !first { | 579 » » if edge.Target != c.First { |
591 fmt.Fprintf(&buffer, ", ") | 580 fmt.Fprintf(&buffer, ", ") |
| 581 fmt.Fprintf(&buffer, "%s", edge.Target.Name()) |
592 } | 582 } |
593 fmt.Fprintf(&buffer, "%s", n.DebugName()) | |
594 first = false | |
595 } | 583 } |
596 fmt.Fprintln(&buffer, "}") | 584 fmt.Fprintln(&buffer, "}") |
597 return buffer.String() | 585 return buffer.String() |
598 } | 586 } |
599 | 587 |
600 func newCycleDescription(path []*nodeHolder, first, last *nodeHolder) *CycleDesc
ription { | 588 // newCycleDescription builds a CycleDescription based on the given data. |
| 589 // |path| should be a nodePath that ends with a cycle. That is it should look li
ke |
| 590 // {x1, ->x2}, {x2, ->x3}, {x3, ->x4}, {x4, ->x5}, {x5, ->x2} |
| 591 // |
| 592 // |cycleStart| should be the first node of |path| included in the cycle. In |
| 593 // the above example it would be x2. |
| 594 // |
| 595 // |end| should be the last node of the path. In the above example it would be x
5. |
| 596 // |
| 597 // The return value using our example would be: |
| 598 // {First: x2, Last: x5, Path:[{x2, x2->x3}, {x3, x3->x4}, {x4, x4->x5}, {x5, x5
->x2}]} |
| 599 func newCycleDescription(path nodePath, cycleStart, end Node) *CycleDescription
{ |
| 600 » lastNode := path[len(path)-1].node |
| 601 » if lastNode != end { |
| 602 » » panic(fmt.Sprintf("%s != %s", lastNode.Name(), end.Name())) |
| 603 » } |
| 604 » lastTarget := path[len(path)-1].edge.Target |
| 605 » if lastTarget != cycleStart { |
| 606 » » panic(fmt.Sprintf("(%T)%s != (%T)%s", lastTarget, lastTarget.Nam
e(), cycleStart, cycleStart.Name())) |
| 607 » } |
601 description := CycleDescription{} | 608 description := CycleDescription{} |
602 » description.first = first.node | 609 » description.First = cycleStart |
603 » description.last = last.node | 610 » description.Last = end |
604 » description.path = make([]Node, 0, len(path)) | 611 » description.Path = make([]OutEdge, 0, len(path)) |
605 » for _, n := range path { | 612 » for _, element := range path { |
606 » » if len(description.path) > 0 || n.node == first.node { | 613 » » if len(description.Path) > 0 || element.node == cycleStart { |
607 » » » description.path = append(description.path, n.node) | 614 » » » description.Path = append(description.Path, element.edge
) |
608 } | 615 } |
609 } | 616 } |
610 » if description.path[len(description.path)-1] != last.node { | 617 » if description.Path[len(description.Path)-1].Target != cycleStart { |
611 » » panic(fmt.Sprintf("%s != %s", description.path[len(description.p
ath)-1], last.node)) | 618 » » panic(fmt.Sprintf("%s != %s", description.Path[len(description.P
ath)-1].Target.Name(), cycleStart.Name())) |
612 } | 619 } |
613 return &description | 620 return &description |
614 } | 621 } |
OLD | NEW |