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)