...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package flow
16
17 import (
18 "fmt"
19 "strings"
20
21 "cuelang.org/go/cue"
22 "cuelang.org/go/cue/errors"
23 "cuelang.org/go/cue/token"
24 )
25
26
27 func checkCycle(a []*Task) errors.Error {
28 cc := cycleChecker{
29 visited: make([]bool, len(a)),
30 stack: make([]*Task, 0, len(a)),
31 }
32 for _, t := range a {
33 if cc.isCyclic(t) {
34 break
35 }
36 }
37 return cc.err
38 }
39
40 type cycleChecker struct {
41 visited []bool
42 stack []*Task
43 err errors.Error
44 }
45
46 func (cc *cycleChecker) isCyclic(t *Task) bool {
47 i := t.index
48 if !cc.visited[i] {
49 cc.visited[i] = true
50 cc.stack = append(cc.stack, t)
51
52 for _, d := range t.depTasks {
53 if !cc.visited[d.index] && cc.isCyclic(d) {
54 return true
55 } else if cc.visited[d.index] {
56 cc.addCycleError(t)
57 return true
58 }
59 }
60 }
61 cc.stack = cc.stack[:len(cc.stack)-1]
62 cc.visited[i] = false
63 return false
64 }
65
66 func (cc *cycleChecker) addCycleError(start *Task) {
67 err := &cycleError{}
68
69 for _, t := range cc.stack {
70 err.path = append(err.path, t.v.Path())
71 err.positions = append(err.positions, t.v.Pos())
72 }
73
74 cc.err = errors.Append(cc.err, err)
75 }
76
77 type cycleError struct {
78 path []cue.Path
79 positions []token.Pos
80 }
81
82 func (e *cycleError) Error() string {
83 msg, args := e.Msg()
84 return fmt.Sprintf(msg, args...)
85 }
86
87 func (e *cycleError) Path() []string { return nil }
88
89 func (e *cycleError) Msg() (format string, args []interface{}) {
90 w := &strings.Builder{}
91 for _, p := range e.path {
92 fmt.Fprintf(w, "\n\ttask %s refers to", p)
93 }
94 fmt.Fprintf(w, "\n\ttask %s", e.path[0])
95
96 return "cyclic task dependency:%v", []interface{}{w.String()}
97 }
98
99 func (e *cycleError) Position() token.Pos {
100 if len(e.positions) == 0 {
101 return token.NoPos
102 }
103 return e.positions[0]
104 }
105
106 func (e *cycleError) InputPositions() []token.Pos {
107 return e.positions
108 }
109
View as plain text