internal/core/export: explicit generation of adt types
Change-Id: Ib51a41d9103c6f9e684395a009c8c3b81462c273
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6622
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/internal/core/export/adt.go b/internal/core/export/adt.go
new file mode 100644
index 0000000..49bb789
--- /dev/null
+++ b/internal/core/export/adt.go
@@ -0,0 +1,392 @@
+// 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"
+ "strconv"
+ "strings"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/ast/astutil"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal/core/adt"
+)
+
+func (e *exporter) ident(x adt.Feature) *ast.Ident {
+ s := e.ctx.IndexToString(int64(x.Index()))
+ if !ast.IsValidIdent(s) {
+ panic(s + " is not a valid identifier")
+ }
+ return ast.NewIdent(s)
+}
+
+func (e *exporter) adt(expr adt.Expr, conjuncts []adt.Conjunct) ast.Expr {
+ switch x := expr.(type) {
+ case adt.Value:
+ return e.expr(x)
+
+ case *adt.ListLit:
+ a := []ast.Expr{}
+ for _, x := range x.Elems {
+ a = append(a, e.elem(x))
+ }
+ return ast.NewList(a...)
+
+ case *adt.StructLit:
+ // TODO: should we use pushFrame here?
+ // _, saved := e.pushFrame([]adt.Conjunct{adt.MakeConjunct(nil, x)})
+ // defer e.popFrame(saved)
+ // s := e.frame(0).scope
+
+ s := &ast.StructLit{}
+
+ for _, d := range x.Decls {
+ s.Elts = append(s.Elts, e.decl(d))
+ }
+ return s
+
+ case *adt.FieldReference:
+ f := e.frame(x.UpCount)
+ ident := e.ident(x.Label)
+ entry := f.fields[x.Label]
+ entry.references = append(entry.references, ident)
+ return ident
+
+ case *adt.LabelReference:
+ // get potential label from Source. Otherwise use X.
+ f := e.frame(x.UpCount)
+ var ident *ast.Ident
+ if f.field == nil {
+ // This can happen when the LabelReference is evaluated outside of
+ // normal evaluation, that is, if a pattern constraint or
+ // additional constraint is evaluated by itself.
+ return ast.NewIdent("string")
+ }
+ list, ok := f.field.Label.(*ast.ListLit)
+ if !ok || len(list.Elts) != 1 {
+ panic("label reference to non-pattern constraint field or invalid list")
+ }
+ if a, ok := list.Elts[0].(*ast.Alias); ok {
+ ident = ast.NewIdent(a.Ident.Name)
+ } else {
+ ident = ast.NewIdent("X" + strconv.Itoa(e.unique))
+ e.unique++
+ list.Elts[0] = &ast.Alias{
+ Ident: ast.NewIdent(ident.Name),
+ Expr: list.Elts[0],
+ }
+ }
+ ident.Scope = f.field
+ ident.Node = f.labelExpr
+ return ident
+
+ case *adt.DynamicReference:
+ // get potential label from Source. Otherwise use X.
+ ident := ast.NewIdent("X")
+ f := e.frame(x.UpCount)
+ ident.Scope = f.field
+ ident.Node = f.field
+ return ident
+
+ case *adt.ImportReference:
+ importPath := x.ImportPath.StringValue(e.index)
+ spec := ast.NewImport(nil, importPath)
+
+ info, _ := astutil.ParseImportSpec(spec)
+ name := info.PkgName
+ if x.Label != 0 {
+ name = x.Label.StringValue(e.index)
+ if name != info.PkgName {
+ spec.Name = ast.NewIdent(name)
+ }
+ }
+ ident := ast.NewIdent(name)
+ ident.Node = spec
+ return ident
+
+ case *adt.LetReference:
+ f := e.frame(x.UpCount)
+ let := f.let[x.X]
+ if let == nil {
+ if f.let == nil {
+ f.let = map[adt.Expr]*ast.LetClause{}
+ }
+ let = &ast.LetClause{Expr: e.expr(x.X)}
+ f.let[x.X] = let
+ }
+ ident := e.ident(x.Label)
+ ident.Node = let
+ ident.Scope = f.scope
+ return ident
+
+ case *adt.SelectorExpr:
+ return &ast.SelectorExpr{
+ X: e.expr(x.X),
+ Sel: e.ident(x.Sel),
+ }
+
+ case *adt.IndexExpr:
+ return &ast.IndexExpr{
+ X: e.expr(x.X),
+ Index: e.expr(x.Index),
+ }
+
+ case *adt.SliceExpr:
+ var lo, hi ast.Expr
+ if x.Lo != nil {
+ lo = e.expr(x.Lo)
+ }
+ if x.Hi != nil {
+ hi = e.expr(x.Hi)
+ }
+ // TODO: Stride not yet? implemented.
+ // if x.Stride != nil {
+ // stride = e.expr(x.Stride)
+ // }
+ return &ast.SliceExpr{X: e.expr(x.X), Low: lo, High: hi}
+
+ case *adt.Interpolation:
+ t := &ast.Interpolation{}
+ multiline := false
+ // TODO: mark formatting in interpolation itself.
+ for i := 0; i < len(x.Parts); i += 2 {
+ str := x.Parts[i].(*adt.String).Str
+ if strings.IndexByte(str, '\n') >= 0 {
+ multiline = true
+ break
+ }
+ }
+ quote := `"`
+ if multiline {
+ quote = `"""`
+ }
+ prefix := quote
+ suffix := `\(`
+ for i, elem := range x.Parts {
+ if i%2 == 1 {
+ t.Elts = append(t.Elts, e.expr(elem))
+ } else {
+ buf := []byte(prefix)
+ if i == len(x.Parts)-1 {
+ suffix = quote
+ }
+ str := elem.(*adt.String).Str
+ if multiline {
+ buf = appendEscapeMulti(buf, str, '"')
+ } else {
+ buf = appendEscaped(buf, str, '"', true)
+ }
+ buf = append(buf, suffix...)
+ t.Elts = append(t.Elts, &ast.BasicLit{
+ Kind: token.STRING,
+ Value: string(buf),
+ })
+ }
+ prefix = ")"
+ }
+ return t
+
+ case *adt.BoundExpr:
+ return &ast.UnaryExpr{
+ Op: x.Op.Token(),
+ X: e.expr(x.Expr),
+ }
+
+ case *adt.UnaryExpr:
+ return &ast.UnaryExpr{
+ Op: x.Op.Token(),
+ X: e.expr(x.X),
+ }
+
+ case *adt.BinaryExpr:
+ return &ast.BinaryExpr{
+ Op: x.Op.Token(),
+ X: e.expr(x.X),
+ Y: e.expr(x.Y),
+ }
+
+ case *adt.CallExpr:
+ a := []ast.Expr{}
+ for _, arg := range x.Args {
+ v := e.expr(arg)
+ if v == nil {
+ e.expr(arg)
+ panic("")
+ }
+ a = append(a, v)
+ }
+ fun := e.expr(x.Fun)
+ return &ast.CallExpr{Fun: fun, Args: a}
+
+ case *adt.DisjunctionExpr:
+ a := []ast.Expr{}
+ for _, d := range x.Values {
+ v := e.expr(d.Val)
+ if d.Default {
+ v = &ast.UnaryExpr{Op: token.MUL, X: v}
+ }
+ a = append(a, v)
+ }
+ return ast.NewBinExpr(token.OR, a...)
+
+ default:
+ panic(fmt.Sprintf("unknown field %T", x))
+ }
+}
+
+func (e *exporter) decl(d adt.Decl) ast.Decl {
+ switch x := d.(type) {
+ case adt.Elem:
+ return e.elem(x)
+
+ case *adt.Field:
+ e.setDocs(x)
+ f := &ast.Field{
+ Label: e.stringLabel(x.Label),
+ Value: e.expr(x.Value),
+ }
+ e.addField(x.Label, f.Value)
+ // extractDocs(nil)
+ return f
+
+ case *adt.OptionalField:
+ e.setDocs(x)
+ f := &ast.Field{
+ Label: e.stringLabel(x.Label),
+ Optional: token.NoSpace.Pos(),
+ Value: e.expr(x.Value),
+ }
+ e.addField(x.Label, f.Value)
+ // extractDocs(nil)
+ return f
+
+ case *adt.BulkOptionalField:
+ e.setDocs(x)
+ // set bulk in frame.
+ frame := e.frame(0)
+
+ expr := e.expr(x.Filter)
+ frame.labelExpr = expr // see astutil.Resolve.
+
+ if x.Label != 0 {
+ expr = &ast.Alias{Ident: e.ident(x.Label), Expr: expr}
+ }
+ f := &ast.Field{
+ Label: ast.NewList(expr),
+ }
+
+ frame.field = f
+
+ f.Value = e.expr(x.Value)
+
+ return f
+
+ case *adt.DynamicField:
+ e.setDocs(x)
+ key := e.expr(x.Key)
+ if _, ok := key.(*ast.Interpolation); !ok {
+ key = &ast.ParenExpr{X: key}
+ }
+ f := &ast.Field{
+ Label: key.(ast.Label),
+ }
+
+ frame := e.frame(0)
+ frame.field = f
+ frame.labelExpr = key
+ // extractDocs(nil)
+
+ f.Value = e.expr(x.Value)
+
+ return f
+
+ default:
+ panic(fmt.Sprintf("unknown field %T", x))
+ }
+}
+
+func (e *exporter) elem(d adt.Elem) ast.Expr {
+
+ switch x := d.(type) {
+ case adt.Expr:
+ return e.expr(x)
+
+ case *adt.Ellipsis:
+ t := &ast.Ellipsis{}
+ if x.Value != nil {
+ t.Type = e.expr(x.Value)
+ }
+ return t
+
+ case adt.Yielder:
+ return e.comprehension(x)
+
+ default:
+ panic(fmt.Sprintf("unknown field %T", x))
+ }
+}
+
+func (e *exporter) comprehension(y adt.Yielder) ast.Expr {
+ c := &ast.Comprehension{}
+
+ for {
+ switch x := y.(type) {
+ case *adt.ForClause:
+ value := e.ident(x.Value)
+ clause := &ast.ForClause{
+ Value: value,
+ Source: e.expr(x.Src),
+ }
+ c.Clauses = append(c.Clauses, clause)
+
+ _, saved := e.pushFrame(nil)
+ defer e.popFrame(saved)
+
+ if x.Key != 0 {
+ key := e.ident(x.Key)
+ clause.Key = key
+ e.addField(x.Key, clause)
+ }
+ e.addField(x.Value, clause)
+
+ y = x.Dst
+
+ case *adt.IfClause:
+ clause := &ast.IfClause{Condition: e.expr(x.Condition)}
+ c.Clauses = append(c.Clauses, clause)
+ y = x.Dst
+
+ case *adt.LetClause:
+ clause := &ast.LetClause{Expr: e.expr(x.Expr)}
+ c.Clauses = append(c.Clauses, clause)
+
+ _, saved := e.pushFrame(nil)
+ defer e.popFrame(saved)
+
+ e.addField(x.Label, clause)
+
+ y = x.Dst
+
+ case *adt.ValueClause:
+ c.Value = e.expr(x.StructLit)
+ return c
+
+ default:
+ panic(fmt.Sprintf("unknown field %T", x))
+ }
+ }
+
+}
diff --git a/internal/core/export/export.go b/internal/core/export/export.go
index 76bbe8a..9c507f4 100644
--- a/internal/core/export/export.go
+++ b/internal/core/export/export.go
@@ -18,50 +18,80 @@
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/ast/astutil"
"cuelang.org/go/cue/errors"
+ "cuelang.org/go/internal"
"cuelang.org/go/internal/core/adt"
"cuelang.org/go/internal/core/eval"
)
+const debug = false
+
type Profile struct {
Simplify bool
// TODO:
// IncludeDocs
+ ShowOptional bool
+ ShowDefinitions bool
+ ShowHidden bool
+ ShowDocs bool
+ ShowAttributes bool
+
+ // AllowErrorType
+ // Use unevaluated conjuncts for these error types
+ // IgnoreRecursive
+
+ // TODO: recurse over entire tree to determine transitive closure
+ // of what needs to be printed.
+ // IncludeDependencies bool
}
var Simplified = &Profile{
Simplify: true,
+ ShowDocs: true,
}
-var Raw = &Profile{}
+var Raw = &Profile{
+ ShowOptional: true,
+ ShowDefinitions: true,
+ ShowHidden: true,
+ ShowDocs: true,
+}
+
+var All = &Profile{
+ Simplify: true,
+ ShowOptional: true,
+ ShowDefinitions: true,
+ ShowHidden: true,
+ ShowDocs: true,
+ ShowAttributes: true,
+}
// Concrete
// Def exports v as a definition.
func Def(r adt.Runtime, v *adt.Vertex) (*ast.File, errors.Error) {
- p := Profile{}
- return p.Def(r, v)
+ return All.Def(r, v)
}
// Def exports v as a definition.
func (p *Profile) Def(r adt.Runtime, v *adt.Vertex) (*ast.File, errors.Error) {
e := newExporter(p, r, v)
+ if v.Label.IsDef() {
+ e.inDefinition++
+ }
expr := e.expr(v)
- return e.toFile(expr)
+ if v.Label.IsDef() {
+ e.inDefinition--
+ if s, ok := expr.(*ast.StructLit); ok {
+ expr = ast.NewStruct(
+ ast.Embed(ast.NewIdent("#_def")),
+ ast.NewIdent("#_def"), s,
+ )
+ }
+ }
+ return e.toFile(v, expr)
}
-// // TODO: remove: must be able to fall back to arcs if there are no
-// // conjuncts.
-// func Conjuncts(conjuncts ...adt.Conjunct) (*ast.File, errors.Error) {
-// var e Exporter
-// // for now just collect and turn into an big conjunction.
-// var a []ast.Expr
-// for _, c := range conjuncts {
-// a = append(a, e.expr(c.Expr()))
-// }
-// return e.toFile(ast.NewBinExpr(token.AND, a...))
-// }
-
func Expr(r adt.Runtime, n adt.Expr) (ast.Expr, errors.Error) {
return Simplified.Expr(r, n)
}
@@ -71,20 +101,43 @@
return e.expr(n), nil
}
-func (e *exporter) toFile(x ast.Expr) (*ast.File, errors.Error) {
+func (e *exporter) toFile(v *adt.Vertex, x ast.Expr) (*ast.File, errors.Error) {
f := &ast.File{}
+ pkgName := ""
+ pkg := &ast.Package{}
+ for _, c := range v.Conjuncts {
+ f, _ := c.Source().(*ast.File)
+ if f == nil {
+ continue
+ }
+
+ if _, name, _ := internal.PackageInfo(f); name != "" {
+ pkgName = name
+ }
+
+ if e.cfg.ShowDocs {
+ if doc := internal.FileComment(f); doc != nil {
+ ast.AddComment(pkg, doc)
+ }
+ }
+ }
+
+ if pkgName != "" {
+ pkg.Name = ast.NewIdent(pkgName)
+ f.Decls = append(f.Decls, pkg)
+ }
+
switch st := x.(type) {
case nil:
panic("null input")
case *ast.StructLit:
- f.Decls = st.Elts
+ f.Decls = append(f.Decls, st.Elts...)
default:
f.Decls = append(f.Decls, &ast.EmbedDecl{Expr: x})
}
-
if err := astutil.Sanitize(f); err != nil {
err := errors.Promote(err, "export")
return f, errors.Append(e.errs, err)
@@ -106,15 +159,17 @@
}
v := e.value(n, n.Conjuncts...)
- return e.toFile(v)
+ return e.toFile(n, v)
}
func Value(r adt.Runtime, n adt.Value) (ast.Expr, errors.Error) {
return Simplified.Value(r, n)
}
+// Should take context.
func (p *Profile) Value(r adt.Runtime, n adt.Value) (ast.Expr, errors.Error) {
e := exporter{
+ ctx: eval.NewContext(r, nil),
cfg: p,
index: r,
}
@@ -123,9 +178,8 @@
}
type exporter struct {
- cfg *Profile
- errs errors.Error
- concrete bool
+ cfg *Profile // Make value todo
+ errs errors.Error
ctx *adt.OpContext
@@ -133,6 +187,10 @@
// For resolving up references.
stack []frame
+
+ inDefinition int // for close() wrapping.
+
+ unique int
}
func newExporter(p *Profile, r adt.Runtime, v *adt.Vertex) *exporter {
@@ -149,23 +207,85 @@
scope *ast.StructLit
todo []completeFunc
+ docSources []adt.Conjunct
+
+ // For resolving dynamic fields.
+ field *ast.Field
+ labelExpr ast.Expr
+ upCount int32 // for off-by-one handling
+
+ // labeled fields
+ fields map[adt.Feature]entry
+ let map[adt.Expr]*ast.LetClause
+
// field to new field
mapped map[adt.Node]ast.Node
}
-// func (e *Exporter) pushFrame(d *adt.StructLit, s *ast.StructLit) (saved []frame) {
-// saved := e.stack
-// e.stack = append(e.stack, frame{scope: s, mapped: map[adt.Node]ast.Node{}})
-// return saved
-// }
+type entry struct {
+ node ast.Node
-// func (e *Exporter) popFrame(saved []frame) {
-// f := e.stack[len(e.stack)-1]
+ references []*ast.Ident
+}
-// for _, f
+func (e *exporter) addField(label adt.Feature, n ast.Node) {
+ frame := e.top()
+ entry := frame.fields[label]
+ entry.node = n
+ frame.fields[label] = entry
+}
-// e.stack = saved
-// }
+func (e *exporter) pushFrame(conjuncts []adt.Conjunct) (s *ast.StructLit, saved []frame) {
+ saved = e.stack
+ s = &ast.StructLit{}
+ e.stack = append(e.stack, frame{
+ scope: s,
+ mapped: map[adt.Node]ast.Node{},
+ fields: map[adt.Feature]entry{},
+ docSources: conjuncts,
+ })
+ return s, saved
+}
+
+func (e *exporter) popFrame(saved []frame) {
+ top := e.stack[len(e.stack)-1]
+
+ for _, f := range top.fields {
+ for _, r := range f.references {
+ r.Node = f.node
+ }
+ }
+
+ e.stack = saved
+}
+
+func (e *exporter) top() *frame {
+ return &(e.stack[len(e.stack)-1])
+}
+
+func (e *exporter) frame(upCount int32) *frame {
+ for i := len(e.stack) - 1; i >= 0; i-- {
+ f := &(e.stack[i])
+ if upCount <= (f.upCount - 1) {
+ return f
+ }
+ upCount -= f.upCount
+ }
+ if debug {
+ // This may be valid when exporting incomplete references. These are
+ // not yet handled though, so find a way to catch them when debugging
+ // printing of values that are supposed to be complete.
+ panic("unreachable reference")
+ }
+
+ return &frame{}
+}
+
+func (e *exporter) setDocs(x adt.Node) {
+ f := e.stack[len(e.stack)-1]
+ f.docSources = []adt.Conjunct{adt.MakeConjunct(nil, x)}
+ e.stack[len(e.stack)-1] = f
+}
// func (e *Exporter) promise(upCount int32, f completeFunc) {
// e.todo = append(e.todo, f)
diff --git a/internal/core/export/export_test.go b/internal/core/export/export_test.go
index 8c7bc83..77032b0 100644
--- a/internal/core/export/export_test.go
+++ b/internal/core/export/export_test.go
@@ -22,6 +22,7 @@
"cuelang.org/go/cue/errors"
"cuelang.org/go/cue/format"
"cuelang.org/go/internal/core/compile"
+ "cuelang.org/go/internal/core/eval"
"cuelang.org/go/internal/core/runtime"
"cuelang.org/go/internal/cuetxtar"
"github.com/rogpeppe/go-internal/txtar"
@@ -45,6 +46,10 @@
if errs != nil {
t.Fatal(errs)
}
+ v.Finalize(eval.NewContext(r, v))
+
+ // TODO: do we need to evaluate v? In principle not necessary.
+ // v.Finalize(eval.NewContext(r, v))
file, errs := Def(r, v)
errors.Print(t, errs, nil)
@@ -70,20 +75,56 @@
-- in.cue --
package test
-import pkg2 "example.com/foo/pkg1"
-#pkg1: pkg2.Object
+// // Foo
+// a: [X=string]: [Y=string]: {
+// name: X+Y
+// }
-"Hello \(#pkg1)!"
+// [Y=string]: [X=string]: name: {Y+X}
+// {
+// name: X.other + Y
+// other: string
+// }
+
+// c: [X=string]: X
+
+// #pkg1: Object
+
+// "Hello \(#pkg1)!"
+
+
+// Object: "World"
+
+// // A Foo fooses stuff.
+// foos are instances of Foo.
+// foos: [string]: {}
+
+// // // My first little foo.
+// foos: MyFoo: {}
+
+bar: 3
+d2: C="foo\(bar)": {
+ name: "xx"
+ foo: C.name
+}
+
`
archive := txtar.Parse([]byte(in))
a := cuetxtar.Load(archive, "/tmp/test")
+ if err := a[0].Err; err != nil {
+ t.Fatal(err)
+ }
+
+ // x := a[0].Files[0]
+ // astutil.Sanitize(x)
r := runtime.New()
v, errs := compile.Files(nil, r, a[0].Files...)
if errs != nil {
t.Fatal(errs)
}
+ v.Finalize(eval.NewContext(r, v))
file, errs := Def(r, v)
if errs != nil {
diff --git a/internal/core/export/expr.go b/internal/core/export/expr.go
index c73e556..3edc5f5 100644
--- a/internal/core/export/expr.go
+++ b/internal/core/export/expr.go
@@ -52,26 +52,28 @@
return nil
case *adt.Vertex:
- if len(x.Conjuncts) == 0 {
+ 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(x.Conjuncts...)
+
+ return e.mergeValues(adt.InvalidLabel, x, a, x.Conjuncts...)
case *adt.StructLit:
- return e.mergeValues(adt.MakeConjunct(nil, x))
+ c := adt.MakeConjunct(nil, x)
+ return e.mergeValues(adt.InvalidLabel, nil, []conjunct{{c: c, up: 0}}, c)
case adt.Value:
- e.value(x)
+ return e.value(x) // Use conjuncts.
default:
- if f, ok := x.Source().(*ast.File); ok {
- return &ast.StructLit{Elts: f.Decls}
- }
-
- return v.Source().(ast.Expr)
+ return e.adt(v, nil)
}
- return nil
}
// Piece out values:
@@ -79,17 +81,25 @@
// For a struct, piece out conjuncts that are already values. Those can be
// unified. All other conjuncts are added verbatim.
-func (x *exporter) mergeValues(a ...adt.Conjunct) ast.Expr {
+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][]adt.Conjunct{},
+ fields: map[adt.Feature]field{},
}
+ _, saved := e.pushFrame(orig)
+ defer e.popFrame(saved)
+
for _, c := range a {
- e.addExpr(c.Env, c.Expr())
+ 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)
@@ -97,14 +107,23 @@
}
// Collect and order set of fields.
+
fields := []adt.Feature{}
for f := range e.fields {
fields = append(fields, f)
}
- m := sortArcs(e.exporter.extractFeatures(e.structs))
+
+ // 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[i]] == 0 {
- return m[fields[j]] != 0
+ if m[fields[j]] == 0 {
+ return m[fields[i]] != 0
}
return m[fields[i]] > m[fields[j]]
})
@@ -112,6 +131,9 @@
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]
@@ -121,52 +143,96 @@
}
}
- s := &ast.StructLit{}
for _, x := range e.exprs {
s.Elts = append(s.Elts, &ast.EmbedDecl{Expr: x})
}
for _, f := range fields {
- c := e.fields[f]
- merged := e.mergeValues(c...)
+ 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)
+ }
+
+ merged := e.mergeValues(f, nil, c, a...)
+
+ if f.IsDef() {
+ x.inDefinition--
+ }
+
d := &ast.Field{Label: label, Value: merged}
- if isOptional(c) {
+ 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
}
-// A conjuncts collects values of a single vertex.
+// 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][]adt.Conjunct
+ 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.MakeConjunct(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) {
- switch src := x.Src.(type) {
- case nil:
- panic("now allowed")
- case *ast.StructLit:
- e.exprs = append(e.exprs, src)
- case *ast.File:
- e.exprs = append(e.exprs, &ast.StructLit{Elts: src.Decls})
- }
+ _, saved := e.pushFrame(nil)
+ e.exprs = append(e.exprs, e.adt(x, nil))
+ e.popFrame(saved)
return
}
// Used for sorting.
@@ -178,9 +244,11 @@
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
@@ -189,21 +257,33 @@
default:
panic("unreachable")
}
- c := adt.MakeConjunct(env, d)
- e.fields[label] = append(e.fields[label], c)
+ e.addConjunct(label, env, d)
}
+ e.top().upCount--
case adt.Value: // other values.
if v, ok := x.(*adt.Vertex); ok {
// if !v.IsList() {
- // panic("what to do?")
+ // panic("what to do?") // TO
// }
+ e.structs = append(e.structs, v.Structs...)
+
// generated, only consider arcs.
- e.exprs = append(e.exprs, e.value(v, v.Conjuncts...))
- return
+ for _, a := range v.Arcs {
+ a.Finalize(e.ctx) // TODO: should we do this?
+
+ e.addConjunct(a.Label, env, a)
+ }
+ x = v.Value
+ // e.exprs = append(e.exprs, e.value(v, v.Conjuncts...))
+ // return
}
- e.values.AddConjunct(adt.MakeConjunct(env, x))
+ switch x.(type) {
+ case *adt.StructMarker, *adt.Top:
+ default:
+ e.values.AddConjunct(adt.MakeConjunct(env, x)) // GOBBLE TOP
+ }
case *adt.BinaryExpr:
switch {
@@ -225,9 +305,16 @@
}
}
+// 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
diff --git a/internal/core/export/extract.go b/internal/core/export/extract.go
index c3b11ef..22d901a 100644
--- a/internal/core/export/extract.go
+++ b/internal/core/export/extract.go
@@ -29,10 +29,14 @@
// foo: bar: 2
//
func ExtractDoc(v *adt.Vertex) (docs []*ast.CommentGroup) {
+ return extractDocs(v, v.Conjuncts)
+}
+
+func extractDocs(v *adt.Vertex, a []adt.Conjunct) (docs []*ast.CommentGroup) {
fields := []*ast.Field{}
// Collect docs directly related to this Vertex.
- for _, x := range v.Conjuncts {
+ for _, x := range a {
f, ok := x.Source().(*ast.Field)
if !ok || hasShorthandValue(f) {
continue
@@ -46,6 +50,10 @@
}
}
+ if v == nil {
+ return docs
+ }
+
// Collect docs from parent scopes in collapsed fields.
for p := v.Parent; p != nil; p = p.Parent {
diff --git a/internal/core/export/label.go b/internal/core/export/label.go
index 0ece0a3..be73089 100644
--- a/internal/core/export/label.go
+++ b/internal/core/export/label.go
@@ -16,7 +16,6 @@
import (
"strconv"
- "strings"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/token"
@@ -24,11 +23,25 @@
)
func (e *exporter) stringLabel(f adt.Feature) ast.Label {
- str := f.SelectorString(e.index)
- if strings.HasPrefix(str, "#") && !f.IsDef() ||
- strings.HasPrefix(str, "_") && !f.IsHidden() ||
- !ast.IsValidIdent(str) {
- return ast.NewLit(token.STRING, strconv.Quote(str))
+ if f == 0 {
+ return ast.NewIdent("_")
}
- return &ast.Ident{Name: str}
+ x := f.Index()
+ switch f.Typ() {
+ case adt.IntLabel:
+ return ast.NewLit(token.INT, strconv.Itoa(int(x)))
+
+ case adt.DefinitionLabel, adt.HiddenLabel, adt.HiddenDefinitionLabel:
+ return ast.NewIdent(e.ctx.IndexToString(int64(x)))
+
+ case adt.StringLabel:
+ s := e.ctx.IndexToString(int64(x))
+ if !ast.IsValidIdent(s) {
+ return ast.NewLit(token.STRING, strconv.Quote(s))
+ }
+ fallthrough
+
+ default:
+ return ast.NewIdent(e.ctx.IndexToString(int64(x)))
+ }
}
diff --git a/internal/core/export/testdata/adt.txtar b/internal/core/export/testdata/adt.txtar
new file mode 100644
index 0000000..aaabe3f
--- /dev/null
+++ b/internal/core/export/testdata/adt.txtar
@@ -0,0 +1,198 @@
+-- in.cue --
+import mystrings "strings"
+
+p1: [X=string]: name: X
+
+d1: "foo\(bar)": int
+bar: "bar"
+
+// XXX: reference not resolving.
+d2: C="foo\(bar)": {
+ name: "xx"
+ foo: C.name
+}
+
+c1: mystrings.Contains("aa", "a")
+
+s1: """
+ multi
+ \(bar)
+ line
+ """
+
+l1: [3, ...int]
+l2: [...int]
+l3: []
+l4: [1, 2]
+
+n1: 1.0
+n10: 10
+
+// Ignored comment.
+
+// t is true
+t: true
+
+// Dangling comment.
+
+e1: <n1
+e2: >n1 & <n10
+e3: l4[2]
+e4: l4[2:3]
+e5: e1 + e2 - e3
+e6: !t
+e7: !t || !false
+e8?: !false
+
+m1: {
+ // this is a pattern constraint
+ {[string]: int}
+ // foo is an optional field
+ foo?: 3
+
+ // Dangling comment.
+
+ // bar is a field
+ bar: 4
+
+ // a comment too many.
+
+ ...
+}
+
+if true {
+ x: int
+}
+
+y1: {
+ src: [1, 2, 3]
+ for i, v in src for j, w in src if i < j {
+ "foo\(i)": v
+ "bar\(j)": w
+ }
+
+ for i, v in src {
+ "foo\(i)": v
+ }
+
+}
+
+-- out/definition --
+import mystrings "strings"
+
+p1: {
+ [X=string]: {
+ name: X
+ }
+}
+d1: {
+ "foo\(bar)": int
+}
+bar: "bar"
+d2: {
+ "foo\(bar)": {
+ name: "xx"
+ foo: X.name
+ }
+}
+c1: mystrings.Contains("aa", "a")
+s1: """multi
+
+ \(bar)
+ line
+ """
+l1: [3, ...int]
+l2: [...int]
+l3: []
+l4: [1, 2]
+n1: 1.0
+n10: 10
+t: true
+e1: <n1
+e2: >n1 & <n10
+e3: l4[2]
+e4: l4[2:3]
+e5: e1 + e2 - e3
+e6: !t
+e7: !t || !false
+e8?: !false
+m1: {
+ {
+ [string]: int
+ }
+
+ // foo is an optional field
+ foo?: 3
+
+ // bar is a field
+ bar: 4
+ ...
+}
+if true {
+ x: int
+}
+y1: {
+ src: [1, 2, 3]
+ for i, v in src for j, w in src if i < j {
+ "foo\(i)": v
+ "bar\(j)": w
+ }
+ for i, v in src {
+ "foo\(i)": v
+ }
+}
+-- out/doc --
+[]
+[p1]
+[d1]
+[d1 foobar]
+[bar]
+[d2]
+- XXX: reference not resolving.
+
+[d2 foobar]
+[d2 foobar name]
+[d2 foobar foo]
+[c1]
+[s1]
+[l1]
+[l1 0]
+[l2]
+[l3]
+[l4]
+[l4 0]
+[l4 1]
+[n1]
+[n10]
+[t]
+- t is true
+
+[e1]
+[e2]
+[e3]
+[e4]
+[e5]
+[e6]
+[e7]
+[m1]
+[m1 bar]
+- bar is a field
+
+[y1]
+[y1 src]
+[y1 src 0]
+[y1 src 1]
+[y1 src 2]
+[y1 foo0]
+[y1 bar1]
+[y1 bar2]
+[y1 foo1]
+[y1 foo2]
+[x]
+-- out/value --
+== Simplified
+_|_ // undefined field 2
+== Raw
+_|_ // undefined field 2
+== All
+_|_ // undefined field 2
diff --git a/internal/core/export/testdata/docs.txtar b/internal/core/export/testdata/docs.txtar
index 63fd83a..55a8a77 100644
--- a/internal/core/export/testdata/docs.txtar
+++ b/internal/core/export/testdata/docs.txtar
@@ -91,34 +91,48 @@
- comment from bar on field 2
-- out/definition --
+// foobar defines at least foo.
+package foobar
+
+// A Foo fooses stuff.
Foo: {
+ // field1 is an int.
field1: int
field2: int
- dup3: int
+
+ // duplicate field comment
+ dup3: int
}
+
+// foos are instances of Foo.
foos: {
{
- [string]: Foo_1
+ [string]: Foo
}
MyFoo: {
+ // local field comment.
field1: 0
+
+ // other field comment.
field2: 1
- dup3: int
+
+ // duplicate field comment
+ dup3: int
}
}
bar: {
+ // comment from bar on field 1
field1: int
+ // comment from bar on field 2
field2: int
}
baz: {
- bar_5
+ bar
+
+ // comment from baz on field 1
field1: int
field2: int
}
-
-let Foo_1 = Foo
-
-let bar_5 = bar
-- out/value --
== Simplified
{
@@ -206,3 +220,46 @@
field2: int
}
}
+== All
+{
+ // A Foo fooses stuff.
+ Foo: {
+ // field1 is an int.
+ field1: int
+ field2: int
+
+ // duplicate field comment
+ dup3: int
+ }
+
+ // foos are instances of Foo.
+ foos: {
+ // My first little foo.
+ MyFoo: {
+ // local field comment.
+
+ // field1 is an int.
+ field1: 0
+
+ // other field comment.
+ field2: 1
+
+ // duplicate field comment
+ dup3: int
+ }
+ }
+ bar: {
+ // comment from bar on field 1
+ field1: int
+ // comment from bar on field 2
+ field2: int
+ }
+ baz: {
+ // comment from bar on field 1
+
+ // comment from baz on field 1
+ field1: int
+ // comment from bar on field 2
+ field2: int
+ }
+}
diff --git a/internal/core/export/testdata/scalardef.txtar b/internal/core/export/testdata/scalardef.txtar
index 3e5b5b3..b328ff5 100644
--- a/internal/core/export/testdata/scalardef.txtar
+++ b/internal/core/export/testdata/scalardef.txtar
@@ -22,6 +22,8 @@
[]
[#pkg1]
-- out/definition --
+package test
+
import pkg2 "example.com/foo/pkg1"
"Hello \(#pkg1)!"
diff --git a/internal/core/export/testdata/simplify.txtar b/internal/core/export/testdata/simplify.txtar
index c8d27e8..2ddd23b 100644
--- a/internal/core/export/testdata/simplify.txtar
+++ b/internal/core/export/testdata/simplify.txtar
@@ -28,3 +28,9 @@
y: >=-9223372036854775808 & <=9223372036854775807 & int
}
}
+== All
+{
+ x: {
+ y: int64
+ }
+}
diff --git a/internal/core/export/testdata/topo.txtar b/internal/core/export/testdata/topo.txtar
index 2d582aa..0d40a0f 100644
--- a/internal/core/export/testdata/topo.txtar
+++ b/internal/core/export/testdata/topo.txtar
@@ -47,3 +47,12 @@
f: 4
g: 4
}
+== All
+{
+ a: 1
+ b: 1
+ c: 2
+ e: 3
+ f: 4
+ g: 4
+}
diff --git a/internal/core/export/value.go b/internal/core/export/value.go
index bf9500a..1761dfb 100644
--- a/internal/core/export/value.go
+++ b/internal/core/export/value.go
@@ -15,6 +15,8 @@
package export
import (
+ "fmt"
+
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/ast/astutil"
"cuelang.org/go/cue/token"
@@ -22,16 +24,23 @@
)
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{Value: x}
+ return e.vertex(a)
+ default:
+ panic("unreachable")
+ }
// TODO: allow a Value context wrapper.
- a := &adt.Vertex{Value: v}
- return e.vertex(a)
}
// 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) {
- switch n.Value.(type) {
+ switch x := n.Value.(type) {
case nil:
// bare
case *adt.StructMarker:
@@ -40,12 +49,36 @@
case *adt.ListMarker:
result = e.listComposite(n)
+ case *adt.Bottom:
+ if x.IsIncomplete() {
+ // fall back to expression mode
+ result = stripRefs(e.expr(n))
+ break
+ }
+ result = e.bottom(x)
+
default:
result = e.value(n.Value, n.Conjuncts...)
}
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) {
// Evaluate arc if needed?
@@ -81,6 +114,9 @@
case *adt.BoundValue:
result = e.boundValue(x)
+ case *adt.Builtin:
+ result = e.builtin(x)
+
case *adt.BuiltinValidator:
result = e.builtinValidator(x)
@@ -88,27 +124,51 @@
result = e.vertex(x)
case *adt.Conjunction:
- if len(x.Values) == 0 {
- result = ast.NewIdent("_")
- break
+ 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 := []ast.Expr{}
+ a := []adt.Value{}
b := boundSimplifier{e: e}
for _, v := range x.Values {
if !e.cfg.Simplify || !b.add(v) {
- a = append(a, e.bareValue(v))
+ a = append(a, v)
}
}
- if !e.cfg.Simplify {
- return ast.NewBinExpr(token.AND, a...)
+ result = b.expr(e.ctx)
+ if result == nil {
+ a = x.Values
}
- result = b.expr(e.ctx) // e.bareValue(x.Values[0])
- for _, c := range a {
- result = &ast.BinaryExpr{X: result, Op: token.AND, Y: c}
+ 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.
@@ -120,16 +180,6 @@
err := &ast.BottomLit{}
if x := n.Err; x != nil {
msg := x.Error()
- // if len(x.sub) > 0 {
- // buf := strings.Builder{}
- // for i, b := range x.sub {
- // if i > 0 {
- // buf.WriteString("; ")
- // buf.WriteString(b.msg())
- // }
- // }
- // msg = buf.String()
- // }
comment := &ast.Comment{Text: "// " + msg}
err.AddComment(&ast.CommentGroup{
Line: true,
@@ -237,24 +287,64 @@
}
func (e *exporter) structComposite(v *adt.Vertex) ast.Expr {
- s := &ast.StructLit{}
- saved := e.stack
- e.stack = append(e.stack, frame{scope: s})
- defer func() { e.stack = saved }()
+ s, saved := e.pushFrame(v.Conjuncts)
+ e.top().upCount++
+ defer func() {
+ e.top().upCount--
+ e.popFrame(saved)
+ }()
- for _, a := range e.sortedArcs(v) {
- if a.Label.IsDef() || a.Label.IsHidden() {
+ p := e.cfg
+ for _, label := range VertexFeatures(v) {
+ if label.IsDef() && !p.ShowDefinitions {
+ continue
+ }
+ if label.IsHidden() && !p.ShowHidden {
continue
}
- f := &ast.Field{
- Label: e.stringLabel(a.Label),
- Value: e.vertex(a),
- Attrs: ExtractFieldAttrs(a.Conjuncts),
+ f := &ast.Field{Label: e.stringLabel(label)}
+
+ if label.IsDef() {
+ e.inDefinition++
}
- docs := ExtractDoc(a)
- ast.SetComments(f, docs)
+ 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(v.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.Conjuncts)
+ }
+
+ if p.ShowDocs {
+ docs := ExtractDoc(arc)
+ ast.SetComments(f, docs)
+ }
s.Elts = append(s.Elts, f)
}
diff --git a/internal/core/export/value_test.go b/internal/core/export/value_test.go
index 6b89513..2467c3d 100644
--- a/internal/core/export/value_test.go
+++ b/internal/core/export/value_test.go
@@ -59,6 +59,7 @@
}{
{"Simplified", Simplified.Value},
{"Raw", Raw.Value},
+ {"All", All.Value},
} {
fmt.Fprintln(t, "==", tc.name)
x, errs := tc.fn(r, v)