this repo has no description
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}