| // 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 || e.cfg.Final: |
| 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 |
| } |