github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/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  	"github.com/joomcode/cue/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 is properly initialized.
    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 structFeatures(a []*adt.StructLit) []adt.Feature {
    55  // 	sets := extractFeatures(a)
    56  // 	return sortedArcs(sets)
    57  // }
    58  
    59  func (e *exporter) sortedArcs(v *adt.Vertex) (sorted []*adt.Vertex) {
    60  	if adt.DebugSort > 0 {
    61  		return v.Arcs
    62  	}
    63  
    64  	a := extractFeatures(v.Structs)
    65  	if len(a) == 0 {
    66  		return v.Arcs
    67  	}
    68  
    69  	sorted = make([]*adt.Vertex, len(v.Arcs))
    70  	copy(sorted, v.Arcs)
    71  
    72  	m := sortArcs(a)
    73  	sort.SliceStable(sorted, func(i, j int) bool {
    74  		if m[sorted[i].Label] == 0 {
    75  			return m[sorted[j].Label] != 0
    76  		}
    77  		return m[sorted[i].Label] > m[sorted[j].Label]
    78  	})
    79  
    80  	return sorted
    81  }
    82  
    83  // TODO: remove
    84  func (e *exporter) extractFeatures(in []*adt.StructInfo) (a [][]adt.Feature) {
    85  	return extractFeatures(in)
    86  }
    87  
    88  func extractFeatures(in []*adt.StructInfo) (a [][]adt.Feature) {
    89  	for _, s := range in {
    90  		sorted := []adt.Feature{}
    91  		for _, e := range s.StructLit.Decls {
    92  			switch x := e.(type) {
    93  			case *adt.Field:
    94  				sorted = append(sorted, x.Label)
    95  
    96  			case *adt.OptionalField:
    97  				sorted = append(sorted, x.Label)
    98  			}
    99  		}
   100  
   101  		// Lists with a single element may still be useful to distinguish
   102  		// between known and unknown fields: unknown fields are sorted last.
   103  		if len(sorted) > 0 {
   104  			a = append(a, sorted)
   105  		}
   106  	}
   107  	return a
   108  }
   109  
   110  // sortedArcs is like sortArcs, but returns a the features of optional and
   111  // required fields in an sorted slice. Ultimately, the implementation should
   112  // use merge sort everywhere, and this will be the preferred method. Also,
   113  // when querying optional fields as well, this helps identifying the optional
   114  // fields.
   115  func sortedArcs(fronts [][]adt.Feature) []adt.Feature {
   116  	m := sortArcs(fronts)
   117  	return sortedArcsFromMap(m)
   118  }
   119  
   120  func sortedArcsFromMap(m map[adt.Feature]int) []adt.Feature {
   121  	a := make([]adt.Feature, 0, len(m))
   122  
   123  	for k := range m {
   124  		a = append(a, k)
   125  	}
   126  
   127  	sort.Slice(a, func(i, j int) bool { return m[a[i]] > m[a[j]] })
   128  
   129  	return a
   130  }
   131  
   132  // sortArcs does a topological sort of arcs based on a variant of Kahn's
   133  // algorithm. See
   134  // https://www.geeksforgeeks.org/topological-sorting-indegree-based-solution/
   135  //
   136  // It returns a map from feature to int where the feature with the highest
   137  // number should be sorted first.
   138  func sortArcs(fronts [][]adt.Feature) map[adt.Feature]int {
   139  	counts := map[adt.Feature]int{}
   140  	for _, a := range fronts {
   141  		if len(a) <= 1 {
   142  			continue // no dependencies
   143  		}
   144  		for _, f := range a[1:] {
   145  			counts[f]++
   146  		}
   147  	}
   148  
   149  	// We could use a Heap instead of simple linear search here if we are
   150  	// concerned about the time complexity.
   151  
   152  	index := -1
   153  outer:
   154  	for {
   155  	lists:
   156  		for i, a := range fronts {
   157  			for len(a) > 0 {
   158  				f := a[0]
   159  				n := counts[f]
   160  				if n > 0 {
   161  					continue lists
   162  				}
   163  
   164  				// advance list and decrease dependency.
   165  				a = a[1:]
   166  				fronts[i] = a
   167  				if len(a) > 1 && counts[a[0]] > 0 {
   168  					counts[a[0]]--
   169  				}
   170  
   171  				if n == 0 { // may be head of other lists as well
   172  					counts[f] = index
   173  					index--
   174  				}
   175  				continue outer // progress
   176  			}
   177  		}
   178  
   179  		for _, a := range fronts {
   180  			if len(a) > 0 {
   181  				// Detected a cycle. Fire at will to make progress.
   182  				counts[a[0]] = 0
   183  				continue outer
   184  			}
   185  		}
   186  		break
   187  	}
   188  
   189  	return counts
   190  }