blob: b61bb8205c0b8470455e022fbf68c987f0ec5e7a [file] [log] [blame]
// 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 (
"fmt"
"sort"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/token"
"cuelang.org/go/internal/core/adt"
)
// Modes:
// raw: as is
// def: merge structs, print reset as is.
//
// Possible simplifications in def mode:
// - merge contents of multiple _literal_ structs.
// - this is not possible if some of the elements are bulk optional
// (or is it?).
// - still do not ever resolve references.
// - to do this, fields must be pre-linked to their destinations.
// - use astutil.Sanitize to resolve shadowing and imports.
//
//
// Categories of printing:
// - concrete
// - optionals
// - references
// - constraints
//
// Mixed mode is also not supported in the old implementation (at least not
// correctly). It requires references to resolve properly, backtracking to
// a common root and prefixing that to the reference. This is now possible
// with the Environment construct and could be done later.
func (e *exporter) expr(v adt.Expr) (result ast.Expr) {
switch x := v.(type) {
case nil:
return nil
case *adt.Vertex:
if len(x.Conjuncts) == 0 || x.IsData() {
// Treat as literal value.
return e.value(x)
} // Should this be the arcs label?
a := []conjunct{}
for _, c := range x.Conjuncts {
a = append(a, conjunct{c, 0})
}
return e.mergeValues(adt.InvalidLabel, x, a, x.Conjuncts...)
case *adt.StructLit:
c := adt.MakeRootConjunct(nil, x)
return e.mergeValues(adt.InvalidLabel, nil, []conjunct{{c: c, up: 0}}, c)
case adt.Value:
return e.value(x) // Use conjuncts.
default:
return e.adt(v, nil)
}
}
// Piece out values:
// For a struct, piece out conjuncts that are already values. Those can be
// unified. All other conjuncts are added verbatim.
func (x *exporter) mergeValues(label adt.Feature, src *adt.Vertex, a []conjunct, orig ...adt.Conjunct) ast.Expr {
e := conjuncts{
exporter: x,
values: &adt.Vertex{},
fields: map[adt.Feature]field{},
}
_, saved := e.pushFrame(orig)
defer e.popFrame(saved)
for _, c := range a {
e.top().upCount = c.up
x := c.c.Expr()
e.addExpr(c.c.Env, x)
}
s := x.top().scope
// Unify values only for one level.
if len(e.values.Conjuncts) > 0 {
e.values.Finalize(e.ctx)
e.exprs = append(e.exprs, e.value(e.values, e.values.Conjuncts...))
}
// Collect and order set of fields.
fields := []adt.Feature{}
for f := range e.fields {
fields = append(fields, f)
}
// Sort fields in case features lists are missing to ensure
// predictability. Also sort in reverse order, so that bugs
// are more likely exposed.
sort.Slice(fields, func(i, j int) bool {
return fields[i] > fields[j]
})
m := sortArcs(extractFeatures(e.structs))
sort.SliceStable(fields, func(i, j int) bool {
if m[fields[j]] == 0 {
return m[fields[i]] != 0
}
return m[fields[i]] > m[fields[j]]
})
if len(e.fields) == 0 && !e.hasEllipsis {
switch len(e.exprs) {
case 0:
if len(e.structs) > 0 {
return ast.NewStruct()
}
return ast.NewIdent("_")
case 1:
return e.exprs[0]
case 2:
// Simplify.
return ast.NewBinExpr(token.AND, e.exprs...)
}
}
for _, x := range e.exprs {
s.Elts = append(s.Elts, &ast.EmbedDecl{Expr: x})
}
for _, f := range fields {
field := e.fields[f]
c := field.conjuncts
label := e.stringLabel(f)
if f.IsDef() {
x.inDefinition++
}
a := []adt.Conjunct{}
for _, cc := range c {
a = append(a, cc.c)
}
d := &ast.Field{Label: label}
top := e.frame(0)
if fr, ok := top.fields[f]; ok && fr.alias != "" {
setFieldAlias(d, fr.alias)
fr.node = d
top.fields[f] = fr
}
d.Value = e.mergeValues(f, nil, c, a...)
if f.IsDef() {
x.inDefinition--
}
if isOptional(a) {
d.Optional = token.Blank.Pos()
}
if x.cfg.ShowDocs {
docs := extractDocs(src, a)
ast.SetComments(d, docs)
}
if x.cfg.ShowAttributes {
d.Attrs = ExtractFieldAttrs(a)
}
s.Elts = append(s.Elts, d)
}
if e.hasEllipsis {
s.Elts = append(s.Elts, &ast.Ellipsis{})
} else if src != nil && src.IsClosed(e.ctx) && e.inDefinition == 0 {
return ast.NewCall(ast.NewIdent("close"), s)
}
return s
}
// Conjuncts if for collecting values of a single vertex.
type conjuncts struct {
*exporter
// Values is used to collect non-struct values.
values *adt.Vertex
exprs []ast.Expr
structs []*adt.StructLit
fields map[adt.Feature]field
hasEllipsis bool
}
func (c *conjuncts) addConjunct(f adt.Feature, env *adt.Environment, n adt.Node) {
x := c.fields[f]
v := adt.MakeRootConjunct(env, n)
x.conjuncts = append(x.conjuncts, conjunct{
c: v,
up: c.top().upCount,
})
// x.upCounts = append(x.upCounts, c.top().upCount)
c.fields[f] = x
}
type field struct {
docs []*ast.CommentGroup
conjuncts []conjunct
}
type conjunct struct {
c adt.Conjunct
up int32
}
func (e *conjuncts) addExpr(env *adt.Environment, x adt.Expr) {
switch x := x.(type) {
case *adt.StructLit:
e.top().upCount++
// Only add if it only has no bulk fields or elipsis.
if isComplexStruct(x) {
_, saved := e.pushFrame(nil)
e.exprs = append(e.exprs, e.adt(x, nil))
e.popFrame(saved)
return
}
// Used for sorting.
e.structs = append(e.structs, x)
for _, d := range x.Decls {
var label adt.Feature
switch f := d.(type) {
case *adt.Field:
label = f.Label
case *adt.OptionalField:
// TODO: mark optional here.
label = f.Label
case *adt.Ellipsis:
e.hasEllipsis = true
continue
case adt.Expr:
e.addExpr(env, f)
continue
// TODO: also handle dynamic fields
default:
panic(fmt.Sprintf("Unexpected type %T", d))
}
e.addConjunct(label, env, d)
}
e.top().upCount--
case adt.Value: // other values.
switch v := x.(type) {
case nil:
case *adt.Top:
default:
e.values.AddConjunct(adt.MakeRootConjunct(env, x)) // GOBBLE TOP
case *adt.Vertex:
e.structs = append(e.structs, v.Structs...)
switch y := v.BaseValue.(type) {
case *adt.ListMarker:
a := []ast.Expr{}
for _, x := range v.Elems() {
a = append(a, e.expr(x))
}
if !v.IsClosed(e.ctx) {
v := &adt.Vertex{}
v.MatchAndInsert(e.ctx, v)
a = append(a, &ast.Ellipsis{Type: e.expr(v)})
}
e.exprs = append(e.exprs, ast.NewList(a...))
return
case *adt.StructMarker:
x = nil
case adt.Value:
e.values.AddConjunct(adt.MakeRootConjunct(env, y)) // GOBBLE TOP
}
// generated, only consider arcs.
for _, a := range v.Arcs {
a.Finalize(e.ctx) // TODO: should we do this?
e.addConjunct(a.Label, env, a)
}
// e.exprs = append(e.exprs, e.value(v, v.Conjuncts...))
}
case *adt.BinaryExpr:
switch {
case x.Op == adt.AndOp:
e.addExpr(env, x.X)
e.addExpr(env, x.Y)
case isSelfContained(x):
e.values.AddConjunct(adt.MakeRootConjunct(env, x))
default:
e.exprs = append(e.exprs, e.expr(x))
}
default:
if isSelfContained(x) {
e.values.AddConjunct(adt.MakeRootConjunct(env, x))
} else {
e.exprs = append(e.exprs, e.expr(x))
}
}
}
// TODO: find a better way to annotate optionality. Maybe a special conjunct
// or store it in the field information?
func isOptional(a []adt.Conjunct) bool {
if len(a) == 0 {
return false
}
for _, c := range a {
switch f := c.Source().(type) {
case nil:
return false
case *ast.Field:
if f.Optional == token.NoPos {
return false
}
}
}
return true
}
func isComplexStruct(s *adt.StructLit) bool {
for _, d := range s.Decls {
switch x := d.(type) {
case *adt.Field:
// TODO: remove this and also handle field annotation in expr().
// This allows structs to be merged. Ditto below.
if x.Src != nil {
if _, ok := x.Src.Label.(*ast.Alias); ok {
return ok
}
}
case *adt.OptionalField:
if x.Src != nil {
if _, ok := x.Src.Label.(*ast.Alias); ok {
return ok
}
}
case adt.Expr:
case *adt.Ellipsis:
if x.Value != nil {
return true
}
default:
return true
}
}
return false
}
func isSelfContained(expr adt.Expr) bool {
switch x := expr.(type) {
case *adt.BinaryExpr:
return isSelfContained(x.X) && isSelfContained(x.Y)
case *adt.UnaryExpr:
return isSelfContained(x.X)
case *adt.BoundExpr:
return isSelfContained(x.Expr)
case adt.Value:
return true
}
return false
}