this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 225 lines 6.5 kB view raw
1// Copyright 2024 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 15package toposort 16 17import ( 18 "cmp" 19 "maps" 20 "slices" 21 22 "cuelang.org/go/internal/core/adt" 23) 24 25const ( 26 NodeUnsorted = -1 27) 28 29type Graph struct { 30 nodes Nodes 31} 32 33type Node struct { 34 Feature adt.Feature 35 Outgoing Nodes 36 structMeta *structMeta 37 // temporary state for calculating the Strongly Connected 38 // Components of a graph. 39 sccNodeState sccNodeState 40 position int 41} 42 43func (n *Node) IsSorted() bool { 44 return n.position >= 0 45} 46 47// SafeName returns a string useful for debugging, regardless of the 48// type of the feature. So for IntLabels, you'll get back `1`, `10` 49// etc; for identifiers, you may get back a string with quotes in it, 50// eg `"runs-on"`. So this is not useful for comparisons, but it is 51// useful (and safe) for debugging. 52func (n *Node) SafeName(index adt.StringIndexer) string { 53 return n.Feature.SelectorString(index) 54} 55 56type Nodes []*Node 57 58func (nodes Nodes) Features() []adt.Feature { 59 features := make([]adt.Feature, len(nodes)) 60 for i, node := range nodes { 61 features[i] = node.Feature 62 } 63 return features 64} 65 66type edge struct { 67 from adt.Feature 68 to adt.Feature 69} 70 71type GraphBuilder struct { 72 allowEdges bool 73 edgesSet map[edge]struct{} 74 nodesByFeature map[adt.Feature]*Node 75} 76 77// NewGraphBuilder is the constructor for GraphBuilder. 78// 79// If you disallow edges, then nodes can still be added to the graph, 80// and the [GraphBuilder.AddEdge] method will not error, but edges 81// will never be added between nodes. This has the effect that 82// topological ordering is not possible. 83func NewGraphBuilder(allowEdges bool) *GraphBuilder { 84 return &GraphBuilder{ 85 allowEdges: allowEdges, 86 edgesSet: make(map[edge]struct{}), 87 nodesByFeature: make(map[adt.Feature]*Node), 88 } 89} 90 91// Adds an edge between the two features. Nodes for the features will 92// be created if they don't already exist. This method is idempotent: 93// multiple calls with the same arguments will not create multiple 94// edges, nor error. 95func (builder *GraphBuilder) AddEdge(from, to adt.Feature) { 96 if !builder.allowEdges { 97 builder.EnsureNode(from) 98 builder.EnsureNode(to) 99 return 100 } 101 102 edge := edge{from: from, to: to} 103 if _, found := builder.edgesSet[edge]; found { 104 return 105 } 106 107 builder.edgesSet[edge] = struct{}{} 108 fromNode := builder.EnsureNode(from) 109 toNode := builder.EnsureNode(to) 110 fromNode.Outgoing = append(fromNode.Outgoing, toNode) 111} 112 113// Ensure that a node for this feature exists. This is necessary for 114// features that are not necessarily connected to any other feature. 115func (builder *GraphBuilder) EnsureNode(feature adt.Feature) *Node { 116 node, found := builder.nodesByFeature[feature] 117 if !found { 118 node = &Node{Feature: feature, position: NodeUnsorted} 119 builder.nodesByFeature[feature] = node 120 } 121 return node 122} 123 124func (builder *GraphBuilder) Build() *Graph { 125 nodes := make(Nodes, 0, len(builder.nodesByFeature)) 126 nodes = slices.AppendSeq(nodes, maps.Values(builder.nodesByFeature)) 127 return &Graph{nodes: nodes} 128} 129 130type indexComparison struct{ adt.StringIndexer } 131 132func (index *indexComparison) compareNodeByName(a, b *Node) int { 133 aFeature, bFeature := a.Feature, b.Feature 134 aIsInt, bIsInt := aFeature.Typ() == adt.IntLabel, bFeature.Typ() == adt.IntLabel 135 136 switch { 137 case aIsInt && bIsInt: 138 return cmp.Compare(aFeature.Index(), bFeature.Index()) 139 case aIsInt: 140 return -1 141 case bIsInt: 142 return 1 143 default: 144 return cmp.Compare(aFeature.RawString(index), bFeature.RawString(index)) 145 } 146} 147 148func (index *indexComparison) compareComponentsByNodes(a, b *StronglyConnectedComponent) int { 149 return slices.CompareFunc(a.Nodes, b.Nodes, index.compareNodeByName) 150} 151 152// Sort the features of the graph into a single slice. 153// 154// As far as possible, a topological sort is used. We first calculate 155// the strongly-connected-components (SCCs) of the graph. If the graph 156// has no cycles then there will be 1 SCC per graph node, which we 157// then walk topologically. When there is a choice as to which SCC to 158// enter into next, a lexicographical comparison is done, and minimum 159// feature chosen. 160// 161// If the graph has cycles, then there will be at least one SCC 162// containing several nodes. When we choose to enter this SCC, we use 163// a lexicographical ordering of its nodes. This avoids the need for 164// expensive and complex analysis of cycles: the maximum possible 165// number of cycles rises with the factorial of the number of nodes in 166// a component. 167func (graph *Graph) Sort(index adt.StringIndexer) []adt.Feature { 168 indexCmp := &indexComparison{index} 169 170 nodesSorted := make(Nodes, 0, len(graph.nodes)) 171 172 scc := graph.StronglyConnectedComponents() 173 var sccReady []*StronglyConnectedComponent 174 for _, component := range scc { 175 component.visited = false 176 slices.SortFunc(component.Nodes, indexCmp.compareNodeByName) 177 if len(component.Incoming) == 0 { 178 sccReady = append(sccReady, component) 179 } 180 } 181 slices.SortFunc(sccReady, indexCmp.compareComponentsByNodes) 182 183 sccVisitedCount := 0 184 for sccVisitedCount != len(scc) { 185 sccCurrent := sccReady[0] 186 sccReady = sccReady[1:] 187 if sccCurrent.visited { 188 continue 189 } 190 sccCurrent.visited = true 191 sccVisitedCount++ 192 debug("scc current: %p %v\n", sccCurrent, sccCurrent) 193 194 nodesSorted = appendNodes(nodesSorted, sccCurrent.Nodes) 195 196 sccReadyNeedsSorting := false 197 SccNextOutgoing: 198 for _, next := range sccCurrent.Outgoing { 199 for _, required := range next.Incoming { 200 if !required.visited { 201 continue SccNextOutgoing 202 } 203 } 204 sccReady = append(sccReady, next) 205 sccReadyNeedsSorting = true 206 } 207 if sccReadyNeedsSorting { 208 slices.SortFunc(sccReady, indexCmp.compareComponentsByNodes) 209 } 210 } 211 212 return nodesSorted.Features() 213} 214 215func appendNodes(nodesSorted, nodesReady Nodes) Nodes { 216 for i, node := range nodesReady { 217 node.position = len(nodesSorted) + i 218 } 219 nodesSorted = append(nodesSorted, nodesReady...) 220 return nodesSorted 221} 222 223func debug(formatting string, args ...any) { 224 // fmt.Printf(formatting, args...) 225}