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  }