blob: 91aeacd4d5f3cab5dbec399b2ff804dd8b0043c9 [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"
"strings"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/ast/astutil"
"cuelang.org/go/cue/literal"
"cuelang.org/go/cue/token"
"cuelang.org/go/internal/core/adt"
)
func (e *exporter) bareValue(v adt.Value) ast.Expr {
switch x := v.(type) {
case *adt.Vertex:
return e.vertex(x)
case adt.Value:
a := &adt.Vertex{BaseValue: x}
return e.vertex(a)
default:
panic("unreachable")
}
// TODO: allow a Value context wrapper.
}
// TODO: if the original value was a single reference, we could replace the
// value with a reference in graph mode.
func (e *exporter) vertex(n *adt.Vertex) (result ast.Expr) {
var attrs []*ast.Attribute
if e.cfg.ShowAttributes {
attrs = ExtractDeclAttrs(n)
}
switch x := n.BaseValue.(type) {
case nil:
// bare
case *adt.StructMarker:
result = e.structComposite(n, attrs)
case *adt.ListMarker:
if e.showArcs(n) || attrs != nil {
result = e.structComposite(n, attrs)
} else {
result = e.listComposite(n)
}
case *adt.Bottom:
switch {
case e.cfg.ShowErrors && x.ChildError:
// TODO(perf): use precompiled arc statistics
if len(n.Arcs) > 0 && n.Arcs[0].Label.IsInt() && !e.showArcs(n) && attrs == nil {
result = e.listComposite(n)
} else {
result = e.structComposite(n, attrs)
}
case !x.IsIncomplete() || len(n.Conjuncts) == 0:
result = e.bottom(x)
}
case adt.Value:
if e.showArcs(n) || attrs != nil {
result = e.structComposite(n, attrs)
} else {
result = e.value(x, n.Conjuncts...)
}
default:
panic("unknown value")
}
if result == nil {
// fall back to expression mode
a := []ast.Expr{}
for _, c := range n.Conjuncts {
a = append(a, e.expr(c.Expr()))
}
result = ast.NewBinExpr(token.AND, a...)
}
return result
}
// TODO: do something more principled. Best would be to have a similar
// mechanism in ast.Ident as others do.
func stripRefs(x ast.Expr) ast.Expr {
ast.Walk(x, nil, func(n ast.Node) {
switch x := n.(type) {
case *ast.Ident:
switch x.Node.(type) {
case *ast.ImportSpec:
default:
x.Node = nil
}
}
})
return x
}
func (e *exporter) value(n adt.Value, a ...adt.Conjunct) (result ast.Expr) {
if e.cfg.TakeDefaults {
n = adt.Default(n)
}
// Evaluate arc if needed?
// if e.concrete && !adt.IsConcrete(n.Value) {
// return e.errf("non-concrete value: %v", e.bareValue(n.Value))
// }
switch x := n.(type) {
case *adt.Bottom:
result = e.bottom(x)
case *adt.Null:
result = e.null(x)
case *adt.Bool:
result = e.bool(x)
case *adt.Num:
result = e.num(x, a)
case *adt.String:
result = e.string(x, a)
case *adt.Bytes:
result = e.bytes(x, a)
case *adt.BasicType:
result = e.basicType(x)
case *adt.Top:
result = ast.NewIdent("_")
case *adt.BoundValue:
result = e.boundValue(x)
case *adt.Builtin:
result = e.builtin(x)
case *adt.BuiltinValidator:
result = e.builtinValidator(x)
case *adt.Vertex:
result = e.vertex(x)
case *adt.Conjunction:
switch len(x.Values) {
case 0:
return ast.NewIdent("_")
case 1:
if e.cfg.Simplify {
return e.expr(x.Values[0])
}
return e.bareValue(x.Values[0])
}
a := []adt.Value{}
b := boundSimplifier{e: e}
for _, v := range x.Values {
if !e.cfg.Simplify || !b.add(v) {
a = append(a, v)
}
}
result = b.expr(e.ctx)
if result == nil {
a = x.Values
}
for _, x := range a {
result = wrapBin(result, e.bareValue(x), adt.AndOp)
}
case *adt.Disjunction:
a := []ast.Expr{}
for i, v := range x.Values {
var expr ast.Expr
if e.cfg.Simplify {
expr = e.bareValue(v)
} else {
expr = e.expr(v)
}
if i < x.NumDefaults {
expr = &ast.UnaryExpr{Op: token.MUL, X: expr}
}
a = append(a, expr)
}
result = ast.NewBinExpr(token.OR, a...)
default:
panic(fmt.Sprintf("unsupported type %T", x))
}
// TODO: Add comments from original.
return result
}
func (e *exporter) bottom(n *adt.Bottom) *ast.BottomLit {
err := &ast.BottomLit{}
if x := n.Err; x != nil {
msg := x.Error()
comment := &ast.Comment{Text: "// " + msg}
err.AddComment(&ast.CommentGroup{
Line: true,
Position: 2,
List: []*ast.Comment{comment},
})
}
return err
}
func (e *exporter) null(n *adt.Null) *ast.BasicLit {
return &ast.BasicLit{Kind: token.NULL, Value: "null"}
}
func (e *exporter) bool(n *adt.Bool) (b *ast.BasicLit) {
return ast.NewBool(n.B)
}
func extractBasic(a []adt.Conjunct) *ast.BasicLit {
for _, v := range a {
if b, ok := v.Source().(*ast.BasicLit); ok {
return &ast.BasicLit{Kind: b.Kind, Value: b.Value}
}
}
return nil
}
func (e *exporter) num(n *adt.Num, orig []adt.Conjunct) *ast.BasicLit {
// TODO: take original formatting into account.
if b := extractBasic(orig); b != nil {
return b
}
kind := token.FLOAT
if n.K&adt.IntKind != 0 {
kind = token.INT
}
s := n.X.String()
if kind == token.FLOAT && !strings.ContainsAny(s, "eE.") {
s += "."
}
return &ast.BasicLit{Kind: kind, Value: s}
}
func (e *exporter) string(n *adt.String, orig []adt.Conjunct) *ast.BasicLit {
// TODO: take original formatting into account.
if b := extractBasic(orig); b != nil {
return b
}
s := literal.String.WithOptionalTabIndent(len(e.stack)).Quote(n.Str)
return &ast.BasicLit{
Kind: token.STRING,
Value: s,
}
}
func (e *exporter) bytes(n *adt.Bytes, orig []adt.Conjunct) *ast.BasicLit {
// TODO: take original formatting into account.
if b := extractBasic(orig); b != nil {
return b
}
s := literal.Bytes.WithOptionalTabIndent(len(e.stack)).Quote(string(n.B))
return &ast.BasicLit{
Kind: token.STRING,
Value: s,
}
}
func (e *exporter) basicType(n *adt.BasicType) ast.Expr {
// TODO: allow multi-bit types?
return ast.NewIdent(n.K.String())
}
func (e *exporter) boundValue(n *adt.BoundValue) ast.Expr {
return &ast.UnaryExpr{Op: n.Op.Token(), X: e.value(n.Value)}
}
func (e *exporter) builtin(x *adt.Builtin) ast.Expr {
if x.Package == 0 {
return ast.NewIdent(x.Name)
}
spec := ast.NewImport(nil, x.Package.StringValue(e.index))
info, _ := astutil.ParseImportSpec(spec)
ident := ast.NewIdent(info.Ident)
ident.Node = spec
return ast.NewSel(ident, x.Name)
}
func (e *exporter) builtinValidator(n *adt.BuiltinValidator) ast.Expr {
call := ast.NewCall(e.builtin(n.Builtin))
for _, a := range n.Args {
call.Args = append(call.Args, e.value(a))
}
return call
}
func (e *exporter) listComposite(v *adt.Vertex) ast.Expr {
l := &ast.ListLit{}
for _, a := range v.Arcs {
if !a.Label.IsInt() {
continue
}
elem := e.vertex(a)
docs := ExtractDoc(a)
ast.SetComments(elem, docs)
l.Elts = append(l.Elts, elem)
}
return l
}
func (e exporter) showArcs(v *adt.Vertex) bool {
p := e.cfg
if !p.ShowHidden && !p.ShowDefinitions {
return false
}
for _, a := range v.Arcs {
switch {
case a.Label.IsDef() && p.ShowDefinitions:
return true
case a.Label.IsHidden() && p.ShowHidden:
return true
}
}
return false
}
func (e *exporter) structComposite(v *adt.Vertex, attrs []*ast.Attribute) ast.Expr {
s, saved := e.pushFrame(v.Conjuncts)
e.top().upCount++
defer func() {
e.top().upCount--
e.popFrame(saved)
}()
showRegular := false
switch x := v.BaseValue.(type) {
case *adt.StructMarker:
showRegular = true
case *adt.ListMarker:
// As lists may be long, put them at the end.
defer e.addEmbed(e.listComposite(v))
case *adt.Bottom:
if !e.cfg.ShowErrors || !x.ChildError {
// Should not be reachable, but just in case. The output will be
// correct.
e.addEmbed(e.value(x))
return s
}
// Always also show regular fields, even when list, as we are in
// debugging mode.
showRegular = true
// TODO(perf): do something better
for _, a := range v.Arcs {
if a.Label.IsInt() {
defer e.addEmbed(e.listComposite(v))
break
}
}
case adt.Value:
e.addEmbed(e.value(x))
}
for _, a := range attrs {
s.Elts = append(s.Elts, a)
}
p := e.cfg
for _, label := range VertexFeatures(v) {
show := false
switch label.Typ() {
case adt.StringLabel:
show = showRegular
case adt.IntLabel:
continue
case adt.DefinitionLabel:
show = p.ShowDefinitions
case adt.HiddenLabel, adt.HiddenDefinitionLabel:
show = p.ShowHidden && label.PkgID(e.ctx) == e.pkgID
}
if !show {
continue
}
f := &ast.Field{Label: e.stringLabel(label)}
e.addField(label, f, f.Value)
if label.IsDef() {
e.inDefinition++
}
arc := v.Lookup(label)
switch {
case arc == nil:
if !p.ShowOptional {
continue
}
f.Optional = token.NoSpace.Pos()
arc = &adt.Vertex{Label: label}
v.MatchAndInsert(e.ctx, arc)
if len(arc.Conjuncts) == 0 {
continue
}
// fall back to expression mode.
f.Value = stripRefs(e.expr(arc))
// TODO: remove use of stripRefs.
// f.Value = e.expr(arc)
default:
f.Value = e.vertex(arc)
}
if label.IsDef() {
e.inDefinition--
}
if p.ShowAttributes {
f.Attrs = ExtractFieldAttrs(arc)
}
if p.ShowDocs {
docs := ExtractDoc(arc)
ast.SetComments(f, docs)
}
s.Elts = append(s.Elts, f)
}
return s
}