cue: extend Option API

- remove AllFields.
- use the new API in the eval comand

Change-Id: Ib0b0da29980e38128e68b3b90e418ec149293f35
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1785
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cmd/cue/cmd/eval.go b/cmd/cue/cmd/eval.go
index 938456c..76a7b96 100644
--- a/cmd/cue/cmd/eval.go
+++ b/cmd/cue/cmd/eval.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 
+	"cuelang.org/go/cue"
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/format"
 	"cuelang.org/go/cue/parser"
@@ -62,16 +63,26 @@
 		for _, inst := range instances {
 			// TODO: use ImportPath or some other sanitized path.
 			fmt.Fprintf(w, "// %s\n", inst.Dir)
+			syn := []cue.Option{
+				cue.Attributes(*attrs),
+				cue.Optional(*all || *optional),
+			}
+			if *compile {
+				syn = append(syn, cue.RequireConcrete())
+			}
+			if *hidden || *all {
+				syn = append(syn, cue.Hidden(true))
+			}
 			opts := []format.Option{
 				format.UseSpaces(4),
 				format.TabIndent(false),
 			}
 			if exprs == nil {
-				format.Node(w, inst.Value().Syntax(), opts...)
+				format.Node(w, inst.Value().Syntax(syn...), opts...)
 				fmt.Fprintln(w)
 			}
 			for _, e := range exprs {
-				format.Node(w, inst.Eval(e).Syntax(), opts...)
+				format.Node(w, inst.Eval(e).Syntax(syn...), opts...)
 				fmt.Fprintln(w)
 			}
 		}
@@ -84,8 +95,29 @@
 
 	expressions = evalCmd.Flags().StringArrayP("expression", "e", nil, "evaluate this expression only")
 
+	compile = evalCmd.Flags().BoolP("concrete", "c", false,
+		"require the evaluation to be concrete")
+
+	hidden = evalCmd.Flags().BoolP("show-hidden", "H", false,
+		"display hidden attributes")
+
+	optional = evalCmd.Flags().BoolP("show-optional", "O", false,
+		"display hidden attributes")
+
+	attrs = evalCmd.Flags().BoolP("attributes", "l", false,
+		"display field attributes")
+
+	all = evalCmd.Flags().BoolP("all", "a", false,
+		"show optional and hidden fields")
+
+	// TODO: Option to include comments in output.
 }
 
 var (
 	expressions *[]string
+	compile     *bool
+	attrs       *bool
+	all         *bool
+	hidden      *bool
+	optional    *bool
 )
diff --git a/cmd/cue/cmd/trim.go b/cmd/cue/cmd/trim.go
index f6484cc..1da1dce 100644
--- a/cmd/cue/cmd/trim.go
+++ b/cmd/cue/cmd/trim.go
@@ -352,7 +352,7 @@
 
 		// Build map of mixin fields.
 		valueMap := map[key]cue.Value{}
-		for mIter, _ := in.AllFields(); mIter.Next(); {
+		for mIter, _ := in.Fields(cue.All()); mIter.Next(); {
 			valueMap[iterKey(mIter)] = mIter.Value()
 		}
 
@@ -360,7 +360,7 @@
 
 		// Process fields.
 		rm := []ast.Node{}
-		for iter, _ := v.AllFields(); iter.Next(); {
+		for iter, _ := v.Fields(cue.All()); iter.Next(); {
 			mSub := valueMap[iterKey(iter)]
 			if fn != nil {
 				mSub = mSub.Unify(fn(iter.Label()))
diff --git a/cue/export.go b/cue/export.go
index 3622e4c..cd4e63c 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -26,22 +26,18 @@
 	"cuelang.org/go/cue/token"
 )
 
-type exportMode int
+func doEval(m options) bool {
+	return !m.raw
+}
 
-const (
-	exportEval exportMode = 1 << iota
-	exportAttrs
-	exportRaw exportMode = 0
-)
-
-func export(ctx *context, v value, m exportMode) ast.Expr {
+func export(ctx *context, v value, m options) ast.Expr {
 	e := exporter{ctx, m, nil}
 	return e.expr(v)
 }
 
 type exporter struct {
 	ctx   *context
-	mode  exportMode
+	mode  options
 	stack []remap
 }
 
@@ -112,10 +108,10 @@
 }
 
 func (p *exporter) expr(v value) ast.Expr {
-	if p.mode == exportEval {
+	if doEval(p.mode) {
 		x := p.ctx.manifest(v)
 		if isIncomplete(x) {
-			p = &exporter{p.ctx, exportRaw, p.stack}
+			p = &exporter{p.ctx, options{raw: true}, p.stack}
 			return p.expr(v)
 		}
 		v = x
@@ -228,7 +224,7 @@
 
 	case *structLit:
 		obj := &ast.StructLit{}
-		if p.mode == exportEval {
+		if doEval(p.mode) {
 			for _, a := range x.arcs {
 				p.stack = append(p.stack, remap{
 					key:  x,
@@ -242,7 +238,7 @@
 		if x.emit != nil {
 			obj.Elts = append(obj.Elts, &ast.EmitDecl{Expr: p.expr(x.emit)})
 		}
-		if p.mode != exportEval && x.template != nil {
+		if !doEval(p.mode) && x.template != nil {
 			l, ok := x.template.evalPartial(p.ctx).(*lambdaExpr)
 			if ok {
 				obj.Elts = append(obj.Elts, &ast.Field{
@@ -257,15 +253,30 @@
 			f := &ast.Field{
 				Label: p.label(a.feature),
 			}
-			if p.mode != exportEval {
+			// TODO: allow the removal of hidden fields. However, hidden fields
+			// that still used in incomplete expressions should not be removed
+			// (unless RequireConcrete is requested).
+			if a.optional {
+				// Optional fields are almost never concrete. We omit them in
+				// concrete mode to allow the user to use the -a option in eval
+				// without getting many errors.
+				if p.mode.omitOptional || p.mode.concrete {
+					continue
+				}
+				f.Optional = 1
+			}
+			if a.feature&hidden != 0 && p.mode.concrete && p.mode.omitHidden {
+				continue
+			}
+			if !doEval(p.mode) {
 				f.Value = p.expr(a.v)
-			} else if v := p.ctx.manifest(x.at(p.ctx, i)); isIncomplete(v) {
-				p := &exporter{p.ctx, exportRaw, p.stack}
+			} else if v := p.ctx.manifest(x.at(p.ctx, i)); isIncomplete(v) && !p.mode.concrete {
+				p := &exporter{p.ctx, options{raw: true}, p.stack}
 				f.Value = p.expr(a.v)
 			} else {
 				f.Value = p.expr(v)
 			}
-			if a.attrs != nil { // TODO: && p.mode&exportAttrs != 0 {
+			if a.attrs != nil && !p.mode.omitAttrs {
 				for _, at := range a.attrs.attr {
 					f.Attrs = append(f.Attrs, &ast.Attribute{Text: at.text})
 				}
diff --git a/cue/export_test.go b/cue/export_test.go
index c77f59a..dad7d3a 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -26,8 +26,8 @@
 
 func TestExport(t *testing.T) {
 	testCases := []struct {
-		raw     bool
-		mode    exportMode
+		raw     bool // skip evaluation the root, fully raw
+		eval    bool // evaluate the full export
 		in, out string
 	}{{
 		in:  `"hello"`,
@@ -164,7 +164,7 @@
 				}`),
 	}, {
 		raw:  true,
-		mode: exportEval,
+		eval: true,
 		in: `{
 			b: {
 				idx: a[str]
@@ -186,7 +186,7 @@
 		}`),
 	}, {
 		raw:  true,
-		mode: exportEval,
+		eval: true,
 		in: `{
 			job <Name>: {
 				name:     Name
@@ -218,7 +218,7 @@
 		}`),
 	}, {
 		raw:  true,
-		mode: exportEval,
+		eval: true,
 		in: `{
 				b: [{
 					<X>: int
@@ -252,7 +252,8 @@
 			v := newValueRoot(ctx, n)
 
 			buf := &bytes.Buffer{}
-			err := format.Node(buf, export(ctx, v.eval(ctx), tc.mode))
+			opts := options{raw: !tc.eval}
+			err := format.Node(buf, export(ctx, v.eval(ctx), opts))
 			if err != nil {
 				log.Fatal(err)
 			}
diff --git a/cue/types.go b/cue/types.go
index c11fb73..df76119 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -170,12 +170,16 @@
 	return i.ctx.labelStr(i.f)
 }
 
-// IsHidden reports if a field is hidden from the data model. This may only
-// be true if the field was obtained using AllFields.
+// IsHidden reports if a field is hidden from the data model.
 func (i *Iterator) IsHidden() bool {
 	return i.f&hidden != 0
 }
 
+// IsOptional reports if a field is optional.
+func (i *Iterator) IsOptional() bool {
+	return i.cur.path.arc.optional
+}
+
 // marshalJSON iterates over the list and generates JSON output. HasNext
 // will return false after this operation.
 func marshalList(l *Iterator) (b []byte, err error) {
@@ -481,7 +485,8 @@
 
 // Label reports he label used to obtain this value from the enclosing struct.
 //
-// TODO: get rid of this somehow. Maybe by passing it to walk
+// TODO: get rid of this somehow. Probably by including a FieldInfo struct
+// or the like.
 func (v Value) Label() (string, bool) {
 	if v.path.feature == 0 {
 		return "", false
@@ -583,12 +588,12 @@
 
 // Syntax converts the possibly partially evaluated value into syntax. This
 // can use used to print the value with package format.
-func (v Value) Syntax() ast.Expr {
+func (v Value) Syntax(opts ...Option) ast.Expr {
 	if v.path == nil || v.path.cache == nil {
 		return nil
 	}
 	ctx := v.ctx()
-	return export(ctx, v.eval(ctx), exportEval)
+	return export(ctx, v.eval(ctx), getOptions(opts))
 }
 
 // Decode initializes x with Value v. If x is a struct, it will validate the
@@ -791,6 +796,14 @@
 
 // structVal returns an structVal or an error if v is not a struct.
 func (v Value) structVal(ctx *context) (structValue, error) {
+	return v.structValOpts(ctx, options{
+		omitHidden:   true,
+		omitOptional: true,
+	})
+}
+
+// structVal returns an structVal or an error if v is not a struct.
+func (v Value) structValOpts(ctx *context, o options) (structValue, error) {
 	if err := v.checkKind(ctx, structKind); err != nil {
 		return structValue{}, err
 	}
@@ -799,15 +812,20 @@
 	// TODO: This is expansion appropriate?
 	obj = obj.expandFields(ctx) // expand comprehensions
 
-	// check if any labels are hidden
-	hasOptional := false
-	f := label(0)
-	for _, a := range obj.arcs {
-		f |= a.feature
-		hasOptional = hasOptional || a.optional
+	// check if any fields can be omitted
+	needFilter := false
+	if o.omitHidden || o.omitOptional {
+		f := label(0)
+		for _, a := range obj.arcs {
+			f |= a.feature
+			if o.omitOptional && a.optional {
+				needFilter = true
+			}
+		}
+		needFilter = needFilter || f&hidden != 0
 	}
 
-	if f&hidden != 0 || hasOptional {
+	if needFilter {
 		arcs := make([]arc, len(obj.arcs))
 		k := 0
 		for _, a := range obj.arcs {
@@ -829,32 +847,13 @@
 	return structValue{ctx, v.path, obj}, nil
 }
 
-func (v Value) structValWithHidden(ctx *context) (structValue, error) {
-	if err := v.checkKind(ctx, structKind); err != nil {
-		return structValue{}, err
-	}
-	obj := v.eval(ctx).(*structLit)
-	obj = obj.expandFields(ctx)
-
-	return structValue{ctx, v.path, obj}, nil
-}
-
 // Fields creates an iterator over v's fields if v is a struct or an error
 // otherwise.
-func (v Value) Fields() (Iterator, error) {
+func (v Value) Fields(opts ...Option) (Iterator, error) {
+	o := options{omitHidden: true, omitOptional: true}
+	o.updateOptions(opts)
 	ctx := v.ctx()
-	obj, err := v.structVal(ctx)
-	if err != nil {
-		return Iterator{ctx: ctx}, err
-	}
-	return Iterator{ctx: ctx, val: v, iter: obj.n, len: len(obj.n.arcs)}, nil
-}
-
-// AllFields creates an iterator over all of v's fields, including the hidden
-// ones, if v is a struct or an error otherwise.
-func (v Value) AllFields() (Iterator, error) {
-	ctx := v.ctx()
-	obj, err := v.structValWithHidden(ctx)
+	obj, err := v.structValOpts(ctx, o)
 	if err != nil {
 		return Iterator{ctx: ctx}, err
 	}
@@ -989,32 +988,83 @@
 }
 
 type options struct {
-	concrete bool
+	concrete     bool // enforce that values are concrete
+	raw          bool // show original values
+	hasHidden    bool
+	omitHidden   bool
+	omitOptional bool
+	omitAttrs    bool
 }
 
 // An Option defines modes of evaluation.
-type Option func(p *options)
+type Option option
 
-// Used in Validate, Subsume?, Fields()
+type option func(p *options)
+
+// Used in Iter, Validate, Subsume?, Fields() Syntax, Export
 
 // TODO: could also be used for subsumption.
 
-// RequireConcrete verifies that all values in a tree are concrete.
+// RequireConcrete ensures that all values are concrete.
+//
+// For Validate this means it returns an error if this is not the case.
+// In other cases a non-concrete value will be replaced with an error.
 func RequireConcrete() Option {
-	return func(p *options) { p.concrete = true }
+	return func(p *options) {
+		p.concrete = true
+		if !p.hasHidden {
+			p.omitHidden = true
+		}
+	}
 }
 
-// VisitHidden(visit bool)
+// All indicates that all fields and values should be included in processing
+// even if they can be elided or omitted.
+func All() Option {
+	return func(p *options) {
+		p.omitAttrs = false
+		p.omitHidden = false
+		p.omitOptional = false
+	}
+}
+
+// Hidden indicates that hidden fields should be included.
 //
+// Hidden fields may still be included if include is false,
+// even if a value is not concrete.
+func Hidden(include bool) Option {
+	return func(p *options) {
+		p.hasHidden = true
+		p.omitHidden = !include
+	}
+}
+
+// Optional indicates that optional fields should be included.
+func Optional(include bool) Option {
+	return func(p *options) { p.omitOptional = !include }
+}
+
+// Attributes indicates that attributes should be included.
+func Attributes(include bool) Option {
+	return func(p *options) { p.omitAttrs = !include }
+}
+
+func getOptions(opts []Option) (o options) {
+	o.updateOptions(opts)
+	return
+}
+
+func (o *options) updateOptions(opts []Option) {
+	for _, fn := range opts {
+		fn(o)
+	}
+}
 
 // Validate reports any errors, recursively. The returned error may be an
 // errors.List reporting multiple errors, where the total number of errors
 // reported may be less than the actual number.
 func (v Value) Validate(opts ...Option) error {
-	var o options
-	for _, fn := range opts {
-		fn(&o)
-	}
+	o := getOptions(opts)
 	list := errors.List{}
 	v.Walk(func(v Value) bool {
 		if err := v.Err(); err != nil {
diff --git a/cue/types_test.go b/cue/types_test.go
index bb33193..93fe468 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -630,7 +630,7 @@
 		t.Run(tc.value, func(t *testing.T) {
 			obj := getInstance(t, tc.value).Value()
 
-			iter, err := obj.AllFields()
+			iter, err := obj.Fields(All())
 			checkFatal(t, err, tc.err, "init")
 
 			buf := []byte{'{'}
diff --git a/cue/value.go b/cue/value.go
index f15d7cf..976cd40 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -15,7 +15,6 @@
 package cue
 
 import (
-	"fmt"
 	"math/big"
 	"sort"
 	"strconv"