| // Copyright 2020 CUE Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package export |
| |
| import ( |
| "sort" |
| |
| "cuelang.org/go/internal/core/adt" |
| ) |
| |
| // TODO: topological sort should go arguably in a more fundamental place as it |
| // may be needed to sort inputs for comprehensions. |
| |
| // VertexFeatures returns the feature list of v. The list may include more |
| // features than for which there are arcs and also includes features for |
| // optional fields. It assumes the Structs fields is properly initialized. |
| func VertexFeatures(v *adt.Vertex) []adt.Feature { |
| sets := extractFeatures(v.Structs) |
| m := sortArcs(sets) // TODO: use for convenience. |
| |
| // Add features that are not in m. This may happen when fields were |
| // dynamically created. |
| var a []adt.Feature |
| for _, arc := range v.Arcs { |
| if _, ok := m[arc.Label]; !ok { |
| a = append(a, arc.Label) |
| } |
| } |
| |
| sets = extractFeatures(v.Structs) |
| if len(a) > 0 { |
| sets = append(sets, a) |
| } |
| |
| return sortedArcs(sets) |
| } |
| |
| // func structFeatures(a []*adt.StructLit) []adt.Feature { |
| // sets := extractFeatures(a) |
| // return sortedArcs(sets) |
| // } |
| |
| func (e *exporter) sortedArcs(v *adt.Vertex) (sorted []*adt.Vertex) { |
| a := extractFeatures(v.Structs) |
| if len(a) == 0 { |
| return v.Arcs |
| } |
| |
| sorted = make([]*adt.Vertex, len(v.Arcs)) |
| copy(sorted, v.Arcs) |
| |
| m := sortArcs(a) |
| sort.SliceStable(sorted, func(i, j int) bool { |
| if m[sorted[i].Label] == 0 { |
| return m[sorted[j].Label] != 0 |
| } |
| return m[sorted[i].Label] > m[sorted[j].Label] |
| }) |
| |
| return sorted |
| } |
| |
| // TODO: remove |
| func (e *exporter) extractFeatures(in []*adt.StructInfo) (a [][]adt.Feature) { |
| return extractFeatures(in) |
| } |
| |
| func extractFeatures(in []*adt.StructInfo) (a [][]adt.Feature) { |
| for _, s := range in { |
| sorted := []adt.Feature{} |
| for _, e := range s.StructLit.Decls { |
| switch x := e.(type) { |
| case *adt.Field: |
| sorted = append(sorted, x.Label) |
| |
| case *adt.OptionalField: |
| sorted = append(sorted, x.Label) |
| } |
| } |
| |
| // Lists with a single element may still be useful to distinguish |
| // between known and unknown fields: unknown fields are sorted last. |
| if len(sorted) > 0 { |
| a = append(a, sorted) |
| } |
| } |
| return a |
| } |
| |
| // sortedArcs is like sortArcs, but returns a the features of optional and |
| // required fields in an sorted slice. Ultimately, the implementation should |
| // use merge sort everywhere, and this will be the preferred method. Also, |
| // when querying optional fields as well, this helps identifying the optional |
| // fields. |
| func sortedArcs(fronts [][]adt.Feature) []adt.Feature { |
| m := sortArcs(fronts) |
| return sortedArcsFromMap(m) |
| } |
| |
| func sortedArcsFromMap(m map[adt.Feature]int) []adt.Feature { |
| a := make([]adt.Feature, 0, len(m)) |
| |
| for k := range m { |
| a = append(a, k) |
| } |
| |
| sort.Slice(a, func(i, j int) bool { return m[a[i]] > m[a[j]] }) |
| |
| return a |
| } |
| |
| // sortArcs does a topological sort of arcs based on a variant of Kahn's |
| // algorithm. See |
| // https://www.geeksforgeeks.org/topological-sorting-indegree-based-solution/ |
| // |
| // It returns a map from feature to int where the feature with the highest |
| // number should be sorted first. |
| func sortArcs(fronts [][]adt.Feature) map[adt.Feature]int { |
| counts := map[adt.Feature]int{} |
| for _, a := range fronts { |
| if len(a) <= 1 { |
| continue // no dependencies |
| } |
| for _, f := range a[1:] { |
| counts[f]++ |
| } |
| } |
| |
| // We could use a Heap instead of simple linear search here if we are |
| // concerned about the time complexity. |
| |
| index := -1 |
| outer: |
| for { |
| lists: |
| for i, a := range fronts { |
| for len(a) > 0 { |
| f := a[0] |
| n := counts[f] |
| if n > 0 { |
| continue lists |
| } |
| |
| // advance list and decrease dependency. |
| a = a[1:] |
| fronts[i] = a |
| if len(a) > 1 && counts[a[0]] > 0 { |
| counts[a[0]]-- |
| } |
| |
| if n == 0 { // may be head of other lists as well |
| counts[f] = index |
| index-- |
| } |
| continue outer // progress |
| } |
| } |
| |
| for _, a := range fronts { |
| if len(a) > 0 { |
| // Detected a cycle. Fire at will to make progress. |
| counts[a[0]] = 0 |
| continue outer |
| } |
| } |
| break |
| } |
| |
| return counts |
| } |