cuelang.org/go@v0.13.0/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  	"cmp"
    19  	"slices"
    20  
    21  	"cuelang.org/go/internal/core/adt"
    22  	"cuelang.org/go/internal/core/toposort"
    23  )
    24  
    25  // TODO: topological sort should go arguably in a more fundamental place as it
    26  // may be needed to sort inputs for comprehensions.
    27  
    28  // VertexFeatures returns the feature list of v. The list may include more
    29  // features than for which there are arcs and also includes features for
    30  // optional fields. It assumes the Structs fields are initialized and evaluated.
    31  func VertexFeatures(c *adt.OpContext, v *adt.Vertex) []adt.Feature {
    32  	if c.TopoSort {
    33  		return toposort.VertexFeatures(c, v)
    34  	} else {
    35  		return vertexFeatures(v)
    36  	}
    37  }
    38  
    39  func vertexFeatures(v *adt.Vertex) []adt.Feature {
    40  	sets := extractFeatures(v.Structs)
    41  	m := sortArcs(sets) // TODO: use for convenience.
    42  
    43  	// Add features that are not in m. This may happen when fields were
    44  	// dynamically created.
    45  	var a []adt.Feature
    46  	for _, arc := range v.Arcs {
    47  		if _, ok := m[arc.Label]; !ok {
    48  			a = append(a, arc.Label)
    49  		}
    50  	}
    51  
    52  	sets = extractFeatures(v.Structs)
    53  	if len(a) > 0 {
    54  		sets = append(sets, a)
    55  	}
    56  
    57  	return sortedArcs(sets)
    58  }
    59  
    60  func extractFeatures(in []*adt.StructInfo) (a [][]adt.Feature) {
    61  	a = make([][]adt.Feature, 0, len(in))
    62  	for _, s := range in {
    63  		sorted := make([]adt.Feature, 0, len(s.Decls))
    64  		for _, e := range s.Decls {
    65  			switch x := e.(type) {
    66  			case *adt.Field:
    67  				sorted = append(sorted, x.Label)
    68  			}
    69  		}
    70  
    71  		// Lists with a single element may still be useful to distinguish
    72  		// between known and unknown fields: unknown fields are sorted last.
    73  		if len(sorted) > 0 {
    74  			a = append(a, sorted)
    75  		}
    76  	}
    77  	return a
    78  }
    79  
    80  // VertexFeaturesUnsorted returns the feature list of v. There will be
    81  // no duplicate features in the returned list, but there is also no
    82  // attempt made to sort the list.
    83  func VertexFeaturesUnsorted(v *adt.Vertex) (features []adt.Feature) {
    84  	seen := make(map[adt.Feature]struct{})
    85  
    86  	for _, s := range v.Structs {
    87  		for _, decl := range s.Decls {
    88  			field, ok := decl.(*adt.Field)
    89  			if !ok {
    90  				continue
    91  			}
    92  			label := field.Label
    93  			if _, found := seen[label]; found {
    94  				continue
    95  			}
    96  			seen[label] = struct{}{}
    97  			features = append(features, label)
    98  		}
    99  	}
   100  
   101  	for _, arc := range v.Arcs {
   102  		label := arc.Label
   103  		if _, found := seen[label]; found {
   104  			continue
   105  		}
   106  		seen[label] = struct{}{}
   107  		features = append(features, label)
   108  	}
   109  
   110  	return features
   111  }
   112  
   113  // sortedArcs is like sortArcs, but returns the features of optional and
   114  // required fields in an sorted slice. Ultimately, the implementation should
   115  // use merge sort everywhere, and this will be the preferred method. Also,
   116  // when querying optional fields as well, this helps identifying the optional
   117  // fields.
   118  func sortedArcs(fronts [][]adt.Feature) []adt.Feature {
   119  	m := sortArcs(fronts)
   120  	return sortedArcsFromMap(m)
   121  }
   122  
   123  func sortedArcsFromMap(m map[adt.Feature]int) []adt.Feature {
   124  	a := make([]adt.Feature, 0, len(m))
   125  
   126  	for k := range m {
   127  		a = append(a, k)
   128  	}
   129  
   130  	slices.SortFunc(a, func(a1, a2 adt.Feature) int { return -cmp.Compare(m[a1], m[a2]) })
   131  
   132  	return a
   133  }
   134  
   135  // sortArcs does a topological sort of arcs based on a variant of Kahn's
   136  // algorithm. See
   137  // https://www.geeksforgeeks.org/topological-sorting-indegree-based-solution/
   138  //
   139  // It returns a map from feature to int where the feature with the highest
   140  // number should be sorted first.
   141  func sortArcs(fronts [][]adt.Feature) map[adt.Feature]int {
   142  	counts := map[adt.Feature]int{}
   143  	for _, a := range fronts {
   144  		if len(a) <= 1 {
   145  			continue // no dependencies
   146  		}
   147  		for _, f := range a[1:] {
   148  			counts[f]++
   149  		}
   150  	}
   151  
   152  	// We could use a Heap instead of simple linear search here if we are
   153  	// concerned about the time complexity.
   154  
   155  	index := -1
   156  outer:
   157  	for {
   158  	lists:
   159  		for i, a := range fronts {
   160  			for len(a) > 0 {
   161  				f := a[0]
   162  				n := counts[f]
   163  				if n > 0 {
   164  					continue lists
   165  				}
   166  
   167  				// advance list and decrease dependency.
   168  				a = a[1:]
   169  				fronts[i] = a
   170  				if len(a) > 1 && counts[a[0]] > 0 {
   171  					counts[a[0]]--
   172  				}
   173  
   174  				if n == 0 { // may be head of other lists as well
   175  					counts[f] = index
   176  					index--
   177  				}
   178  				continue outer // progress
   179  			}
   180  		}
   181  
   182  		for _, a := range fronts {
   183  			if len(a) > 0 {
   184  				// Detected a cycle. Fire at will to make progress.
   185  				counts[a[0]] = 0
   186  				continue outer
   187  			}
   188  		}
   189  		break
   190  	}
   191  
   192  	return counts
   193  }