cue: added Eval, Default, IsConcrete, Expr

These API changes allow for analyzing
values at the expression level.

Change-Id: I44d495aef2794bab2a4172c93d8b4dae133d0689
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1965
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/op.go b/cue/op.go
index 6385903..214e3c8 100644
--- a/cue/op.go
+++ b/cue/op.go
@@ -16,6 +16,74 @@
 
 import "cuelang.org/go/cue/token"
 
+// Op indicates the operation at the top of an expression tree of the expression
+// use to evaluate a value.
+type Op int
+
+// Values of Op.
+const (
+	NoOp Op = iota
+
+	AndOp
+	OrOp
+
+	SelectorOp
+	IndexOp
+	SliceOp
+	CallOp
+
+	BooleanAndOp
+	BooleanOrOp
+
+	EqualOp
+	NotOp
+	NotEqualOp
+	LessThanOp
+	LessThanEqualOp
+	GreaterThanOp
+	GreaterThanEqualOp
+
+	RegexMatchOp
+	NotRegexMatchOp
+
+	AddOp
+	SubtractOp
+	MultiplyOp
+	FloatQuotientOp
+	FloatRemainOp
+	IntQuotientOp
+	IntRemainderOp
+	IntDivideOp
+	IntModuloOp
+
+	InterpolationOp
+)
+
+var opToOp = map[op]Op{
+	opUnify:       AndOp,
+	opDisjunction: OrOp,
+	opLand:        BooleanAndOp,
+	opLor:         BooleanOrOp,
+	opEql:         EqualOp,
+	opNot:         NotOp,
+	opNeq:         NotEqualOp,
+	opLss:         LessThanOp,
+	opLeq:         LessThanEqualOp,
+	opGtr:         GreaterThanOp,
+	opGeq:         GreaterThanEqualOp,
+	opMat:         RegexMatchOp,
+	opNMat:        NotRegexMatchOp,
+	opAdd:         AddOp,
+	opSub:         SubtractOp,
+	opMul:         MultiplyOp,
+	opQuo:         FloatQuotientOp,
+	opRem:         FloatRemainOp,
+	opIQuo:        IntQuotientOp,
+	opIRem:        IntRemainderOp,
+	opIDiv:        IntDivideOp,
+	opIMod:        IntModuloOp,
+}
+
 func opIn(op op, anyOf ...op) bool {
 	for _, o := range anyOf {
 		if o == op {
diff --git a/cue/types.go b/cue/types.go
index 22bd98a..f9aa791 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -460,6 +460,13 @@
 	return Value{obj.ctx.index, &valueData{obj.path, uint32(i), a}}
 }
 
+func remakeValue(base Value, v value) Value {
+	path := *base.path
+	path.v = v
+	path.cache = v.evalPartial(base.ctx())
+	return Value{base.idx, &path}
+}
+
 func (v Value) ctx() *context {
 	return v.idx.newContext()
 }
@@ -476,6 +483,28 @@
 	return ctx.manifest(v.path.cache)
 }
 
+// Eval resolves the references of a value and returns the result.
+// This method is not necessary to obtain concrete values.
+func (v Value) Eval() Value {
+	if v.path == nil {
+		return v
+	}
+	return remakeValue(v, v.path.v.evalPartial(v.ctx()))
+}
+
+// Default reports the default value and whether it existed. It returns the
+// normal value if there is no default.
+func (v Value) Default() (Value, bool) {
+	if v.path == nil {
+		return v, false
+	}
+	x := v.ctx().manifest(v.path.v)
+	if x != v.path.v {
+		return remakeValue(v, x), true
+	}
+	return v, false
+}
+
 // Label reports he label used to obtain this value from the enclosing struct.
 //
 // TODO: get rid of this somehow. Probably by including a FieldInfo struct
@@ -595,7 +624,11 @@
 		return nil
 	}
 	ctx := v.ctx()
-	return export(ctx, v.eval(ctx), getOptions(opts))
+	o := getOptions(opts)
+	if o.raw {
+		return export(ctx, v.path.v, o)
+	}
+	return export(ctx, v.path.cache, o)
 }
 
 // Decode initializes x with Value v. If x is a struct, it will validate the
@@ -634,7 +667,7 @@
 
 func separate(v value) (a []value) {
 	c := v.computed()
-	if c == nil {
+	if c == nil || c.op != opUnify {
 		return []value{v}
 	}
 	if c.x != nil {
@@ -674,9 +707,30 @@
 	return v.idx.fset.Position(pos)
 }
 
-// IsIncomplete indicates that the value cannot be fully evaluated due to
+// IsConcrete reports whether the current value is a concrete scalar value,
+// not relying on default values, a terminal error, a list, or a struct.
+// It does not verify that values of lists or structs are concrete themselves.
+// To check whether there is a concrete default, use v.Default().IsConcrete().
+func (v Value) IsConcrete() bool {
+	if v.path == nil {
+		return false // any is neither concrete, not a list or struct.
+	}
+	x := v.path.v.evalPartial(v.ctx())
+
+	// Errors marked as incomplete are treated as not complete.
+	if isIncomplete(x) {
+		return false
+	}
+	// Other errors are considered ground.
+	return x.kind().isGround()
+}
+
+// IsIncomplete is deprecated.
+//
+// It indicates that the value cannot be fully evaluated due to
 // insufficient information.
 func (v Value) IsIncomplete() bool {
+	// TODO: remove
 	x := v.eval(v.ctx())
 	if x.kind().hasReferences() || !x.kind().isGround() {
 		return true
@@ -724,6 +778,53 @@
 	return nil
 }
 
+func makeInt(v Value, x int64) Value {
+	n := &numLit{numBase: numBase{baseValue: v.path.v.base()}}
+	n.v.SetInt64(x)
+	return remakeValue(v, n)
+}
+
+// Len returns the number of items of the underlying value.
+// For lists it reports the capacity of the list. For structs it indicates the
+// number of fields, for bytes the number of bytes.
+func (v Value) Len() Value {
+	if v.path != nil {
+		switch x := v.path.v.evalPartial(v.ctx()).(type) {
+		case *list:
+			return remakeValue(v, x.len.evalPartial(v.ctx()))
+		case *bytesLit:
+			return makeInt(v, int64(x.len()))
+		case *stringLit:
+			return makeInt(v, int64(x.len()))
+		}
+	}
+	const msg = "len not supported for type %v"
+	return remakeValue(v, v.ctx().mkErr(v.path.v, msg, v.Kind()))
+}
+
+// Elem returns the value of undefined element types of lists and structs.
+func (v Value) Elem() (Value, bool) {
+	ctx := v.ctx()
+	switch x := v.path.cache.(type) {
+	case *structLit:
+		if x.template == nil {
+			break
+		}
+		fn, ok := ctx.manifest(x.template).(*lambdaExpr)
+		if !ok {
+			// TODO: return an error instead.
+			break
+		}
+		// Note, this template does not maintain the relation between the
+		// the attribute value and the instance.
+		y := fn.call(ctx, x, &basicType{x.baseValue, stringKind})
+		return newValueRoot(ctx, y), true
+	case *list:
+		return newValueRoot(ctx, x.typ), true
+	}
+	return Value{}, false
+}
+
 // List creates an iterator over the values of a list or reports an error if
 // v is not a list.
 func (v Value) List() (Iterator, error) {
@@ -942,8 +1043,47 @@
 	io.WriteString(state, debugStr(ctx, v.path.cache))
 }
 
+// Reference returns path from the root of the file referred to by this value
+// or no path if this value is not a reference.
+func (v Value) Reference() []string {
+	// TODO: don't include references to hidden fields.
+	if v.path == nil {
+		return nil
+	}
+	sel, ok := v.path.v.(*selectorExpr)
+	if !ok {
+		return nil
+	}
+	return mkPath(v.ctx(), v.path, sel, 0)
+}
+
+func mkPath(c *context, up *valueData, sel *selectorExpr, d int) (a []string) {
+	switch x := sel.x.(type) {
+	case *selectorExpr:
+		a = mkPath(c, up.parent, x, d+1)
+	case *nodeRef:
+		// the parent must exist.
+		for ; up != nil && up.cache != x.node.(value); up = up.parent {
+		}
+		a = mkFromRoot(c, up, d+1)
+	default:
+		panic("should not happend")
+	}
+	return append(a, c.labelStr(sel.feature))
+}
+
+func mkFromRoot(c *context, up *valueData, d int) []string {
+	if up == nil || up.parent == nil {
+		return make([]string, 0, d)
+	}
+	a := mkFromRoot(c, up.parent, d+1)
+	return append(a, c.labelStr(up.feature))
+}
+
 // References reports all references used to evaluate this value. It does not
 // report references for sub fields if v is a struct.
+//
+// Deprecated: can be implemented in terms of Reference and Expr.
 func (v Value) References() [][]string {
 	ctx := v.ctx()
 	pf := pathFinder{up: v.path}
@@ -1245,3 +1385,103 @@
 	}
 	return "", false, nil
 }
+
+// Expr reports the operation of the underlying expression and the values it
+// operates on. The returned values are appended to the given slice, which may
+// be nil.
+//
+// For unary expressions, it returns the single value of the expression.
+//
+// For binary expressions it returns first the left and right value, in that
+// order. For associative operations however, (for instance '&' and '|'), it may
+// return more than two values, where the operation is to be applied in
+// sequence.
+//
+// For selector and index expressions it returns the subject and then the index.
+// For selectors, the index is the string value of the identifier. For slice
+// expressions, it returns the subject, low value, and high value, in that
+// order.
+//
+// For interpolations it returns a sequence of values to be concatenated, some
+// of which will be literal strings and some unevaluated expressions.
+//
+// A builtin call expression returns the value of the builtin followed by the
+// args of the call.
+//
+// If v is not an expression, Partial append v itself.
+// TODO: return v if this is complete? Yes for now
+// TODO: add values if a == nil?
+func (v Value) Expr() (Op, []Value) {
+	if v.path == nil {
+		return NoOp, nil
+	}
+	// TODO: replace appends with []Value{}. For not leave.
+	a := []Value{}
+	op := NoOp
+	switch x := v.path.v.(type) {
+	case *binaryExpr:
+		a = append(a, remakeValue(v, x.left))
+		a = append(a, remakeValue(v, x.right))
+		op = opToOp[x.op]
+	case *unaryExpr:
+		a = append(a, remakeValue(v, x.x))
+		op = opToOp[x.op]
+	case *bound:
+		a = append(a, remakeValue(v, x.value))
+		op = opToOp[x.op]
+	case *unification:
+		// pre-expanded unification
+		for _, conjunct := range x.values {
+			a = append(a, remakeValue(v, conjunct))
+		}
+		op = AndOp
+	case *disjunction:
+		// Filter defaults that are subsumed by another value.
+		count := 0
+	outer:
+		for _, disjunct := range x.values {
+			if disjunct.marked {
+				for _, n := range x.values {
+					if !n.marked && subsumes(v.ctx(), n.val, disjunct.val, 0) {
+						continue outer
+					}
+				}
+			}
+			count++
+			a = append(a, remakeValue(v, disjunct.val))
+		}
+		if count > 1 {
+			op = OrOp
+		}
+	case *interpolation:
+		for _, p := range x.parts {
+			a = append(a, remakeValue(v, p))
+		}
+		op = InterpolationOp
+	case *selectorExpr:
+		a = append(a, remakeValue(v, x.x))
+		a = append(a, remakeValue(v, &stringLit{
+			x.baseValue,
+			v.ctx().labelStr(x.feature),
+		}))
+		op = SelectorOp
+	case *indexExpr:
+		a = append(a, remakeValue(v, x.x))
+		a = append(a, remakeValue(v, x.index))
+		op = IndexOp
+	case *sliceExpr:
+		a = append(a, remakeValue(v, x.x))
+		a = append(a, remakeValue(v, x.lo))
+		a = append(a, remakeValue(v, x.hi))
+		op = SliceOp
+	case *callExpr:
+		a = append(a, remakeValue(v, x.x))
+		for _, arg := range x.args {
+			a = append(a, remakeValue(v, arg))
+		}
+		op = CallOp
+	default:
+		a = append(a, v)
+	}
+	return op, a
+}
diff --git a/cue/types_test.go b/cue/types_test.go
index 3d199d7..0429e90 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -45,6 +45,7 @@
 		incompleteKind Kind
 		json           string
 		valid          bool
+		concrete       bool
 		// pos            token.Pos
 	}{{ // Not a concrete value.
 		value:          `_`,
@@ -54,18 +55,22 @@
 		value:          `_|_`,
 		kind:           BottomKind,
 		incompleteKind: BottomKind,
+		concrete:       true,
 	}, {
 		value:          `1&2`,
 		kind:           BottomKind,
 		incompleteKind: BottomKind,
+		concrete:       true,
 	}, { // TODO: should be error{
 		value:          `b`,
 		kind:           BottomKind,
 		incompleteKind: BottomKind,
+		concrete:       true,
 	}, {
 		value:          `(b[a])`,
 		kind:           BottomKind,
 		incompleteKind: BottomKind,
+		concrete:       true,
 	}, { // TODO: should be error{
 		value: `(b)
 			b: bool`,
@@ -75,18 +80,22 @@
 		value:          `([][b])`,
 		kind:           BottomKind,
 		incompleteKind: BottomKind,
+		concrete:       true,
 	}, {
 		value:          `null`,
 		kind:           NullKind,
 		incompleteKind: NullKind,
+		concrete:       true,
 	}, {
 		value:          `true`,
 		kind:           BoolKind,
 		incompleteKind: BoolKind,
+		concrete:       true,
 	}, {
 		value:          `false`,
 		kind:           BoolKind,
 		incompleteKind: BoolKind,
+		concrete:       true,
 	}, {
 		value:          `bool`,
 		kind:           BottomKind,
@@ -95,18 +104,22 @@
 		value:          `2`,
 		kind:           NumberKind,
 		incompleteKind: NumberKind,
+		concrete:       true,
 	}, {
 		value:          `2.0`,
 		kind:           FloatKind,
 		incompleteKind: FloatKind,
+		concrete:       true,
 	}, {
 		value:          `2.0Mi`,
 		kind:           NumberKind,
 		incompleteKind: NumberKind,
+		concrete:       true,
 	}, {
 		value:          `14_000`,
 		kind:           NumberKind,
 		incompleteKind: NumberKind,
+		concrete:       true,
 	}, {
 		value:          `>=0 & <5`,
 		kind:           BottomKind,
@@ -119,10 +132,12 @@
 		value:          `"str"`,
 		kind:           StringKind,
 		incompleteKind: StringKind,
+		concrete:       true,
 	}, {
 		value:          "'''\n'''",
 		kind:           BytesKind,
 		incompleteKind: BytesKind,
+		concrete:       true,
 	}, {
 		value:          "string",
 		kind:           BottomKind,
@@ -131,10 +146,16 @@
 		value:          `{}`,
 		kind:           StructKind,
 		incompleteKind: StructKind,
+		concrete:       true,
 	}, {
 		value:          `[]`,
 		kind:           ListKind,
 		incompleteKind: ListKind,
+		concrete:       true,
+	}, {
+		value:    `{a: int, b: [1][a]}.b`,
+		kind:     BottomKind,
+		concrete: false,
 	}}
 	for _, tc := range testCases {
 		t.Run(tc.value, func(t *testing.T) {
@@ -151,6 +172,9 @@
 			if got := v.IsIncomplete(); got != incomplete {
 				t.Errorf("IsIncomplete: got %v; want %v", got, incomplete)
 			}
+			if got := v.IsConcrete(); got != tc.concrete {
+				t.Errorf("IsConcrete: got %v; want %v", got, tc.concrete)
+			}
 			invalid := tc.kind == BottomKind
 			if got := v.IsValid(); got != !invalid {
 				t.Errorf("IsValid: got %v; want %v", got, !invalid)
@@ -653,6 +677,83 @@
 	}
 }
 
+func TestDefaults(t *testing.T) {
+	testCases := []struct {
+		value string
+		def   string
+		val   string
+		err   string
+	}{{
+		value: `number | *1`,
+		def:   "1",
+		val:   "number",
+	}, {
+		value: `1 | 2 | *3`,
+		def:   "3",
+		val:   "1|2|3",
+	}, {
+		value: `*{a:1,b:2}|{a:1}|{b:2}`,
+		def:   "<0>{a: 1, b: 2}",
+		val:   "<0>{a: 1}|<0>{b: 2}",
+	}}
+	for _, tc := range testCases {
+		t.Run(tc.value, func(t *testing.T) {
+			v := getInstance(t, "a: "+tc.value).Lookup("a")
+
+			_, val := v.Expr()
+			d, _ := v.Default()
+
+			if got := fmt.Sprint(d); got != tc.def {
+				t.Errorf("default: got %v; want %v", got, tc.def)
+			}
+
+			vars := []string{}
+			for _, v := range val {
+				vars = append(vars, fmt.Sprint(v))
+			}
+			if got := strings.Join(vars, "|"); got != tc.val {
+				t.Errorf("value: got %v; want %v", got, tc.val)
+			}
+		})
+	}
+}
+
+func TestLen(t *testing.T) {
+	testCases := []struct {
+		input  string
+		length string
+	}{{
+		input:  "[1, 3]",
+		length: "2",
+	}, {
+		input:  "[1, 3, ...]",
+		length: ">=2",
+	}, {
+		input:  `"foo"`,
+		length: "3",
+	}, {
+		input:  `'foo'`,
+		length: "3",
+		// TODO: Currently not supported.
+		// }, {
+		// 	input:  "{a:1, b:3, a:1, c?: 3, _hidden: 4}",
+		// 	length: "2",
+	}, {
+		input:  "3",
+		length: "_|_(3:len not supported for type 24)",
+	}}
+	for _, tc := range testCases {
+		t.Run(tc.input, func(t *testing.T) {
+			v := getInstance(t, "a: "+tc.input).Lookup("a")
+
+			length := v.Len()
+			if got := fmt.Sprint(length); got != tc.length {
+				t.Errorf("length: got %v; want %v", got, tc.length)
+			}
+		})
+	}
+}
+
 func TestTemplate(t *testing.T) {
 	testCases := []struct {
 		value string
@@ -1440,6 +1541,33 @@
 	}
 }
 
+func TestReference(t *testing.T) {
+	testCases := []struct {
+		input string
+		want  string
+	}{{
+		input: "v: _|_",
+		want:  "",
+	}, {
+		input: "v: 2",
+		want:  "",
+	}, {
+		input: "v: a, a: 1",
+		want:  "a",
+	}, {
+		input: "v: a.b.c, a b c: 1",
+		want:  "a b c",
+	}}
+	for _, tc := range testCases {
+		t.Run("", func(t *testing.T) {
+			v := getInstance(t, tc.input).Lookup("v")
+			if got := strings.Join(v.Reference(), " "); got != tc.want {
+				t.Errorf("\n got %v;\nwant %v", got, tc.want)
+			}
+		})
+	}
+}
+
 func TestReferences(t *testing.T) {
 	config1 := `
 	a: {
@@ -1522,3 +1650,148 @@
 	}
 	return true
 }
+
+func TestExpr(t *testing.T) {
+	testCases := []struct {
+		input string
+		want  string
+	}{{
+		input: "v: 3",
+		want:  " 3",
+	}, {
+		input: "v: 3 + 4",
+		want:  "+ 3 4",
+	}, {
+		input: "v: !a, a: 3",
+		want:  "! <0>.a",
+	}, {
+		input: "v: 1 | 2 | 3 | *4",
+		want:  "| 1 2 3 4",
+	}, {
+		input: "v: 2 & 5",
+		want:  "& 2 5",
+	}, {
+		input: "v: 2 | 5",
+		want:  "| 2 5",
+	}, {
+		input: "v: 2 && 5",
+		want:  "&& 2 5",
+	}, {
+		input: "v: 2 || 5",
+		want:  "|| 2 5",
+	}, {
+		input: "v: 2 == 5",
+		want:  "== 2 5",
+	}, {
+		input: "v: !b, b: true",
+		want:  "! <0>.b",
+	}, {
+		input: "v: 2 != 5",
+		want:  "!= 2 5",
+	}, {
+		input: "v: <5",
+		want:  "< 5",
+	}, {
+		input: "v: 2 <= 5",
+		want:  "<= 2 5",
+	}, {
+		input: "v: 2 > 5",
+		want:  "> 2 5",
+	}, {
+		input: "v: 2 >= 5",
+		want:  ">= 2 5",
+	}, {
+		input: "v: 2 =~ 5",
+		want:  "=~ 2 5",
+	}, {
+		input: "v: 2 !~ 5",
+		want:  "!~ 2 5",
+	}, {
+		input: "v: 2 + 5",
+		want:  "+ 2 5",
+	}, {
+		input: "v: 2 - 5",
+		want:  "- 2 5",
+	}, {
+		input: "v: 2 * 5",
+		want:  "* 2 5",
+	}, {
+		input: "v: 2 / 5",
+		want:  "/ 2 5",
+	}, {
+		input: "v: 2 % 5",
+		want:  "% 2 5",
+	}, {
+		input: "v: 2 quo 5",
+		want:  "quo 2 5",
+	}, {
+		input: "v: 2 rem 5",
+		want:  "rem 2 5",
+	}, {
+		input: "v: 2 div 5",
+		want:  "div 2 5",
+	}, {
+		input: "v: 2 mod 5",
+		want:  "mod 2 5",
+	}, {
+		input: "v: a.b, a b: 4",
+		want:  `. <0>.a "b"`,
+	}, {
+		input: `v: a["b"], a b: 3 `,
+		want:  `[] <0>.a "b"`,
+	}, {
+		input: "v: a[2:5], a: [1, 2, 3, 4, 5]",
+		want:  "[:] <0>.a 2 5",
+	}, {
+		input: "v: len([])",
+		want:  "() builtin:len []",
+	}, {
+		input: `v: "Hello, \(x)! Welcome to \(place)", place: string, x: string`,
+		want:  `\() "Hello, " <0>.x "! Welcome to " <0>.place ""`,
+	}}
+	for _, tc := range testCases {
+		t.Run(tc.input, func(t *testing.T) {
+			v := getInstance(t, tc.input).Lookup("v")
+			op, operands := v.Expr()
+			got := opToString[op]
+			for _, v := range operands {
+				got += " "
+				got += debugStr(v.ctx(), v.path.v)
+			}
+			if got != tc.want {
+				t.Errorf("\n got %v;\nwant %v", got, tc.want)
+			}
+		})
+	}
+}
+
+var opToString = map[Op]string{
+	AndOp:              "&",
+	OrOp:               "|",
+	BooleanAndOp:       "&&",
+	BooleanOrOp:        "||",
+	EqualOp:            "==",
+	NotOp:              "!",
+	NotEqualOp:         "!=",
+	LessThanOp:         "<",
+	LessThanEqualOp:    "<=",
+	GreaterThanOp:      ">",
+	GreaterThanEqualOp: ">=",
+	RegexMatchOp:       "=~",
+	NotRegexMatchOp:    "!~",
+	AddOp:              "+",
+	SubtractOp:         "-",
+	MultiplyOp:         "*",
+	FloatQuotientOp:    "/",
+	FloatRemainOp:      "%",
+	IntQuotientOp:      "quo",
+	IntRemainderOp:     "rem",
+	IntDivideOp:        "div",
+	IntModuloOp:        "mod",
+
+	SelectorOp:      ".",
+	IndexOp:         "[]",
+	SliceOp:         "[:]",
+	CallOp:          "()",
+	InterpolationOp: `\()`,
+}