cuelang.org/go@v0.10.1/internal/core/export/toposort.go (about) 1 // Copyright 2020 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package export 16 17 import ( 18 "sort" 19 20 "cuelang.org/go/internal/core/adt" 21 ) 22 23 // TODO: topological sort should go arguably in a more fundamental place as it 24 // may be needed to sort inputs for comprehensions. 25 26 // VertexFeatures returns the feature list of v. The list may include more 27 // features than for which there are arcs and also includes features for 28 // optional fields. It assumes the Structs fields are initialized and evaluated. 29 func VertexFeatures(c *adt.OpContext, v *adt.Vertex) []adt.Feature { 30 sets := extractFeatures(v.Structs) 31 m := sortArcs(sets) // TODO: use for convenience. 32 33 // Add features that are not in m. This may happen when fields were 34 // dynamically created. 35 var a []adt.Feature 36 for _, arc := range v.Arcs { 37 if _, ok := m[arc.Label]; !ok { 38 a = append(a, arc.Label) 39 } 40 } 41 42 sets = extractFeatures(v.Structs) 43 if len(a) > 0 { 44 sets = append(sets, a) 45 } 46 47 a = sortedArcs(sets) 48 if adt.DebugSort > 0 { 49 adt.DebugSortFields(c, a) 50 } 51 return a 52 } 53 54 func extractFeatures(in []*adt.StructInfo) (a [][]adt.Feature) { 55 a = make([][]adt.Feature, 0, len(in)) 56 for _, s := range in { 57 sorted := make([]adt.Feature, 0, len(s.Decls)) 58 for _, e := range s.Decls { 59 switch x := e.(type) { 60 case *adt.Field: 61 sorted = append(sorted, x.Label) 62 } 63 } 64 65 // Lists with a single element may still be useful to distinguish 66 // between known and unknown fields: unknown fields are sorted last. 67 if len(sorted) > 0 { 68 a = append(a, sorted) 69 } 70 } 71 return a 72 } 73 74 // sortedArcs is like sortArcs, but returns a the features of optional and 75 // required fields in an sorted slice. Ultimately, the implementation should 76 // use merge sort everywhere, and this will be the preferred method. Also, 77 // when querying optional fields as well, this helps identifying the optional 78 // fields. 79 func sortedArcs(fronts [][]adt.Feature) []adt.Feature { 80 m := sortArcs(fronts) 81 return sortedArcsFromMap(m) 82 } 83 84 func sortedArcsFromMap(m map[adt.Feature]int) []adt.Feature { 85 a := make([]adt.Feature, 0, len(m)) 86 87 for k := range m { 88 a = append(a, k) 89 } 90 91 sort.Slice(a, func(i, j int) bool { return m[a[i]] > m[a[j]] }) 92 93 return a 94 } 95 96 // sortArcs does a topological sort of arcs based on a variant of Kahn's 97 // algorithm. See 98 // https://www.geeksforgeeks.org/topological-sorting-indegree-based-solution/ 99 // 100 // It returns a map from feature to int where the feature with the highest 101 // number should be sorted first. 102 func sortArcs(fronts [][]adt.Feature) map[adt.Feature]int { 103 counts := map[adt.Feature]int{} 104 for _, a := range fronts { 105 if len(a) <= 1 { 106 continue // no dependencies 107 } 108 for _, f := range a[1:] { 109 counts[f]++ 110 } 111 } 112 113 // We could use a Heap instead of simple linear search here if we are 114 // concerned about the time complexity. 115 116 index := -1 117 outer: 118 for { 119 lists: 120 for i, a := range fronts { 121 for len(a) > 0 { 122 f := a[0] 123 n := counts[f] 124 if n > 0 { 125 continue lists 126 } 127 128 // advance list and decrease dependency. 129 a = a[1:] 130 fronts[i] = a 131 if len(a) > 1 && counts[a[0]] > 0 { 132 counts[a[0]]-- 133 } 134 135 if n == 0 { // may be head of other lists as well 136 counts[f] = index 137 index-- 138 } 139 continue outer // progress 140 } 141 } 142 143 for _, a := range fronts { 144 if len(a) > 0 { 145 // Detected a cycle. Fire at will to make progress. 146 counts[a[0]] = 0 147 continue outer 148 } 149 } 150 break 151 } 152 153 return counts 154 }