cuelang.org/go@v0.13.0/internal/core/toposort/graph.go (about)

     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  
    15  package toposort
    16  
    17  import (
    18  	"cmp"
    19  	"slices"
    20  
    21  	"cuelang.org/go/internal/core/adt"
    22  )
    23  
    24  const (
    25  	NodeUnsorted = -1
    26  )
    27  
    28  type Graph struct {
    29  	nodes Nodes
    30  }
    31  
    32  type Node struct {
    33  	Feature    adt.Feature
    34  	Outgoing   Nodes
    35  	Incoming   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  
    43  func (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.
    52  func (n *Node) SafeName(index adt.StringIndexer) string {
    53  	return n.Feature.SelectorString(index)
    54  }
    55  
    56  type Nodes []*Node
    57  
    58  func (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  
    66  type edge struct {
    67  	from adt.Feature
    68  	to   adt.Feature
    69  }
    70  
    71  type 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.
    83  func 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.
    95  func (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  	toNode.Incoming = append(toNode.Incoming, fromNode)
   112  }
   113  
   114  // Ensure that a node for this feature exists. This is necessary for
   115  // features that are not necessarily connected to any other feature.
   116  func (builder *GraphBuilder) EnsureNode(feature adt.Feature) *Node {
   117  	node, found := builder.nodesByFeature[feature]
   118  	if !found {
   119  		node = &Node{Feature: feature, position: NodeUnsorted}
   120  		builder.nodesByFeature[feature] = node
   121  	}
   122  	return node
   123  }
   124  
   125  func (builder *GraphBuilder) Build() *Graph {
   126  	nodesByFeature := builder.nodesByFeature
   127  	nodes := make(Nodes, 0, len(nodesByFeature))
   128  	for _, node := range nodesByFeature {
   129  		nodes = append(nodes, node)
   130  	}
   131  	return &Graph{nodes: nodes}
   132  }
   133  
   134  type indexComparison struct{ adt.StringIndexer }
   135  
   136  func (index *indexComparison) compareNodeByName(a, b *Node) int {
   137  	aFeature, bFeature := a.Feature, b.Feature
   138  	aIsInt, bIsInt := aFeature.Typ() == adt.IntLabel, bFeature.Typ() == adt.IntLabel
   139  
   140  	switch {
   141  	case aIsInt && bIsInt:
   142  		return cmp.Compare(aFeature.Index(), bFeature.Index())
   143  	case aIsInt:
   144  		return -1
   145  	case bIsInt:
   146  		return 1
   147  	default:
   148  		return cmp.Compare(aFeature.RawString(index), bFeature.RawString(index))
   149  	}
   150  }
   151  
   152  func (index *indexComparison) compareComponentsByNodes(a, b *StronglyConnectedComponent) int {
   153  	return slices.CompareFunc(a.Nodes, b.Nodes, index.compareNodeByName)
   154  }
   155  
   156  // Sort the features of the graph into a single slice.
   157  //
   158  // As far as possible, a topological sort is used. We first calculate
   159  // the strongly-connected-components (SCCs) of the graph. If the graph
   160  // has no cycles then there will be 1 SCC per graph node, which we
   161  // then walk topologically. When there is a choice as to which SCC to
   162  // enter into next, a lexicographical comparison is done, and minimum
   163  // feature chosen.
   164  //
   165  // If the graph has cycles, then there will be at least one SCC
   166  // containing several nodes. When we choose to enter this SCC, we use
   167  // a lexicographical ordering of its nodes. This avoids the need for
   168  // expensive and complex analysis of cycles: the maximum possible
   169  // number of cycles rises with the factorial of the number of nodes in
   170  // a component.
   171  func (graph *Graph) Sort(index adt.StringIndexer) []adt.Feature {
   172  	indexCmp := &indexComparison{index}
   173  
   174  	nodesSorted := make(Nodes, 0, len(graph.nodes))
   175  
   176  	scc := graph.StronglyConnectedComponents()
   177  	var sccReady []*StronglyConnectedComponent
   178  	for _, component := range scc {
   179  		component.visited = false
   180  		slices.SortFunc(component.Nodes, indexCmp.compareNodeByName)
   181  		if len(component.Incoming) == 0 {
   182  			sccReady = append(sccReady, component)
   183  		}
   184  	}
   185  	slices.SortFunc(sccReady, indexCmp.compareComponentsByNodes)
   186  
   187  	sccVisitedCount := 0
   188  	for sccVisitedCount != len(scc) {
   189  		sccCurrent := sccReady[0]
   190  		sccReady = sccReady[1:]
   191  		if sccCurrent.visited {
   192  			continue
   193  		}
   194  		sccCurrent.visited = true
   195  		sccVisitedCount++
   196  		debug("scc current: %p %v\n", sccCurrent, sccCurrent)
   197  
   198  		nodesSorted = appendNodes(nodesSorted, sccCurrent.Nodes)
   199  
   200  		sccReadyNeedsSorting := false
   201  	SccNextOutgoing:
   202  		for _, next := range sccCurrent.Outgoing {
   203  			for _, required := range next.Incoming {
   204  				if !required.visited {
   205  					continue SccNextOutgoing
   206  				}
   207  			}
   208  			sccReady = append(sccReady, next)
   209  			sccReadyNeedsSorting = true
   210  		}
   211  		if sccReadyNeedsSorting {
   212  			slices.SortFunc(sccReady, indexCmp.compareComponentsByNodes)
   213  		}
   214  	}
   215  
   216  	return nodesSorted.Features()
   217  }
   218  
   219  func appendNodes(nodesSorted, nodesReady Nodes) Nodes {
   220  	for i, node := range nodesReady {
   221  		node.position = len(nodesSorted) + i
   222  	}
   223  	nodesSorted = append(nodesSorted, nodesReady...)
   224  	return nodesSorted
   225  }
   226  
   227  func debug(formatting string, args ...any) {
   228  	//	fmt.Printf(formatting, args...)
   229  }