| 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...) Expand all 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...) Expand 10 before | Expand all | Expand 10 after 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...) Expand all 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...) Expand all 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...) Expand 10 before | Expand all | Expand 10 after 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...) Expand 10 before | Expand all | Expand 10 after 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...) Expand 10 before | Expand all | Expand 10 after 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 |